圖說演算法使用JavaScript(三十二)

10-1-4有向圖形

有向圖形(Digrph)是一種每一個邊都可使用有序對<V1,V2>來表示,並且<V1,V2>與<V2,V1>是表示方向不同的邊,而所謂<V1,V2>,是指V1為尾端指向為頭部的V2。如下圖所示:

V={A,B,C,D,E}
E={<A,B>、<B,C>、<C,D>、<C,E>、<E,D>、<D,B>}

接下來則是有向圖形的相關定義介紹:

*完整圖形(Complete Graph):具有n個頂點且恰有n*(n-1)個邊的有向圖形,如下圖所示:

*路徑(Path):有向圖形中從頂點Vp到頂點Vq的路徑是指一串由頂點所組成的連續有像序列。

*強連接(Strongly Connected):有向圖形中,如果每個相異的成對頂點Vi,Vj有直接路徑,同時,有另一條路徑從Vj到Vi,則稱此圖為強連接。如下圖:

*強連接單元(Strong Connected Component):有向圖形中構成強連接的最大子圖,在下圖(A)中是強連接,但(B)就不是。

而圖(B)中的強連接單元如下:

*出分支度(Out-degree):是指有向圖形中,以頂點V為劍尾的邊數目。

*入分支度(In-degree):是指有向圖形中,以頂點V為箭頭的邊數目,下圖中V4的入分支度為1,出分支度為0,V2的入分支度為4,出分支度為1。

10-2不能不學的圖形表示法

知道圖形的各種定義與觀念後,有關圖形的資料表示法就易顯重要了。常用來表達圖形資料結構的方法很多,本節中將介紹四種表示法。

10-2-1相鄰矩陣法

圖形A有n個頂點,以n*n的二為矩陣陣列表示。此矩陣的定義如下:

相關特性說明如下:

接著就實際來看一個範例,請以相鄰矩陣表示右邊的無向圖:由於下圖共有5個頂點,故使用5*5的二維陣列存放圖形。下圖中,先找和1相鄰的頂點有哪些,把和1相鄰的頂點座標填入1。

跟頂點1相鄰的有頂點2及頂點5,所以完成下表:

其他頂點依此類推可以得到相鄰矩陣:

無向/有向圖形的6*6相鄰矩陣演算法如下:

for (i=0; i<10; i++) {
for (j=0; j<2; j++) {
for (k=0; k<6; k++) {
tmpi=data[i][0];
tmpj=data[i][1];
arr[tmpi][tmpj]=1;
}
}
}
process.stdout.write('無向圖形矩陣:\n');
for (i=1; i<6; i++) {
for (j=1; j<6; j++)
process.stdout.write('['+arr[i][j]+'] ');
process.stdout.write('/n');
}

無向圖形

JS                 undirected.js

arr=[[0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0]];

data=[[1,2],[2,1],[1,5],[5,1],
	  [2,3],[3,2],[2,4],[4,2],
	  [3,4],[4,3]];

for (i=0; i<10; i++) {
	for (j=0; j<2; j++) {
		for (k=0; k<6; k++) {
			tmpi=data[i][0];
			tmpj=data[i][1];
			arr[tmpi][tmpj]=1;
		}
	}
}
process.stdout.write('無向圖形矩陣:\n');
for (i=1; i<6; i++) {
	for (j=1; j<6; j++) 
		process.stdout.write('['+arr[i][j]+'] ');
	process.stdout.write('\n');
}

PHP                    undirected.php

$arr = array(
    array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0)
);

//圖形各邊的起點值及終點值
$data = array(
	array(1,2),array(2,1),array(1,5),
	array(5,1),array(2,3),array(3,2),
	array(2,4),array(4,2),array(3,4),
	array(4,3)
);

for ($i=0; $i<10; $i++) {
	for ($j=0; $j<2; $j++) {
		for ($k=0; $k<6; $k++) {
			$tmpi=$data[$i][0];
			$tmpj=$data[$i][1];
			$arr[$tmpi][$tmpj]=1;
		}
	}
}

for ($i=1; $i<6; $i++) {
	for ($j=1; $j<6; $j++) {
		echo "[".$arr[$i][$j]."]";
	}
	echo "<br>";
}

有向圖形

JS                directed.js

arr=[[0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0],
	 [0,0,0,0,0,0]];

data=[[1,2],[2,1],[2,3],[2,4],
	  [4,3],[4,1],[2,4]];

for (i=0; i<6; i++) {
	for (j=0; j<6; j++) {
			tmpi=data[i][0];
			tmpj=data[i][1];
			arr[tmpi][tmpj]=1;
	}
}
process.stdout.write('有向圖形矩陣:\n');
for (i=1; i<6; i++) {
	for (j=1; j<6; j++) 
		process.stdout.write('['+arr[i][j]+'] ');
	process.stdout.write('\n');
}

