Nadesłany przez Dawid Fatyga, 01 marca 2011 00:46
Kod przedstawiony poniżej przedstawia główną część rozwiązania problemu.Pobierz pełne rozwiązanie.
kruskal_1_rb.rb:
#!/usr/bin/env ruby
#
# Implementacja algorytmu Kruskala, z wykorzystaniem struktury zbiorow
# rozlacznych.
#
# www.algorytm.org
#
# Graf odczytywany na wejsciu powinien zawierac kilka linii, w kazdej linii
# powinien znajdowac sie opis jednej krawedzi. Opis krawedzi to trojka napisow
# oddzielonych bialym znakiem:
# * pierwszy to liczba rzeczywista
# * drugi to indeks pierwszego konca krawedzi
# * trzeci to indeks drugiego konca krawedzi
# Koniec grafu oznaczony jest przez koniec pliku.
#
# Przykladowy graf (opisany w artykule) wyglada nastepujaco:
#
# 7 e f
# 2 a f
# 6 f d
# 1 a e
# 2 b c
# 4 a b
# 8 c d
# 2 b e
# 3 d e
#
# Wynik dzialania algorytmu mozna zwizualizowac za pomoca programu graphviz,
# uruchamiajac polecenie:
#
# ruby kruskal.rb | dot -og.png -Tpng
#
# i wpisujac dane grafu lub polecenie:
#
# ruby kruskal.rb < dane.txt | dot -og.png -Tpng
#
# Plik 'dane.txt' powinien zawierac graf wejsciowy. Wynikiem bedzie plik 'g.png'
# ze zwizualizowanym grafem wejsciowym i zaznaczonym na czerwono drzewem
# rozpinajacym.
#
# Autor: Dawid Fatyga
#
#
# Klasa Vertex reprezentuje wierzcholek grafu, jednoczesnie implementujac
# strukture zbiorow rozlacznych.
#
# http://en.wikipedia.org/wiki/Disjoint-set_data_structure
#
class Vertex
attr_accessor :name, :parent, :rank
def initialize(name)
self.name = name
self.rank = 0
end
# Odnajduje reprezentanta danego zbioru rozlacznego, stosujac kompresje
# sciezki.
def find
if self.parent.nil?
self
else
self.parent = parent.find
end
end
# Scala dwa zbiory rozlaczne, dolaczajac mniejsze drzewo do wiekszego drzewa.
def union(other)
self_root = self.find
other_root = other.find
if self_root.rank > other_root.rank
other_root.parent = self_root
elsif self_root.rank < other_root.rank
self_root.parent = other_root
elsif self_root != other_root
other_root.parent = self_root
self_root.rank += 1
end
end
end
#
# Klasa Edge reprezentuje pojedyncza krawedz grafu:
# :weight waga krawedzi
# :a pierwszy koniec krawedzi (instancja klasy Vertex)
# :b drugi koniec krawedzi (instancja klasy Vertex)
#
class Edge < Struct.new(:weight, :a, :b)
#
# Sprawdza czy konce krawedzi nie naleza do tego samego zbioru, nastepnie
# laczy zbiory skojarzone z wierzcholkami, a nastepnie zwraca wynik testu.
#
# Zbiory sa laczone zawsze, poniewaz wg. algorytmu jesli konce krawedzi
# naleza do innych zbiorow, to nalezy je polaczyc; laczenie juz polaczonych
# zbiorow nie wplywa na dzialanie algorytmu.
#
def disjoint_ends?
(a.find != b.find).tap { a.union(b) }
end
end
class Graph
def initialize
@edges = []
@vertices = {}
end
#
# Algorytm (http://pl.wikipedia.org/wiki/Algorytm_Kruskala):
#
# 1. Utworz las L z wierzcholkow oryginalnego grafu - kazdy wierzcholek jest na poczatku osobnym drzewem.
# 2. Utworz zbior S zawierający wszystkie krawedzie oryginalnego grafu.
# 3. Dopoki S nie jest pusty:
# 4. Wybierz i usun z S krawedz o minimalnej wadze.
# 5. Jesli krawedz ta laczyla dwa rozne drzewa, to dodaj ja do lasu L, tak aby polaczyla dwa odpowiadające drzewa w jedno.
# 6. W przeciwnym wypadku odrzuc ja
#
# Po zakonczeniu algorytmu L jest minimalnym drzewem rozpinającym.
#
def kruskal(input=$stdin)
# W tym miejscu
# * @edges jest zainicjalizowane pusta lista []
# * @vertices jest zainicjalizowane pusta tablica haszujaca {}
until (line = input.gets).nil?
w, a, b = line.split(/\s+/)
# Instancje klasy Vertex sa zapamietywane w tablicy haszujacej byly
# inicjalizowane tylko jeden raz.
@vertices[a] ||= Vertex.new(a)
@vertices[b] ||= Vertex.new(b)
@edges.push(Edge.new(Float(w), @vertices[a], @vertices[b]))
end
# W tym miejscu:
# * @edges to zbior S zawierajacy wszystkie krawedzie grafu
# * @vertices to las L z wierzcholkow grafu
# Zbior krawedzi S jest sortowany wg. wag; metoda select iteruje
# po wynikowej tablicy wybierajac krawedzie, dla ktorych metoda
# :disjoint_ends? zwroci wartosc 'true' (ktora to jednoczesnie laczy
# wszystkie napotkane zbiory rozlaczne).
#
# W Wyniku zwracana jest lista krawedzi nalezaca do drzewa rozpinajacego.
@edges.sort_by(&:weight).select(&:disjoint_ends?)
end
# Zwraca reprezentacje grafu w postaci ciagu znakow, w jezyku dot; krawedzie
# nalezace do drzewa rozpinajacego zostana pokolorowane na czerwono
def to_s(spanning_tree = [])
@edges.map do |edge|
out = "#{edge.a.name} -- #{edge.b.name}"
out += " [color = red]" if spanning_tree.include?(edge)
out
end.join("\n")
end
end
graph = Graph.new
spanning_tree = graph.kruskal
puts "graph {\n#{graph.to_s(spanning_tree)}\n}"

