
КР. Минимальное остовное дерево
.docxМИНОБРНАУКИ РОССИИ
Санкт-Петербургский государственный
электротехнический университет
«ЛЭТИ» им. В.И. Ульянова (Ленина)
Кафедра информационных систем
Курсовая РАБОТА
по дисциплине «Алгоритмы и структуры данных»
Тема: Минимальное остовное дерево
Студент гр. 9300 |
|
ФИО |
Преподаватель |
|
Пелевин М. С. |
Санкт-Петербург
2020
ЗАДАНИЕ
на курсовую работу
Студент ФИО |
||
Группа 9300 |
||
Тема работы: Минимальное остовное дерево
|
||
Исходные данные: Текстовый файл с набором троек. |
||
|
||
|
||
Дата выдачи задания: 01.12.2020 |
||
Дата сдачи реферата: 24.12.2020 |
||
Дата защиты реферата: 27.12.2020 |
||
Студент |
|
ФИО |
Преподаватель |
|
Пелевин М. С. |
Аннотация
Реализован алгоритм поиска минимального остова на основе алгоритма Краскала. Продемонстрированы знания по сортировкам и системе непересекающихся множеств.
Summary
The algorithm for finding a minimum spanning tree based on Kruskal's algorithm was developed. Sorting and disjoint-set data structures were demonstrated.
Содержание
Введение 5
1. Алгоритмы и структуры данных 6
1.1. Минимальное остовное дерево 6
1.2. Алгоритм Краскала 6
1.3. Система непересекающихся множеств. 6
1.4. Сортировка Timsort 7
1.5. Стек 7
Заключение 8
Приложение 1. Код программы 9
Введение
Необходимо реализовать алгоритм поиска минимального остова на основе алгоритма Краскала. Для этого задействуется система непересекающихся множеств и сортировка Timsort. Курсовая работа написана на языке программирования Java.
1. Алгоритмы и структуры данных
1.1. Минимальное остовное дерево
Минимальное остовное дерево — ациклический подграф с тем же числом вершин, что у исходного графа, с минимально возможным суммарным весом.
Для нахождения минимального остовного дерева в данной работе используется алгоритм Краскала.
1.2. Алгоритм Краскала
Алгоритм Краскала помогает построить минимальное остовное дерево взвешенного связного неориентированного графа.
Алгоритм работы такой: пока не построено остовное дерево исходного графа, выбираем наименьшее ребро, не образующее цикл с другими выбранными рёбрами.
В данной работе для алгоритма Краскала используются сортировка Timsort и система непересекающихся множеств. Сначала в систему добавляются все вершины графа. Рёбра сортируются по весу и для каждого ребра по порядку происходит попытка слияния его вершин; если слияние успешно, тогда в основное дерево добавляется текущее ребро, если неуспешно, то это означает, что в дереве образуется цикл.
1.3. Система непересекающихся множеств.
Система непересекающихся множеств — структура данных, содержащая в себе непересекающиеся наборы элементов. Каждому набору назначен представитель из этого набора. Система поддерживает следующие операции:
создание подмножества, содержащего один указанный элемент
определение представителя подмножества указанного элемента
объединение двух подмножеств, содержащих указанные элементы
Для системы непересекающихся множеств в данном проекте реализован словарь с записями вида «ключ: представитель, ранг».
1.4. Сортировка Timsort
Общая последовательность действий алгоритма сортировки Timsort:
вычисление минимального размера подмассива
разделение входного массива на подмассивы
сортировка подмассивов вставками
объединение подмассивов сортировкой слиянием
В данной работе для этого алгоритма реализована структура стека.
1.5. Стек
Стек — список элементов, обладающий двумя основными операциями:
добавление элемента в список
вытаскивание элемента, добавленного последним
Заключение
Был реализован алгоритм поиска минимального остова на основе алгоритма Краскала и продемонстрированы знания по сортировкам и системе непересекающихся множеств.
Приложение 1. Код программы
package j; import j.course.DisjointSet; import j.course.Edge; import j.course.StringPair; import j.lab1.DynamicArray; import j.lab2.Timsort; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; public class Main { public static void main(String[] args) { // Вводим рёбра из файла input.txt. DynamicArray<Edge> edges = new DynamicArray<>(); try { FileReader fileReader = new FileReader("input.txt"); BufferedReader reader = new BufferedReader(fileReader); reader.lines().forEach(line -> { String[] split = line.split(" "); if (split.length != 3) return; try { edges.append(new Edge(split[0], split[1], Integer.parseInt(split[2]))); } catch (NumberFormatException ignored) {} }); } catch (FileNotFoundException e) { System.out.println("Ошибка: Файл input.txt не найден!"); return; } // Сортируем рёбра по весу. Edge[] sortedEdges = edges.toArray(new Edge[]{}); Timsort.sort(sortedEdges); // Создаём систему непересекающихся множеств. DisjointSet disjointSet = new DisjointSet(); // Добавляем все вершины в систему непересекающихся множеств. for (Edge edge : sortedEdges) { disjointSet.makeSet(edge.getFirstNode()); disjointSet.makeSet(edge.getSecondNode()); } int weightSum = 0; DynamicArray<StringPair> spanningPairs = new DynamicArray<>(); // В отсортированном порядке пытаемся сделать слияние множества. // Если слияние успешно (цикл не образуется), тогда добавляем // ребро в список остовного дерева и прибавляем вес к сумме. for (Edge edge : sortedEdges) { String first = edge.getFirstNode(); String second = edge.getSecondNode(); if (disjointSet.union(first, second)) { weightSum += edge.getWeight(); spanningPairs.append(new StringPair(first, second)); } } // Сортируем рёбра остовного дерева по алфавиту. StringPair[] sortedSpanningPairs = spanningPairs.toArray(new StringPair[]{}); Timsort.sort(sortedSpanningPairs); // Выводим рёбра остовного дерева и суммарный вес. for (StringPair pair : sortedSpanningPairs) { System.out.println(pair.getKey() + " " + pair.getValue()); } System.out.println(weightSum); } }
package j.lab1; import java.util.Arrays; import java.util.function.Predicate; @SuppressWarnings("unchecked") public class DynamicArray<T> { private static final int MIN_CAPACITY = 1; private static final int MAX_CAPACITY = Integer.MAX_VALUE - 1; private static final float EXPAND_FACTOR = 1.2f; private static final float CONTRACT_FACTOR = 0.8f; private Object[] data = new Object[MIN_CAPACITY]; private int size = 0; private void expand() { int newCapacity = (int) (data.length * EXPAND_FACTOR); if (newCapacity == data.length) newCapacity++; if (newCapacity < MIN_CAPACITY || newCapacity > MAX_CAPACITY) newCapacity = MAX_CAPACITY; Object[] newData = new Object[newCapacity]; System.arraycopy(data, 0, newData, 0, data.length); data = newData; } private void contract() { int newCapacity = (int) (data.length * CONTRACT_FACTOR); if (newCapacity == data.length) newCapacity--; if (newCapacity < MIN_CAPACITY) newCapacity = MIN_CAPACITY; Object[] newData = new Object[newCapacity]; System.arraycopy(data, 0, newData, 0, newCapacity); data = newData; } private void internalInsert(T element, int index) { if (index != size) { Object tmpInsert = element; Object tmp; for (int i = index; i < size; i++) { tmp = data[i]; data[i] = tmpInsert; tmpInsert = tmp; } data[size] = tmpInsert; } else data[size] = element; size++; } public void insert(T element, int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException(); if (size < data.length) { internalInsert(element, index); } else if (size == data.length) { expand(); internalInsert(element, index); } else throw new ArrayStoreException(); } public void append(T element) { insert(element, size); } public void prepend(T element) { insert(element, 0); } public T removeByIndex(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); T removed = (T) data[index]; System.arraycopy(data, index + 1, data, index, size - 1 - index); data[--size] = null; if ((float) size / data.length <= CONTRACT_FACTOR) contract(); return removed; } public void remove(T object) { int index = findIndex(object); if (index >= 0) removeByIndex(index); } public int findIndex(T object) { for (int i = 0; i < size; i++) { if (object.equals(data[i])) return i; } return -1; } public T find(Predicate<T> predicate) { for (int i = 0; i < size; i++) { if (predicate.test((T) data[i])) { return (T) data[i]; } } return null; } public int getSize() { return size; } public T get(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); return (T) data[index]; } public T[] toArray(T[] a) { return (T[]) Arrays.copyOf(data, size, a.getClass()); } @Override public String toString() { final StringBuilder sb = new StringBuilder("["); for (int i = 0; i < size; i++) { sb.append(data[i]); if (i != size - 1) sb.append(", "); } sb.append(']'); return sb.toString(); } }
package j.lab1; import java.util.EmptyStackException; public class Stack<T> { private final DynamicArray<T> data = new DynamicArray<>(); public void push(T element) { data.append(element); } public T peek() { return peek(1); } public T peek(int i) { if (data.getSize() == 0) throw new EmptyStackException(); if (data.getSize() < i) throw new IndexOutOfBoundsException(); return data.get(data.getSize() - i); } public T pop() { if (data.getSize() == 0) throw new EmptyStackException(); return data.removeByIndex(data.getSize() - 1); } public int getSize() { return data.getSize(); } @Override public String toString() { return data.toString(); } }
package j.lab2; public class Pair<K extends Comparable<K>, V extends Comparable<V>> implements Comparable<Pair<K, V>> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } @Override public int compareTo(Pair<K, V> o) { if (key.compareTo(o.key) < 0) return -1; if (key.compareTo(o.key) > 0) return +1; if (value.compareTo(o.value) < 0) return -1; if (value.compareTo(o.value) > 0) return +1; return 0; } @Override public String toString() { return "(" + key + ", " + value + ")"; } }
package j.lab2; import j.lab1.Stack; import java.lang.reflect.Array; public class Timsort { private static final int MIN_GALLOP = 7; public static <T extends Comparable<T>> void sort(T[] array) { // Если длина массива < 64, сортируем его вставками. if (array.length < 64) { insertionSort(array, 0, array.length - 1); return; } // Вычисляем minrun. int r = 0; int n = array.length; while (n >= 64) { r |= n & 1; n >>= 1; } int minrun = n + r; // Создаём стек пар {<индекс начала>, <размер>} подмассива. Stack<Pair<Integer, Integer>> stack = new Stack<>(); // Пробегаемся по массиву, разбивая его на подмассивы и записывая эти подмассивы в стек. for (int i = 0, run; i < array.length; i += run) { // Ищем run. run = findRun(array, i); // Если run < minrun, достраиваем до minrun и сортируем подмассив вставками. if (run < minrun) { if (minrun <= array.length - i) { insertionSort(array, i, i + minrun - 1); run = minrun; } else { insertionSort(array, i, array.length - 1); run = array.length - i; } } // Записываем информацию о подмассиве в стек. stack.push(new Pair<>(i, run)); // Проверяем стек, и, если необходимо, объединяем подмассивы. if (stack.getSize() > 1) checkStack(stack, array); } // Если остались необъединённые подмассивы в стеке, объединяем их. while (stack.getSize() > 1) mergeStackEntries(stack, array); } private static <T extends Comparable<T>> void insertionSort(T[] array, int startIndex, int endIndex) { for (int i = startIndex; i <= endIndex; i++) { T value = array[i]; for (int j = i - 1; j >= startIndex && value.compareTo(array[j]) < 0; j--) { array[j + 1] = array[j]; array[j] = value; } } } private static <T extends Comparable<T>> int findRun(T[] array, int start) { // Если индекс начала — конец массива, то это означает, что остался один элемент. if (start == array.length - 1) return 1; int current = start + 1; // Определяем отношение первых двух элементов и входим в цикл увеличения текущего // индекса, пока отношение остаётся таким же. if (array[start].compareTo(array[current++]) <= 0) while (current < array.length && array[current - 1].compareTo(array[current]) <= 0) current++; else { while (current < array.length && array[current - 1].compareTo(array[current]) > 0) current++; // В случае с обратным отношением упорядочиваем элементы // в обратном порядке, чтобы они шли по неубыванию. reverse(array, start, current - 1); } return current - start; } private static <T extends Comparable<T>> void checkStack(Stack<Pair<Integer, Integer>> stack, T[] array) { int x = stack.peek(1).getValue(); int y = stack.peek(2).getValue(); int z = (stack.getSize() >= 3) ? stack.peek(3).getValue() : Integer.MAX_VALUE; // Проверяем нарушение правил сохранения баланса. while (z <= y + x || y <= x) { // Начинаем слияние второго в стеке подмассива с меньшим соседним. if (x <= z) mergeStackEntries(stack, array); else { Pair<Integer, Integer> pop = stack.pop(); mergeStackEntries(stack, array); stack.push(pop); } if (stack.getSize() <= 1) return; x = stack.peek(1).getValue(); y = stack.peek(2).getValue(); z = (stack.getSize() >= 3) ? stack.peek(3).getValue() : Integer.MAX_VALUE; } } @SuppressWarnings("unchecked") private static <T extends Comparable<T>> void mergeStackEntries(Stack<Pair<Integer, Integer>> stack, T[] array) { // Берём два верхних подмассива из стека. Pair<Integer, Integer> right = stack.pop(); Pair<Integer, Integer> left = stack.pop(); int rStart = right.getKey(); int lStart = left.getKey(); int rLength = right.getValue(); int lLength = left.getValue(); // Записываем их объединение в стек. stack.push(new Pair<>(lStart, lLength + rLength)); // Создаём временную копию левого подмассива. T[] lCopy = (T[]) Array.newInstance(Comparable.class, lLength); System.arraycopy(array, lStart, lCopy, 0, lLength); int lCount = 0, rCount = 0; // — счётчики копирования элементов из одного подмассива подряд. // Сортируем объединение подмассивов слиянием. // k — счётчик объединения подмассивов // i — счётчик временной копии левого подмассива // j — счётчик правого подмассива for (int k = lStart, i = 0, j = rStart; (i < lLength) && (k < rStart + rLength); k++) { // Сравниваем элементы левого и правого подмассивов // и копируем меньший из них в объединение подмассивов. if (lCopy[i].compareTo(array[j]) < 0) { array[k] = lCopy[i++]; // Отсчитываем число подряд идущих копирований из одного подмассива. // Если оно стало равным MIN_GALLOP, входим в «режим галопа». rCount = 0; if (++lCount == MIN_GALLOP) { lCount = 0; // До индекса, на котором закончился галоп, копируем часть подмассива в объединение. int gallopIndex = leftGallop(lCopy, i, lLength - 1, array[j]); System.arraycopy(lCopy, i, array, ++k, gallopIndex - i + 1); // Обновляем счётчики i и k. k += gallopIndex - i; i = gallopIndex + 1; } } else { array[k] = array[j++]; lCount = 0; if (++rCount == MIN_GALLOP) { rCount = 0; int gallopIndex = rightGallop(array, j, rStart + rLength - 1, lCopy[i]); System.arraycopy(array, j, array, ++k, gallopIndex - j + 1); k += gallopIndex - j; j = gallopIndex + 1; } } // Если правый подмассив закончился, копируем оставшиеся данные левого подмассива в объединение. if (j == rStart + rLength) { System.arraycopy(lCopy, i, array, k + 1, lLength - i); break; } } } private static <T extends Comparable<T>> int leftGallop(T[] array, int start, int end, T comparingValue) { int add = 1; int current = start; // Пока текущий элемент меньше элемента правого подмассива, // увеличиваем текущий индекс бинарным поиском. while (current <= end && array[current].compareTo(comparingValue) < 0) { add <<= 1; current += add; } // Корректируем текущий индекс, чтобы он не был за пределами подмассива, // и элемент по этому индексу был меньше элемента правого подмассива. if (current > end && array[end].compareTo(comparingValue) < 0) current = end; if (current > end || array[current].compareTo(comparingValue) >= 0) current -= add; return current; } private static <T extends Comparable<T>> int rightGallop(T[] array, int start, int end, T comparingValue) { int add = 1; int current = start; while (current <= end && array[current].compareTo(comparingValue) <= 0) { add <<= 1; current += add; } if (current > end && array[end].compareTo(comparingValue) <= 0) current = end; if (current > end || array[current].compareTo(comparingValue) > 0) current -= add; return current; } private static <T extends Comparable<T>> void reverse(T[] array, int startIndex, int endIndex) { int centerIndex = (startIndex + endIndex - 1) / 2; for (int i = startIndex; i <= centerIndex; i++) { T tmp = array[i]; array[i] = array[endIndex - i + startIndex]; array[endIndex - i + startIndex] = tmp; } } }
package j.course; public class Edge implements Comparable<Edge> { private final String firstNode; private final String secondNode; private final int weight; public Edge(String firstNode, String secondNode, int weight) { this.firstNode = firstNode; this.secondNode = secondNode; this.weight = weight; } public String getFirstNode() { return firstNode; } public String getSecondNode() { return secondNode; } public int getWeight() { return weight; } @Override public int compareTo(Edge o) { return Integer.compare(this.weight, o.weight); } }
package j.course; import j.lab2.Pair; // Обёртка для Pair<String, String>, чтобы можно было создать из этого массив. public class StringPair extends Pair<String, String> { public StringPair(String key, String value) { super(key, value); } }
package j.course; import j.lab1.DynamicArray; public class DisjointSet { private final Dictionary dictionary = new Dictionary(); // Создаёт подмножество, содержащего один элемент x. public void makeSet(String x) { dictionary.set(x, x, 0); } // Возвращает представителя подмножества, куда входит x. public String find(String x) { Dictionary.Entry entry = dictionary.get(x); if (x.equals(entry.getParent())) return x; entry.setParent(find(entry.getParent())); return entry.getParent(); } // Объединяет подмножества представителей r и s. Представителя большего // из подмножеств делает представителем нового подмножества. // Возвращает успешность операции объединения. public boolean union(String r, String s) { // Заменим r и s на их представителей. r = find(r); s = find(s); // Если r и s уже в одном множестве, возвращаем false. if (r.equals(s)) return false; Dictionary.Entry rEntry = dictionary.get(r); Dictionary.Entry sEntry = dictionary.get(s); int rRank = rEntry.getRank(); int sRank = sEntry.getRank(); if (rRank < sRank) // Делаем s новым представителем для r. rEntry.setParent(s); else { // Делаем r новым представителем для s. sEntry.setParent(r); // Если нужно, увеличиваем ранг r. if (rRank == sRank) rEntry.setRank(rRank + 1); } return true; } // Класс словаря, содержащего ключи, каждому из которых соответствуют представитель и ранг. private static class Dictionary { private final DynamicArray<Entry> entries = new DynamicArray<>(); // Возвращает запись по указанному ключу. Если запись не найдена, // возвращает null. public Entry get(String key) { return entries.find(e -> e.getKey().equals(key)); } // Задаёт представителя и ранг по заданному ключу. Если значения // уже заданы, перезаписывает их новыми. public void set(String key, String parent, int rank) { Entry entry = get(key); if (entry != null) { entry.setParent(parent); entry.setRank(rank); } else entries.append(new Entry(key, parent, rank)); } // Класс одной записи словаря. private static class Entry { private final String key; private String parent; private int rank; public Entry(String key, String parent, int rank) { this.key = key; this.parent = parent; this.rank = rank; } public String getKey() { return key; } public String getParent() { return parent; } public int getRank() { return rank; } public void setParent(String parent) { this.parent = parent; } public void setRank(int rank) { this.rank = rank; } } } }