PHP                directed.php

$arr = array(
    array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0),
	array(0,0,0,0,0,0)
);

//圖形各邊的起點值及終點值
$data = array(
	array(1,2),array(2,1),array(2,3),
	array(2,4),array(4,3),array(4,1)
	);

$count=count($data);
//echo $count."<br>";

for ($i=0; $i<$count; $i++) {      //讀取圖形資料
	for ($j=0; $j<$count; $j++) {       //填入arr矩陣
			$tmpi=$data[$i][0];    //tmpi為起始頂點
			$tmpj=$data[$i][1];    //tmpj為終止頂點
			$arr[$tmpi][$tmpj]=1;  //有邊的點填入1
	}
}

for ($i=1; $i<6; $i++) {
	for ($j=1; $j<6; $j++) {
		echo "[".$arr[$i][$j]."]"; //列印矩陣內容
	}
	echo "<br>";
}

圖說演算法使用JavaScript(三十一)

圖形演算法的關鍵課程

圖形除了被活用在演算法領域中最短路徑搜尋、拓樸排序外,還能應用在系統分析中以時間為評核標準的計畫平核術(Performance Evaluation and Review Technique, PERT),或者像一般生活中的(IC版設計)、(交通網路規劃)等都可以看作是圖形的應用。利用兩點之間的距離,如何計算兩節點之間最短距離,就變成圖形要處理的問題,也就是網路的定義,以Dijkstra 這種圖形演算法就能快速尋找出兩個節點之間的最短路徑,如果沒有Dijkstra 演算法,現在網路的運作效率必將大大降低。

圖形理論起源1736年,一位瑞士數學家尤拉(Euler)為了解決(肯尼茲堡橋梁)問題,所想出來的一種資料結構理論,就是著名的七橋理論。簡單來說,就是有七座橋橫跨四個城市的大橋。尤拉索思考的問題是這樣的,(是否有人在只經過每一座橋梁一次的情況下,把所有地方走過一次而且回到原點。)

10-1-1尤拉環與尤拉鏈

尤拉當時使用的方法就是以圖形結構進行分析。他先以頂點表示土地,以邊表示橋梁,並定義連接每個頂點的邊數稱為該頂點的分支度。我們將以下面簡圖來表示(肯尼茲堡橋梁)問題。

最後尤拉找到一個結論:(當所有頂點的分支度皆為偶數時,才能從某頂點出發,經過每一邊一次,再回到起點。)也就是說,在上圖中每個頂點的分支度都是奇數,所以尤拉所思考的問題是不可能發生的,這個理論就是有名的(尤拉環)(Eulerian cycle)理論。
但如果條件改成從某頂點出發,經過每邊一次,不一定要回到起點,亦即只允許其中兩個頂點的分支度是奇數,其餘則必須全部為偶數,符合這樣的結果稱為尤拉鏈(Eulerian chain)。

10-1-2圖形的定義

圖形是由(頂點)和(邊)所組成的集合,通常用G=(V,E)來表示,其中V是所有頂點的集合,而E代表所有的集合。圖形的種類有兩種:一是無向圖形,一是有向圖形,無向圖形以(V1,V2)表示,有向圖形則以<V1,V2>表示其邊緣。

10-1-3無向圖形

無向圖形(Graph)是一種具備同邊的兩個項點沒有次序關係,例如(A,B)與(B,A)是代表相同的邊。如下圖所示:

V={A,B,C,D,E}
E={(A,B),(A,E),(B,C),(B,D),(C,D),(C,E),(D,E)}

接下來是無向圖形的重要術語介紹:

完整圖形:在(無向圖形)中,N個頂點正好有N(N-1)/2條邊,則稱為(完整圖形)。如下圖

路徑(PATH):對於從頂點Vi到頂點Vj的一條路徑,是由所經過頂點所成的連續數列,如上圖G中,A到E的路徑有{(A,B)、(B,E)}及{(A,B)、(B,C)、(C,D)、(D,E)}等等。

簡單路徑(Simple Path):除了起點和終點可能相同外,其他經過的頂點都不同,在圖G中,(A,B)、(B,C)、(C,A)、(A,E)不是一條簡單路徑。

路徑長度(Path Length):是指路徑上所包含的數目,在圖G中,(A,B)、(B,C)、(C,D)、(D,E),是一條路徑,其長度為4,且為一簡單路徑。

循環(Cycle):起始頂點及終止頂點為同一個點的簡單路徑稱為循環。如上圖G,{(A,B)、(B,D)、(D,E)、(E,C)、(C,A)}起點及終點都是A,所以是一個循環。

