Скачиваний:
0
Добавлен:
22.09.2026
Размер:
217 Кб
Скачать
☆
  1. Результаты работы программы

Для сравнения эффективности проведены замеры работы алгоритмов на графах от 50 до 500 вершин (шаг 50, полные графы с плотностью рёбер 100%, 1000 повторений). На рисунке 2 представлена зависимость времени выполнения от числа вершин.

Рисунок 2 – Сравнение алгоритмов построения остовного дерева.

Как видно из рисунка, алгоритм Прима работает быстрее алгоритма Крускала.

Экспериментальные данные показывают:

  • Для малых графов (V <= 150) оба алгоритма работают сопоставимо, время выполнения менее 5 мс.

  • Начиная с V = 200 разница становится заметной: алгоритм Прима обгоняет Крускала.

  • При V = 500 время выполнения алгоритма Крускала составляет 64 мс, тогда как алгоритм Прима – около 18 мс, что даёт выигрыш в производительности около 70-75% в пользу Прима.

Причины различий:

Алгоритм Крускала требует глобальной сортировки всех рёбер графа. Для полного графа это означает сортировку O(V²) элементов, что создаёт значительные накладные расходы как по времени, так и по памяти.

Алгоритм Прима работает с приоритетной очередью и просматривает рёбра по мере необходимости, не требуя предварительной сортировки. Локальность доступа к данным у Прима лучше, так как он оперирует со списками смежности.

Дополнительным фактором является то, что DSU в алгоритме Крускала на полных графах выполняет большое количество операций find и unite (почти E операций), тогда как Прим выполняет O(V) извлечений из очереди и O(E) обновлений.

Таким образом, выбор алгоритма зависит от плотности графа:

Для разреженных графов (E ≈ V) оба алгоритма работают быстро, но Крускал может быть предпочтительнее из-за простоты реализации.

Для плотных (полных) графов алгоритм Прима показывает лучшую производительность благодаря отсутствию глобальной сортировки и лучшей локальности данных.

Полученные экспериментальные данные полностью согласуются с теоретическими оценками сложности и подтверждают, что на плотных графах Прим эффективнее Крускала.

Выводы

В ходе выполнения лабораторной работы были достигнуты следующие результаты:

  1. Были изучены и реализованы два классических алгоритма построения минимального остовного дерева — Крускала и Прима. Оба алгоритма успешно справляются с поставленной задачей, однако их эффективность существенно зависит от структуры входного графа.

  2. Проведённые экспериментальные замеры на полных графах различного размера показали, что алгоритм Прима демонстрирует более высокую производительность, особенно при увеличении числа вершин. При V = 500 выигрыш в скорости составил около 70–75% по сравнению с алгоритмом Крускала. Это объясняется необходимостью глобальной сортировки всех рёбер в алгоритме Крускала, тогда как Прим работает с приоритетной очередью и обладает лучшей локальностью данных.

  3. Алгоритм Крускала имеет сложность O( ) для полных графов, алгоритм Прима имеет сложность O( ).

  4. Для плотных графов предпочтительным является алгоритм Прима, в то время как для разреженных графов оба алгоритма пригодны, и выбор может определяться простотой реализации или требованиями к памяти.

Таким образом, цель работы достигнута: алгоритмы реализованы, их характеристики исследованы, а полученные результаты сопоставлены с теорией.

Приложение 1.

Листинг программных модулей

#include <iostream>

#include <vector>

#include <random>

#include <chrono>

#include <algorithm>

#include <iomanip>

#include <queue>

using namespace std;

const unsigned long ITERATIONS = 1000;

struct Edge {

int u, v, weight;

bool operator<(const Edge& other) const {

return weight < other.weight;

}

};

struct DSU {

vector<int> parent;

DSU(int n) {

parent.resize(n);

for (int i = 0; i < n; i++) parent[i] = i;

}

int find(int i) {

if (parent[i] == i) return i;

return parent[i] = find(parent[i]);

}

void unite(int i, int j) {

int root_i = find(i);

int root_j = find(j);

if (root_i != root_j) parent[root_i] = root_j;

}

};

void kruskal(int V, vector<Edge>& edges) {

sort(edges.begin(), edges.end());

DSU dsu(V);

int edges_count = 0;

for (const auto& edge : edges) {

if (dsu.find(edge.u) != dsu.find(edge.v)) {

dsu.unite(edge.u, edge.v);

edges_count++;

if (edges_count == V - 1) break;

}

}

}

void prim(int V, const vector<vector<pair<int, int>>>& adj) {

vector<int> min_e(V, 1e9);

vector<bool> visited(V, false);

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

min_e[0] = 0;

pq.push({0, 0});

int edges_count = 0;

while (!pq.empty()) {

int u = pq.top().second;

int w = pq.top().first;

pq.pop();

if (visited[u]) continue;

visited[u] = true;

edges_count++;

for (auto& edge : adj[u]) {

int v = edge.first;

int weight = edge.second;

if (!visited[v] && weight < min_e[v]) {

min_e[v] = weight;

pq.push({min_e[v], v});

}

}

}

}

void generateGraph(int V, vector<Edge>& edges, vector<vector<pair<int, int>>>& adj) {

random_device rd;

mt19937 gen(rd());

uniform_int_distribution<> dis(1, 1000);

edges.clear();

adj.assign(V, vector<pair<int, int>>());

for (int i = 0; i < V; i++) {

for (int j = i + 1; j < V; j++) {

int w = dis(gen);

edges.push_back({i, j, w});

adj[i].push_back({j, w});

adj[j].push_back({i, w});

}

}

}

int main() {

cout << setw(10) << "Vertices" << setw(20) << "Kruskal (ms)" << setw(20) << "Prim (ms)" << endl;

cout << string(50, '-') << endl;

for (int V = 50; V <= 500; V += 50) {

vector<Edge> edges;

vector<vector<pair<int, int>>> adj;

generateGraph(V, edges, adj);

auto start_prep = chrono::high_resolution_clock::now();

for (unsigned long i = 0; i < ITERATIONS; i++) {

vector<Edge> work_edges = edges;

vector<vector<pair<int, int>>> work_adj = adj;

}

auto end_prep = chrono::high_resolution_clock::now();

double prep_time = chrono::duration<double>(end_prep - start_prep).count() / ITERATIONS;

auto start_kruskal = chrono::high_resolution_clock::now();

for (unsigned long i = 0; i < ITERATIONS; i++) {

vector<Edge> work_edges = edges;

kruskal(V, work_edges);

}

auto end_kruskal = chrono::high_resolution_clock::now();

double kruskal_time = (chrono::duration<double>(end_kruskal - start_kruskal).count() / ITERATIONS) - prep_time;

auto start_prim = chrono::high_resolution_clock::now();

for (unsigned long i = 0; i < ITERATIONS; i++) {

vector<vector<pair<int, int>>> work_adj = adj;

prim(V, work_adj);

}

auto end_prim = chrono::high_resolution_clock::now();

double prim_time = (chrono::duration<double>(end_prim - start_prim).count() / ITERATIONS) - prep_time;

cout << setw(10) << V << setw(20) << fixed << setprecision(6) << max(0.0, kruskal_time * 1000.0)

<< setw(20) << fixed << setprecision(6) << max(0.0, prim_time * 1000.0) << endl;

}

return 0;

}

Соседние файлы в папке Отчёты Дагаев