Strona główna » Algorytmy » Artykuły » Najszybciej do Rogu!
 

Najszybciej do Rogu!

Zadanie

Dana jest mapa labiryntu opisana zerami (wolne pole) i jedynkami (zajęte pole). Napisz program, który znajdzie najkrótszą drogę do dowolnego rogu mapy. Można się poruszać tylko lewo, prawo, góra i dół. Jako dane na wejściu otrzyma rozmiar planszy (w, h), zapis labiryntu w postaci macierzy zer i jedynek o podanym wcześniej rozmiarze oraz punkt startowy. Wiersze i kolumny indeksujemy od 0.

Przykład

Przypuśćmy, że dana jest następująca mapa labiryntu o wymiarach 8x6.

01110000
01110110
00000011
11101001
1110S101
11111100

Pole z wartością S wskazuje na punkt początkowy (4, 4). Można z niego osiągnąć trzy rogi z czterech w następującej ilości kroków: lewy, górny w 8 krokach, prawy, górny w 9, a ostatni prawy, dolny w 10. Ostateczna odpowiedź to oczywiście 8.

Analiza Zadania

W celu rozwiązania tego zadania można skorzystać z schematu przeszukiwania wszerz BFS. Mianowicie na początku na kolejkę pól wrzucane jest pole startowe. Następnie dla każdego punktu w kolejce sprawdzamy sąsiednie pola (na górze, na dole, po prawej i po lewej). Przy każdym przejściu zwiększamy aktualną odległość o 1. Jeśli zostanie napotkany róg planszy to aktualizujemy minimalną odległość do rogu o ile aktualna odległość jest mniejsza niż zapisana.

Taki algorytm będzie przeglądać wszystkie możliwe pola, ale proces ten można ograniczyć poprzez sprawdzenie czy dodawane pole do kolejki ma odległość mniejszą niż aktualne minimum. Dzięki temu nie będą sprawdzane pola, które i tak nie ma ją szans wskazać najkrótszej ścieżki. Dodatkowo należy pamiętać, że na samym początku trzeba sprawdzić czy punkt startowy nie jest w rogu. Wtedy wynikiem jest wartość 0.

Implementacja

Dane

Podczas przeglądania mapy będzie potrzebne zapisanie aktualnie przeglądanych pól oraz ich aktualnej odległości. Ułatwi to struktura Dane.

  1. class Dane
  2. {
  3. public int odleglosc;
  4. public Point punkt;
  5. public Dane(int odleglosc, Point punkt)
  6. {
  7. this.odleglosc = odleglosc;
  8. this.punkt = punkt;
  9. }
  10. }

Typ pola

W tym zadaniu ważne jest, aby móc sprawdzić czy dane pole nie jest rogiem planszy. Funkcja CzyRog() odpowiada na to pytanie. Wystarczy podać rozmiar planszy oraz aktualnie przeglądaną pozycję.

  1. static bool CzyRog(Point rozmiar, Point poz)
  2. {
  3. return (poz.X % (rozmiar.X - 1) == 0
  4. && poz.Y % (rozmiar.Y - 1) == 0);
  5. }

W przedstawionym w dalszej częściu algorytmu program na ślepo wyznacza kolejne pola, dlatego należy sprawdzić funkcją CzyOk() czy dane pole istnieje.

  1. static bool CzyOk(Point rozmiar, Point poz)
  2. {
  3. return poz.X >= 0 && poz.Y >= 0
  4. && poz.X < rozmiar.X && poz.Y < rozmiar.Y;
  5. }

Funkcja główna

Oto kod głównej funkcji SzukajDrogi(), która dla danej macierzy o rozmiarze rozmiar i danym punkcie startowym punktStart zwróci najkrótszą odległość do dowolnego rogu planszy.

  1. static int SzukajDrogi(int[,] macierz, Point rozmiar, Point punktStart)
  2. {
  3. if (CzyRog(rozmiar, punktStart))
  4. {
  5. return 0;
  6. }
  7. Queue<Dane> q = new Queue<Dane>();
  8. int min = int.MaxValue;
  9. Dane start = new Dane(0, punktStart);
  10. macierz[start.punkt.Y, start.punkt.X] = 1;
  11. q.Enqueue(start);
  12. Point[] przes = new Point[]{
  13. new Point(0, 1), new Point(0, -1),
  14. new Point(1, 0), new Point(-1, 0)
  15. };
  16. while (q.Count != 0)
  17. {
  18. Dane aktualny = q.Dequeue();
  19. if (CzyRog(rozmiar, aktualny.punkt)
  20. && aktualny.odleglosc < min)
  21. {
  22. min = aktualny.odleglosc;
  23. continue;
  24. }
  25. for (int k = 0; k < 4; k++)
  26. {
  27. Point nastepny = new Point(
  28. aktualny.punkt.X + przes[k].X,
  29. aktualny.punkt.Y + przes[k].Y
  30. );
  31. Dane nowy = new Dane(aktualny.odleglosc + 1, nastepny);
  32. if (CzyOk(rozmiar, nowy.punkt)
  33. && macierz[nowy.punkt.Y, nowy.punkt.X] == 0
  34. && nowy.odleglosc < min)
  35. {
  36. q.Enqueue(nowy);
  37. macierz[nowy.punkt.Y, nowy.punkt.X] = 1;
  38. }
  39. }
  40. }
  41. return min;
  42. }

Na początku algorytm sprawdza czy start nie znajduje się w rogu. W przeciwnym razie przechodzimy do inicjalizacji kolejki i umieszczamy na niej punky startowy. Deklarowane są również możliwe przesunięcia względem aktualnie wybranego punktu. Następnie dopóki kolejka nie jest pusta pobieramy pierwszy element i jeśli jest narożnikiem to aktualizujemy aktualne minimum. Potem wyliczamy sąsiednie pola i dodajemy do kolejki tylko te, które spełniają warunek.

Testowanie funkcji

W celu przetestowania kodu można skorzystać z poniższego fragmentu programu:

  1. static void Main(string[] args)
  2. {
  3. Console.Write("Podaj rozmiary labiryntu\n w, h = ");
  4. string[] data = Console.ReadLine().Split(' ');
  5. int w = Convert.ToInt32(data[0]);
  6. int h = Convert.ToInt32(data[1]);
  7. int[,] macierz = new int[h, w];
  8. for (int i = 0; i < h; i++)
  9. {
  10. data = Console.ReadLine().Split(' ');
  11. for (int j = 0; j < w; j++)
  12. {
  13. macierz[i, j] = Convert.ToInt32(data[j]);
  14. }
  15. }
  16. Console.Write("Podaj punkt początkowy\n x, y = ");
  17. data = Console.ReadLine().Split(' ');
  18. int X = Convert.ToInt32(data[0]);
  19. int Y = Convert.ToInt32(data[1]);
  20. int wynik = SzukajDrogi(macierz, new Point(w, h), new Point(X, Y));
  21. if (wynik == int.MaxValue)
  22. {
  23. Console.WriteLine("Nie istnieje trasa do rogu!");
  24. }
  25. else
  26. {
  27. Console.WriteLine("Najbliższy róg jest w odległości {0}", wynik);
  28. }
  29. Console.ReadKey();
  30. }

Jako dane testowe można podać następujące dane:

  1. 8 6
  2. 0 1 1 1 0 0 0 0
  3. 0 1 1 1 0 1 1 0
  4. 0 0 0 0 0 0 1 1
  5. 1 1 1 0 1 0 0 1
  6. 1 1 1 0 0 1 0 1
  7. 1 1 1 1 1 1 0 0
  8. 4 4