資料結構學習(C++)——圖【3】(無向圖)(上) (轉)

amyz發表於2007-11-25
資料結構學習(C++)——圖【3】(無向圖)(上) (轉)[@more@]

要是在紙上隨便畫畫,或者只是對圖做點示範性的說明,大多數人都會選擇無向圖。然而在中,無向圖卻是按照有向圖的方法來儲存的——存兩條有向邊。實際上,當我們說到無向的時候,只是忽略方向——在紙上畫一條線,難不成那線“嗖”的就出現了,不是從一頭到另一頭畫出來的?

無向圖有幾個特有的概念,連通分量、關節點、最小生成樹。下面將分別介紹,在此之前,先完成無向圖類的基本操作。

無向圖類

template

class Graph : publicwork

{

public:

  Graph() {}

  Graph(dist maxdist) : Network (maxdist) {}

bool insertE(name v1, name v2, dist cost)

  {

  ifwork::insertE(v1, v2, cost))

    return Network::insertE(v2, v1, cost);

  return false;

  }

};

僅僅是新增邊的時候,再新增一條反向邊,很簡單。

連通分量

這是無向圖特有的,有向圖可要複雜多了(強、單、弱連通),原因就是無向圖的邊怎麼走都行,有向圖的邊好像掉下無底深淵就再也爬不上來了。有了DFS,求連通分量的演算法就變得非常簡單了——對每個沒有訪問的頂點DFS就可以了。

  void components()

  {

  visited = new bool[vNum()]; int i, j = 0;

  for (i = 0; i < vNum(); i++) visited[i] = false;

  cout << "Components:" << endl;

  for (i = 0; i < vNum(); i++):namespace prefix = o ns = "urn:schemas--com::office" />

  {

  if (!visited[i]) { cout << '(' << ++j << ')'; DFS(i); cout << endl; }

  }

  delete []visited;

  }

黑體的部分就是核心。

關節點

下定義是人們認識事物的一個方法,因為概念使得人們能夠區分事物——關於這個還有個絕對的運動和相對的靜止的哲學觀點(河水總在流,但是長江還叫長江,記得那個著名的“不可能踏進同一條河裡”嗎?)因此,能否有個準確的概念往往是一門學科發展程度的標誌,而能否下一個準確的定義反映了一個人的思維能力。說這麼多廢話,原因只有一個,我沒搞清楚什麼叫“關節點”——參考書有限,不能仔細的考究了,如有誤解,還望指正。

嚴版是這麼說的:如果刪除某個頂點,將圖的一個連通分量分割成兩個或兩個以上的連通分量,稱該頂點為關節點。——雖然沒有提到圖必須是無向的,但是提到了連通分量已經預設是無向圖了。

殷版是這麼說的:在一個無向連通圖中,……(餘下同嚴版)。

問題出來了,非連通圖是否可以討論含有關節點?我們是否可以說某個連通分量中含有關節點?遺憾的是,嚴版留下這個問題之後,在後面給出的演算法是按照連通圖給的,這樣當圖非連通時結果就是錯的。殷版更是滑頭,只輸出重連通分量,不輸出關節點,自己雖然假定圖是連通的,同樣沒有連通判斷。

翻翻離散數學吧,結果沒找到什麼“關節點”,只有“割點”,是這樣的:一個無向連通圖,如果刪除某個頂點後,變為非連通圖,該頂點稱為割點。權當“割點”就是“關節點”,那麼演算法至少也要先判斷是否連通吧?可是書上都直接當連通的了……

關於演算法不再細說,書上都有。下面的示例,能輸出每個連通分量的“關節點”(是不是可以這樣叫,我也不清楚)。dfn儲存的是每個頂點的訪問序號,low是深度優先生成樹上每個非葉子頂點的子女透過回邊所能到達的頂點最小的訪問序號。把指向雙親的邊也當成回邊並不影響判斷,因此不必特意區分,殷版顯式區分了,屬於畫蛇添足。這樣一來,if (low[n] >= dfn[i]) cout << getV(i);這個輸出關節點的判斷中的>=就顯得很尷尬了,因為只能等於不可能大於。還要注意的是,生成樹的根(DFS的起始點)是單獨判斷的。

  void articul()

  {

  dfn = new int[vNum()]; low = new int[vNum()]; int i, j = 0, n;

  for(i = 0; i < vNum(); i++) dfn[i] = low[i] = 0;//初始化

  for (i = 0; i < vNum(); i++)

  {

  if (!dfn[i])

  {

    cout << '(' << ++j << ')'; dfn[i] = low[i] = count = 1;

    if ((n = nextV(i)) != -1) articul(n); bool out = false;//訪問樹根

    while ((n = nextV(i, n)) != -1)

      {

    if (dfn[n]) continue;

    if (!out) { cout << getV(i); out = true; }//樹根有不只一個子女

      articul(n);//訪問其他子女

    }

    cout << endl;

  }

  }

  delete []dfn; delete []low;

  }

 

private:

  void articul(int i)

  {

  dfn[i] = low[i] = ++count;

  for (int n = nextV(i); n != -1; n = nextV(i, n))

  {

  if (!dfn[n])

  {

    articul(n);

    if (low[n] < low[i]) low[i] = low[n];

    if (low[n] >= dfn[i]) cout << getV(i);//這裡只可能=

  }

    else if (dfn[n] < low[i]) low[i] = dfn[n];//回邊判斷

  }

  }

  int *dfn, *low, count;


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-984514/,如需轉載,請註明出處,否則將追究法律責任。

相關文章