Защо DFS не е достатъчно бърз при проверката дали дадена графика е дърво

Опитах се да реша този проблем Описание на проблема Изглежда правилната идея е да проверите дали дадените графики имат цикли (дали е дърво). Моят код обаче не можа да премине тест 7 (винаги превишено времево ограничение), някаква идея как да направя това по-бързо? Използвах DFS. Много благодаря Да, най-накрая ме приеха. Проблемът е dfs на всеки връх, което е ненужно. функцията dfs трябва да е така.

function dfs(idx: integer; id: integer): boolean;
begin
  if (visited[idx] = id) then
  begin
    Result := false;
    Exit;
  end;
  if (tree[idx] <> 0) then
  begin
    visited[idx] := id;
    Result := dfs(tree[idx], id);
    Exit;
  end;
  Result := true;
end;



program Project2;

{$APPTYPE CONSOLE}

var
  i, m, j, n, k: integer;
  tree: array [1 .. 25001] of integer;
  visited: array [1 .. 25001] of boolean;

function dfs(idx: integer): boolean;
label
  fin;
var
  buf: array[1 .. 25001] of integer;
  i, cnt: integer;
begin
  cnt := 1;
  while (true) do
  begin
    if (visited[idx]) then
    begin
      Result := false;
      goto fin;
    end;
    if (tree[idx] <> 0) then
    begin
      visited[idx] := true;
      buf[cnt] := idx;
      Inc(cnt);
      idx := tree[idx];
    end
    else
    begin
      break;
    end;
  end;
  Result := true;
fin:
  for i := 1 to cnt - 1 do
  begin
    visited[buf[i]] := false;
  end;
end;

function chk(n: integer): boolean;
var
  i: integer;
begin
  for i := 1 to n do
  begin
    if (tree[i] = 0) then continue;
    if (visited[i]) then continue;
    if (dfs(i) = false) then
    begin
      Result := false;
      Exit;
    end;
  end;
  Result := true;
end;

begin
  Readln(m);
  for i := 1 to m do
  begin
    Readln(n);
    k := 0;
    for j := 1 to n do
    begin
      Read(tree[j]);
      if (tree[j] = 0) then
      begin
        Inc(k);
      end;
    end;
    if (k <> 1) then
    begin
      Writeln('NO');
    end
    else
    if (chk(n)) then
    begin
      Writeln('YES');
    end
    else
    begin
      Writeln('NO');
    end;
    Readln;
  end;
  //Readln;
end.

person doctorlai    schedule 06.11.2012    source източник
comment
общото правило е, че рекурсията е по-бавна от итеративните еквиваленти. Опитайте да използвате bfs, ако е възможно?   -  person FUD    schedule 06.11.2012
comment
добре съгласна съм. но аз премахнах "рекурсия", като използвах буферен масив, за да демаркирам посетения масив. Освен това, мисля, че DFS намира цикъл по-бързо....   -  person doctorlai    schedule 06.11.2012
comment
Със сигурност бихте могли да използвате известно свойство на дърветата, да кажете нещо за броя на ръбовете спрямо броя на върховете, за да избегнете по-голямата част от тази работа? Ще трябва да направите това за всеки свързан компонент.   -  person A. Webb    schedule 06.11.2012
comment
@FUD: Твърдението е грешно. Сравних го (и го доказах статистически) в тази тема за итеративно/рекурсивно бързо сортиране. Трябва да имате много оптимизирана реализация на стека (за конкретната нужда), за да бъде това твърдение вярно.   -  person amit    schedule 06.11.2012
comment
някакви идеи как да направя алгоритъма по-бърз, за ​​да премине всички тестове?   -  person doctorlai    schedule 06.11.2012
comment
@DoctorLai: Трудно ми е да следвам кода (наистина не съм паскал програмист, съжалявам) - но графиката е дърво, ако и само ако е (1) свързана. (2) |V| = |E| + 1. Не съм сигурен какво правите, но бих използвал DFS или BFS само за да проверя дали графиката е свързана и след това да проверя броя на върховете и ръбовете в графиката - бих се опитал да минимизирам итерацията работете колкото е възможно повече.   -  person amit    schedule 06.11.2012
comment
Благодаря. всъщност в този конкретен проблем. правилната идея е да се провери дали дадената графика има цикли.   -  person doctorlai    schedule 06.11.2012
comment
Графът е дърво тогава и само ако (1) е свързан и (2) няма цикли. Тези (1) и (2) са еквивалентни на (1) и (2), дадени от @amit.   -  person A. Webb    schedule 06.11.2012
comment
Мисля, че най-накрая мога да разбера това, което сте направили тук (не говоря Pascal), и мисля, че основният проблем е демаркирането, направено на fin:. Това ви принуждава да правите DFS от всеки връх, което е ненужно тук.   -  person A. Webb    schedule 06.11.2012
comment
да, най-накрая го приеха!   -  person doctorlai    schedule 07.11.2012


Отговори (1)


Не знам почти нищо за Pascal, така че може да тълкувам погрешно това, което правите, но мисля, че главният виновник е в fin, където демаркирате посетените върхове. Това ви принуждава да правите DFS от всеки връх, докато трябва да правите само един на компонент.

В случай, че има повече от един свързан компонент, движението или ще спре

  • тъй като връх сочи към вече маркиран връх, в който случай просто спираме поради открит цикъл
  • тъй като върхът не сочи към никого (освен към себе си), в който случай трябва да намерим следващия немаркиран връх и да стартираме друг DFS отново от там

