为什么DFS在检查图形是否为树时速度不够快

时间:2012-11-06 14:54:51

标签: algorithm graph tree pascal

我试图解决这个问题 Problem Description 似乎正确的想法是检查给定的图形是否有循环(是否是树)。但是,我的代码无法通过测试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.

1 个答案:

答案 0 :(得分:2)

我对帕斯卡几乎一无所知,所以我可能误解了你在做什么,但我认为主要的罪魁祸首是fin,你在那里取消标记被访问的顶点。这会强制您从每个顶点执行DFS,而您只需要为每个组件执行一个DFS。

如果有多个连接组件,则移动将停止

  • 因为顶点指向已标记的顶点,在这种情况下我们只是因为找到了一个循环而停止
  • 因为顶点指向没有人(但本身),在这种情况下我们需要找到下一个未标记的顶点并从那里再次启动另一个DFS

您不必担心回溯记录,因为此问题中每个顶点最多指向另一个顶点。也没有必要担心哪个DFS做了哪个标记,因为每个标记只能在其连接的组件中工作。

如果首先遇到指向自身的顶点,则不应该标记它,而是跳过它。

使用Set Union和Vertex / Edge Count的替代解决方案

由于树具有边数比顶点数少一个的特性,因此还有另一种思考问题的方法 - 确定(1)连通分量和(2)比较边和顶点计算每个组件。

在许多语言中,您都有一个Set数据结构,具有接近恒定时间的Union / Find方法。在这种情况下,解决方案简单快速 - 边缘数量接近线性。

为表示其连接组件的每个顶点创建一个Set。然后处理边缘列表。对于每个边,Union the Sets由两个顶点表示。随着时间的推移,跟踪每个Set中的顶点数和数字边。同样的例子:

初始设置

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指向自身或其他人,则无关紧要。

对于后代,这是中的实现。已经有一段时间了,所以原谅这种邋..它不是最快的,但它确实在时限内通过了所有测试。不相交的集合编码直接来自Wikipedia's pseudocode

#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");
    }
}