資料結構知識整理
第一章:緒論
1.資料結構:是一門研究非數值計算的程式設計問題中計算機的操作物件以及他們之間的關係和操作等的學科。
2.資料結構涵蓋的內容:
3.基本概念和術語:
資料:對客觀事物的符號表示,在電腦科學中是指所有能輸入到計算機中並被計算機程式處理的符號的總稱。
資料元素:資料的基本單位,在計算機程式中通常作為一個整體進行考慮和處理。
資料物件:性質相同的資料元素的集合,是資料的一個子集。
資料結構:相互之間存在一種或多種特定關係的資料元素的集合。
資料型別:一個值的集合和定義在這個值集上的一組操作的總稱。
4.演算法和演算法分析
1)演算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每一條指令表示一個或多個操作。
演算法五個特性:有窮性,確定性,可行性,輸入,輸出。
2)演算法設計要求:正確性,可讀性,健壯性,效率與低儲存量需求。
3)演算法分析:時間複雜度,空間複雜度,穩定性
第二章:線性表
1.線性結構特點:在資料元素的非空有限集合中,(1)存在唯一的一個被稱做“第一個”的資料元素;(2)存在唯一的一個被稱做“最後一個”的資料元素;(3)除第一個之外,集合中的每個資料元素均只有一個前驅;(4)除最後一個之外,集合中每個資料元素均只有一個後繼。
2.線性表定義:有限個性質相同的資料元素組成的序列。
3.線性表的儲存結構:順序儲存結構和鏈式儲存結構
順序儲存定義:把邏輯上相鄰的資料元素儲存在物理上相鄰的儲存單元中的儲存結構。
通常用陣列來描述資料結構中的順序儲存結構。
鏈式儲存結構: 其結點在儲存器中的位置是隨意的,即邏輯上相鄰的資料元素在物理上不一定相鄰。通過指標來實現。
資料結構的基本運算:修改、插入、刪除、查詢、排序
4.線性表的順序表示和實現
1)修改:通過陣列的下標便可訪問某個特定元素並修改。
時間複雜度O(1)
2) 插入:線上性表的第i個位置前插入一個元素
實現步驟:
①將第n至第i 位的元素逐一向後移動一個位置;
②將要插入的元素寫到第i個位置;
③表長加1。
注意:事先應判斷: 插入位置i 是否合法?表是否已滿?
應當符合條件: 1≤i≤n+1 或 i=[1, n+1]
核心語句:
for (j=n; j>=i; j--)
a[j+1]=a[ j ];
a[ i ]=x;
n++;
插入時的平均移動次數為:n(n+1)/2÷(n+1)=n/2≈O(n)
3)刪除:刪除線性表的第i個位置上的元素
實現步驟:
①將第i+1 至第n 位的元素向前移動一個位置;
②表長減1。
注意:事先需要判斷,刪除位置i 是否合法?
應當符合條件:1≤i≤n 或 i=[1, n]
核心語句:
{
for ( j=i+1; j<=n; j++ )
a[j-1]=a[j];
n--;
}
順序表刪除一元素的時間效率為:T(n)=(n-1)/2 ≈O(n)
順序表插入、刪除演算法的平均空間複雜度為O(1)
5.線性表的鏈式表示和實現
線性連結串列:用一組任意的儲存單元儲存線性表的資料元素(這組儲存單元可以是連續的,也可以是不連續的)。
一個數據元素稱為一個結點,包括兩個域:儲存資料元素資訊的域稱為資料域;儲存直接後繼儲存位置的域稱為指標域。指標域中儲存的資訊稱作指標或鏈。
由於連結串列的每個結點中只包含一個指標域,故線性連結串列又稱為單鏈表。
1)單鏈表的修改(或讀取)
思路:要修改第i個數據元素,必須從頭指標起一直找到該結點的指標p,return p;
然後才能:p->data=new_value
讀取第i個數據元素的核心語句是:
Linklist *find(Linklist *head ,int i)
{
int j=1;
Linklist *p;
P=head->next;
While((p!=NULL)&&(j<i))
{
p=p->next;
j++;
}
}
2)單鏈表的插入
連結串列插入的核心語句:
Step 1:s->next=p->next;
Step 2:p->next=s;
3)單鏈表的刪除
刪除動作的核心語句(要藉助輔助指標變數q):
q = p->next; //首先儲存b的指標,靠它才能找到c;
p->next=q->next; //將a、c兩結點相連,淘汰b結點;
free(q) ; //徹底釋放b結點空間
4)雙向連結串列的插入操作
設p已指向第i 元素,請在第 i 元素前插入元素 x:
① ai-1的後繼從 ai ( 指標是p)變為 x(指標是s) :
s->next = p ; p->prior->next = s ;
② ai 的前驅從ai-1 ( 指標是p->prior)變為 x ( 指標是s);
s->prior = p ->prior ; p->prior = s ;
5)雙向連結串列的刪除操作
設p指向第i 個元素,刪除第 i 個 元素
後繼方向:ai-1的後繼由ai ( 指標p)變為ai+1(指標 p ->next );
p ->prior->next = p->next ;
前驅方向:ai+1的前驅由ai ( 指標p)變為ai-1 (指標 p -> prior );
p->next->prior = p ->prior ;
6.迴圈連結串列
迴圈連結串列是另一種形式的鏈式儲存結構。它的特點是表中最後一個結點的指標域指向頭結點,整個連結串列形成一個環。
迴圈連結串列的操作和線性連結串列基本一致,差別僅在於演算法中的迴圈條件不是p或p->next是否為空,而是它們是否等於頭指標。
學習重點:
-
線性表的邏輯結構,指線性表的資料元素間存在著線性關係。在順序儲存結構中,元素儲存的先後位置反映出這種線性關係,而在鏈式儲存結構中,是靠指標來反映這種關係的。
-
順序儲存結構用一維陣列表示,給定下標,可以存取相應元素,屬於隨機存取的儲存結構。
-
連結串列操作中應注意不要使鏈意外“斷開”。因此,若在某結點前插入一個元素,或刪除某元素,必須知道該元素的前驅結點的指標。
-
掌握通過畫出結點圖來進行連結串列(單鏈表、迴圈連結串列等)的生成、插入、刪除、遍歷等操作。
-
陣列(主要是二維)在以行序/列序為主的儲存中的地址計算方法。
補充重點:
-
每個儲存結點都包含兩部分:資料域和指標域(鏈域)
-
在單鏈表中,除了首元結點外,任一結點的儲存位置由 其直接前驅結點的鏈域的值 指示。
-
在連結串列中設定頭結點有什麼好處?
頭結點即在連結串列的首元結點之前附設的一個結點,該結點的資料域可以為空,也可存放表長度等附加資訊,其作用是為了對連結串列進行操作時,可以對空表、非空表的情況以及對首元結點進行統一處理,程式設計更方便。
-
如何表示空表?
(1)無頭結點時,當頭指標的值為空時表示空表;
(2)有頭結點時,當頭結點的指標域為空時表示空表。
-
連結串列的資料元素有兩個域,不再是簡單資料型別,程式設計時該如何表示?
因每個結點至少有兩個分量,且資料型別通常不一致,所以要採用結構資料型別。
-
sizeof(x)—— 計算變數x的長度(位元組數);
malloc(m) — 開闢m位元組長度的地址空間,並返回這段空間的首地址;
free(p) —— 釋放指標p所指變數的儲存空間,即徹底刪除一個變數。
-
連結串列的運算效率分析:
(1)查詢
因線性連結串列只能順序存取,即在查詢時要從頭指標找起,查詢的時間複雜度為 O(n)。
(2) 插入和刪除
因線性連結串列不需要移動元素,只要修改指標,一般情況下時間複雜度為 O(1)。
但是,如果要在單鏈表中進行前插或刪除操作,因為要從頭查詢前驅結點,所耗時間複雜度將是 O(n)。
例:在n個結點的單鏈表中要刪除已知結點*P,需找到它的前驅結點的地址,其時間複雜度為 O(n)
-
順序儲存和鏈式儲存的區別和優缺點?
順序儲存時,邏輯上相鄰的資料元素,其物理存放地址也相鄰。順序儲存的優點是儲存密度大,儲存空間利用率高;缺點是插入或刪除元素時不方便。
鏈式儲存時,相鄰資料元素可隨意存放,但所佔儲存空間分兩部分,一部分存放結點值,另一部分存放表示結點間關係的指標。鏈式儲存的優點是插入或刪除元素時很方便,使用靈活。缺點是儲存密度小,儲存空間利用率低。
-
順序表適宜於做查詢這樣的靜態操作;
-
連結串列宜於做插入、刪除這樣的動態操作。
-
若線性表的長度變化不大,且其主要操作是查詢,則採用順序表;
-
若線性表的長度變化較大,且其主要操作是插入、刪除操作,則採用連結串列。
① 陣列中各元素具有統一的型別;
② 陣列元素的下標一般具有固定的上界和下界,即陣列一旦被定義,它的維數和維界就不再改變。
③陣列的基本操作比較簡單,除了結構的初始化和銷燬之外,只有存取元素和修改元素值的操作。
-
三元素組表中的每個結點對應於稀疏矩陣的一個非零元素,它包含有三個資料項,分別表示該元素的 行下標 、列下標 和 元素值 。
第三章:棧和佇列
1.棧:限定僅在表尾進行插入或刪除操作的線性表。
棧的基本操作:在棧頂進行插入或刪除,棧的初始化、判空及取棧頂元素等。
入棧口訣:堆疊指標top “先壓後加”
出棧口訣:堆疊指標top “先減後彈”
top=0表示空棧。
2.棧的表示和實現
1)構造一個空棧S
Status InitStack(SqStack &S)
{
S.base = (SElemType *) malloc(STACK_INIT_SIZE * sizeof(SElemType));
if(!S.base) exit (OVERFLOW); //儲存分配失敗
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
2)返回棧頂元素
Status GetTop(SqStack S, SElemType e)
{//若棧不空,則用e返回S的棧頂元素,並返回OK,否則返回ERROR
if(S.top == S.base) return ERROR;
e = *(S.top-1);
return OK;
}//GetTop
3)順序棧入棧函式PUSH()
Status Push(SqStack &S, SElemType e)
{ //插入元素e為新的棧頂元素
if(S.top-S.base>=S.stacksize)//棧滿,追加儲存空間
{
s.base = (SElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!S.base) exit(OVERFLOW);//儲存分配失敗
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ =e;
return OK:
}//PUSH
4)順序棧出棧函式POP()
status Pop( SqStack &S,SElemType &e)
{ //若棧不空,則刪除S的棧頂元素,用e返回其值,並返回OK,否則返回ERROR
if(S.top == S.base) return ERROR;
e=* —S.top;
return OK;
}
3.棧的應用
數制轉換,括號匹配的檢驗,行編輯程式,迷宮求解,表示式求值,遞迴實現。
4.佇列:是一種先進先出的線性表,它只允許在表的一端進行插入,而在另一端刪除元素。
允許插入的一端叫做隊尾,允許刪除的一端叫做隊頭。
除了棧和佇列外,還有一種限定性資料結構是雙端佇列。雙端佇列是限定插入和刪除操作在表的兩端進行的線性表。
5.鏈佇列結點型別定義:
typedef Struct QNode{
QElemType data; //元素
Struct QNode *next; //指向下一結點的指標
}Qnode , * QueuePtr ;
鏈佇列型別定義:
typedef struct {
QueuePtr front ; //隊首指標
QueuePtr rear ; //隊尾指標
} LinkQueue;
鏈隊示意圖:
① 空鏈隊的特徵:front=rear
② 鏈隊會滿嗎?一般不會,因為刪除時有free動作。除非記憶體不足!
③ 入隊(尾部插入):rear->next=S; rear=S;
出隊(頭部刪除):front->next=p->next;
6.順序隊
順序隊型別定義:
#define QUEUE-MAXSIZE 100 //最大佇列長度
typedef struct {
QElemType *base; //佇列的基址
int front; //隊首指標
int rear; //隊尾指標
}SqQueue
建隊核心語句:
q.base=(QElemType *)malloc(sizeof (QElemType)* QUEUE_MAXSIZE); //分配空間
順序隊示意圖:
7.迴圈佇列:
隊空條件 : front = rear (初始化時:front = rear )
隊滿條件: front = (rear+1) % N (N=maxsize)
佇列長度(即資料元素個數):L=(N+rear-front)% N
1)初始化一個空佇列
Status InitQueue ( SqQueue &q ) //初始化空迴圈佇列 q
{
q.base=(QElemType *)malloc(sizeof(QElemType)* QUEUE_MAXSIZE); //分配空間
if (!q.base) exit(OVERFLOW);//記憶體分配失敗,退出程式
q.front =q.rear=0; //置空佇列
return OK;
} //InitQueue;
2)入隊操作
Status EnQueue(SqQueue &q, QElemType e)
{//向迴圈佇列 q 的隊尾加入一個元素 e
if ( (q.rear+1) % QUEUE_MAXSIZE == q.front)
return ERROR ; //隊滿則上溢,無法再入隊
q.rear = ( q.rear + 1 ) % QUEUE_MAXSIZE;
q.base [q.rear] = e; //新元素e入隊
return OK;
}// EnQueue;
3)出隊操作
Status DeQueue ( SqQueue &q, QElemType &e)
{//若佇列不空,刪除迴圈佇列q的隊頭元素,
//由 e 返回其值,並返回OK
if ( q.front = = q.rear ) return ERROR;//佇列空
q.front=(q.front+1) % QUEUE_MAXSIZE;
e = q.base [ q.front ] ;
return OK;
}// DeQueue
-
鏈佇列空的條件是首尾指標相等,而迴圈佇列滿的條件的判定,則有隊尾加1等於隊頭和設標記兩種方法。
補充重點:
-
為什麼要設計堆疊?它有什麼獨特用途?
-
什麼叫“假溢位” ?如何解決?
① 呼叫函式或子程式非它莫屬;
② 遞迴運算的有力工具;
③ 用於保護現場和恢復現場;
④ 簡化了程式設計的問題。
2.為什麼要設計佇列?它有什麼獨特用途?
① 離散事件的模擬(模擬事件發生的先後順序,例如 CPU晶片中的指令譯碼佇列);
② 作業系統中的作業排程(一個CPU執行多個作業);
③ 簡化程式設計。
答:在順序隊中,當尾指標已經到了陣列的上界,不能再有入隊操作,但其實陣列中還有空位置,這就叫“假溢位”。解決假溢位的途徑———採用迴圈佇列。
4.在一個迴圈佇列中,若約定隊首指標指向隊首元素的前一個位置。那麼,從迴圈佇列中刪除一個元素時,其操作是先 移動隊首位置 ,後 取出元素。
5.線性表、棧、隊的異同點:
相同點:邏輯結構相同,都是線性的;都可以用順序儲存或連結串列儲存;棧和佇列是兩種特殊的線性表,即受限的線性表(只是對插入、刪除運算加以限制)。
不同點:① 運算規則不同:
線性表為隨機存取;
而棧是隻允許在一端進行插入和刪除運算,因而是後進先出表LIFO;
佇列是隻允許在一端進行插入、另一端進行刪除運算,因而是先進先出表FIFO。
② 用途不同,線性表比較通用;堆疊用於函式呼叫、遞迴和簡化設計等;佇列用於離散事件模擬、OS作業排程和簡化設計等。
第四章:串
1.串是資料元素為字元的線性表,串的定義及操作。
串即字串,是由零個或多個字元組成的有限序列,是資料元素為單個字元的特殊線性表。
串比較:int strcmp(char *s1,char *s2);
求串長:int strlen(char *s);
串連線:char strcat(char *to,char *from)
子串T定位:char strchr(char *s,char *c);
2.串的儲存結構,因串是資料元素為字元的線性表,所以存在“結點大小”的問題。
模式匹配演算法 。
串有三種機內表示方法:
3.模式匹配演算法 :
演算法目的:確定主串中所含子串第一次出現的位置(定位)
定位問題稱為串的模式匹配,典型函式為Index(S,T,pos)
BF演算法的實現—即編寫Index(S, T, pos)函式
BF演算法設計思想:
將主串S的第pos個字元和模式T的第1個字元比較,
若相等,繼續逐個比較後續字元;
若不等,從主串S的下一字元(pos+1)起,重新與T第一個字元比較。
直到主串S的一個連續子串字元序列與模式T相等。返回值為S中與T匹配的子序列第一個字元的序號,即匹配成功。
否則,匹配失敗,返回值 0。
Int Index_BP(SString S, SString T, int pos)
{ //返回子串T在主串S中第pos個字元之後的位置。若不存在,則函式值為0.
// 其中,T非空,1≤pos≤StrLength(S)
i=pos; j=1;
while ( i<=S[0] && j<=T[0] ) //如果i,j二指標在正常長度範圍,
{
if (S[i] = = T[j] ) {++i, ++j; } //則繼續比較後續字元
else {i=i-j+2; j=1;} //若不相等,指標後退重新開始匹配
}
if(j>T[0]) return i-T[0]; //T子串指標j正常到尾,說明匹配成功, else return 0; //否則屬於i>S[0]情況,i先到尾就不正常
} //Index_BP
補充重點:
1.空串和空白串有無區別?
答:有區別。
空串(Null String)是指長度為零的串;
而空白串(Blank String),是指包含一個或多個空白字元‘ ’(空格鍵)的字串.
2.“空串是任意串的子串;任意串S都是S本身的子串,除S本身外,S的其他子串稱為S的真子串。”
第五章:陣列和廣義表
重點:二維陣列的位置計算。
矩陣的壓縮儲存:特殊矩陣(三角矩陣,對稱矩陣),稀疏矩陣,
第六章:樹和二叉樹
1.樹:是n(n≥0)個結點的有限集。(1)有且僅有一個特定的稱為根(root)的結點;(2)當n>1時,其餘的結點分為m(m≥0)個互不相交的有限集合T1,T2,…,Tm。每個集合本身又是棵樹,被稱作這個根的子樹 。
2.二叉樹:是n(n≥0)個結點的有限集合,由一個根結點以及兩棵互不相交的、分別稱為左子樹和右子樹的二叉樹組成。
二叉樹的性質,儲存結構。
性質1: 在二叉樹的第i層上至多有2^(i-1)個結點(i>0)。
性質2: 深度為k的二叉樹至多有2^k-1個結點(k>0)。
性質3: 對於任何一棵二叉樹,如果其終端結點數為n0,度為2的結點數有n2個,則葉子數n0=n2+1
性質4: 具有n個結點的完全二叉樹的深度必為 [log2n]+1
性質5: 對完全二叉樹,若從上至下、從左至右編號,則編號為i 的結點,其左孩子編號必為2i,其右孩子編號為2i+1;其雙親的編號必為i/2(i=1 時為根,除外)。
二叉樹的儲存結構:
1).順序儲存結構
按二叉樹的結點“自上而下、從左至右”編號,用一組連續的儲存單元儲存。
若是完全/滿二叉樹則可以做到唯一復原。
不是完全二叉樹:一律轉為完全二叉樹!
方法很簡單,將各層空缺處統統補上“虛結點”,其內容為空。
缺點:①浪費空間;②插入、刪除不便
2).鏈式儲存結構
用二叉連結串列即可方便表示。一般從根結點開始儲存。
lchild |
data |
rchild |
優點:①不浪費空間;②插入、刪除方便
3.二叉樹的遍歷。
指按照某種次序訪問二叉樹的所有結點,並且每個結點僅訪問一次,得到一個線性序列。
遍歷規則:二叉樹由根、左子樹、右子樹構成,定義為D、 L、R
若限定先左後右,則有三種實現方案:
DLR LDR LRD
先序遍歷 中序遍歷 後序遍歷
4.線索二叉樹
1)線索二叉樹可以加快查詢前驅與後繼結點,實質就是將二叉連結串列中的空指標改為指向前驅或後繼的線索,線索化就是在遍歷中修改空指標。
通常規定:對某一結點,若無左子樹,將lchild指向前驅結點;若無右子樹,將rchild指向後繼結點。
還需要設定左右兩個tag,用來標記當前結點是否有子樹。
若ltag==1,lchild指向結點前驅;若rtag==1,rchild指向結點後繼。
2)線索二叉樹的儲存結構如下:
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag;
}ThreadNode, *ThreadTree;
5.樹和森林
1)樹有三種常用儲存方式:
①雙親表示法 ②孩子表示法 ③孩子—兄弟表示法
2)森林、樹、二叉樹的轉換
(1)將樹轉換為二叉樹
樹中每個結點最多隻有一個最左邊的孩子(長子)和一個右鄰的兄弟。按照這種關係很自然地就能將樹轉換成相應的二叉樹:a.在所有兄弟結點之間加一連線
b.對每個結點,除了保留與其長子的連線外,去掉該結點與其它孩子的連線。
(2)將一個森林轉換為二叉樹:
具體方法是:a.將森林中的每棵樹變為二叉樹;
b.因為轉換所得的二叉樹的根結點的右子樹均為空,故可將各二叉樹的根結點視為兄弟從左至右連在一起,就形成了一棵二叉樹。
(3)二叉樹轉換為樹
是樹轉換為二叉樹的逆過程。
a.加線。若某結點X的左孩子結點存在,則將這個左孩子的右孩子結點、右孩子的右孩子結點、右孩子的右孩子的右孩子結點,都作為結點X的孩子。將結點X與這些右孩子結點用線連線起來。
b.去線。刪除原二叉樹中所有結點與其右孩子結點的連線。
(4)二叉樹轉換為森林:
假如一棵二叉樹的根節點有右孩子,則這棵二叉樹能夠轉換為森林,否則將轉換為一棵樹。
a.從根節點開始,若右孩子存在,則把與右孩子結點的連線刪除。再檢視分離後的二叉樹,若其根節點的右孩子存在,則連線刪除。直到所有這些根節點與右孩子的連線都刪除為止。
b.將每棵分離後的二叉樹轉換為樹。
6.樹和森林的遍歷
-
樹的遍歷
① 先根遍歷:訪問根結點;依次先根遍歷根結點的每棵子樹。
② 後根遍歷:依次後根遍歷根結點的每棵子樹;訪問根結點。
-
森林的遍歷
① 先序遍歷
若森林為空,返回;
訪問森林中第一棵樹的根結點;
先根遍歷第一棵樹的根結點的子樹森林;
先根遍歷除去第一棵樹之後剩餘的樹構成的森林。
② 中序遍歷
若森林為空,返回;
中根遍歷森林中第一棵樹的根結點的子樹森林;
訪問第一棵樹的根結點;
中根遍歷除去第一棵樹之後剩餘的樹構成的森林。
7.哈夫曼樹及其應用
Huffman樹:最優二叉樹(帶權路徑長度最短的樹)
Huffman編碼:不等長編碼。
樹的帶權路徑長度:(樹中所有葉子結點的帶權路徑長度之和)
構造Huffman樹的基本思想:權值大的結點用短路徑,權值小的結點用長路徑。
構造Huffman樹的步驟(即Huffman演算法):
(1) 由給定的 n 個權值{ w1, w2, …, wn }構成n棵二叉樹的集合F = { T1, T2, …, Tn } (即森林) ,其中每棵二叉樹 Ti 中只有一個帶權為 wi 的根結點,其左右子樹均空。
(2) 在F 中選取兩棵根結點權值最小的樹 做為左右子樹構造一棵新的二叉樹,且讓新二叉樹根結點的權值等於其左右子樹的根結點權值之和。
(3) 在F 中刪去這兩棵樹,同時將新得到的二叉樹加入 F中。
(4) 重複(2) 和(3) , 直到 F 只含一棵樹為止。這棵樹便是Huffman樹。
具體操作步驟:
應用:用於通訊編碼
在通訊及資料傳輸中多采用二進位制編碼。為了使電文儘可能的縮短,可以對電文中每個字元出現的次數進行統計。設法讓出現次數多的字元的二進位制碼短些,而讓那些很少出現的字元的二進位制碼長一些。假設有一段電文,其中用到 4 個不同字元A,C,S,T,它們在電文中出現的次數分別為 7 , 2 , 4 , 5 。把 7 , 2 , 4 , 5 當做 4 個葉子的權值構造哈夫曼樹如圖(a) 所示。在樹中令所有左分支取編碼為 0 ,令所有右分支取編碼為1。將從根結點起到某個葉子結點路徑上的各左、右分支的編碼順序排列,就得這個葉子結點所代表的字元的二進位制編碼,如圖(b) 所示。這些編碼拼成的電文不會混淆,因為每個字元的編碼均不是其他編碼的字首,這種編碼稱做字首編碼。
第七章 圖
1)圖的定義
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成;
通常表示為:G(V,E),G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合;
注意:在圖中資料元素稱之為頂點(Vertex),而且頂點集合有窮非空;在圖中任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示。
2)圖的分類
-
按照有無方向,分為無向圖和有向圖;
無向圖:如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖為無向圖。
無向邊:若頂點M到頂點N的邊沒有方向,稱這條邊為無向邊,用無序偶對(M,N)或(N,M)表示。
無向圖是有邊和頂點構成。如下圖所示就是一個無向圖G1:
無向圖G1= (V1,{E1}),其中頂點集合 V1={A,B,C,D};邊集合E1={(A,B),(B,C),(C,D),(D,A)}
有向圖:如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為有向圖。
有向邊:若頂點M到頂點N的邊有方向,稱這條邊為有向邊,也稱為弧,用偶序對 < M, N >表示;M表示弧尾,N表示弧頭
有向圖是有弧和頂點構成,如下圖所示是一個有向圖G2:
有向圖G2=(V2,{E2}),其中頂點集合 V2={A,B,C,D};弧集合E2={< A,D>,< B,A>,< C,A>,< B,C>}
對於弧< A,D>來說, A是弧尾,D是弧頭
注意:無向邊用 小括號 “()”表示,有向邊用“<>”表示。
無向完全圖:在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖為無向完全圖。
含有n個頂點的無向完全圖有n * (n-1)/2條邊。下面是一個無向完全圖
4個頂點,6條無向邊,每個頂點對應3條邊 ,一共4個頂點 總共 4*3,每個頂點對應的邊都重複計算了一次,所以整體要除以2。
對於n各 頂點和e條邊的無向圖滿足:0<=e <= n(n-1)/2
有向完全圖:在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,則稱該圖為有向完全圖。
含有n個頂點的無向完全圖有n * (n-1)條邊。下面是一個有向完全圖
4個頂點,12條弧,一共4個頂點 總共 4*3。
2,按照弧或邊的多少,分為稀疏圖和稠密圖;
若邊或弧的個數e<=NlogN(N是頂點的個數),稱為係數圖,否則稱為稠密圖;
3,按照邊或弧是否帶權,其中帶權的圖統稱為網
有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權。
有向網中弧帶權的圖叫做有向網;
無向網中邊帶權的圖叫做無向網;
比如下圖就是一個無向圖
圖的頂點和邊間關係
鄰接點 度 入度 出度
對於無向圖,假若頂點v和頂點w之間存在一條邊,則稱頂點v和頂點w互為鄰接點,邊(v,w)和頂點v和w相關聯。
頂點v的度是和v相關聯的邊的數目,記為TD(v);
上面這個無向圖G1,A和B互為鄰接點,A和D互為鄰接點,B和C互為鄰接點,C和D互為鄰接點;
A的度是2,B的度是2,C的度是2,D的度是2;所有頂點度的和為8,而邊的數目是4;
圖中邊的數目e = 各個頂點度數和的一半。
對於有向圖來說,與某個頂點相關聯的弧的數目稱為度(TD);以某個頂點v為弧尾的弧的數目定義為頂點v的出度(OD);以頂點v為弧頭的弧的數目定義為頂點的入度(ID)
度(TD) = 出度(OD) + 入度(ID);
比如上面有向圖,
A的度為3 ,A的入度 2,A的出度是1
B的度為2 ,B的入度 0,B的出度是2
C的度為2 ,C的入度 1,C的出度是1
D的度為1 ,D的入度 1,D的出度是0
所有頂點的入度和是4,出度和也是4,而這個圖有4個弧
所以 有向圖的弧 e = 所有頂點入度的和 = 所有頂點出度的和
路徑 路徑長度 簡單路徑 迴路 (環) 簡單迴路(簡單環)
設圖G=(V,{E})中的一個頂點序列{u=Fi0,Fi1,Fi2,….Fim=w}中,(Fi,j-1,Fi,j)∈E 1 ≤j ≤m,則稱從頂點u到頂點w之間存在一條路徑,路徑上邊或弧的數目稱作路徑長度,
若路徑中的頂點不重複出現的路徑稱為簡單路徑
若路徑中第一個頂點到最後一個頂點相同的路徑稱為迴路或環
若路徑中第一個頂點和最後一個頂點之外,其餘頂點不重複出現的迴路,稱為簡單迴路或簡單環
比如下圖 :
從B 到 D 中頂點沒有重複出現 ,所以是簡單路徑 ,邊的數目是2,所以路徑長度為 2。
圖1和圖2都是一個迴路(環),圖1中出了第一個頂點和最後一個頂點相同之外,其餘頂點不相同,所以是簡單環(簡單迴路),圖2,有與頂點C重複就不是一個簡單環了;
連通圖相關術語
連通圖
在無向圖G(V,{E})中,如果從頂點V到頂點W有路徑,則稱V和W是連通的。如果對於圖中任意兩個頂點Vi、Vj∈V,Vi和Vj都是連通的,則稱G是連通圖。
如下圖所示:
圖1,頂點A到頂點E就無法連通,所以圖1不是連通;圖2,圖3,圖4屬於連通圖;
連通分量
若無向圖為非連通圖,則圖中各個極大連通子圖稱作此圖的連通分量;
圖1是無向非連通圖,由兩個連通分量,分別是圖2和圖3。圖4儘管也是圖1的子圖,但是它不滿足極大連通,也就說極大連通應當是包含ABCD四個頂點,比如圖2那樣;
強連通圖
在有向圖G(V,{E})中,如果對於每一對Vi ,Vj∈V,Vi≠Vj,從Vi到Vj和從Vj到Vi都存在有向路徑,則稱G是強連通圖。
圖1不是強連通圖因為D到A不存在路徑,圖2屬於強連通圖。
強連通分量
若有向圖不是強連通圖,則圖中各個極大強連通子圖稱作此圖的強連通分量;
圖1不是強連通圖,但是圖2是圖1的強連通子圖,也就是強連通分量;
生成樹和生成森林
生成樹
假設一個連通圖有n個頂點和e條邊,其中n-1條邊和n個頂點構成一個極小連通子圖,稱該極小連通子圖為此連通圖的生成樹;
圖1是一個連通圖含有4個頂點和4條邊,圖2,圖3,圖4含有3條邊和4個頂點,構成了一個極小連通圖子圖,也稱為生成樹,為什麼是極小連通子圖,因為圖2,圖3,圖4中少一條邊都構不成一個連通圖,多一條邊就變成一個迴路(環),所以是3條邊和4個頂點構成的極小連通子圖。圖5儘管也是3個邊4個頂點,但不是連通圖。
生成森林
如果一個有向圖恰有一個頂點的入度為0,其餘頂點的入度為1,則是一顆有向樹;
入度為0,相當於根節點,入度為1,相當於分支節點;,比如下面的有向圖就是一個有向樹
頂點B的入度是0,其餘頂點的入度是1;
一個有向圖的生成森林由若干顆有向樹組成,含有圖中全部頂點,但有足以構成若干顆不相交的有向樹的弧;
有向圖1去掉一些弧後分解成2顆有向樹,圖2和圖3,這兩顆樹就是有向圖圖1的生成森林;
2.圖的儲存結構
1).鄰接矩陣(陣列)表示法
① 建立一個頂點表和一個鄰接矩陣
② 設圖 A = (V, E) 有 n 個頂點,則圖的鄰接矩陣是一個二維陣列 A.Edge[n][n]。
注:在有向圖的鄰接矩陣中,
第i行含義:以結點vi為尾的弧(即出度邊);
第i列含義:以結點vi為頭的弧(即入度邊)。
鄰接矩陣法優點:容易實現圖的操作,如:求某頂點的度、判斷頂點之間是否有邊(弧)、找頂點的鄰接點等等。
鄰接矩陣法缺點:n個頂點需要n*n個單元儲存邊(弧);空間效率為O(n^2)。
2).鄰接表(鏈式)表示法
① 對每個頂點vi 建立一個單鏈表,把與vi有關聯的邊的資訊(即度或出度邊)連結起來,表中每個結點都設為3個域:
② 每個單鏈表還應當附設一個頭結點(設為2個域),存vi資訊;
③ 每個單鏈表的頭結點另外用順序儲存結構儲存。
鄰接表的優點:空間效率高;容易尋找頂點的鄰接點;
鄰接表的缺點:判斷兩頂點間是否有邊或弧,需搜尋兩結點對應的單鏈表,沒有鄰接矩陣方便。
3.圖的遍歷
遍歷定義:從已給的連通圖中某一頂點出發,沿著一些邊,訪遍圖中所有的頂點,且使每個頂點僅被訪問一次,就叫做圖的遍歷,它是圖的基本運算。
圖的遍歷演算法求解圖的連通性問題、拓撲排序和求關鍵路徑等演算法的基礎。
圖常用的遍歷:一、深度優先搜尋;二、廣度優先搜尋
深度優先搜尋(遍歷)步驟:(如下圖)
① 訪問起始點 v;
② 若v的第1個鄰接點沒訪問過,深度遍歷此鄰接點;
③ 若當前鄰接點已訪問過,再找v的第2個鄰接點重新遍歷。
基本思想:——仿樹的先序遍歷過程。
遍歷結果:v1->v2->v4->v8->v5-v3->v6->v7
廣度優先搜尋(遍歷)步驟:
① 在訪問了起始點v之後,依次訪問 v的鄰接點;
② 然後再依次(順序)訪問這些點(下一層)中未被訪問過的鄰接點;
③ 直到所有頂點都被訪問過為止。
遍歷結果:v1->v2->v3->v4->v5-v6->v7->v8
4.圖的連通性問題
1)對無向圖進行遍歷時,對於連通圖,僅需從圖中任一頂點出發,進行深度優先搜尋或廣度優先搜尋,便可訪問到圖中所有頂點。
2)最小生成樹:在連通網的所有生成樹中,所有邊的代價和最小的生成樹。
構造最小生成樹有很多演算法,但是他們都是利用了最小生成樹的同一種性質:MST性質(假設N=(V,{E})是一個連通網,U是頂點集V的一個非空子集,如果(u,v)是一條具有最小權值的邊,其中u屬於U,v屬於V-U,則必定存在一顆包含邊(u,v)的最小生成樹),下面就介紹兩種使用MST性質生成最小生成樹的演算法:普里姆演算法和克魯斯卡爾演算法。
Kruskal演算法特點:將邊歸併,適於求稀疏網的最小生成樹。
Prime演算法特點: 將頂點歸併,與邊數無關,適於稠密網。
Prime演算法構造最小生成樹過程如下圖:
Kruskal演算法構造最小生成樹過程如下圖:
5.有向無環圖及其應用
有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u線上性序列中出現在v之前。
1)拓撲排序
拓撲排序對應施工的流程圖具有特別重要的作用,它可以決定哪些子工程必須要先執行,哪些子工程要在某些工程執行後才可以執行。
我們把頂點表示活動、邊表示活動間先後關係的有向圖稱做頂點活動網(Activity On Vertex network),簡稱AOV網。
一個AOV網應該是一個有向無環圖,即不應該帶有迴路,因為若帶有迴路,則迴路上的所有活動都無法進行(對於資料流來說就是死迴圈)。在AOV網中,若不存在迴路,則所有活動可排列成一個線性序列,使得每個活動的所有前驅活動都排在該活動的前面,我們把此序列叫做拓撲序列(Topological order),由AOV網構造拓撲序列的過程叫做拓撲排序(Topological sort)。AOV網的拓撲序列不是唯一的,滿足上述定義的任一線性序列都稱作它的拓撲序列。
拓撲排序的實現:
a.在有向圖中選一個沒有前驅的頂點並且輸出
b.從圖中刪除該頂點和所有以它為尾的弧(白話就是:刪除所有和它有關的邊)
c.重複上述兩步,直至所有頂點輸出,或者當前圖中不存在無前驅的頂點為止,後者代表我們的有向圖是有環的,因此,也可以通過拓撲排序來判斷一個圖是否有環。
2)關鍵路徑
AOE-網是一個帶權的有向無環圖,其中,頂點表示事件,弧表示活動,權表示活動持續的時間。通常,AOE-網可用來估算工程的完成時間。
關鍵路徑:在AOE網中,從始點到終點具有最大路徑長度(該路徑上的各個活動所持續的時間之和)的路徑稱為關鍵路徑。
關鍵活動:關鍵路徑上的活動稱為關鍵活動。關鍵活動:e[i]=l[i]的活動
由於AOE網中的某些活動能夠同時進行,故完成整個工程所必須花費的時間應該為始點到終點的最大路徑長度。關鍵路徑長度是整個工程所需的最短工期。
與關鍵活動有關的量:
(1)事件的最早發生時間ve[k]:ve[k]是指從始點開始到頂點vk的最大路徑長度。這個長度決定了所有從頂點vk發出的活動能夠開工的最早時間。
(2)事件的最遲發生時間vl[k]:vl[k]是指在不推遲整個工期的前提下,事件vk允許的最晚發生時間。
(3)活動的最早開始時間e[i]:若活動ai是由弧<vk , vj>表示,則活動ai的最早開始時間應等於事件vk的最早發生時間。因此,有:e[i]=ve[k]
(4)活動的最晚開始時間l[i]:活動ai的最晚開始時間是指,在不推遲整個工期的前提下, ai必須開始的最晚時間。若ai由弧<vk,vj>表示,則ai的最晚開始時間要保證事件vj的最遲發生時間不拖後。因此,有:l[i]=vl[j]-len<vk,vj>
示例如下:
6.最短路徑
從某頂點出發,沿圖的邊到達另一頂點所經過的路徑中,各邊上權值之和最小的一條路徑叫做最短路徑。
1)迪傑斯塔拉演算法--單源最短路徑
所有頂點間的最短路徑—用Floyd(弗洛伊德)演算法
第八章:查詢
查詢表是稱為集合的資料結構。是元素間約束力最差的資料結構:元素間的關係是元素僅共在同一個集合中。(同一型別的資料元素構成的集合)
1.靜態查詢表
1)順序查詢(線性查詢)
技巧:把待查關鍵字key存入表頭或表尾(俗稱“哨兵”),這樣可以加快執行速度。
int Search_Seq( SSTable ST , KeyType key ){
ST.elem[0].key =key;
for( i=ST.length; ST.elem[ i ].key!=key; - - i );
return i;
} // Search_Seq
//ASL=(1+n)/2,時間效率為 O(n),這是查詢成功的情況:
順序查詢的特點:
優點:演算法簡單,且對順序結構或連結串列結構均適用。
缺點: ASL 太大,時間效率太低。
2)折半查詢(二分查詢)——只適用於有序表,且限於順序儲存結構。
若關鍵字不在表中,怎樣得知並及時停止查詢?
典型標誌是:當查詢範圍的上界≤下界時停止查詢。
ASL的含義是“平均每個資料的查詢時間”,而前式是n個數據查詢時間的總和,所以:
3)分塊查詢(索引順序查詢)
思路:先讓資料分塊有序,即分成若干子表,要求每個子表中的資料元素值都比後一塊中的數值小(但子表內部未必有序)。然後將各子表中的最大關鍵字構成一個索引表,表中還要包含每個子表的起始地址(即頭指標)。
特點:塊間有序,塊內無序。
查詢:塊間折半,塊內線性
查詢步驟分兩步進行:
① 對索引表使用折半查詢法(因為索引表是有序表);
② 確定了待查關鍵字所在的子表後,在子表內採用順序查詢法(因為各子表內部是無序表);
查詢效率ASL分析:
2.動態查詢表
1)二叉排序樹和平衡二叉樹
-
二叉排序樹的定義----或是一棵空樹;或者是具有如下性質的非空二叉樹:
(1)若它的左子樹不空,則左子樹上所有結點的值均小於根的值;
(2)若它的右子樹不空,則右子樹的所有結點的值均大於根的值;
(3)它的左右子樹也分別為二叉排序樹。
二叉排序樹又稱二叉查詢樹。
二叉排序樹的查詢過程:
BiTree SearchBST(BiTree T, KeyType key)
{
//在根指標T所指二叉排序樹中遞迴地查詢某關鍵字等於key的資料元素,
//若查詢成功,則返回指向該資料元素結點的指標,否則返回空指標
if ((!T)||EQ(key, T->data.key)) return(T); //查詢結束
else if LT(key, T->data.key) return (SearchBST(T->lchild, key)); //在左子樹中繼續查詢
else return (SearchBST(T->rchild,key));