第三至五講 樹
node
1.掌握查找的概念。c++
Q1:靜態查找和動態查找的區別是?算法
靜態查找中,集合的記錄是固定的;而動態查找中記錄是動態變化的,除了查找,還可能發生插入和刪除。數組
2.掌握技巧"哨兵"的設置。數據結構
Q1:哨兵的做用?ide
用於順序表查找,所謂「哨兵」即用一個特殊值來做爲數組的邊界,能夠減小一條判斷語句(i<n),提升程序的效率。函數
3.掌握二分查找。性能
Q1:二分查找能夠應用的前提?測試
①用於查找的數據元素的關鍵字知足有序ui
②而且連續存放(必須是數組,鏈表不能夠)
Q2:二分查找的算法和時間複雜度?
二分查找的時間複雜度爲O(log2N)
1 int BinarySearch(StaticTable*Tbl,ElementType K)//在靜態表Tbl中查找關鍵字爲K的數據元素 2 { 3 int left,right,mid,NoFound=-1; 4 left=1;//初始左邊界 5 right=Tbl->Length;//初始右邊界 6 while(left<=right) 7 { 8 mid=(left+right)/2; 9 if(K<Tbl->Element[mid]) 10 right=mid-1;//調整右邊界 11 else if(K>Tbl->Element[mid]) 12 left=mid+1;//調整左邊界 13 else return mid;//查找成功 14 } 15 return NotFound;//查找不成功,返回-1 16 }
Q3:二分查找對數據的存儲有什麼啓示?
若是數據按照如圖所示的方式存儲,能夠達到相同的查找效果。
4.掌握樹的概念。
Q1:二叉樹的性質及其證實?
對於任何非空二叉樹T,若n0表示葉結點的個數、n2是度爲2的非葉結點的個數,那麼二者知足關係n0=n2+1,證實略。(這條性質很是不熟悉!)
應用該性質的一道課後題:
如有一二叉樹的總結點數爲98,只有一個兒子的結點數爲48,則該樹的葉結點數是多少?
這樣的樹不存在。總結點數=n0+n1+n2=98,已知n1=48,n2=n0-1,,代入得n0=51/2,故這樣的樹不存在。
其他性質略。
Q2: 課後思考題:一棵度爲 m的樹有n個節點。若每一個節點直接用m個鏈指向相應的兒子,則表示這個樹所須要的總空間是n*(m+1) ,(假定每一個鏈以及表示節點的數據域都是一個單位空間).。當採用兒子/兄弟(First Child/Next Sibling)表示法時,所需的總空間是
由於每一個節點都得須要m個鏈來指向相應的兒子,再加上一共有n個節點,故一共有n*(m+1)個子節點。現考慮兒子/兄弟表示法,將n個節點串聯,由於每一個節點都由兩個部分組成,一個是指向下一個兄弟節 點,一個是指向子節點,因此在樹中,這兩個部分都是須要做爲一個單位(即鏈)來存儲的,加上n個節點自己,一共須要3n個空間。
5.掌握樹的存儲方式。
6. 掌握樹的遍歷方法(遞歸、非遞歸遍歷)
二叉樹遍歷的核心問題是 二維結構的線性化,須要一個存儲結構保暫時不訪問的結點。
Q1: 樹的非遞歸遍歷如何實現?
非遞歸算法實現的基本思路:使用堆棧。
以中序遍歷爲例:
算法思想:①遇到一個結點,將其壓入棧中,並遍歷它的左子樹。
②當左子樹遍歷結束後,從棧頂彈出這個節點並訪問它
③按照右指針中序遍歷該結點的右子樹。
void InOrderTraversal(BinTree BT) { BinTree T=BT; Stack S=CreatStack(MaxSize);//建立並初始化堆棧 while(T||!IsEmpty(S)) { while(T) {//一直向左並將沿途結點壓入堆棧 Push(S,T); T=T->Left; } if(!IsEmpty(S)) { T=Pop(S);//結點彈出堆棧 printf("%5d",T->Data);//訪問打印結點 T=T->Right;//轉向右子樹 } } } 中序
Q2:如何實現樹的層序遍歷?
利用隊列實現。
Step 1:先將根結點入隊。
Step 2:從隊列中取出一個元素。
Step 3:訪問該元素所指的結點。
Step 4:若該元素所指結點的左、右孩子結點非空,則將其左、右孩子的指針順序入隊。
1 void LevelOrderTraversal(BinTree BT) 2 { 3 Queue Q;BinTree T; 4 if(!BT)return; 5 Q=CreateQueue(MaxSize); 6 AddQ(Q,BT); 7 while(!IsEmptyQ(Q)) 8 { 9 T=DeleteQ(Q); 10 printf("%d\n",T->data); 11 if(T->Left) AddQ(Q,T->Left); 12 if(T->Right) AddQ(Q,T->Right); 13 } 14 15 }
Q3: 如何求二叉樹的高度?
1 int PostOrderGetHeight(BinTree BT) 2 { 3 int HL,HR,MaxH; 4 if(BT) 5 { 6 HL=PostOrderGetHeight(BT->Left); 7 HR=PostOrderGetHeight(BT->Right); 8 maxH=(HL>HR)?HL:HR; 9 return(MaxH+1); 10 } 11 else 12 return 0; 13 }
Q4:如何根據二元表達式樹遍歷獲得算式?
經過三種順序遍歷獲得三種表達式。
注意:中綴表達式會受到運算符優先級的影響。
Q5:若是由兩種遍歷序列肯定一棵二叉樹,那麼必需要有的遍歷?
中序遍歷。
Q6:課後思考題:假定只有四個結點A、B、C、D的二叉樹,其前序遍歷序列爲ABCD,則下面哪一個序列是不可能的中序遍歷序列?
A. ABCD
B. ACDB
C. DCBA
D. DABC
注意解題方法,容易思路混亂。逐一分析:
A
B
C
7.掌握二叉搜索樹及其相關操做。
Q1:請給出二叉搜索樹的定義?
二叉搜索樹又稱二叉排序樹或者二叉查找樹,能夠爲空,若是不爲空,它知足如下條件:
①非空左子樹的全部鍵值小於其根結點的鍵值;
②非空右子樹的全部鍵值大於其根結點的鍵值;
③左右子樹都是二叉搜索樹。
Q2:如何進行樹的查找操做?如何改進算法使效率更高?
1 Position Find(ElementType X,BinTree BST) 2 { 3 if(!BST)return NULL; 4 if(X>BST->Data) 5 return Find(X,BST->Right); 6 else if(X<BST->Data) 7 return Find(X,BST->Left); 8 else 9 return BST; 10 }
上述算法採用尾遞歸的方法(尾遞歸:即指在函數返回時用到遞歸)由於非遞歸函數的執行效率更高,所以將尾遞歸改成迭代函數。
1 Position IterFind(ElementType X,BinTree BST) 2 { 3 while(BST) 4 { 5 if(X>BST->Data) 6 BST=BST->Right; 7 else if(X<BST->Data) 8 BST=BST->Left; 9 else 10 return BST; 11 } 12 return NULL; 13 }
Q3:如何查找最大和最小元素?
基本原則:最大元素必定在樹的最右分枝的端節點上;最小元素必定在樹的最左分枝的端節點上。
1 Position FindMin(BinTree BST) 2 { 3 if(!BST)return NULL; 4 else if(!BST->Left) 5 return BST; 6 else 7 return FindMin(BST->Left); 8 } 9 Position FindMax(BinTree BST) 10 { 11 if(BST) 12 while(BST->Right) BST=BST->Right; 13 return BST; 14 }
Q4:如何進行二叉搜索樹的插入?
1 BinTree Insert(ElementType X,BinTree BST) 2 { 3 if(!BST)//若原樹爲空,生成並返回一個結點的二叉搜索樹 4 { 5 BST=malloc(sizeof(struct TreeNode)); 6 BST->Data=X; 7 BST->Left=BST->Right=NULL; 8 } 9 else 10 if(X<BST->Data) 11 BST->Left=Insert(X,BST->Left); 12 else if(X>BST->Data) 13 BST->Right=Insert(X,BST->Right); 14 return BST; 15 16 }
Q5:如何進行二叉搜索樹的刪除?
思路:分三種狀況考慮:
①要刪除的是葉結點:直接刪除,而後將其父結點所指的指針置爲NULL
②要刪除的是隻有一個孩子的結點:將其父結點的指針指向要刪除結點的孩子結點。
③要刪除的是有左右兩子樹的結點:用另外一結點(取右子樹中的最小元素或者左子樹中的最大元素)替代被刪除的結點。
1 BinTree Delete(ElementType X,BinTree BST)//本算法由兩部分組成,一是遞歸,二是上述討論的刪除操做 2 { 3 Position Tmp; 4 if(!BST) printf("要刪除的元素未找到"); 5 else if(X<BST->Data) 6 BST->Left=Delete(X,BST->Left);//左子樹遞歸刪除 7 else if(X>BST->Data) 8 BST->Right=Delete(X,BST->Right);//右子樹遞歸刪除 9 else//找到要刪除的結點 10 if(BST->Left&&BST->Right)//被刪除結點有左右兩個子結點 11 { 12 Tmp=FindMin(BST->Right);//在右子樹中找最小的元素填充刪除結點 13 BST->Data=Tmp->Data; 14 BST->Right=Delete(BST->Data,BST->Right);//在刪除結點的右子樹中刪除最小元素 15 } 16 else 17 {//被刪除結點有一個或無子結點 18 Tmp=BST; 19 if(!BST->Left)//有右孩子或無子結點 20 { 21 BST=BST->Right; 22 } 23 else if(!BST->Right)//有左孩子或無子結點 24 BST=BST->Left; 25 free(Tmp); 26 } 27 return BST; 28 }
8.掌握平衡二叉樹及其相關操做。
Q1:請給出平衡因子以及平衡二叉樹的定義?
平衡因子(Balance Factor,簡稱BF)BF(T)=hL-hR,hL和hR分別爲T左右兩棵子樹的高度。
平衡二叉樹:空樹或者任一結點左右子樹高度差的絕對值不超過1,即|BF(T)|≤1
Q2:有關平衡二叉樹結點及高度的結論?
9.掌握堆。
Q1:爲何要定義堆這一數據結構?
優先隊列是一種特殊的隊列,其取出元素的順序是按照元素優先權(關鍵字)的大小,而不是元素進入隊列的前後順序。比較多種數據結構後,決定採用二叉樹結構做爲優先隊列的存儲方式,而且考慮樹的結構和樹的結點存儲順序:將最大關鍵字存儲在樹的根結點,爲了保證在插入和刪除元素時樹能保持平衡,採用徹底二叉樹的結構。
Q2:堆的兩個特性?
結構性:用數組表示的徹底二叉樹
有序性:任意結點的關鍵字是其子樹全部結點的最大值(最小值)。最大堆又稱大頂堆,最大值;最小堆又稱小頂堆,最小值。(注:從根結點到任意結點路徑上結點序列都存在有序性)
Q3:堆的數據結構及相關操做?
1 typedef struct HeapStruct *MaxHeap; 2 struct HeapStruct 3 { 4 ElementType *Elements;//存儲堆元素的數組 5 int Size;//堆的當前元素個數 6 int Capacity;//堆的最大容量 7 }; 8 MaxHeap Create(int MaxSize) 9 {//建立容量爲MaxSize的空的最大堆 10 MaxHeap H=malloc(sizeof(struct HeapStruct)); 11 H->Elements=malloc((MaxSize+1)*sizeof(ElementType)); 12 H->Size=0; 13 H->Capacity=MaxSize; 14 H->Elements[0]=MaxData;//定義「哨兵」 爲大於堆中全部可能元素的值,便於之後更快操做 15 return H; 16 }
void Insert(MaxHeap H,ElementType item) { //將元素item插入最大堆H,其中H->Elements[0]已經定義爲哨兵 int i; if(IsFull(H)) { printf("最大堆已滿"); return; } i=++H->Size;//i指向插入後堆中的最後一個元素的位置 for(;H->Element[i/2]<item;i/=2) H->Elements[i]=H->Elements[i/2];//向根部過濾結點 H->Elements[i]=item;//將item插入 //H->Element[0]是哨兵元素,它不小於堆中的最大元素,控制循環結束 }
1 ElementType DeleteMax (MaxHeap H) 2 //從最大堆中取出鍵值爲最大的元素並刪除一個結點 3 { 4 int Parent,Child; 5 ElementType MaxItem,temp; 6 if(IsEmpty(H)) 7 { 8 printf("最大堆已爲空"); 9 } 10 MaxItem=H->Elements[1];//取出根結點最大值 11 //用最大堆中最後一個元素從根結點開始向上過濾下層結點 12 temp=H->Elements[H->Size--];//將最大堆中最後一個元素存放在temp中,由於刪除了根結點,因此H->Size自減1 13 for(Parent=1;Parent*2<=H->Size;Parent=Child) 14 {//從根結點的位置開始循環 15 //Parent*2<=H->Size是爲了判斷是否存在左孩子(根節點編號爲1時,結點i的左右孩子分別爲:2i和2i+1,若是不存在左孩子天然也不存在右孩子,循環就能夠結束了) 16 //Parent=Child向下繼續循環直到找到合適的位置 17 Child=Parent*2;//指針指向左孩子 18 if((Child))!=H->Size)&&(H->Elements[Child]<H->Elements[Child+1])//Child!=H->Size是左右孩子都存在的狀況 19 Child++;//本段語句用來將指針指向左右孩子中最大的那個 20 if(temp>=H->Elements[Child])break;//若是temp的值大於等於左右孩子中最大的那個值,說明這裏就是temp最合適的位置,循環結束 21 else 22 H->Elements[Parent]=H->Elements[Child];//繼續向下尋找合適的位置 23 } 24 H->Elements[Parent]=temp;//將temp的值放在合適的位置上 25 return MaxItem;//返回最大值 26 }
Q4:如何創建最大堆:將已經存在的N個元素按照最大堆的要求存放在一個一維數組中?
Step 1:將N個元素按輸入順序存入,先知足徹底二叉樹的結構特性。
Step 2:調整各結點的位置,以知足最大堆的有序特性。
Q5:課後錯題:
建堆時,最壞狀況下須要挪動元素次數是等於樹中各結點的高度和。問:對於元素個數爲12的堆,其各結點的高度之和是多少?
該題即求最壞狀況下須要下沉的結點的挪動次數。最壞狀況就是最小堆調整爲最大堆:倒數第二層向下移動一次,倒數第三層向下移動兩次,倒數第四層向下移動三次。即3*1+2*2+1*3=10
10.掌握哈夫曼樹與哈夫曼編碼。
Q1:請給出哈夫曼樹的定義?
帶權路徑長度(WPL):設二叉樹有n個葉子結點,每一個葉子結點帶有權值wk,從根結點到每一個葉子結點的長度爲Ik,則每一個葉子結點的帶權路徑長度之和就是:WPL=ΣwkIk
而最優二叉樹(即哈夫曼樹)就是WPL最小的樹
Q2:哈夫曼樹如何構造?
每次將權值最小的兩棵樹合併。而如何選取最小,能夠用堆解決問題。
Q3:請給出哈夫曼樹的特色?
①沒有度爲1的結點
②n個葉子結點的哈夫曼樹共有2n-1個結點(推導過程:n0:葉結點數;n1:只有一個兒子的結點總數;n2:有兩個兒子的結點總數;n2=n0-1)。
③哈夫曼樹的任意非葉結點的左右子樹交換後仍然爲哈夫曼樹。
Q4:課後錯題:
1.爲五個使用頻率不一樣的字符設計哈夫曼編碼,下列方案中哪一個不多是哈夫曼編碼?A.00,100,101,110,111 B.000,001,01,10,11 C.0000,0001,001,01,1 D.000,001,010,011,1
A畫出來如圖所示,不符合哈夫曼樹的性質:沒有度爲1的結點。
2.一段文本中包含對象{a,b,c,d,e},其出現次數相應爲{3,2,4,2,1},則通過哈夫曼編碼後,該文本所佔總位數爲:
文本所佔總位數=3X1+3X2+2X2+2X3+2X4=27(最後一步算錯了,忘記乘以頻率)
11.掌握集合及運算。(期中測試中該知識點出錯)
Q1:什麼是並查集?
並查集是數據結構之一,主要用於解決一些元素分組的問題。它管理一系列不相交的集合,並支持兩種操做:合併(Union):把兩個不相交的集合合併爲一個集合;查詢(Find):查詢兩個元素是否在同一個集合中。並查集的重要思想在於,用集合中的一個元素表明集合。
Q2:並查集中集合存儲如何實現?
Q3:請寫出集合運算的算法?
第六講 圖
1.重點介紹圖的鄰接矩陣存儲。
Q1:對無向圖的鄰接矩陣如何存儲才能節省空間?
Q2:鄰接矩陣表示的優勢是?
① 簡單直觀 ②便於找出任一頂點全部的「鄰接點」
③便於檢查任意頂點之間是否存在邊
④便於計算任一頂點的度(出度和入度都很明確)【注意:有向圖的出度是對應行的非零元素的個數,入度是對應列的非零元素的個數】
Q3:鄰接矩陣表示的缺點是?
①浪費空間,存稀疏圖時有大量無效元素
②浪費時間,如想要統計圖中共有多少條邊
2.重點介紹圖的鄰接表存儲。
Q1:用簡練的語言描述一下鄰接表存儲如何實現?
G[N]爲指針數組,對應矩陣每行一個鏈表,用來存儲非零元素。
Q2:鄰接表表示的優勢是?
①便於找到任意頂點的全部「鄰接點」。
②節約稀疏圖的空間。須要N個頭指針+2E個結點(每一個結點至少兩個域)。
Q3:鄰接表表示的缺點是?
①對於無向圖來講易計算度,而對於有向圖來講只能計算出度,還須要逆鄰接表計算入度。
②不便於檢查任意兩個頂點間是否存在邊。
3.重點掌握兩種表示方法的C語言實現。
1 /* 圖的鄰接矩陣表示法(C語言實現) */
2 #define MaxVertexNum 100 /* 最大頂點數設爲100 */
3 #define INFINITY 65535 /* ∞設爲雙字節無符號整數的最大值65535*/
4 typedef char VertexType; /* 頂點類型設爲字符型 */
5 typedef int EdgeType; /* 邊的權值設爲整型 */
6 enum GraphType { DG, UG, DN, UN }; 7 /* 有向圖,無向圖,有向網圖,無向網圖*/
8
9 typedef struct { 10 VertexType Vertices[ MaxVertexNum ]; /* 頂點表 */
11 EdgeType Edges[ MaxVertexNum ][ MaxVertexNum ]; 12 /* 鄰接矩陣,即邊表 */
13 int n, e; /* 頂點數n和邊數e */
14 enum GraphType GType; /* 圖的類型分4種:UG、DG、UN、DN */
15 } MGraph; /* MGragh是以鄰接矩陣存儲的圖類型 */
16
17 void CreateMGraph ( MGraph *G ) 18 { 19 int i, j, k, w; 20 G-> GType = UN; /* Undirected Network 無向網圖 */
21 printf( "請輸入頂點數和邊數(輸入格式爲:頂點數, 邊數):\n" ); 22 scanf( "%d, %d",&(G->n), &(G->e) ); /* 輸入頂點數和邊數 */
23 printf("請輸入頂點信息(輸入格式爲:頂點號<CR>):\n"); 24 for ( i = 0; i < G->n; i++ ) 25 scanf( "%c",&(G-> Vertices[i]) ); /* 輸入頂點信息,創建頂點表 */
26 for ( i = 0; i < G->n; i++ ) 27 for ( j = 0; j < G->n; j++ ) 28 G->Edges[i][j] = INFINITY; /* 初始化鄰接矩陣 */
29 printf( "請輸入每條邊對應的兩個頂點的序號和權值,輸入格式爲:i, j, w:\n" ); 30 for ( k = 0; k < G->e; k++ ) { 31 scanf("%d,%d,%d ",&i, &j, &w); /* 輸入e條邊上的權,創建鄰接矩陣 */
32 G->Edges[i][j] = w; 33 G->Edges[j][i] = w; /* 由於無向網圖的鄰接矩陣是對稱的 */
34 } 35 }
1 /* 圖的鄰接表表示法(C語言實現) */ 2 #define MaxVertexNum 100 /* 最大頂點數爲100 */ 3 enum GraphType { DG, UG, DN, UN }; 4 /* 有向圖,無向圖,有向網圖,無向網圖*/ 5 typedef struct node{ /* 邊表結點 */ 6 int AdjV; /* 鄰接點域 */ 7 struct node *Next; /* 指向下一個鄰接點的指針域 */ 8 /* 若要表示邊上的權值信息,則應增長一個數據域Weight */ 9 } EdgeNode; 10 typedef char VertexType; /* 頂點用字符表示 */ 11 typedef struct Vnode{ /* 頂點表結點 */ 12 VertexType Vertex; /* 頂點域 */ 13 EdgeNode *FirstEdge; /* 邊表頭指針 */ 14 } VertexNode; 15 typedef VertexNode AdjList[ MaxVertexNum ]; /* AdjList是鄰接表類型 */ 16 typedef struct{ 17 AdjList adjlist; /* 鄰接表 */ 18 int n, e; /* 頂點數和邊數 */ 19 enum GraphType GType; /* 圖的類型分4種:UG、DG、UN、DN */ 20 } ALGraph; /*ALGraph是以鄰接表方式存儲的圖類型 */ 21 22 void CreateALGraph( ALGraph *G ) 23 { 24 int i, j, k; 25 EdgeNode *edge; 26 G-> GType = DG; /* Directed Graph 有向圖 */ 27 printf( "請輸入頂點數和邊數(輸入格式爲:頂點數,邊數):\n" ); 28 scanf( "%d,%d", &(G->n), &(G->e) ); /* 讀入頂點數和邊數 */ 29 printf( "請輸入頂點信息(輸入格式爲:頂點號<CR>):\n" ); 30 for ( i=0; i < G->n; i++ ) { /* 創建有n個頂點的頂點表 */ 31 scanf( " %c", &(G->adjlist[i].Vertex) ); /* 讀入頂點信息 */ 32 G->adjlist[i].FirstEdge = NULL; /* 頂點的邊表頭指針設爲空 */ 33 } 34 printf( "請輸入邊的信息(輸入格式爲: i, j <CR>):\n" ); 35 for ( k=0; k < G->e; k++ ){ /* 創建邊表 */ 36 scanf( "\n%d,%d", &i, &j); /* 讀入邊<vi,vj>的頂點對應序號*/ 37 edge = (EdgeNode*)malloc(sizeof(EdgeNode)); /* 生成新邊結點edge */ 38 edge->AdjV = j; /* 鄰接點序號爲j */ 39 edge->Next = G->adjlist[i].FirstEdge; 40 /* 將新邊表結點edge插入到頂點vi的邊表頭部 */ 41 G->adjlist[i].FirstEdge = edge; 42 /* 如果無向圖,還要生成一個結點,用來表示邊< vj, vi> */ 43 } 44 }圖的
4.重點掌握DFS.BFS
Q1:DFS的時間複雜度?
①採用鄰接表存儲:O(N+E)
②採用鄰接矩陣存儲:O(N2)
Q2:BFS的算法(c++函數實現)
1 void BFS(Graph G,int v) 2 { 3 cout<<v; 4 visited[v]=true;//訪問第v個頂點 5 InitQueue(Q);//輔助隊列Q初始化,置空 6 EnQueue(Q,v);//v進隊 7 while(!QueueEmpty(Q))//隊列非空 8 { 9 DeQueue(Q,u);//隊頭元素出隊並置爲u 10 for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)) 11 if(!visited[w])//w爲u的還沒有訪問的鄰接頂點 12 { 13 cout<<w; 14 visited[w]=true; 15 EnQueue(Q,w);//w進隊 16 } 17 } 18 }
Q3:DFS算法和BFS算法(C語言實現)
1 /* 鄰接表存儲的圖 – DFS(C語言實現) */ 2 /* Visited[]爲全局變量,已經初始化爲FALSE */ 3 void DFS( ALGraph *G, int i ) 4 { /* 以Vi爲出發點對鄰接表存儲的圖G進行DFS搜索 */ 5 EdgeNode *W; 6 printf( "visit vertex: %c\n", G->adjlist[i].Vertex ); 7 /* 至關於訪問頂點Vi */ 8 Visited[i] = TRUE; /* 標記Vi已訪問 */ 9 for( W = G->adjlist[i].FirstEdge; W; W = W->Next ) 10 if ( !Visited[ W->AdjV ] ) 11 DFS( G, W->AdjV ); 12 } 13 14 /* 鄰接矩陣存儲的圖 – BFS(C語言實現) */ 15 void BFS ( MGraph G ) 16 { /* 按廣度優先遍歷圖G。使用輔助隊列Q和訪問標誌數組Visited */ 17 Queue *Q; 18 VertexType U, V, W; 19 for ( U = 0; U < G.n; ++U ) 20 Visited[U] = FALSE; 21 Q = CreatQueue( MaxSize ); /* 建立空隊列Q */ 22 for ( U = 0; U<G.n; ++U ) 23 if ( !Visited[U] ) { /* 若U還沒有訪問 */ 24 Visited[U] = TRUE; 25 printf( "visit vertex: %c\n", G.Vertices[U] ); 26 /* 至關於訪問頂點U */ 27 AddQ (Q, U); /* U入隊列 */ 28 while ( ! IsEmptyQ(Q) ) { 29 V = DeleteQ( Q ); /* 隊頭元素出隊並置爲V */ 30 for( W = FirstAdjV(G, V); W; W = NextAdjV(G, V, W) ) 31 if ( !Visited[W] ) { 32 Visited[W] = TRUE; 33 printf( "visit vertex: %c\n", G.Vertices[W] ); 34 /* 至關於訪問頂點W */ 35 AddQ (Q, W); 36 } 37 } /* while結束*/ 38 } /* 結束從U開始的BFS */ DFS
5.掌握圖相關名詞的概念
Q1:什麼是圖的連通份量?
無向圖的極大連通子圖
Q2:什麼是無向徹底圖?什麼是有向徹底圖?
在無向圖中,若是任意兩個頂點之間都存在邊,則稱該圖爲無向徹底圖。
在有向圖中,若是任意兩個頂點之間都存在方向互爲相反的兩條弧,則稱爲有向徹底圖。
6.掌握圖的應用示例 :拯救007
在老電影「007之生死關頭」(Live and Let Die)中有一個情節,007被毒販抓到一個鱷魚池中心的小島上,他用了一種極爲大膽的方法逃脫 —— 直接踩着池子裏一系列鱷魚的大腦殼跳上岸去!
設鱷魚池是長寬爲100米的方形,中心座標爲 (0, 0),且東北角座標爲 (50, 50)。池心島是以 (0, 0) 爲圓心、直徑15米的圓。給定池中分佈的鱷魚的座標、以及007一次能跳躍的最大距離,你須要告訴他是否有可能逃出生天。
輸入格式:
首先第一行給出兩個正整數:鱷魚數量 N(≤)和007一次能跳躍的最大距離 D。隨後 N 行,每行給出一條鱷魚的 ( 座標。注意:不會有兩條鱷魚待在同一個點上)。
輸出格式:
若是007有可能逃脫,就在一行中輸出"Yes",不然輸出"No"。
題目分析:
Step 1:從題目中抽象出圖的模型。注意一個誤區:可能認爲圖的頂點只是鱷魚頭,而忽略了岸邊也是「圖的頂點」。
Step 2:圖的邊如何創建?
①以孤島爲圓心,以007跳躍的最大距離爲半徑,觀察有哪些頂點落入圓中
②選定一個頂點進行DFS,當發現該頂點沒法靠岸,則返回,選取下一個頂點,進行遞歸調用剛纔的函數
③直到找到能夠靠岸的頂點爲止
算法設計:
1 void Save007(Graph G) 2 { 3 for(each V in G) 4 { 5 if(!visited[V]&&FirstJump(V))//FirstJump函數是用來判斷是否在跳躍範圍內 6 { 7 answer=DFS(V); 8 if(answer==YES) break; 9 } 10 11 } 12 if(answer==YES)output("YES"); 13 else output("NO"); 14 } 15 int DFS(Vertex V)//在DFS算法的基礎上進行改良 16 { 17 visited[V]=true; 18 if(IsSafe(V))answer=YES;//IsSafe函數用來判斷該頂點是否能夠一步到岸 19 else 20 { 21 for(each W in G) 22 { 23 if(!visited[W]&&Jump(V,W)) 24 { 25 answer=DFS(W); 26 if(answer==YES) break; 27 } 28 } 29 } 30 return answer; 31 }
7.掌握圖的應用示例:六度空間
算法思路:對每一個結點進行廣度優先搜索,搜素過程當中累計訪問的結點數,須要記錄層數:僅計算六層之內的結點數。
8.掌握最小生成樹。
Q1:如何理解最小生成樹?
是一棵樹代表其沒有迴路,若是存在|V|個頂點則必定有|V-1|條邊;是生成樹代表其包含所有頂點且|V-1|條邊均在樹裏;最小則是指邊的權重最小。
Q2:如何理解Prim算法和Kruskal算法?
Prim算法「讓一棵小樹長大」是指選準一個頂點添加邊;Krusal算法「將森林合併成樹」是指直接按權值選取邊。
9.掌握拓撲排序。
Q1:如何理解拓撲排序及相關算法?
Q2:如何理解及計算關鍵路徑?(易出錯)
Q3:課後錯題
錯因:誤解題意。
從0到3的時間是12,是由0-2-3決定的,而不是0-1-3。而4也是由0-2影響的,因此加快之後,能提早完工。
期中測試錯題彙總
1.有向圖鄰接矩陣中第i行非零元素的個數爲第i個頂點的出度,第i列非零元素的個數爲第i個頂點的入度。
2.設h爲不帶頭結點的單向鏈表。在h的頭上插入一個新結點t的語句是:t->next=h; h=t;
3.採用多項式的非零項鍊式存儲表示法,若是兩個多項式的非零項分別爲N1和N2個,最高項指數分別爲M1和M2,則實現兩個多項式相加的時間複雜度是:O(N1+N2)
M1和M2爲干擾選項,與本題無關。實現兩個多項式相加,必須把每一個多項式都遍歷一遍。
4.一棵共有 18 個結點的三叉樹中,度爲 2 的結點 3 個,度爲 3 的結點 2 個,問該樹度爲 1 的結點有幾個?
樹中結點數 = 全部結點度數之和+1 故爲5個
5.若一搜索樹(查找樹)也是棵有 n 個結點的徹底二叉樹,則不正確的說法是:最大值必定在葉結點上,反例以下:
正確:中位值結點在根結點或根的左子樹上。
第九至十講 排序
1.掌握簡單排序:冒泡排序和插入排序。
Q1:如何理解簡單排序?
Q2:如何理解冒泡排序?
Q3:如何理解插入排序?
Q4:插入排序中時間複雜度的下界由什麼決定?
2.掌握希爾排序。
Q1:請問希爾排序的原理是?
Q2:希爾排序的基本算法是?
Q3:希爾排序中要注意什麼狀況?
增量元素不互質的狀況,會致使部分增量達不到排序效果。
3.掌握堆排序。
Q1:請給出堆排序的兩種算法並分析優劣?
Q2:堆排序是不穩定的,請舉個例子解釋?
1 2(1) 3 2(2)
1
/ \
2(1) 3
/
2(2)
2(2)
/ \
2(1) 3
/
1
3
/ \
2(1) 2(2)
/
1
4.掌握歸併排序。
Q1:歸併排序的合併的核心是?
有序子列的合併。
Q2:如何用算法實現有序子列的合併?
Q3:如何用遞歸算法實現歸併排序?
Q4:如何用非遞歸算法實現歸併排序?
5.掌握快速排序。
Q1:如何描述快速排序?
Q2:子集劃分時若是出現元素等於主元,如何解決?
第二種處理方法的時間複雜度如右圖。
Q3:請給出快速排序的算法實現並解釋部分設計的思路?
爲何要設計Cutoff及Insertion_Sort()函數?
爲何要設計void Quick_Sort()?
統一函數接口。
6.掌握表排序。
Q1:如何進行間接排序(不移動數據的位置,只改變數據的輸出順序)?
排序時採用插入排序。
Q2:1.如何進行物理排序(移動數據)?
N個數字的排列由若干個獨立的環組成,只需在環的內部進行數據順序的調整。如圖:不一樣顏色表示不一樣的環。
2. 如何判斷一個環的結束?
每訪問一個空位i後,就令table[i]=i。當發現table[i]==i時,環結束。
3.該算法的時間複雜度分析?
7.掌握桶排序和基數排序。
Q1:請用簡要描述桶排序?
把數據放入到多個桶裏面,在對桶裏面的數據進行排序,而後遍歷各桶獲得元素序列。
Q2:請簡要描述基數排序?
將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。
Q3:請給出基數排序的例子?
Q4:請給出兩種基數排序MSD和LSD的例子?
就本例來講,LSD的方法只需放入桶後按照桶的順序取出,不用再對桶中的數據進行排序,效率更高。
8.掌握排序算法的比較。
課後錯題:
1.下列排序算法中,哪一種算法可能出現:在最後一趟開始以前,全部的元素都不在其最終的位置上?
A.堆排序 B.插入排序 C.冒泡排序 D.快速排序
解析:注意是全部的元素!插入排序可能使所有的元素髮生位移,而堆排序使左右兩個子樹,某個子樹上的元素髮生位移。
2.數據序列(3,2,4,9,8,11,6,20)只能是下列哪一種排序算法的兩趟排序結果?
解析:對於後三種排序方法,兩趟排序後,序列的首部或尾部的兩個元素應是有序的兩個極值,而給定的序列不知足。
Q2:請簡述散列函數的構造?
①數字關鍵詞散列函數的構造:直接定址法(取關鍵詞的某個線性函數值爲散列地址)、除留餘數法、數字分析法、摺疊法、平方取中法。
②字符關鍵詞散列函數的構造:
注:C 庫函數 int atoi(const char *str) 把參數 str 所指向的字符串轉換爲一個整數(類型爲 int 型)。
散列表是一個包含關鍵字的具備固定大小的數組,表的大小記爲 TableSize.
<<在C語言中表明左移運算符。
Q3:處理衝突的方法有哪些?
①換個位置:開放定址法 ②同一個位置的衝突對象組織在一塊兒:鏈地址法
Q4:開放地址法分爲哪些?
①線性探測 ②平方探測 ③雙散列
ASLu的計算方法(以該散列表爲例):
地址0,到第一個關鍵字爲空的地址2須要比較3次,所以查找不成功的次數爲3.
地址1,到第一個關鍵字爲空的地址2須要比較2次,所以查找不成功的次數爲2.
地址3,到第一個關鍵字爲空的地址2須要比較1次,所以查找不成功的次數爲1.
地址7,到第一個關鍵字爲空的地址2須要比較9次,所以查找不成功的次數爲9.(從地址6開始,再循環回去).
Q5:請寫出平方探測法的算法?
Q6:什麼是雙散列探測法?
Q7:什麼是再散列探測法?
Q8:請描述分裂連接法及其算法?
10.掌握散列表的性能分析。
Q1:影響散列表性能的因素有哪些?
Q2:請分析①線性探測 ②平方探測 ③雙散列④分離連接的性能?
Q2:請給出散列方法、開放地址法和分離連接法的優劣勢分析?
Q3:請給出散列表查找的應用實例?