依附(Incident):如果Vi與Vj相鄰,我們則稱(Vi,Vj)這邊依附於頂點Vi及頂點Vj,或者依附於頂點B的邊有(A,B)、(B,D)、(B,E)、(B,C)。

子圖(Subgraoh):當我們稱G’為G的子圖時,必定存在V(G’)<=V(G)與E(G’)<=E(G),如下圖是上圖G的子圖。

相鄰(Adjacent):如果(Vi,Vj)是E(G)中的一邊,則稱Vi與Vj相鄰。

相連單元(Connected Component):在無向圖形中,相連在一起的最大子圖(Subgraph),如下圖有2個相連單位。

分支度:在無向圖形中,一個頂點所擁有邊的總數為分支度。如上圖G,頂點A的分支度為4。

圖說演算法使用JavaScript(三十)

9-10平衡樹

由於二元搜尋樹的缺點是無法永遠保持在最佳狀態。當加入之資料部分以排序的情況下,極有可能產生歪斜樹,因而使樹的高度增加,導致搜尋效率降低。所以二元搜尋樹較不利於資料的經常變動(加入或刪除)。為了能夠盡量降低搜尋所需要的時間,讓我們在搜尋的時候能夠很快找到所要的鍵值,我們必須讓樹的高度越小越好。

所謂平衡樹(Balance Binary Tree),又稱之為AVL樹(是由Adelson-Velskii 和 Landis兩人所發明的),本身也是一棵二元搜尋樹,在AVL樹中,每次在插入資料和刪除資料後,必要的時候會對二元樹做一些高度的調整動作,而這些調整動作就是要二元搜尋樹的高度隨時維持平衡。T是一個非空的二元樹,Tl及Tr分別是它的左右子樹,若符合下列兩條件,則稱T是個高度平衡樹:

至於如何調整一二元搜尋樹成為一平衡樹,最重要找出(不平衡點),再依照以下四種不同旋轉型式,重新調整其左右子樹的長度。首先,令新插入的節點為N,且其最近的一個具有+-2的平衡因子點為A,下一層為B,再下一層C,分述如下:

現在我們來實作一個範例,下圖的二元樹,下圖的二元樹原是平衡的,加入節點12後變為不平衡,請重新調整成平衡樹,但不可破壞原有的次序結構:


9-11決策樹的智慧

我們也常把決策樹(Decision Tree)稱為(遊戲樹),這是因為遊戲中的AI經常以決策樹資料結構來實作的緣故。對資料結構而言,決策樹本身是人工智慧(AI)中一個重要理念,在資訊管理系統(MIS)中,也是決策支援系統(Decision Support System,DSS)的執行基礎。
簡單來說,決策樹是一種利用樹狀結構的方法,來討論一個問題的各種情況分布的可能性。例如最典型的(8枚金幣)問題來闡釋決策樹的觀念,內容是假設有8枚金幣a,b,c,d,e,f,g,h且其中有一枚是儰造的,儰造金幣的特徵為重量稍輕或偏重。請問如何使用決策樹方法,找出這枚儰造的錢幣;如果是以L表示輕於真品,H表示重於真品。第一次比較從8枚中挑選6枚a,b,c,d,e,f分2組來比較,則會有下列三種情形產生:
(a+b+c)>(d+e+f)
(a+b+c)=(d+e+f)
(a+b+c)<(d+e+f)

不過如果今天您要設計的遊戲並不是屬於(棋類)或是(紙牌類)的話,所採用的技巧在於實現遊戲作決策的能力,簡單的說,該下哪一步或者該出哪一張牌,因為通常可能的狀況有很多,例如象棋遊戲的人工智慧就必須在所有可能的情況中選擇一步對自己最有利的棋,想想看如果開發此類的遊戲,您會怎麼作?這時決策樹也可派上用場。
通常此類遊戲的AI實現技巧為先找出所有可走的棋(或可出的牌),然後逐一判斷如果走這步棋(或出這張牌)的優劣程度如何,或者說是替這步棋打個分數,然後選擇走得分最高的那步棋。
一個最常被用來討論決策型AI的簡單例子是(井字遊戲),因為它的可能狀況不多,也許您只要花個十分鐘便能分析完所有的狀況,並且找出最佳的玩法,例如下圖可表示某個狀況下的O方的決策樹:

上圖是井字遊戲的某個決策區域,下一步是X方下棋,很明顯的X方絕對不能選擇第二層的第二個下法,因為X方必敗無疑,而您也看出這個決策形成樹狀結構,所以也稱之為(決策樹),而樹狀結構正是資料結構所討論的範圍,這也說明了資料結構正是人工知會的基礎,而決定型人工智慧的基礎則是搜尋,在所有可能的狀況下,搜尋可能獲勝的方法。