Genişlik üzrə axtarış 0-1#10056

Genişlik üzrə axtarış 0-1 (Həll təhlili)

0 - 1 eninə axtarış (və ya 0 - 1 BFS) — qraflarda ən qısa yolları tapmaq üçün istifadə olunan klassik enliyinə axtarış (BFS) alqoritminin modifikasiyasıdır. Bu alqoritmin fərqi ondan ibarətdir ki, qrafdakı tillərin çəkiləri yalnız və ya qiymətlərini ala bilər, bu da axtarışı əhəmiyyətli dərəcədə optimallaşdırmağa imkan verir.

Məsələnin qoyuluşu. Təsəvvür edək ki, bizdə tillərinin çəkiləri ya , ya da olan çəkili qraf var. Məsələn, belə bir qrafa şəhərlə işlədiyimiz zaman rast gəlmək olar — bəzi yollar pulsuzdur (çəki ), digərləri üçün isə pul ödəmək lazımdır (çəki ). Biz bu çəkiləri nəzərə alaraq qrafın bir təpəsindən digərinə qədər olan ən qısa yolu tapmaq istəyirik.

Standart eninə axtarış alqoritminin problemi. Klassik enliyinə axtarış (BFS) alqoritminin mürəkkəblik qiymətləndirməsi O(V + E)-dir və qrafda ən qısa yolların tapılması üçün istifadə olunur, lakin o, yalnız bütün tillərin eyni çəkiyə malik olduğu qraflar üçün uyğundur. Əgər tillərin çəkiləri müxtəlifdirsə, bu yanaşma artıq səmərəsizdir — bu məqsədlər üçün adətən mürəkkəblik qiymətləndirməsi O(V^2 + E) və ya O(E \cdot log_2V) olan Deykstra alqoritmi tətbiq olunur. Lakin yalnız və çəkilərinə malik qraflar üçün daha sürətli yanaşma mövcuddur — 0 - 1 BFS.

0-1 BFS Alqoritmi

0 - 1 BFS-də biz ikitərəfli növbədən (deque) istifadə edirik. Fikir aşağıdakından ibarətdir:

  1. Biz başlanğıc təpəsindən başlayırıq və onu -a bərabər başlanğıc məsafə ilə növbəyə əlavə edirik.

  2. Hər bir təpəsinin (növbənin başından çıxarılan) emalı zamanı onun qonşularına baxırıq:

    • Əgər tilinin çəkisi -dırsa, onda qonşu təpəsi cari məsafə ilə (dist[v] = dist[u]) növbənin əvvəlinə yerləşdirilir.

    • Əgər tilinin çəkisi -dirsə, onda qonşu təpəsi məsafəni bir vahid artıraraq (dist[v] = dist[u] + 1) növbənin sonuna yerləşdirilir.

  3. Bu, təpələrin emalının düzgün qaydasını səmərəli şəkildə saxlamağa imkan verir: daha kiçik çəkili tillər vasitəsilə əldə edilə bilən təpələr daha əvvəl emal olunur.

Beləliklə, ikitərəfli növbədən istifadə bizim həmişə növbənin əvvəlində minimal məsafəli təpəyə çıxışımızın olmasını təmin edir, alqoritmin işinin mürəkkəbliyi isə O(V + E) olaraq qalır.

0 - 1 eninə axtarışda tillərin relaksasiyası Deykstra alqoritmindəki klassik relaksasiyaya bənzəyir, lakin tillərin çəkisi əsasında təpələrin sıralanması üçün prioritet növbə (priority queue) əvəzinə ikitərəfli növbədən (deque) istifadə edilməsi ilə fərqlənir.

0-1 BFS alqoritmi (klassik BFS-dən fərqli olaraq) hər bir təpənin ilk dəfədən minimal məsafə ilə ziyarət ediləcəyinə zəmanət vermir, xüsusən əgər eyni təpəyə bir neçə yolla çatmaq mümkündürsə. Buna görə də tillərin relaksasiyası mütləqdir.

Aşağıdakı nümunəyə baxaq. Ən qısa məsafələr massivini və növbəsini işə salaq:

təpəsindən çıxan tillərə baxaq. və tillərini relaksasiya edək. təpəsi növbənin əvvəlinə, təpəsi isə növbənin sonuna yerləşdiriləcək.

Ancaq dist[2] = 1 qiyməti yekun deyil. 3 - 4 tili boyunca keçək və dist[4] = 0, queue = (4, 2) təyin edək. Sonra 4 - 2 tilinə baxaq və qiyməti -a bərabər olacaq. Beləliklə, relaksasiya nəticəsində iki müxtəlif qiymət aldı (əvvəlcə , sonra ).

Nümunə

Nümunədə verilmiş qraf aşağıdakı formaya malikdir:

təpəsindən təpəsinə qədər olan ən qısa yolun uzunluğu -ə bərabərdir, ən qısa yolun özü isə aşağıdakı formadadır: 1 - 2 - 3.

Alqoritmin realizasiyası

Sonsuzluq sabitini elan edək.

#define INF 0x3F3F3F3F

Ən qısa məsafələr massivini və qrafın qonşuluq siyahısı -ni elan edək.

vector\u003cint\u003e dist;
vector\u003cvector\u003cpair\u003cint, int\u003e\u003e\u003e g;

bfs funksiyası təpəsindən enliyinə axtarışı reallaşdırır.

void bfs(int start)
{

Ən qısa məsafələr massivini işə salaq.

  dist = vector\u003cint\u003e(n + 1, INF);
  dist[start] = 0;

Növbəni elan edək və ona başlanğıc təpəsini daxil edək.

  deque\u003cint\u003e q;
  q.push_back(start);

Növbə boş olmayana qədər enliyinə axtarış alqoritminə davam edirik.

  while (!q.empty())
  {

Növbənin başından təpəsini çıxarırıq.

    int v = q.front(); q.pop_front();

təpəsindən çıxan tilləri gəzirik.

    for (auto edge : g[v])
    {
      int to = edge.first;
      int w = edge.second;

Cari (v, to) tilinin çəkisi -dir. Onun relaksasiyasını aparırıq.

      if (dist[to] \u003e dist[v] + w)
      {

Əgər til relaksasiya olunursa, onda dist[to] ən qısa məsafəsini yenidən hesablayırıq.

        dist[to] = dist[v] + w;

tilinin çəkisindən asılı olaraq təpəsini növbənin sonuna və ya əvvəlinə əlavə edirik.

        if (w == 1)
          q.push_back(to);
        else
          q.push_front(to);
      }
    }
  }
}

Proqramın əsas hissəsi. Giriş məlumatlarını oxuyuruq.

scanf("%d %d %d %d", \u0026n, \u0026m, \u0026s, \u0026d);
g.resize(n + 1);

Qrafı oxuyuruq.

for (i = 0; i \u003c m; i++)
{
  scanf("%d %d %d", \u0026a, \u0026b, \u0026w);
  g[a].push_back({ b, w });
  g[b].push_back({ a, w });
}

başlanğıc təpəsindən enliyinə axtarışı işə salırıq.

bfs(s);

təpəsinə qədər olan ən qısa məsafəni çap edirik.

printf("%d\n", dist[d]);