Köprü, kaldırılması grafın bağlı bileşen sayısını artıran kenara denir. Bir kenar ancak ve ancak hiçbir basit çevrim üzerinde yer almıyorsa köprüdür: eğer bir kenar bir çevrime aitse, bu kenar kaldırıldıktan sonra kenarın uç noktaları çevrimin geri kalan kısmı üzerinden bağlı kalır.
Derinlik öncelikli arama (DFS) başlatalım. Graftaki her kenar ya arama ağacının bir kenarı ya da geri kenar (back edge) olacaktır. Geri kenar, uç noktaları arasındaki ağaç yolu ile birlikte bir çevrim oluşturur, bu yüzden köprü olamaz. Dolayısıyla, yalnızca arama ağacının kenarları köprü olabilir.
Arama sırasında her tepesi için iki etiket hesaplayalım:
— tepesine giriş zamanı, yani tepesinin ilk ziyaret edildiği sıra numarası;
— tepesinden, ağaç kenarları boyunca torunlarına inerek ve ardından en fazla bir geri kenar geçerek ulaşılabilecek en küçük giriş zamanı.
'nun 'nin çocuğu olduğu (v, to) ağaç kenarını ele alalım. Üç durum mümkündür:
up[to] < d[v]. alt ağacının bir tepesinden, 'nin kendi atasına giden bir geri kenar vardır. Başka bir deyişle, 'den veya onun bir torunundan 'nin bir atasına giden bir geri kenar bulunması, ancak ve ancak 'nin up[to] < d[v] olan bir çocuğu varsa gerçekleşir.
up[to] = d[v]. alt ağacı, tam olarak tepesine giden bir geri kenar içerir.
up[to] > d[v]. alt ağacının hiçbir tepesi, (v, to) kenarından geçmeden tepesine veya onun atalarına ulaşamaz. Dolayısıyla, (v, to) kenarı bir köprüdür.
İlk iki durumda (v, to) kenarı bir çevrim üzerinde yer alır ve köprü değildir. Böylece şu kriteri elde ederiz:
Teorem. 'nun 'nin çocuğu olduğu (v, to) arama ağacı kenarı, ancak ve ancak up[to] > d[v] ise köprüdür.
Derinlik öncelikli arama O(n + m) zamanda çalışır. Bulunan köprülerin numaraları çıktıdan önce sıralanır.
Örnek
Örnekte verilen graf aşağıdaki gibidir. Her kenarın yanında girdi verisindeki numarası belirtilmiştir:
tepesinden derinlik öncelikli aramayı başlatalım. Her tepenin komşularını girdi verisindeki kenarların görünme sırasına göre inceleyeceğiz. Gezinti, tepeleri 1, 2, 3, 4, 5, 6 sırasıyla ziyaret eder. Arama ağacının kenarları 1, 2, 3, 5 ve numaralı kenarlar olur, 4 = (3, 1) ve 6 = (6, 4) kenarları ise geri kenar olur.
Ağacın tüm kenarları için up[to] > d[v] koşulunu kontrol edelim:
Kenar (v, to) | Numara | Sonuç | ||
1 | 1 | 1 | up[2] = d[1], köprü değil | |
2 | 1 | 2 | up[3] < d[2], köprü değil | |
3 | 4 | 3 | up[4] > d[3], köprü | |
5 | 4 | 4 | up[5] = d[4], köprü değil | |
7 | 4 | 5 | up[6] < d[5], köprü değil |
Böylece, grafta tam olarak bir köprü vardır — ve tepelerini birleştiren numaralı kenar.
Algoritmanın Gerçeklenmesi
Grafı bir komşuluk listesi olarak saklıyoruz. Her tepesi için içinde (to, id) çiftleri kayıtlıdır; burada — komşu tepe, — (v, to) kenarının girdi verisindeki numarasıdır. Kenar numarası iki sebepten gereklidir: gezinti sırasında geldiğimiz kenardan geri dönmemek için ve köprüleri tepe çiftleri olarak değil, girdi kenarlarının numaraları olarak çıktı olarak vermek için.
ve dizileri ve etiketlerini içerir. 'nin sıfır değeri, tepesinin henüz ziyaret edilmediği anlamına gelir. bridges dizisinde köprü olan kenarların numaralarını topluyoruz.
vector<vector<pair<int, int> > > g;
vector<int> d, up, bridges;dfs fonksiyonu, tepesinden derinlik öncelikli aramayı gerçekleştirir. parametresi, tepesine gelmemizi sağlayan kenarın numarasıdır. Arama ağacının kökü için böyle bir kenar yoktur, bu yüzden varsayılan olarak p = -1'dir. Bir tepeye girerken ve 'ye geçerli zamanı atarız.
void dfs(int v, int p = -1)
{
d[v] = up[v] = timer++;tepesinden çıkan tüm (v, to) kenarlarını geziyoruz. numaralı kenarı atlıyoruz: bu kenardan 'ye geldik ve ebeveyne gider.
for (auto edge : g[v])
{
int to = edge.first;
int id = edge.second;
if (id == p) continue;Eğer tepesi zaten ziyaret edilmişse (d[to] \ne 0), o zaman (v, to) — bir geri kenardır. 'yi 'ya giriş zamanını dikkate alarak yeniden hesaplıyoruz.
if (d[to] != 0)
up[v] = min(up[v], d[to]);Aksi halde (v, to) — bir ağaç kenarıdır. tepesinden kenar numarasını vererek aramayı başlatıyoruz. Geri döndükten sonra 'yi üzerinden yeniden hesaplıyoruz.
else
{
dfs(to, id);
up[v] = min(up[v], up[to]);Eğer up[to] > d[v] ise, alt ağacından (v, to) kenarını kullanmadan 'ye veya atalarına ulaşmak mümkün değildir, bu yüzden numaralı kenar bir köprüdür.
if (up[to] > d[v]) bridges.push_back(id);
}
}
}Programın ana bölümü. Girdi grafını okuyoruz. Her kenarını, numarasıyla birlikte her iki tepenin komşuluk listelerine ekliyoruz.
scanf("%d %d", &n, &m);
g.resize(n + 1);
for (i = 1; i <= m; i++)
{
scanf("%d %d", &a, &b);
g[a].push_back(make_pair(b, i));
g[b].push_back(make_pair(a, i));
}Etiket dizilerini başlatıyoruz.
d.resize(n + 1);
up.resize(n + 1);değişkeni, aramanın geçerli zamanını tutar.
timer = 1;Graf bağlı olmayabilir, bu yüzden henüz ziyaret edilmemiş her tepeden derinlik öncelikli aramayı başlatıyoruz.
for (i = 1; i <= n; i++)
if (d[i] == 0) dfs(i);Köprü numaralarını artan sırada sıralıyoruz.
sort(bridges.begin(), bridges.end());Köprü sayısını, bir sonraki satırda ise numaralarını yazdırıyoruz.
printf("%d\n", bridges.size());
for (i = 0; i < bridges.size(); i++)
printf("%d ", bridges[i]);
printf("\n");