Не е нужно да се притеснявате за счетоводството за обратно проследяване, тъй като всеки връх най-много сочи към един друг връх в този проблем. Също така няма нужда да се притеснявате кой DFS кое маркиране е направил, тъй като всеки така или иначе ще работи само в рамките на своя свързан компонент.

В случай, че първо се срещне връх, който сочи към себе си, той все още не трябва да се маркира, а да се прескочи.

Алтернативно решение, използващо Set Union и Vertex/Edge Count

Тъй като едно дърво има свойството, че броят на ръбовете е с едно по-малък от броя на върховете, има друг начин да мислим за проблема - определете (1) свързаните компоненти и (2) сравнете броя на ръбовете и върховете във всеки компонент.

На много езици имате структура от данни Set с лесно достъпни методи Union/Find с почти постоянно време. В този случай решението е лесно и бързо - почти линейно по брой ръбове.

Създайте набор за всеки връх, представляващ неговия свързан компонент. След това обработете вашия списък с ръбове. За всяко ребро обединете множествата, представени от двата върха. Докато вървите, следете броя на върховете във всеки комплект и броя на ръбовете. Същият пример:

Първоначални комплекти

Vertex         1  2  3  4  5
Belongs to     S1 S2 S3 S4 S5

Set            S1 S2 S3 S4 S5
Has # vertices 1  1  1  1  1
And # edges    0  0  0  0  0

Обработете ръба от 1 до 2

Vertex         1  2  3  4  5
Belongs to     S1 S1 S3 S4 S5

Set            S1 S3 S4 S5
Has # vertices 2  1  1  1
And # edges    1  0  0  0

Обработете ръба от 2 до 3

Vertex         1  2  3  4  5
Belongs to     S1 S1 S1 S4 S5


Set            S1 S4 S5
Has # vertices 3  1  1
And # edges    2  0  0

Обработете ръба от 3 до 4

Vertex         1  2  3  4  5
Belongs to     S1 S1 S1 S1 S5

Set            S1 S5
Has # vertices 4  1
And # edges    3  0

Обработете ръба от 4 на 1

Vertex         1  2  3  4  5
Belongs to     S1 S1 S1 S1 S5

Set            S1 S5
Has # vertices 4  1
And # edges    4  0

И можем да спрем до тук, защото S1 в този момент нарушава броя на върховете спрямо ръбовете на дърветата. Има цикъл в S1. Няма значение дали връх 5 сочи към себе си или към някой друг.

За бъдещето ето внедряване в c. Мина доста време, така че простете за небрежността. Не е най-бързият, но преминава всички тестове в рамките на срока. Кодирането на несвързан набор е направо от псевдокода на Wikipedia.

#include <stdio.h>

struct ds_node
{
    struct ds_node *parent;
    int rank;
};

struct ds_node v[25001];

void ds_makeSet(struct ds_node *x)
{
    x->parent = x;
    x->rank = 0;
}

struct ds_node* ds_find(struct ds_node *x)
{
    if (x->parent != x) x->parent = ds_find(x->parent);
    return x->parent;
}

int ds_union(struct ds_node *x, struct ds_node *y)
{
    struct ds_node * xRoot;
    struct ds_node * yRoot;

    xRoot = ds_find(x);
    yRoot = ds_find(y);

    if (xRoot == yRoot) return 0;

    if (xRoot->rank < yRoot->rank) 
    {
        xRoot->parent = yRoot;
    }
    else if (xRoot->rank > yRoot->rank) 
    {
        yRoot->parent = xRoot;
    }
    else 
    {
        yRoot->parent = xRoot;
        xRoot->rank++;
    }
    return 1;
}

int test(int n)
{
    int i, e, z = 0;

    for(i=1;i<=n;i++)
    {
        ds_makeSet(&v[i]);
    }
    for(i=1;i<=n;i++)
    {
        scanf("%d",&e);
        if (e)
        {
            if ( !ds_union(&v[i],&v[e]) ) 
            {
                for(i++;i<=n;i++) scanf("%d",&e);
                return 0;
            }
        }
        else
        {
            z++;
        }
    }
    return (z == 1);
}
int main()
{
    int runs; int n;

    scanf("%d", &runs);
    while(runs--)
    {
        scanf("%d", &n); 
        getc(stdin);

        test(n) ? puts("YES") : puts("NO");
    }
}
person A. Webb    schedule 06.11.2012
comment
Интересно решение, но бихте ли обяснили как това ще бъде по-бързо от dfs? Начинът, по който го виждам, вашето решение е еквивалентно на dfs с ранно излизане - person GreyGeek; 06.11.2012
comment
@GreyGeek Да, вярвам, че си прав. Мисля, че ще редактирам отговора си, за да опиша това просто като друг начин на мислене за проблема. Ако знаехме, че графиката е свързана, сравняването на броя на върховете и ръбовете със сигурност можеше да бъде по-бързо от откриването на цикъл чрез DFS. Но както е, трябва да определим свързаността, което DFS прави едновременно. - person A. Webb; 06.11.2012
comment
@DoctorLai Да, така че DFS трябва да е също толкова бърз, наистина по-бърз поради по-ниските разходи. Вижте началото на моя отговор за опита ми да диагностицирам какво може да забавя вашия DFS код. - person A. Webb; 07.11.2012
comment
благодаря.. да, прав си.. не е необходимо dfs на всеки връх. - person doctorlai; 07.11.2012