Sort the edges by weight: AB, BC, AC.
Week 10 · Lectures 12, 13
DFS and minimum spanning trees
Trace depth-first exploration and distinguish a traversal tree from a minimum-weight connecting tree. Introduce topological order and greedy MST choices.
Read alongside the document
Use the lecture headings below in the English–Turkish notes. Document lecture numbers and course week numbers are different.
- Lecture 12: Depth-First Search
Warm-up: edge types in BFS; DFS; What DFS does to the edges; Application: finding a cycle; Application: articulation vertices; Topological sorting; Strongly connected components. - Lecture 13: Minimum Spanning Trees
Warm-up: lining up children who hate each other; Weighted graphs and spanning trees; Prim’s algorithm; Kruskal’s algorithm; Union-find.
Does any tree connecting all vertices have the smallest total weight?
A triangle has undirected edge weights AB=1, BC=2, AC=3. Apply Kruskal’s algorithm.
Accept AB: it joins different components.
Accept BC: all three vertices are now connected. Total weight is 3.
Reject AC because it closes a cycle. The cut property justifies choosing a lightest edge across a cut.
Change the case. An MST minimises total tree weight, not every pairwise path. Here its A-to-C path weighs 3; change AC to 2.5 and the MST stays AB,BC although the direct path is shorter.
Explain before coding
State the input and required output. Trace a small example. Explain why each step is valid, count the work under a stated model, and test an edge case.
Python support for this week
DFS uses recursion or an explicit stack. Track visited vertices. Union–find is a separate tool for Kruskal’s cycle checks.
g = {1:[2,3], 2:[1,4], 3:[1,4], 4:[2,3,5], 5:[4]}
seen = set()
def dfs(u):
seen.add(u)
print(u)
for v in g[u]:
if v not in seen:
dfs(v)
dfs(1) # 1, 2, 4, 3, 5Run this small demonstration after predicting its result. It illustrates the selected example; its input assumptions are part of the example.
Open the optional Python support library. Programming fluency is not required to complete the paper trace.
Read the related bilingual explanations here
Sections from the supplied lecture notes. Turkish paragraphs appear in red. Use this as a reference after the worked example.
Lecture 12 · Warm-up: edge types in BFS
Hazırlık: BFS’de kenar türleri
Claim: in a BFS of an undirected graph, every edge is either a tree edge (it discovered a vertex) or a cross edge (joining two vertices neither of which is an ancestor of the other). Why: if edge (x, y) joined an ancestor x to a descendant y two or more levels down, then x was processed before y’s parent, and x’s edge to y would have discovered y first, making (x, y) a tree edge. So non-tree edges only join vertices at the same level or adjacent levels, and those are cross edges.
İddia: Yönsüz bir çizgenin BFS’sinde her kenar ya ağaç kenarıdır (bir tepeyi keşfetmiştir) ya da çapraz kenardır (hiçbiri diğerinin atası olmayan iki tepeyi birleştirir). Nedeni: (x, y) kenarı, x atasını iki veya daha fazla düzey aşağıdaki y torununa bağlasaydı x, y’nin ebeveyninden önce işlenmiş olurdu. x’ten y’ye giden kenar y’yi önce keşfeder ve (x, y) bir ağaç kenarı olurdu. Dolayısıyla ağaç dışı kenarlar yalnızca aynı düzeydeki veya komşu düzeylerdeki tepeleri birleştirir; bunlar çapraz kenarlardır.
Lecture 12 · DFS
DFS
DFS has a tidy recursive form; the recursion itself acts as the stack, so no explicit stack is needed. It records an entry time when a vertex is discovered and an exit time when it is finished, using a global clock:
DFS’nin düzenli bir özyinelemeli biçimi vardır; özyinelemenin kendisi yığın görevini üstlenir, dolayısıyla ayrıca açık bir yığın gerekmez. Küresel bir saat kullanarak bir tepe keşfedildiğinde giriş zamanını, tamamlandığında ise çıkış zamanını kaydeder:
dfs(G, v):
discovered[v] = true
time = time + 1; entry_time[v] = time
process_vertex_early(v)
for each edge (v, y):
if not discovered[y]:
parent[y] = v
process_edge(v, y)
dfs(G, y)
else if (not processed[y] and parent[v] != y) or G is directed:
process_edge(v, y)
process_vertex_late(v)
time = time + 1; exit_time[v] = time
processed[v] = truedfs(G, v):
discovered[v] = true
time = time + 1; entry_time[v] = time
process_vertex_early(v)
for her (v, y) kenarı:
if not discovered[y]:
parent[y] = v
process_edge(v, y)
dfs(G, y)
else if (not processed[y] and parent[v] != y) or G yönlü ise:
process_edge(v, y)
process_vertex_late(v)
time = time + 1; exit_time[v] = time
processed[v] = trueThe condition parent[v] != y stops an undirected tree edge from being reported a second time when we look back at our own parent. A global finished flag (omitted above) lets a search stop early once it has found what it wanted.
parent[v] != y koşulu, kendi ebeveynimize geri baktığımızda yönsüz bir ağaç kenarının ikinci kez bildirilmesini engeller. Küresel finished bayrağı (yukarıda gösterilmemiştir), aramanın aradığını bulduğunda erken sonlanmasını sağlar.
DFS is the same idea as backtracking (Lecture 15): advance whenever you can, back up as soon as there is nowhere new to go. Both are most naturally recursive.
DFS, geri izleme (Ders 15) ile aynı fikirdir: ilerleyebildiğiniz sürece ilerleyin, gidilecek yeni bir yer kalmadığı anda geri dönün. Her ikisinin de en doğal biçimi özyinelemelidir.
Lecture 12 · What DFS does to the edges
DFS kenarlara ne yapar?
A DFS gives every edge of an undirected graph a direction: from the vertex that was being explored when the edge was first met. It then falls into one of four classes:
DFS, yönsüz çizgenin her kenarına bir yön verir: kenarla ilk karşılaşıldığında araştırılmakta olan tepeden başlayan yön. Ardından kenar dört sınıftan birine girer:
| Class | Meaning | How the code recognises it when looking at (x, y) |
| Sınıf | Anlamı | Kod, (x, y)’ye bakarken bunu nasıl tanır? |
| Tree edge | The edge that discovered y | parent[y] == x |
| Ağaç kenarı | y’yi keşfeden kenar | parent[y] == x |
| Back edge | y is an ancestor of x, still being explored | y discovered but not yet processed |
| Geri kenar | y, x’in henüz araştırılmakta olan bir atasıdır | y keşfedilmiş, fakat henüz işlenmemiştir |
| Forward edge | y is a descendant of x, already finished | y processed and entry_time[y] > entry_time[x] |
| İleri kenar | y, x’in işlemi tamamlanmış bir torunudur | y işlenmiştir ve entry_time[y] > entry_time[x] |
| Cross edge | y is in a different, already finished part | y processed and entry_time[y] < entry_time[x] |
| Çapraz kenar | y, işlemi tamamlanmış başka bir bölümde bulunur | y işlenmiştir ve entry_time[y] < entry_time[x] |
The reason DFS is so important: in an undirected graph, every edge is either a tree edge or a back edge. Forward edges cannot happen because we would have met the edge from the descendant’s side first, when it was still being explored, which makes it a back edge. Cross edges cannot happen because when we were exploring the earlier vertex we would have discovered the other endpoint through that very edge, making it a tree edge. (In directed graphs all four classes can occur.)
DFS’nin çok önemli olmasının nedeni şudur: yönsüz çizgede her kenar ya ağaç kenarı ya da geri kenardır. İleri kenarlar oluşamaz; çünkü kenarla önce torun tarafından, o hâlâ araştırılırken karşılaşırdık ve bu onu geri kenar yapardı. Çapraz kenarlar da oluşamaz; çünkü önceki tepeyi araştırırken diğer ucu tam bu kenar üzerinden keşfederdik ve bu da onu ağaç kenarı yapardı. (Yönlü çizgelerde dört sınıfın tamamı görülebilir.)
Lecture 12 · Application: finding a cycle
Uygulama: Döngü bulma
Back edges are exactly what cycles look like to DFS. A back edge from x to an ancestor y, together with the tree path from y down to x, forms a cycle. So an undirected graph has a cycle if and only if a DFS meets a back edge:
Geri kenarlar, döngülerin DFS’ye görünen biçimidir. x’ten y atasına giden bir geri kenar, y’den x’e inen ağaç yoluyla birlikte bir döngü oluşturur. Dolayısıyla yönsüz bir çizgede döngü bulunması için ve ancak bunun için DFS’nin bir geri kenarla karşılaşması gerekir:
process_edge(x, y):
if parent[y] != x: (not a tree edge, so a back edge)
print "Cycle from", y, "to", x
find_path(y, x, parent)
finished = trueprocess_edge(x, y):
if parent[y] != x: (ağaç kenarı değil, dolayısıyla geri kenar)
print "Döngü:", y, "→", x
find_path(y, x, parent)
finished = trueLecture 12 · Application: articulation vertices
Uygulama: Eklem tepeleri
An articulation vertex (or cut vertex) of a connected graph is a vertex whose removal disconnects the graph; the phone exchange that, if it failed, would split the network in two. Network designers want to know where these weak points are.
Bağlı bir çizgenin eklem tepesi (veya kesme tepesi), çıkarılması çizgenin bağlantısını koparan tepedir; arızalandığında ağı ikiye bölen bir telefon santrali gibi. Ağ tasarımcıları bu zayıf noktaların nerede olduğunu bilmek ister.
The obvious algorithm deletes each vertex in turn and checks with a DFS whether the rest is still connected: n searches of O(n + m) each, O(n(n + m)).
İlk akla gelen algoritma, her tepeyi sırayla siler ve kalan kısmın hâlâ bağlı olup olmadığını DFS ile denetler: her biri O(n + m) olan n arama, yani O(n(n + m)).
One DFS suffices, O(n + m). In the DFS tree, a non-root vertex v is an articulation vertex exactly when v is not a leaf and some subtree below v has no back edge reaching above v. Such a subtree’s only connection to the rest of the graph runs through v. The slides name the cases: the root is a cut vertex if it has two or more children; a vertex whose child’s subtree sends no back edge up is a bridge cut vertex; a vertex whose subtree’s highest back edge reaches only its parent is a parent cut vertex. Leaves are never articulation vertices.
Tek bir DFS yeterlidir: O(n + m). DFS ağacında kök olmayan bir v tepesi, ancak ve ancak yaprak değilse ve altındaki bir alt ağacın v’nin üstüne ulaşan hiçbir geri kenarı yoksa eklem tepesidir. Böyle bir alt ağacın çizgenin geri kalanıyla tek bağlantısı v üzerinden geçer. Slaytlarda durumlar şöyle adlandırılır: kökün iki veya daha fazla çocuğu varsa kök bir kesme tepesidir; çocuğunun alt ağacı yukarıya hiçbir geri kenar göndermeyen tepe bir köprü kesme tepesidir; alt ağacının en yükseğe çıkan geri kenarı yalnızca ebeveynine ulaşan tepe bir ebeveyn kesme tepesidir. Yapraklar hiçbir zaman eklem tepesi değildir.
Lecture 12 · Topological sorting
Topolojik sıralama
A directed acyclic graph (DAG) has no directed cycles. A topological sort of a DAG is an ordering of its vertices such that every edge points from left to right. The slides’ example DAG orders as G, A, B, C, F, E, D. Every DAG has at least one topological sort, and only DAGs do: a cycle can never be laid out left-to-right.
Yönlü döngüsüz çizge (DAG), yönlü döngü içermez. Bir DAG’nin topolojik sıralaması, her kenar soldan sağa yönelecek biçimde tepelerinin sıralanmasıdır. Slaytlardaki örnek DAG, G, A, B, C, F, E, D biçiminde sıralanır. Her DAG’nin en az bir topolojik sıralaması vardır ve bu özellik yalnızca DAG’lerde bulunur: bir döngü hiçbir zaman soldan sağa yerleştirilemez.
Topological sorting is the tool for scheduling under precedence constraints: which tasks must come before which. Getting dressed is a DAG (socks before shoes, shirt before jacket). Another example from the slides: assembling DNA fragments, where overlaps constrain each fragment to lie left or right of others; build the DAG of constraints, and any topological sort is a consistent assembly, while a cycle proves the data contains errors.
Topolojik sıralama, öncelik kısıtları altında çizelgelemenin aracıdır: hangi işler hangilerinden önce gelmelidir? Giyinmek bir DAG’dir (çoraplar ayakkabılardan, gömlek ceketten önce). Slaytlardan başka bir örnek DNA parçalarının birleştirilmesidir: örtüşmeler, her parçanın diğerlerinin solunda veya sağında bulunmasını gerektirir. Kısıtların DAG’sini kurun; herhangi bir topolojik sıralama tutarlı bir birleştirme verirken bir döngü, veride hata bulunduğunu kanıtlar.
Via DFS. A directed graph is a DAG if and only if a DFS finds no back edges. And listing the vertices in reverse order of finishing (reverse of exit time) gives a topological sort. Why? Consider any edge (x, y) as it is met while exploring x:
DFS ile. Yönlü bir çizge, ancak ve ancak DFS hiçbir geri kenar bulmazsa DAG’dir. Tepeleri tamamlanma sırasının tersinde (çıkış zamanının tersinde) listelemek topolojik sıralamayı verir. Neden? x araştırılırken karşılaşılan herhangi bir (x, y) kenarını düşünün:
If y is undiscovered, we immediately DFS into y, so y finishes before x. Reversing finishing order puts x before y, as required.
y keşfedilmemişse hemen y üzerinde DFS’ye geçeriz; böylece y, x’ten önce tamamlanır. Tamamlanma sırasını ters çevirmek, gerektiği gibi x’i y’den önceye koyar.
If y is discovered but not finished, (x, y) is a back edge, impossible in a DAG.
y keşfedilmiş ama tamamlanmamışsa (x, y) bir geri kenardır; DAG’de bu mümkün değildir.
If y is already finished, it finished before x, so again x precedes y in the reversed order.
y zaten tamamlanmışsa x’ten önce tamamlanmıştır; dolayısıyla ters sırada yine x, y’den önce gelir.
Implementation: push each vertex onto a stack in process_vertex_late, warn if process_edge ever sees a back edge, and pop the stack at the end:
Gerçekleme: process_vertex_late içinde her tepeyi bir yığına ekleyin, process_edge bir geri kenar görürse uyarı verin ve sonunda yığını boşaltın:
topsort(G):
for each vertex i:
if not discovered[i]: dfs(G, i)
pop and print the stack (reverse finishing order)topsort(G):
for her i tepesi:
if not discovered[i]: dfs(G, i)
yığından çıkar ve yazdır (tamamlanma sırasının tersi)Via repeatedly removing a vertex with no incoming edges. Compute the in-degree (number of incoming edges) of every vertex. Put every vertex with in-degree 0 in a queue. Repeatedly take a vertex from the queue, output it, and decrement the in-degree of everything it points to; any vertex whose in-degree drops to 0 joins the queue. If fewer than n vertices come out, the graph had a cycle. Both methods are O(n + m).
Gelen kenarı olmayan bir tepeyi tekrar tekrar çıkararak. Her tepenin giriş derecesini (gelen kenar sayısını) hesaplayın. Giriş derecesi 0 olan her tepeyi kuyruğa koyun. Kuyruktan sırayla bir tepe alın, çıktıya yazın ve onun işaret ettiği tüm tepelerin giriş derecelerini bir azaltın; giriş derecesi 0’a düşen her tepe kuyruğa katılır. Çıktıda n’den az tepe varsa çizgede döngü vardır. Her iki yöntem de O(n + m)’dir.
Lecture 12 · Strongly connected components
Güçlü bağlı bileşenler
A directed graph is strongly connected if there is a directed path between every ordered pair of vertices. Its strongly connected components are the maximal subsets of vertices that are strongly connected among themselves. No vertex can belong to two maximal components, so the components partition the vertices. There is an elegant O(n + m) algorithm based on DFS (closely related to the articulation-vertex algorithm) that finds them; the slides show an example where eight vertices fall into components such as {1, 2, 3, 4, 8} and smaller ones.
Her sıralı tepe çifti arasında yönlü bir yol varsa yönlü çizge güçlü bağlıdır. Güçlü bağlı bileşenleri, kendi içlerinde güçlü bağlı olan maksimal tepe alt kümeleridir. Hiçbir tepe iki maksimal bileşene ait olamaz; dolayısıyla bileşenler tepeleri bölümlere ayırır. Bunları bulan, DFS’ye dayalı (eklem tepesi algoritmasıyla yakından ilişkili) zarif bir O(n + m) algoritması vardır. Slaytlarda sekiz tepenin {1, 2, 3, 4, 8} gibi bir bileşene ve daha küçük bileşenlere ayrıldığı bir örnek gösterilir.
Lecture 13 · Warm-up: lining up children who hate each other
Hazırlık: Birbirinden nefret eden çocukları sıraya dizme
You have n children and m statements “i hates j”. Child i must not stand behind j, or i will throw something.
n çocuk ve “i, j’den nefret ediyor” biçiminde m ifade var. i çocuğu j’nin arkasında durmamalıdır; aksi hâlde i bir şey fırlatacaktır.
One straight line in O(m + n): make a directed graph with an edge i → j for each “i hates j”, meaning i must stand in front of j. A valid line is a topological sort (Lecture 12), and a cycle means no line is possible.
O(m + n) sürede tek bir sıra: Her “i, j’den nefret ediyor” ifadesi için i’nin j’nin önünde durması gerektiğini belirten i → j kenarını içeren yönlü bir çizge kurun. Geçerli sıra bir topolojik sıralamadır (Ders 12); döngü varsa böyle bir sıra mümkün değildir.
Fewest rows, with i in a lower-numbered row than j whenever i hates j: put every child with no incoming edge in row 1, remove them, put the newly source-free children in row 2, and so on. Equivalently, each child’s row number is 1 plus the length of the longest path leading into it, which DFS computes on a DAG in O(m + n). The number of rows is the length of the longest path plus one.
i, j’den nefret ettiğinde i’nin daha küçük numaralı sırada bulunacağı en az sayıda sıra: Gelen kenarı olmayan bütün çocukları 1. sıraya koyup çıkarın; bunun ardından gelen kenarı kalmayanları 2. sıraya koyun ve sürdürün. Eşdeğer olarak, her çocuğun sıra numarası, ona gelen en uzun yolun uzunluğunun 1 fazlasıdır; DFS bunu bir DAG üzerinde O(m + n) sürede hesaplar. Sıra sayısı, en uzun yolun uzunluğunun bir fazlasıdır.
Lecture 13 · Weighted graphs and spanning trees
Ağırlıklı çizgeler ve kapsayan ağaçlar
Beyond BFS and DFS lies a whole world of algorithms for graphs whose edges carry weights (lengths, costs, times). The adjacency-list structure from Lecture 10 already has a weight field in each edge node, so nothing changes in the representation.
BFS ve DFS’nin ötesinde, kenarları ağırlık (uzunluk, maliyet, süre) taşıyan çizgeler için geniş bir algoritmalar dünyası vardır. Ders 10’daki komşuluk listesi yapısında her kenar düğümünde zaten bir weight alanı bulunduğundan, gösterimde hiçbir şey değişmez.
A tree is a connected graph with no cycles. A spanning tree of G is a subgraph that contains all the vertices of G and is a tree; it uses exactly n − 1 edges. A minimum spanning tree is a spanning tree whose edge weights add up to the smallest possible total. There may be several (think of a graph where all weights are equal).
Ağaç, döngüsü olmayan bağlı çizgedir. G’nin kapsayan ağacı, G’nin bütün tepelerini içeren ve bir ağaç olan alt çizgedir; tam n − 1 kenar kullanır. Minimum kapsayan ağaç, kenar ağırlıklarının toplamı mümkün olan en küçük değeri veren kapsayan ağaçtır. Birden fazla olabilir (bütün ağırlıkların eşit olduğu bir çizgeyi düşünün).
MSTs are taught because they arise everywhere, because the greedy algorithm provably works, and because clever data structures are needed to make it fast. The first MST algorithm dates from 1926.
MST’ler her yerde ortaya çıktıkları, açgözlü algoritmanın çalıştığı kanıtlanabildiği ve onu hızlandırmak için akıllıca veri yapıları gerektiği için öğretilir. İlk MST algoritması 1926’ya uzanır.
Applications. Building networks: connect a set of sites with the least total wire. Clustering: points joined by an MST fall into natural groups, and deleting the longest MST edges partitions the points into compact clusters (the textbook has a war story about this). The travelling salesman problem: for points in the plane, the optimal tour is at most twice the MST’s length, so walking around the MST gives a decent heuristic tour.
Uygulamalar. Ağ kurma: bir yerleşimler kümesini toplam kablo uzunluğu en az olacak biçimde bağlayın. Kümeleme: MST ile bağlanan noktalar doğal gruplara ayrılır; en uzun MST kenarlarını silmek noktaları sıkı kümelere böler (ders kitabında buna ilişkin bir uygulama öyküsü vardır). Gezgin satıcı problemi: düzlemdeki noktalar için optimal turun uzunluğu, MST uzunluğunun en fazla iki katıdır; dolayısıyla MST’nin çevresinde yürümek makul bir sezgisel tur verir.
Lecture 13 · Prim’s algorithm
Prim algoritması
Start with a single vertex as the tree. Repeatedly add the cheapest edge that connects a tree vertex to a non-tree vertex, together with that vertex. During the run, each vertex is in the tree, on the fringe (one edge from the tree) or unseen.
Ağaç olarak tek bir tepeyle başlayın. Ağaç içindeki bir tepeyi ağaç dışındaki bir tepeye bağlayan en ucuz kenarı, o tepeyle birlikte tekrar tekrar ekleyin. Çalışma sırasında her tepe ya ağacın içinde, ya sınırda (ağaçtan bir kenar uzakta) ya da henüz görülmemiş durumdadır.
Prim(G):
pick any start vertex s; tree = {s}
while some vertex is not in the tree:
pick the minimum-weight edge (x, y) with x in the tree and y not
add (x, y) and y to the treePrim(G):
herhangi bir s başlangıç tepesi seç; tree = {s}
while ağaç dışında bir tepe varsa:
x ağaçta, y ağaç dışında olacak şekilde
minimum ağırlıklı (x, y) kenarını seç
(x, y) kenarını ve y’yi ağaca ekleThis always yields a spanning tree, since adding an edge to a new vertex can never close a cycle. But is it a minimum one?
Bu yöntem her zaman kapsayan ağaç üretir; çünkü yeni bir tepeye kenar eklemek asla bir döngüyü kapatamaz. Peki bu ağaç minimum mudur?
Proof that Prim is correct (by contradiction). Suppose Prim fails on some graph. Then there is a first edge (x, y) it adds such that the partial tree can no longer be extended to any MST. Take an actual MST. It does not contain (x, y), but being connected it contains a path from x to y, and that path must at some point leave the partial tree along some edge (v₁, v₂) with exactly one end inside. Swap: remove (v₁, v₂) and add (x, y). The result is still a spanning tree, and since Prim chose (x, y) as the cheapest edge leaving the partial tree, w(x, y) ≤ w(v₁, v₂). If strictly less, the “MST” was not minimum: contradiction. If equal, the new tree is also an MST that contains (x, y), so adding (x, y) was not a fatal mistake after all: contradiction again.
Prim’in doğruluğunun kanıtı (çelişki yoluyla). Prim’in bir çizgede başarısız olduğunu varsayın. O zaman, eklendikten sonra kısmi ağacın artık hiçbir MST’ye genişletilemediği ilk bir (x, y) kenarı vardır. Gerçek bir MST alın. Bu ağaç (x, y)’yi içermez; fakat bağlı olduğu için x’ten y’ye bir yol içerir. Bu yol bir noktada, uçlarından tam biri içeride olan bir (v₁, v₂) kenarı üzerinden kısmi ağacın dışına çıkmalıdır. Değiş tokuş yapın: (v₁, v₂)’yi çıkarın, (x, y)’yi ekleyin. Sonuç hâlâ kapsayan ağaçtır. Prim, (x, y)’yi kısmi ağaçtan çıkan en ucuz kenar olarak seçtiğinden w(x, y) ≤ w(v₁, v₂)’dir. Kesin olarak küçükse “MST” minimum değildir: çelişki. Eşitse yeni ağaç da (x, y)’yi içeren bir MST’dir; dolayısıyla (x, y)’yi eklemek geri dönülmez bir hata değildir: yine çelişki.
Cost. The naive implementation rescans all edges for the cheapest fringe edge each round: n rounds × O(m) = O(nm). The standard implementation keeps, for every non-tree vertex w, distance[w] = the weight of the cheapest edge from the tree to w, and parent[w] = the tree endpoint of that edge:
Maliyet. Naif gerçekleme, her turda en ucuz sınır kenarı için bütün kenarları yeniden tarar: n tur × O(m) = O(nm). Standart gerçekleme, ağaç dışındaki her w tepesi için distance[w] = ağaçtan w’ye giden en ucuz kenarın ağırlığı ve parent[w] = bu kenarın ağaç içindeki ucu bilgilerini tutar:
prim(G, start):
for every vertex i: intree[i] = false; distance[i] = ∞; parent[i] = −1
distance[start] = 0; v = start
while v is not in the tree:
intree[v] = true (v joins the tree via edge (parent[v], v))
for each edge (v, w) with weight wt:
if not intree[w] and distance[w] > wt:
distance[w] = wt; parent[w] = v
v = the non-tree vertex with the smallest distance[]prim(G, start):
for her i tepesi:
intree[i] = false; distance[i] = ∞; parent[i] = −1
distance[start] = 0; v = start
while v ağaçta değilse:
intree[v] = true (v, (parent[v], v) kenarıyla ağaca katılır)
for ağırlığı wt olan her (v, w) kenarı:
if not intree[w] and distance[w] > wt:
distance[w] = wt; parent[w] = v
v = distance[] değeri en küçük olan ağaç dışı tepeEach round costs O(n) to update the neighbours’ distances and O(n) to scan for the minimum, and there are n rounds: O(n²). That is independent of m, which makes Prim ideal for dense graphs (where m is about n² anyway; every edge must be looked at, so O(n²) is optimal there).
Her turda komşuların uzaklıklarını güncellemek O(n), minimumu tarayarak bulmak O(n) sürer ve n tur vardır: O(n²). Bu maliyet m’den bağımsızdır; dolayısıyla Prim yoğun çizgeler için idealdir (buralarda m zaten yaklaşık n²’dir; her kenara bakılmak zorunda olduğundan O(n²) optimaldir).
Lecture 13 · Kruskal’s algorithm
Kruskal algoritması
On sparse graphs a different greedy order wins. Kruskal considers all the edges from cheapest to most expensive, and adds each one to the growing forest unless it would create a cycle:
Seyrek çizgelerde farklı bir açgözlü sıra üstün gelir. Kruskal bütün kenarları en ucuzdan en pahalıya doğru ele alır ve döngü oluşturmadığı sürece her birini büyüyen ormana ekler:
Kruskal(G):
sort the edges by weight
for each edge (x, y) in that order:
if x and y are in different trees of the forest:
add (x, y), merging the two treesKruskal(G):
kenarları ağırlıklarına göre sırala
for bu sıradaki her (x, y) kenarı:
if x ve y ormanın farklı ağaçlarındaysa:
iki ağacı birleştirerek (x, y)’yi ekleThe slides run both algorithms on the same 7-vertex graph and, as they must, arrive at the same minimum weight, though the two algorithms add edges in different orders.
Slaytlarda iki algoritma aynı 7 tepeli çizgede çalıştırılır. Kenarları farklı sıralarla ekleseler de, gerektiği gibi aynı minimum ağırlığa ulaşırlar.
Proof that Kruskal is correct. Again suppose there is a first edge (x, y) that Kruskal adds which cannot be extended to an MST. When it was added, x and y were in different trees, so any true MST plus (x, y) contains a cycle through (x, y). Some other edge of that cycle joins the same two pieces, and it was still available when Kruskal chose (x, y); since Kruskal takes edges in increasing order, that edge is at least as heavy. Delete it from the MST and keep (x, y): a spanning tree no heavier than the MST, containing (x, y). Contradiction.
Kruskal’ın doğruluğunun kanıtı. Yine Kruskal’ın eklediği, bir MST’ye genişletilemeyen ilk (x, y) kenarı bulunduğunu varsayın. Eklendiğinde x ve y farklı ağaçlardadır; dolayısıyla herhangi bir gerçek MST’ye (x, y) eklemek, (x, y)’den geçen bir döngü oluşturur. Bu döngünün başka bir kenarı aynı iki parçayı birleştirir ve Kruskal (x, y)’yi seçtiğinde hâlâ kullanılabilir durumdadır. Kruskal kenarları artan sırayla aldığı için bu kenar en az onun kadar ağırdır. Onu MST’den silip (x, y)’yi tutun: MST’den daha ağır olmayan ve (x, y)’yi içeren bir kapsayan ağaç elde edilir. Çelişki.
Cost. Sorting the edges costs O(m log m). The question is how to test “are x and y already connected?” quickly. A BFS through the forest per edge costs O(n), for O(mn) in total. Better: notice that Kruskal is really maintaining connected components, and an edge closes a cycle exactly when both endpoints are already in the same component. So we need a structure supporting two operations:
Maliyet. Kenarları sıralamak O(m log m) sürer. Soru, “x ve y zaten bağlı mı?” sınamasının nasıl hızla yapılacağıdır. Her kenar için ormanda BFS yapmak O(n), toplamda O(mn) maliyetlidir. Daha iyi yaklaşım: Kruskal’ın aslında bağlı bileşenleri tuttuğunu ve bir kenarın ancak iki ucu zaten aynı bileşendeyse döngü kapattığını fark edin. İki işlemi destekleyen bir yapıya ihtiyacımız var:
same_component(v₁, v₂): are these two vertices in the same component?
same_component(v₁, v₂): Bu iki tepe aynı bileşende mi?
merge_components(C₁, C₂): join two components into one.
merge_components(C₁, C₂): İki bileşeni tek bileşende birleştir.
With both in O(log n), Kruskal runs in O(m log m). (Is O(m log n) better than O(m log m)? No: m ≤ n², so log m ≤ 2 log n, and they are the same within a constant.)
Her iki işlem O(log n) olduğunda Kruskal O(m log m) sürede çalışır. (O(m log n), O(m log m)’den daha mı iyidir? Hayır: m ≤ n² olduğundan log m ≤ 2 log n’dir ve sabit çarpan dışında aynıdırlar.)
Lecture 13 · Union-find
Birleştir-bul (union-find)
Union-find maintains a collection of disjoint sets with two operations: Find(i) returns a label identifying the set containing i, and Union(i, j) merges the sets containing i and j. Each set is stored as a tree of parent pointers whose root is the set’s label; an array p[i] holds the parent of element i (with p[root] = root), and size[i] holds the number of elements under i.
Union-find, iki işlemle ayrık kümeler topluluğunu tutar: Find(i), i’yi içeren kümeyi tanımlayan etiketi döndürür; Union(i, j), i ve j’yi içeren kümeleri birleştirir. Her küme, kökü kümenin etiketi olan bir ebeveyn işaretçileri ağacı olarak saklanır. p[i] dizisi i elemanının ebeveynini tutar (p[root] = root); size[i] ise i’nin altındaki eleman sayısını tutar.
find(x):
if p[x] == x: return x
return find(p[x]) (walk up to the root)
union(a, b):
r1 = find(a); r2 = find(b)
if r1 == r2: return (already in the same set)
hang the smaller tree under the larger one's root, and add the sizesfind(x):
if p[x] == x: return x
return find(p[x]) (köke doğru yukarı yürü)
union(a, b):
r1 = find(a); r2 = find(b)
if r1 == r2: return (zaten aynı kümedeler)
küçük ağacı büyük ağacın kökünün altına bağla
ve boyutları toplaWhy hang the smaller under the larger? Without that rule the tree can degenerate into a chain: union(1, 2), union(2, 3), … followed by repeated find(1) costs O(n) each. With the rule, a tree’s height only grows when two trees of equal height are merged, so a tree of height h contains at least 2ʰ elements. Hence after any sequence of unions every tree of k nodes has height at most ⌊lg k⌋ (proof by induction on k: merging trees of heights d₁ and d₂ with k₁ ≥ k₂ nodes gives height at most max(d₁, d₂ + 1) ≤ ⌊lg(k₁ + k₂)⌋). So both operations cost O(log n), which is all Kruskal needs.
Küçüğü neden büyüğün altına bağlarız? Bu kural olmadan ağaç bir zincire dönüşebilir: union(1, 2), union(2, 3), … ardından tekrarlanan find(1) çağrılarının her biri O(n) sürer. Kural uygulandığında bir ağacın yüksekliği yalnızca eşit yükseklikte iki ağaç birleştirildiğinde artar; dolayısıyla yüksekliği h olan bir ağaç en az 2ʰ eleman içerir. Böylece herhangi bir birleştirme dizisinden sonra k düğümlü her ağacın yüksekliği en fazla ⌊lg k⌋ olur (k üzerinde tümevarımla kanıt: yükseklikleri d₁ ve d₂, düğüm sayıları k₁ ≥ k₂ olan ağaçların birleştirilmesi en fazla max(d₁, d₂ + 1) ≤ ⌊lg(k₁ + k₂)⌋ yükseklik verir). Dolayısıyla iki işlem de O(log n) maliyetlidir; Kruskal’ın ihtiyacı olan budur.
Path compression. The ideal tree has depth 1, with every element pointing straight at the root. Since a find walks up a path anyway, why not re-point every node on that path directly to the root as we go? With this trick a sequence of n operations costs O(n α(n)), where α is the inverse Ackermann function, a function that grows so slowly that α(number of atoms in the universe) = 5. For every practical purpose union-find with path compression is linear.
Yol sıkıştırma. İdeal ağacın derinliği 1’dir ve her eleman doğrudan kökü gösterir. find zaten bir yol boyunca yukarı yürüdüğüne göre, ilerlerken o yoldaki her düğümü doğrudan köke yönlendirmeyelim mi? Bu teknikle n işlemden oluşan bir dizinin maliyeti O(n α(n)) olur. Buradaki α, ters Ackermann fonksiyonudur; o kadar yavaş büyür ki α(evrendeki atom sayısı) = 5’tir. Pratikte her amaç için, yol sıkıştırmalı union-find doğrusaldır.
Begin with the easy tests and the worked example. Remaining questions provide repeated teaching practice; hard questions are optional depth. Some tests revisit earlier prerequisites. Answers stay visible.
Graph W is undirected: A–B (4), A–C (1), B–C (2), B–D (5), C–D (8), D–E (3), C–E (10). Parenthesised numbers are edge weights.
Practice with answers
10 test questions · 18 written questions · 28 total
Each question is followed by its answer and explanation. Hard questions also include a starting hint and smaller reasoning steps.
Test questions
Choose one option. Cost questions state their model and whether the bound is tight, expected or worst-case. Here lg means log₂, and heap positions start at 1.
Question 1 · easy
A tree with 12 vertices has how many edges?
Answer: option A. Always n − 1.
Question 2 · easy
A directed graph has a topological sort if and only if it is:
Answer: option B. A cycle can never be laid out left to right.
Question 3 · easy · course question
Kruskal rejects an edge when it:
Answer: option B. That edge would form a cycle with the accepted edges.
Question 4 · easy · course question
An MST minimises:
Answer: option B. Minimum total connecting weight is different from a shortest-path objective.
Question 5 · medium
On the undirected graph with edges 1–2, 1–3, 2–4, 3–4, 4–5, run recursive DFS from 1, visiting neighbours in increasing order. What is the discovery order?
Answer: option A. From 4 the smallest undiscovered neighbour is 3, so 3 comes before 5.
Question 6 · medium
The weight of the minimum spanning tree of graph W is:
Graph W is undirected: A–B (4), A–C (1), B–C (2), B–D (5), C–D (8), D–E (3), C–E (10). Parenthesised numbers are edge weights.
Answer: option B. Edges A–C (1), B–C (2), D–E (3) and B–D (5).
Question 7 · medium
Running Kruskal’s algorithm on graph W, the first edge REJECTED is:
Graph W is undirected: A–B (4), A–C (1), B–C (2), B–D (5), C–D (8), D–E (3), C–E (10). Parenthesised numbers are edge weights.
Answer: option A. After A–C, B–C and D–E are accepted, A–B (weight 4) would close the cycle A–C–B.
Question 8 · medium
In recursive DFS of a simple undirected graph, classify each undirected edge once. Every non-tree edge connects a descendant to an ancestor, so it is a:
Answer: option A. DFS explores an entire active branch before finishing it. A non-tree edge therefore joins a descendant to an ancestor. The reverse direction is the same undirected edge, not a separate forward edge.
Question 9 · medium
For the DAG with edges 1→2, 1→3, 2→4, 3→4, which is a valid topological order?
Answer: option C. In (a) 2 precedes 1, in (b) 4 precedes 2, and (d) is fully reversed.
Question 10 · medium
The articulation vertices of the path 1–2–3–4–5 are:
Answer: option B. Removing any interior vertex disconnects the path; removing an endpoint does not.
Written questions
Read each question together with its explanation, trace or proof. Numbering continues from the test questions.
Question 11 · easy
Same graph, DFS from vertex 1 with neighbours in increasing order. In what order are vertices discovered?
Use the undirected graph with edges 1–2, 1–3, 2–4, 3–4, 4–5; visit neighbours in increasing order.
Answer & reasoning
1, 2, 4, 3, 5: from 1 go to 2, from 2 to 4, from 4 to 3 (its smallest undiscovered neighbour), 3 has nothing new, back to 4, then 5.
Question 12 · easy
How many edges does a tree with n vertices have? What happens if you add one more edge between two of its vertices?
Answer & reasoning
n − 1; the new edge closes exactly one cycle, since the tree already contains one path between its endpoints.
Question 13 · easy
List every topological sort of the DAG with edges A→B, B→C and A→C.
Answer & reasoning
only A, B, C. A must come before B and C, and B before C.
Question 14 · easy
What is the weight of the minimum spanning tree of a triangle whose edges weigh 1, 2 and 3?
Answer & reasoning
3, using the edges of weight 1 and 2. A spanning tree of three vertices has exactly two edges.
Question 15 · easy
What is a DAG, and what kind of vertex must every DAG contain?
Answer & reasoning
A DAG is a directed graph with no directed cycle. Every nonempty finite DAG has a vertex with in-degree zero: otherwise repeatedly follow an incoming edge backwards; finiteness forces a repeated vertex and hence a directed cycle.
Question 16 · medium
Run Prim’s algorithm on graph W starting from A. Give the order in which edges are added and the total weight.
Answer & reasoning
A–C (1), then C–B (2), then B–D (5) (cheaper than C–D at 8 and C–E at 10), then D–E (3). Total 1 + 2 + 5 + 3 = 11.
Question 17 · medium
Run Kruskal’s algorithm on graph W. List the edges in the order considered and say which are accepted or rejected.
Answer & reasoning
A–C (1) accepted; B–C (2) accepted; D–E (3) accepted; A–B (4) rejected, it would close the cycle A–C–B; B–D (5) accepted, joining {A, B, C} to {D, E}; four edges are now in, so C–D (8) and C–E (10) are not needed. Total 11, the same tree as Prim’s.
Question 18 · medium
Does a minimum spanning tree always contain the shortest path between every pair of vertices? Prove or give a counterexample.
Answer & reasoning
no. Triangle A–B (2), B–C (2), A–C (3): the MST is A–B, B–C with weight 4. The shortest path from A to C in the graph is the direct edge of weight 3, but in the tree the path A–B–C has weight 4.
Question 19 · medium
Run the DFS-based topological sort on the DAG with edges 1→2, 1→3, 2→4, 3→4, 4→5, 3→5, starting from 1 and exploring neighbours in increasing order. Give the finishing order and the resulting topological order.
Answer & reasoning
1 → 2 → 4 → 5; 5 finishes, 4 finishes, 2 finishes; back at 1, go to 3; its neighbours 4 and 5 are done, so 3 finishes; then 1. Finishing order 5, 4, 2, 3, 1; reversed: 1, 3, 2, 4, 5. Check: every edge points left to right.
Question 20 · medium
Run the in-degree-zero-removal method on the same DAG, with the queue processed in increasing order.
Use the DAG with edges 1→2, 1→3, 2→4, 3→4, 4→5, 3→5.
Answer & reasoning
in-degrees are 1:0, 2:1, 3:1, 4:2, 5:2. Output 1, reducing 2 and 3 to zero. Output 2 (4 drops to 1), output 3 (4 drops to 0, 5 to 1), output 4 (5 drops to 0), output 5. Order 1, 2, 3, 4, 5, also valid; a DAG may have several topological sorts.
Question 21 · medium
How do you test whether a directed graph contains a cycle? Give two methods.
Answer & reasoning
run DFS and watch for a back edge (an edge to a vertex that is discovered but not yet finished); or run the in-degree removal method and check whether fewer than n vertices come out.
Question 22 · medium
Which vertices are articulation vertices in the path 1–2–3–4? In the cycle 1–2–3–4–1?
Answer & reasoning
in the path, 2 and 3 (removing either disconnects it); the endpoints are not. In the cycle, none: removing any single vertex leaves a path, still connected.
Question 23 · medium
Process the edges (1, 2), (3, 4), (2, 3), (5, 6), (1, 4) with union-find, in that order. What are the sets after each step, and which edge would Kruskal reject?
Answer & reasoning
{1,2} {3} {4} {5} {6}; then {1,2} {3,4} {5} {6}; then {1,2,3,4} {5} {6}; then {1,2,3,4} {5,6}; finally (1, 4): find(1) = find(4) already, so nothing merges; Kruskal would reject this edge as cycle-forming.
Question 24 · hard · optional challenge
Prove that in a depth-first search of an undirected graph every non-tree edge is a back edge, so there are no forward or cross edges.
In simpler words: Explain why an undirected DFS edge cannot jump between finished branches.
Starting hint: DFS examines every neighbour before finishing a vertex.
Answer & reasoning
Step by step
- Let x be discovered before y, with edge {x,y}.
- If y is still undiscovered when this edge is examined, DFS descends toward y before finishing x.
- Thus the two endpoints lie on an ancestor–descendant chain. A non-tree edge is viewed upward as a back edge; its reverse is not a second edge.
For an undirected edge {x,y}, suppose x is discovered first. DFS cannot finish x while y remains undiscovered, because it examines every neighbour before finishing; that edge would discover y. Thus y is discovered while x is active and lies in its DFS subtree. Every undirected edge therefore connects ancestor and descendant. If it is not the discovery edge, classify it as a back edge when viewed from descendant toward ancestor. The reverse view is the same undirected edge, not a separate forward edge.
Question 25 · hard · optional challenge
Prove that if all edge weights in a connected graph are distinct, the minimum spanning tree is unique.
In simpler words: Show that two different cheapest trees cannot coexist with distinct weights.
Starting hint: Exchange one carefully chosen edge between the two trees.
Answer & reasoning
Step by step
- Choose the lightest edge e that appears in exactly one proposed MST, say T₁.
- Adding e to T₂ creates a cycle. That cycle contains an edge f outside T₁; otherwise T₁ would contain a cycle too.
- Because weights are distinct and e was lightest among differing edges, f is heavier. Replacing f by e makes T₂ cheaper, a contradiction.
suppose T₁ ≠ T₂ are both minimum. Let e be the lightest edge that is in one of them but not both, say e ∈ T₁ \ T₂. Adding e to T₂ closes a cycle, which must contain an edge f not in T₁ (otherwise T₁ would contain a cycle). f is in T₂ but not T₁, and since e was the lightest such edge and weights are distinct, w(f) > w(e). Then T₂ − f + e is a spanning tree lighter than T₂, contradicting minimality.
Question 26 · hard · optional challenge
Show that a minimum spanning tree also minimises the heaviest edge among all spanning trees.
In simpler words: Show that an MST also makes its largest edge as small as possible.
Starting hint: Remove a largest MST edge and look at the two resulting parts.
Answer & reasoning
Step by step
- Call that edge weight B. Suppose another spanning tree uses only edges lighter than B.
- That other tree must cross the cut between the two parts using some edge f of weight below B.
- Substitute f for the removed edge: the original MST would become cheaper. The contradiction proves no spanning tree has a smaller bottleneck.
suppose some spanning tree S has maximum edge weight w less than the MST’s maximum edge weight, and let e be an MST edge with w(e) > w. Removing e splits the MST into two parts; S is connected, so it contains an edge f crossing between those parts with w(f) ≤ w < w(e). Then MST − e + f is a lighter spanning tree, contradicting minimality. (Kruskal’s order makes this vivid: it never adds an edge heavier than necessary to connect.)
Question 27 · hard · optional challenge
You are given a graph and its minimum spanning tree T. Describe how to find the second-best spanning tree (the lightest spanning tree different from T), and give a running time.
In simpler words: Find the cheapest tree that differs from a given MST.
Starting hint: Add one unused edge, then remove an edge from its newly formed cycle.
Answer & reasoning
Step by step
- Each non-tree edge e creates exactly one cycle with T.
- Remove a heaviest edge on that cycle to get the cheapest tree obtainable with this e. Record its weight, even if tied with T.
- Try every non-tree edge and keep the best. A standard tree-exchange argument guarantees a best alternative among these swaps. Finding each tree path by a scan costs O(n), for O(nm) overall.
Assume a connected graph and seek the lightest spanning tree different from the given MST T; it may have equal weight. There exists a best alternative obtainable by one swap. For each non-tree edge e, find the unique tree path between its endpoints, remove a maximum-weight edge f on that path, and evaluate T − f + e. Keep the cheapest alternative. Scanning each path takes O(n), giving O(nm). If there is no non-tree edge, no alternative spanning tree exists. “Strictly heavier second-best” is a different problem when weights tie.
Question 28 · hard · optional challenge
Prove by induction that every tree with n vertices has exactly n − 1 edges.
In simpler words: Remove one leaf, use the smaller tree, then add the leaf back.
Starting hint: A finite tree with at least two vertices has a leaf.
Answer & reasoning
Step by step
- For one vertex there are zero edges, matching n−1.
- Remove a leaf and its single incident edge. Remaining vertices still form a tree.
- By induction that smaller tree has n−2 edges. Restoring one edge gives n−1.
base case n = 1: no edges. For n ≥ 2, a tree has a leaf (a vertex of degree 1): start anywhere and keep walking along unused edges; you cannot revisit a vertex because there are no cycles, so the walk ends at a vertex with no other edge, a leaf. Remove that leaf and its edge; the rest is still connected and acyclic, so it is a tree with n − 1 vertices and, by the hypothesis, n − 2 edges. Adding the leaf’s edge back gives n − 1.