»
Y
A
N
M
E
N
Ü
«
Dijkstra’nın En Kısa Yol Algoritması
ibrahim dursun @ 18 Mayıs 2008 19:06

Dijkstra’nın en kısa yol algoritması (DJK) ya da Dijkstra’nın deyişiyle “benim en kısa yol algoritmam” graf üzerinde bir noktadan diğer tüm noktalara en kısa yolu çıkarır ve bunu n noktalı bir grafta en kötü olarak O(n*n) zorlukta tamamlar. Daha önceden bahsettiğim Floyd-Warshall algoritması ise tüm noktalar arasındaki en kısa mesafeyi O(nnn) zorlukta çıkarıyordu. Dikkat ettiyseniz birisi yolu diğeri mesafeyi çıkarıyor. Algoritmanın aşamalarını ayrı ayrı değerlendirdiğimizde anlamamız daha kolay olacaktır. Örnek olarak 10×10′luk bir tahtanın üzerinde olduğumuzu düşünün. İlerlemeye (0,0) noktasından başlayıp, (9,9) noktasına ulaşmaya çalışacağız. Tahta üzerindeki her karede bir sayı olsun ve biz o kare üzerine geldiğimizde orada yazılı olan sayı kadar saniye beklemek zorunda kalalım. Bu durumda hedefimiz olan (9,9) noktasına en kısa zamanda nasıl ulaşırız? Başlangıç noktasının diğer tüm noktalara olan mesafesini tutmak için bir tane Distance dizisine ve gittiğimiz yolu hatırlamamız için ise Previous dizisine ihtiyacımız olacak. Dizinin her elemanı tahta üzerindeki bir koordinata karşılık gelecektir. Örneğin (3,8) noktasının indeksini 3*Genişlik + 8 = 38 olarak hesaplayabiliriz. Başlangıç noktasının üzerinde olduğumuz için buraya olan mesafemiz 0, diğer tüm noktalara olan mesafemiz ise sonsuz olsun:

Distances = new int[W*H];
Previous = new int[W*H];
for (int i = 0; i < W*H; i++)
{
  Distances[i] = INF;
  Previous[i] = -1;
}
Distances[0] = 0;

Algoritmadaki her adımda kaynak bir nokta seçip, bu noktadan komşu noktalara gitmenin bize maliyetini hesaplayacağız ve bu nokta için bir daha hesap yapmayacağız. Bu yüzden hangi noktalar üzerinde çalıştığımızı da bir şekilde tutmamız gerekiyor. Bunun için bir liste kullanabilirsiniz. Gidilen noktaları listeden çıkarıp geriye kalanlar içinde hedefe en yakın olanı seçerek devam edebilirsiniz. Liste boşalana kadar bu işleme devam edip tüm noktalar için hesaplamalarımızı yapmış oluruz:

var list = new List<int>();
for (int i = 0; i < W * H; i++) list.Add(i);
while(list.Count > 0)
{
  //hesaplamalar
}

Çapraz hareketler yapamayacağımız için her kare üzerinde gidilebilecek 4 yön var: sağa, sol, yukarı ve aşağı.

private static readonly int[] dx = { 1, 0, -1, 0 };
private static readonly int[] dy = { 0, 1, 0, -1 };

Tüm bu yönlere hareket etmenin bizim için maliyeti nedir hesaplayalım ve Distance dizisinde bu noktaya karşılık gelen indekse bu değeri yazalım.

while (list.Count > 0)
{
  // hedefe en yakın noktanın indeksini al
  int bi = GetClosestIndex(list);
  int cx = bi % W, cy = bi / W; 

  for (int i = 0; i < dx.Length; i++ )
  {
    int nx = cx + dx[i];
    int ny = cy + dy[i];
    // dışarıda kalıyorsa çık
    if (0 > nx || nx >= W || 0 > ny || ny >= H) continue;
    int ci = ny * W + nx;
    // bi'den ci'ye gidersek maliyeti nedir
    int cost = CostOfGoing(bi,ci);
    int alt = Distances[bi] + cost;
    //daha kazançlıysa burdan gidelim
    if (Distances[ci] > alt)
    {
      Distances[ci] = alt;
      Previous[ci] = bi;
    }
  }
}

Bu işlem tüm noktalar için yapıldığında hedef noktanın kaynak noktaya olan mesafesi Distance[Hedef] olacaktır. Gidilecek yol ise şöyle çıkarılabilir:

int target = 99;
List path = new List<int>();
while(Previous[target]!=-1)
{
    path.Add(target);
    target = Previous[target];
}
path.Reverse();

Dijkstra Algoritmasının en güçlü taraflarından birisi, çalışma süresinin kısa olmasıdır. Örnekte, kaynaktan başlayarak diğer tüm noktalara olan mesafeyi hesaplıyoruz sonra hedefimize giden yolu çıkarıyoruz. Bunu tüm noktalar için yapmak zorunda değiliz; eğer isterseniz algoritma içinde bi = target olduğu zaman algoritmayı sonlandırarak çalışma süresini de kısaltmış olabiliriz. İkinci olarak ise; bir noktanın diğer noktaya olan mesafesini bir maliyet fonksiyonu ile hesaplıyorruz. Sadece bu fonksiyonu değiştirerek farklı sonuçlar elde edebiliriz. Örneğin; tahtanın üzerindeki her kareyi bir tepe olarak düşünelim ve üzerindeki sayı ise o tepenin yüksekliği olsun. Bir tepeden diğerine geçerken; tırmanmamız yükseklik farkı kadar, aşağı inmemiz ise yükseklik farkının yarısı kadar zaman alsın. Bu durumda maliyet fonksiyonumuzu aşağıdaki gibi değiştirmek bize istediğimiz sonucu verecektir:

private int GetCostOfGoing(int bi, int ci)
{
    int diff = Height[bi] - Height[ci];
    if (diff < 0) return -diff / 2;
    return diff;
}

Tüm bu işlemi görsel olarak takip etmeniz için bir WPF projesi hazırladım. Buradan indirip inceleyebilirsiniz.

DipNot: Bu yazıyı, yazarken kaybettiğim o tuşuma armağan ediyorum.


3 Yorum  
sadettinpolat
3 Haziran 2008 00:09

Wordpress hayirli olsun :) lakin drupal daha iyi degil miydi ?

ibrahim dursun
3 Haziran 2008 00:40

Merhaba Sadettin,

Drupal 2005′te daha iyiydi tabi ama artık daha iyi değil :) Geçenlerde drupal’ın rss beslemesi ile google reader sorun yaşadı, yeni yazılarım görünmüyordu. Hatta besleme adresi değişikliğini haber verebilmek için apache ile bir sürü yönlendirme yapmam gerekti.

Drupal 4 ile başlamıştım, 5′e geçtim ama görünürde hiçbir değişiklik olmadı, üstüne üstlük database de biraz karıştı. Kitaplarım yok oldu. Hibernate yazılarım çift oldu. Drupal 6′ya ise geçemedim :) Modüllerimin uygun sürümleri yoktu. Sonra Wordpress’e bir daha baktım ve hiç acımadan Drupal’in kellesini uçurdum. Şimdi en azından yorum geldiğinde bana mail gelecek, yazı yazarken bir editör kullanabileceğim, OpenID desteğim olacak, tek bir sayfadan kontrol yapabileceğim, vs vs..

Temayı da wordpress klasik yaptım :) Baya bir minimal oldu…

ahmet alp balkan
16 Mayıs 2010 21:29

görsel animasyon da güzelmiş :) elinize sağlık.

Bir yorum bırakın

»  Substance: WordPress   »  Style: Ahren Ahimsa