diff --git a/CMakeLists.txt b/CMakeLists.txt index fd8b3fd..a93d126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,27 +9,46 @@ FetchContent_Declare( SFML GIT_REPOSITORY https://github.com/SFML/SFML.git GIT_TAG 2.6.x - GIT_SHALLOW ON - EXCLUDE_FROM_ALL - SYSTEM + GIT_SHALLOW ON + EXCLUDE_FROM_ALL + SYSTEM ) FetchContent_MakeAvailable(SFML) -add_executable(SortLab - src/main.cpp - src/App.cpp - src/App_audio.cpp - src/Array.cpp - src/Sorter.cpp - src/UI.cpp - src/OperationsHistory.cpp - src/ProgressMap.cpp - src/sorters/BubbleSorter.cpp - src/sorters/SelectionSorter.cpp - src/sorters/InsertionSorter.cpp - src/sorters/MergeSorter.cpp - src/sorters/QuickSorter.cpp -) +if(WIN32) + add_executable(SortLab + src/main.cpp + src/App.cpp + src/App_audio.cpp + src/Array.cpp + src/Sorter.cpp + src/UI.cpp + src/OperationsHistory.cpp + src/ProgressMap.cpp + src/sorters/BubbleSorter.cpp + src/sorters/SelectionSorter.cpp + src/sorters/InsertionSorter.cpp + src/sorters/MergeSorter.cpp + src/sorters/QuickSorter.cpp + assets/app.rc + ) +else() + add_executable(SortLab + src/main.cpp + src/App.cpp + src/App_audio.cpp + src/Array.cpp + src/Sorter.cpp + src/UI.cpp + src/OperationsHistory.cpp + src/ProgressMap.cpp + src/sorters/BubbleSorter.cpp + src/sorters/SelectionSorter.cpp + src/sorters/InsertionSorter.cpp + src/sorters/MergeSorter.cpp + src/sorters/QuickSorter.cpp + ) +endif() if(MSVC) target_compile_options(SortLab PRIVATE /utf-8) @@ -39,7 +58,6 @@ target_include_directories(SortLab PRIVATE ${CMAKE_SOURCE_DIR}/include) target_link_libraries(SortLab PRIVATE sfml-graphics sfml-window sfml-system sfml-audio) - if(WIN32) add_custom_command(TARGET SortLab POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/README.md b/README.md index e69de29..cacbe95 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,190 @@ +# 🎯 SortLab + +### ⚡ Интерактивный визуализатор алгоритмов сортировки + +![C++](https://img.shields.io/badge/C++-17-00599C?style=flat&logo=cplusplus&logoColor=white) +![SFML](https://img.shields.io/badge/SFML-2.6-8CC445?style=flat&logo=sfml&logoColor=white) +![CMake](https://img.shields.io/badge/CMake-3.16+-064F8C?style=flat&logo=cmake&logoColor=white) +![Platform](https://img.shields.io/badge/Platform-Windows-0078D4?style=flat&logo=windows&logoColor=white) +![License](https://img.shields.io/badge/License-MIT-yellow?style=flat) + +--- + +![SortLab Preview](assets/preview.png) + +--- + +## 📖 О проекте + +**SortLab** — учебный инструмент для наглядного изучения алгоритмов сортировки. +Каждый элемент массива отображается как столбик. Высота = значение. Цвет = состояние. + +Программа показывает работу алгоритма в реальном времени, шаг за шагом, с подсветкой +сравниваемых и переставляемых элементов, счётчиком операций и визуализацией прогресса. + +--- + +## 🧮 Алгоритмы + +| # | Алгоритм | Время (лучшее / среднее / худшее) | Память | +|---|---|---|---| +| `1` | 🫧 Bubble Sort | O(n) / O(n²) / O(n²) | O(1) | +| `2` | 🔍 Selection Sort | O(n²) / O(n²) / O(n²) | O(1) | +| `3` | 🃏 Insertion Sort | O(n) / O(n²) / O(n²) | O(1) | +| `4` | 🔀 Merge Sort | O(n log n) / O(n log n) / O(n log n) | O(n) | +| `5` | ⚡ Quick Sort | O(n log n) / O(n log n) / O(n²) | O(log n) | + +--- + +## 🎮 Управление + +| Клавиша | Действие | +|---|---| +| `Space` | ▶️ Старт / ⏸️ Пауза | +| `R` | 🔀 Перемешать массив | +| `→` | 👣 Один шаг (в режиме паузы) | +| `↑` / `↓` | 🐇 / 🐢 Увеличить / уменьшить скорость | +| `1` — `5` | 🔢 Выбрать алгоритм | +| `I` | ℹ️ Справка о программе | +| `Q` | 🚪 Выход | + +--- + +## 🖥️ Интерфейс + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 📊 Algorithm: Quick Sort Time: O(n log n) Comparisons: 1.2k │ ← UI панель +├────────────────────────────────────┬─────────────────────────┤ +│ │ │ +│ 📈 Столбики массива │ 🗺️ Progress Map │ +│ │ │ +├────────────────────────────────────┴─────────────────────────┤ +│ 📉 Δ Comparisons / step [гистограмма] │ ← нижняя панель +└──────────────────────────────────────────────────────────────┘ +``` + +- 📊 **Главная область** — столбики с цветовой индикацией состояний +- 🗺️ **Progress Map** — миниатюрная сетка всего массива, зеленеет по мере сортировки +- 📉 **Δ Гистограмма** — количество сравнений на каждом шаге (пульс алгоритма) + +### 🎨 Цветовая схема + +| Цвет | Состояние | +|---|---| +| 🔵 Синий | Обычный элемент | +| 🩵 Голубой | Сравниваемые элементы | +| 🟠 Оранжевый | Переставляемые элементы | +| 🟢 Зелёный | Отсортированный элемент | + +--- + +## 🔧 Сборка + +### 📦 Зависимости +- 🛠️ [CMake](https://cmake.org/download/) ≥ 3.16 +- 💻 [Visual Studio 2022](https://visualstudio.microsoft.com/) с компонентом **Desktop development with C++** +- 🌐 Git (для FetchContent — SFML скачивается автоматически) + +### 🚀 Установка + +```bash +git clone https://github.com/your-username/SortLab.git +cd SortLab +cmake -S . -B build +cmake --build build --config Release +.\build\Release\SortLab.exe +``` + +> 💡 SFML 2.6 скачивается и компилируется автоматически при первой сборке. +> ⏱️ Первый запуск `cmake -S . -B build` займёт 1–3 минуты. + +### 🔤 Шрифт +Скачай [JetBrains Mono](https://www.jetbrains.com/lp/mono/) и положи файл в: +``` +assets/fonts/JetBrainsMono-Regular.ttf +``` + +--- + +## 📁 Структура проекта + +``` +SortLab/ +├── 🎨 assets/ +│ ├── fonts/JetBrainsMono-Regular.ttf +│ ├── icon.ico +│ ├── icon.png +│ ├── preview.png +│ └── app.rc +├── 📋 include/ +│ ├── App.hpp +│ ├── Array.hpp +│ ├── Sorter.hpp +│ ├── UI.hpp +│ ├── OperationsHistory.hpp +│ ├── ProgressMap.hpp +│ └── sorters/ +│ ├── BubbleSorter.hpp +│ ├── SelectionSorter.hpp +│ ├── InsertionSorter.hpp +│ ├── MergeSorter.hpp +│ └── QuickSorter.hpp +├── 💻 src/ +│ ├── main.cpp +│ ├── App.cpp +│ ├── App_audio.cpp +│ ├── Array.cpp +│ ├── Sorter.cpp +│ ├── UI.cpp +│ ├── OperationsHistory.cpp +│ ├── ProgressMap.cpp +│ └── sorters/ +│ ├── BubbleSorter.cpp +│ ├── SelectionSorter.cpp +│ ├── InsertionSorter.cpp +│ ├── MergeSorter.cpp +│ └── QuickSorter.cpp +└── ⚙️ CMakeLists.txt +``` + +--- + +## 🏗️ Архитектура + +``` +main() + └── App::run() + ├── handleEvents() — ввод с клавиатуры + ├── update(dt) — шаг сортировки + гистограмма + └── render() + ├── Array bars — основная визуализация + ├── UI overlay — текст, статистика + ├── ProgressMap — мини-карта + └── HistogramRenderer — Δ гистограмма + +Sorter (abstract) + ├── BubbleSorter + ├── SelectionSorter + ├── InsertionSorter + ├── MergeSorter + └── QuickSorter +``` + +> 🔑 Ключевой паттерн: все алгоритмы реализованы как **конечные автоматы** (state machines). +> Метод `step()` выполняет ровно одну атомарную операцию и возвращает управление, +> не блокируя главный поток рендеринга. + +--- + +## 📜 Лицензия + +``` +MIT License — 2026 +``` + +--- + +
+ 🎓 made for college coursework  |  built with ❤️ and C++17 +
diff --git a/assets/app.rc b/assets/app.rc new file mode 100644 index 0000000..a841e99 --- /dev/null +++ b/assets/app.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "icon.ico" diff --git a/assets/icon.ico b/assets/icon.ico new file mode 100644 index 0000000..c2c888e Binary files /dev/null and b/assets/icon.ico differ diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..64f0b9e Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/preview.png b/assets/preview.png new file mode 100644 index 0000000..a5e58a9 Binary files /dev/null and b/assets/preview.png differ diff --git a/include/sorters/QuickSorter.hpp b/include/sorters/QuickSorter.hpp index fb0e1ba..6b6739b 100644 --- a/include/sorters/QuickSorter.hpp +++ b/include/sorters/QuickSorter.hpp @@ -5,30 +5,21 @@ class QuickSorter : public Sorter { public: QuickSorter(); - void step(Array& array) override; bool isFinished() const override; std::string getName() const override; - void reset() override; std::string getTimeComplexity() const override; std::string getSpaceComplexity() const override; + void reset() override; private: + struct Range { int low; int high; }; enum class Phase { PARTITIONING, SWAPPING_PIVOT, PUSHING_RANGES }; - - struct Range { - int low; - int high; - }; - + std::vector stack_; - int currentLow_; - int currentHigh_; - int pivotIndex_; - int i_; - int j_; + Phase phase_; + int currentLow_, currentHigh_, pivotIndex_, i_, j_, n_; float pivotValue_; bool finished_; - Phase phase_; - int n_; + bool needNewRange_; }; diff --git a/src/App.cpp b/src/App.cpp index 9312c25..fd100e5 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -27,7 +27,12 @@ App::App() generateBeepSound(); beepSound_.setBuffer(beepBuffer_); beepSound_.setVolume(100.0f); - + + sf::Image icon; + if (icon.loadFromFile("assets/icon.png")) { + window_.setIcon(icon.getSize().x, icon.getSize().y, icon.getPixelsPtr()); + } + if (bottomPanelFont_.loadFromFile("assets/fonts/JetBrainsMono-Regular.ttf")) { bottomPanelFontLoaded_ = true; } diff --git a/src/sorters/QuickSorter.cpp b/src/sorters/QuickSorter.cpp index b614379..d2f49b4 100644 --- a/src/sorters/QuickSorter.cpp +++ b/src/sorters/QuickSorter.cpp @@ -1,74 +1,61 @@ #include "sorters/QuickSorter.hpp" -QuickSorter::QuickSorter() - : currentLow_(0), currentHigh_(0), pivotIndex_(0), i_(0), j_(0) - , pivotValue_(0.0f), finished_(false), phase_(Phase::PARTITIONING), n_(0) { +QuickSorter::QuickSorter() { + reset(); } void QuickSorter::step(Array& array) { - if (finished_) { - return; - } - + if (finished_) return; + if (n_ == 0) { n_ = array.getSize(); stack_.push_back({0, n_ - 1}); + needNewRange_ = true; } - - if (stack_.empty()) { - array.resetStates(); - for (int k = 0; k < n_; ++k) { - array.setState(k, Array::State::SORTED); + + if (needNewRange_) { + while (!stack_.empty() && stack_.back().low >= stack_.back().high) { + stack_.pop_back(); } - finished_ = true; + + if (stack_.empty()) { + array.resetStates(); + for (int k = 0; k < n_; ++k) + array.setState(k, Array::State::SORTED); + finished_ = true; + return; + } + + Range range = stack_.back(); + stack_.pop_back(); + currentLow_ = range.low; + currentHigh_ = range.high; + pivotIndex_ = currentHigh_; + pivotValue_ = array.getValue(pivotIndex_); + i_ = currentLow_ - 1; + j_ = currentLow_; + needNewRange_ = false; + phase_ = Phase::PARTITIONING; return; } - + if (phase_ == Phase::PARTITIONING) { - if (j_ == currentLow_) { - Range range = stack_.back(); - stack_.pop_back(); - - if (range.low >= range.high) { - if (stack_.empty()) { - array.resetStates(); - for (int k = 0; k < n_; ++k) { - array.setState(k, Array::State::SORTED); - } - finished_ = true; - } - return; - } - - currentLow_ = range.low; - currentHigh_ = range.high; - pivotIndex_ = currentHigh_; - pivotValue_ = array.getValue(pivotIndex_); - i_ = currentLow_ - 1; - j_ = currentLow_; - } - if (j_ < currentHigh_) { array.resetStates(); array.setState(j_, Array::State::COMPARE); array.setState(pivotIndex_, Array::State::SWAP); - array.incrementComparisons(); - + if (array.getValue(j_) < pivotValue_) { i_++; - if (i_ != j_) { array.setState(i_, Array::State::SWAP); - float temp = array.getValue(i_); array.setValue(i_, array.getValue(j_)); array.setValue(j_, temp); - array.incrementSwaps(); } } - j_++; } else { phase_ = Phase::SWAPPING_PIVOT; @@ -77,26 +64,21 @@ void QuickSorter::step(Array& array) { array.resetStates(); array.setState(i_ + 1, Array::State::SWAP); array.setState(pivotIndex_, Array::State::SWAP); - + float temp = array.getValue(i_ + 1); array.setValue(i_ + 1, array.getValue(pivotIndex_)); array.setValue(pivotIndex_, temp); - array.incrementSwaps(); + pivotIndex_ = i_ + 1; - phase_ = Phase::PUSHING_RANGES; } else if (phase_ == Phase::PUSHING_RANGES) { - if (pivotIndex_ - 1 > currentLow_) { + if (pivotIndex_ - 1 > currentLow_) stack_.push_back({currentLow_, pivotIndex_ - 1}); - } - - if (pivotIndex_ + 1 < currentHigh_) { + if (pivotIndex_ + 1 < currentHigh_) stack_.push_back({pivotIndex_ + 1, currentHigh_}); - } - - j_ = currentLow_; - phase_ = Phase::PARTITIONING; + + needNewRange_ = true; } } @@ -113,7 +95,7 @@ std::string QuickSorter::getTimeComplexity() const { } std::string QuickSorter::getSpaceComplexity() const { - return "O(n)"; + return "O(log n)"; } void QuickSorter::reset() { @@ -125,6 +107,7 @@ void QuickSorter::reset() { j_ = 0; pivotValue_ = 0.0f; finished_ = false; + needNewRange_ = true; phase_ = Phase::PARTITIONING; n_ = 0; }