1. 程式人生 > >資料結構第二章筆記

資料結構第二章筆記

第二章線性表本章的基本內容是: 線性表的邏輯結構 線性表的順序儲存及實現 線性表的連結儲存及實現 順序表和單鏈表的比較 線性表的其他儲存及實現學生成績登記表 學號 姓 名 資料結構 英語 高數 0101 丁一 78 96 87 0102 李二 90 87 78 0103 張三 86 67 86 0104 孫紅 81 69 96 0105 王冬 74 87 66 職工工資登記表 職工號 姓 名 基本工資 崗位津貼 獎金 0101 丁一 278 600 200 0102 李二 190 300 100 0103 張三 186 300 100 0104 孫紅 218 500 200 0105 王冬 190 300 100 資料元素之間的關係是什麼? 線性表( Linear List )的定義 線性表:簡稱表,是n(n≥0)個具有相同型別的資料元素的有限序列。 線性表的長度:線性表中資料元素的個數。空表:長度等於零的線性表,記為:L=( )。 非空表記為:L=(a1, a2 , …, ai-1, ai , …, an) 其中,ai(1≤i≤n)稱為資料元素;下角標 i 表示該元素線上性表中的位置或序號。 線性表的圖形表示 線性表(a1, a2 , …, ai-1, ai , …, an)的圖形表示如下:

2.1 線性表的邏輯結構線性表的特性 1.有限性:線性表中資料元素的個數是有窮的。 2.相同性:線性表中資料元素的型別是同一的。 3.順序性:線性表中相鄰的資料元素ai-1和ai之間存在 序偶關係(ai-1, ai),即ai-1是ai的前驅, ai是ai-1的後繼; a1 無前驅,an無後繼,其它每個元素有且僅有一個前驅和一個後繼。

線性表的抽象資料型別定義 ADT List Data 線性表中的資料元素具有相同型別,相鄰元素具有前驅和後繼關係 Operation InitList 前置條件:表不存在輸入:無功能:表的初始化輸出:無後置條件:建一個空表 線性表的抽象資料型別定義 DestroyList 前置條件:表已存在輸入:無功能:銷燬表輸出:無 後置條件:釋放表所佔用的儲存空間 Length 前置條件:表已存在輸入:無功能:求表的長度 輸出:表中資料元素的個數後置條件:表不變 線性表的抽象資料型別定義 Get 前置條件:表已存在輸入:元素的序號i 功能:在表中取序號為i的資料元素 輸出:若i合法,返回序號為i的元素值,否則丟擲異常後置條件:表不變 Locate 前置條件:表已存在輸入:資料元素x 功能:線上性表中查詢值等於x的元素輸出:若查詢成功,返回x在表中的序號,否則返回0 後置條件:表不變 線性表的抽象資料型別定義 Insert 前置條件:表已存在輸入:插入i;待插x 功能:在表的第i個位置處插入一個新元素x 輸出:若插入不成功,丟擲異常 後置條件:若插入成功,表中增加一個新元素 Delete 前置條件:表已存在輸入:刪除位置i 功能:刪除表中的第i個元素 輸出:若刪除成功,返回被刪元素,否則丟擲異常後置條件:若刪除成功,表中減少一個元素 線性表的抽象資料型別定義 Empty 前置條件:表已存在輸入:無功能:判斷表是否為空輸出:若是空表,返回1,否則返回0 後置條件:表不變 PrintList 前置條件:表已存在輸入:無功能:遍歷操作,按序號依次輸出表中元素輸出:表的各資料元素後置條件:表不變 endADT 進一步說明: (1) 線性表的基本操作根據實際應用而定; (2) 複雜的操作可以通過基本操作的組合來實 現; – 假設利用兩個線性表LA和LB分別表示兩個集合A和 B(即:線性表中的資料元素即為集合中的成員),現要求一個新的集合A=AUB: Get(one from B)-Locate(in A)-Insert(to A)?

(3) 對不同的應用,操作的介面可能不同。 – 刪除表中的值為x的元素 – 刪除表中的第i個元素

順序表——線性表的順序儲存結構例:(34, 23, 67, 43) 34 23 67 43 4 用一段地址連續的儲存單元儲存要點依次儲存線性表中的資料元素 順序表——線性表的順序儲存結構例:(34, 23, 67, 43) 用什麼屬性來描述順序表? 儲存空間的起始位置 順序表的容量(最大長度) 順序表的當前長度 線性表的順序儲存結構——順序表例:(34, 23, 67, 43) 34 23 67 43 4 如何實現順序表的記憶體分配?順序表 一維陣列 順序表 一般情況下,(a1,a2,…, ai-1,ai , …, an)的順序儲存: 0 … i-2 i-1 … n-1 Max-1

Loc(a1) Loc(ai) 如何求得任意元素的儲存地址? 順序表 一般情況下,(a1,a2,…, ai-1,ai , …, an)的順序儲存: 0 … i-2 i-1 … n-1 Max-1

Loc(a1) Loc(ai) Loc(ai)=Loc(a1) + (i -1)×c 隨機存取:在O(1)時間記憶體取資料元素儲存結構和存取結構 儲存結構是資料及其邏輯結構在計算機中的表示;存取結構是在一個數據結構上對查詢操作的時間效能的一種描述。 “順序表是一種隨機存取的儲存結構”的含義為:在順序表這種儲存結構上進行的查詢操作,其時間效能為O(1)。 const int MaxSize=100; template //模板類 class SeqList { 順序表類的宣告 T Get(int i); public: SeqList( ) ; //建構函式 SeqList(T a[ ], int n); ~SeqList( ) ; //解構函式 int Length( ){return length;} int Locate(T x ); void Insert(int i, T x); T Delete(int i); void PrintList(); private: T data[MaxSize]; int length; }; 順序表的實現——無參建構函式 操作介面:SeqList( )

data	length

演算法描述: template SeqList:: SeqList( ) { length=0;} 順序表的實現——有參建構函式操作介面:SeqList(T a[ ], int n)

順序表的實現——有參建構函式 演算法描述: template SeqList::SeqList(T a[ ], int n) { if (n>MaxSize) throw “引數非法”; for (i=0; i<n; i+ +) data[i]=a[i]; length=n; } 順序表的實現——按位查詢操作介面: T Get(int i)

演算法描述: template 時間複雜度? T SeqList::Get( int i ) { if (i>=1 && i<=length) return data[i-1]; } 順序表的實現——按值查詢操作介面: int Locate(T x ) 例:在(35, 33, 12, 24, 42)中查詢值為12的元素,返回在表中的序號。 注意序號和下標之間的關係

順序表的實現——按值查詢演算法描述: template int SeqList::Locate(T x) { for (i=0; i<length; i++) if (data[i]==x) return i+1; return 0; } 時間複雜度? 順序表的實現——遍歷演算法描述: template void SeqList::PrintList() { for (i=0; i<length; i++) cout<<data[i]; } 時間複雜度? 順序表的實現——插入 操作介面: void Insert(int i, T x) 插入前:(a1, … , ai-1, ai, … , an) 插入後:(a1, … , ai-1, x , ai, … , an) ai-1和ai之間的邏輯關係發生了變化

順序儲存要求儲存位置反映邏輯關係

儲存位置要反映這個變化 順序表的實現——插入 例:(35,12,24,42),在i=2的位置上插入33。 0 1 2 3 4 a1 35 a2 1233 a3 1224 a4 2442 42 4 5

33 什麼時候不能插入? 注意邊界條件 表滿:length>=MaxSize 合理的插入位置:1≤i≤length+1(i指的是元素的序號) 順序表的實現——插入演算法描述——虛擬碼

  1. 如果表滿了,則丟擲上溢異常;
  2. 如果元素的插入位置不合理,則丟擲位置異常;
  3. 將最後一個元素至第i個元素分別向後移動一個位置;
  4. 將元素x填入位置i處;
  5. 表長加1; 順序表的實現——插入 演算法描述——C++描述 基本語句? template void SeqList::Insert(int i, T x) { if (length>=MaxSize) throw “上溢”; if (i<1 | | i>length+1) throw “位置”; for (j=length; j>=i; j–) data[j]=data[j-1]; data[i-1]=x; length++; } 順序表的實現——插入 時間效能分析 最好情況( i=n+1): 基本語句執行0次,時間複雜度為O(1)。最壞情況( i=1):基本語句執行n次,時間複雜度為O(n)。 順序表的實現——插入 時間效能分析 平均情況(1≤i≤n+1): n+1 n  順序表的實現——刪 除操作介面: T Delete(int i) 刪除前:(a1, …, ai-1,ai,ai+1,…,an) 刪除後:(a1,…,ai-1,ai+1, …,an) ai-1和ai之間的邏輯關係發生了變化

順序儲存要求儲存位置反映邏輯關係

儲存位置要反映這個變化順序表的實現——刪除例:(35, 33, 12, 24, 42),刪除i=2的資料元素。 0 1 2 3 4 a1 35 a2 1233 a3 2412 a4 2442 a5 42 5 4 仿照順序表的插入操作,完成:

  1. 分析邊界條件;
  2. 分別給出虛擬碼和C++描述的演算法;
  3. 分析時間複雜度。 順序表的實現——刪 除例:(35, 33, 12, 24, 42),刪除i=2的資料元素。 0 1 2 3 4 a1 35 a2 1233 a3 2412 a4 2442 a5 42 5 4 什麼時候不能刪除? 注意邊界條件 表空:length0 合理的刪除位置:1≤i≤length(i指的是元素的序號) 順序表的實現——刪除 演算法描述——C++描述 基本語句? template T SeqList::Delete(int i) { if (lengthMaxSize) throw “下溢"; if (i<1 | | i>length) throw “位置”; x=data[i-1]; for (j=i;; j<length; j++) data[j-1]=data[j]; length–; return x; } 順序表的實現——刪除 時間效能分析 最好情況( i=n): 基本語句執行0次,時間複雜度為O(1)。 最壞情況( i=1):基本語句執行n-1次,時間複雜度為O(n)。 順序表的實現——刪除 時間效能分析平均情況(1≤i≤n):  順序表的優缺點 順序表的優點: ⑴無需為表示表中元素之間的邏輯關係而增加額外的儲存空間; ⑵隨機存取:可以快速地存取表中任一位置的元素。 順序表的缺點: ⑴插入和刪除操作需要移動大量元素; ⑵表的容量難以確定,表的容量難以擴充; ⑶造成儲存空間的碎片。

單鏈表 順序表 靜態儲存分配 事先確定容量 鏈 表 動態儲存分配 執行時分配空間 單鏈表:線性表的連結儲存結構。 儲存思想:用一組任意的儲存單元存放線性表的元素。 連續不連續零散分佈 單鏈表 例:(a1, a2 ,a3, a4)的儲存示意圖 儲存特點:

  1. 邏輯次序和物理次序不一定相同。 2.元素之間的邏輯關係用指標表示。 單鏈表 單鏈表是由若干結點構成的;單鏈表的結點只有一個指標域。 單鏈表的結點結構: 資料域 指標域 data next data:儲存資料元素 next:儲存指向後繼結點的地址 單鏈表 data next 單鏈表的結點結構: template struct Node 如何申請一個結點? { T data; Node *next; }; 單鏈表 data next 單鏈表的結點結構: template s=new Node ; struct Node { T data; Node *next; }; Node *s, *first; data next 單鏈表 如何引用資料元素? *s.data ; s=new Node ; s->data ; 如何引用指標域? s->next;

a2 0325 a1 0200 … a4 ∧ … a3 0300 … 單鏈表 … 0200 什麼是儲存結構? 0208 重點在資料元素之間的邏輯關係的 表示,所以,將實際儲存地址抽象。 空表 0300 first=NULL 非空表 0325 first

a2 0325 a1 0200 … a4 ∧ … a3 0300 … 單鏈表 … 0200 頭指標:儲存第一個結點的地址, 0208 即,指向第一個結點。 尾標誌:終端結點的指標域為空。 空表 0300 first=NULL 非空表 0325 first

a2 0325 a1 0200 … a4 ∧ … a3 0300 … 單鏈表 … 0200 空表和非空表不統一,缺點? 如何將空表與非空表統一?0208 空表 0300 first=NULL 非空表 0325 first

單鏈表 頭結點:在單鏈表的第一個元素結點之前附設一個型別相同的結點,以便空表和非空表處理統一。 空表 first 非空表 first template class LinkList { 單鏈表類的宣告 ~LinkList( ); public: LinkList( ); LinkList(T a[ ], int n); int Length( ); T Get(int i); int Locate(T x); void Insert(int i, T x); T Delete(int i); void PrintList( ); private: Node *first; }; 單鏈表的實現——遍歷 操作介面: void PrintList(); an ∧ p p 核心操作(關鍵操作):工作指標後移。從頭結點(或開始結點)出發,通過工作指標的反覆後移而將整個單鏈表“審視”一遍的方法稱為掃描(或遍歷)。 單鏈表的實現——遍歷 操作介面: void PrintList(); an ∧ p p template int LinkList:: PrintList() { p=first->next; while (p!=NULL) { cout<data; p=p->next; } } 單鏈表的實現——求線性表的長度操作介面: int Length(); an ∧ p p template int LinkList:: Length() { p=first->next; count=0; while (p!=NULL) { count++; p=p->next; } return count; } 單鏈表的實現——按位查詢操作介面: T Get(int i); an ∧ p p 查詢成功 查詢失敗 核心操作(關鍵操作):工作指標後移——遍歷 單鏈表的實現——按位查詢演算法描述——虛擬碼

  1. 工作指標p初始化; 累加器j初始化;

  2. 迴圈直到p為空或p指向第i個結點 2.1 工作指標p後移; 2.2 累加器j加1;

  3. 若p為空,則第i個元素不存在,丟擲查詢位置異常;否則查詢成功,返回結點p的資料元素; 單鏈表的實現——按位查詢演算法描述——C++描述 template T LinkList::Get(int i) { p=first->next; j=1; while (p!=NULL && j<i) { p=p->next; j++; } if (p==NULL) throw “位置”; else return p->data; }

  4. 3 線性表的連結儲存結構及實現 單鏈表的實現——按值查詢操作介面: int Locate(T x ) an ∧ p p 查詢成功 查詢失敗

  5. 3 線性表的連結儲存結構及實現 單鏈表的實現——按值查詢 演算法描述: template int LinkList::Locate(T x) { p=first->next; j=1; while §{ if(p->data==x) return j; else { p=p->next; j++; } } return 0; } 單鏈表的實現———插入 操作介面: void Insert(int i, T x); 如何實現結點ai-1、x和ai之間邏輯關係的變化?

an ∧

演算法描述: s=new Node; s->data=x; s->next=p->next; p->next=s;

單鏈表的實現———插入注意分析邊界情況——表頭、表尾。 演算法描述: 由於單鏈錶帶頭結點, s=new Node; s->data=x; 表頭、表中、表尾三種 s->next=p->next; p->next=s; 情況的操作語句一致。 單鏈表的實現———插入演算法描述——虛擬碼

  1. 工作指標p初始化;累加器j清零;
  2. 查詢第i-1個結點並使工作指標p指向該結點;
  3. 若查詢不成功,說明插入位置不合理,丟擲插入位置異常;否則, 3.1 生成一個元素值為x的新結點s; 3.2 將新結點s插入到結點p之後; 單鏈表的實現———插入演算法描述——C++描述 template void LinkList::Insert(int i, T x)if (p==NULL) throw “位置”; { else { p=first ; j=0; s=new Node; while (p!=NULL && j<i-1)s->data=x; {s->next=p->next; p=p->next; p->next=s; j++;} },} 基本語句?時間複雜度? 單鏈表的實現———建構函式 操作介面:LinkList() ——無參建構函式初始化 演算法描述: first=new Node; first ∧ first->next=NULL; template LinkList:: LinkList() { first=new Node; first->next=NULL; } 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 頭插法:將待插入結點插在頭結點的後面。 陣列a 35 12 24 33 42 初始化 first 演算法描述: first=new Node; first->next=NULL; 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 頭插法:將待插入結點插在頭結點的後面。陣列a 35 12 24 33 42 插入第一個元素結點 演算法描述: firsts=new Node; s->data=a[0]; s->next=first->next; first->next=s; 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 頭插法:將待插入結點插在頭結點的後面。陣列a 35 12 24 33 42 依次插入每一個結點 演算法描述: firsts=new Node; s->data=a[1]; s->next=first->next; first->next=s; 單鏈表的實現———建構函式 演算法描述: template LinkList:: LinkList(T a[ ], int n) { first=new Node; first->next=NULL; for (i=0; i<n; i++) { s=new Node; s->data=a[i]; s->next=first->next; first->next=s; } } 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 尾插法:將待插入結點插在終端結點的後面。 陣列a 35 12 24 33 42 初始化 first 演算法描述: first=new Node; rear=first; rear 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 尾插法:將待插入結點插在終端結點的後面。 陣列a 35 12 24 33 42 插入第一個元素結點 演算法描述: s=new Node; first 35 s->data=a[0]; rear rear rear->next=s; rear=s; 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 尾插法:將待插入結點插在終端結點的後面。陣列a 35 12 24 33 42 依次插入每一個結點 演算法描述: firsts=new Node; 35 12 s->data=a[1]; rear rear rear rear->next=s; rear=s; 單鏈表的實現———建構函式操作介面:LinkList(T a[ ], int n) 尾插法:將待插入結點插在終端結點的後面。陣列a 35 12 24 33 42 演算法描述: 最後一個結點插入後 rear->next=NULL; first 35 42 rear rear rear 單鏈表的實現———建構函式 演算法描述: template LinkList:: LinkList(T a[ ], int n) { first=new Node ; rear=first; for (i=0; i<n; i++) { s=new Node ; s->data=a[i]; rear->next=s; rear=s; } rear->next=NULL; } 單鏈表的實現———刪除操作介面: T Delete(int i); 如何實現結點ai-1和ai之間邏輯關係的變化?

演算法描述: q=p->next; x=q->data; p->next=q->next; delete q; 單鏈表的實現———刪除注意分析邊界情況——表頭、表尾。 q p->next=NULL 演算法描述: 表尾的特殊情況: q=p->next; x=q->data; 雖然被刪結點不存在, p->next=q->next; delete q; 但其前驅結點卻存在。 單鏈表的實現———刪除演算法描述——虛擬碼 1.工作指標p初始化;累加器j清零; 2. 查詢第i-1個結點並使工作指標p指向該結點; 3. 若p不存在或p不存在後繼結點,則丟擲位置異常;否則, 3.1 暫存被刪結點和被刪元素值; 3.2 摘鏈,將結點p的後繼結點從連結串列上摘下; 3.3 釋放被刪結點; 3.4 返回被刪元素值;

單鏈表的實現———刪除演算法描述——C++描述 template T LinkList::Delete(int i) { p=first ; j=0; while (p && j<i-1) { p=p->next; j++; } if (p==NULL | | p->next ==NULL ) throw “位置”; else { q=p->next; x=q->data; p->next=q->next; delete q; return x; } }

單鏈表的實現——解構函式操作介面:~LinkList( ); 解構函式將單鏈表中所有結點的儲存空間釋放。

an ∧

演算法描述: 注意:保證連結串列未處理的部分不斷開 q=p; p=p->next; Delete q; 單鏈表的實現——解構函式 演算法描述: template LinkList:: ~LinkList( ) { p=first; while (p!=NULL) { q=p; p=p->next; delete q; } }

2.3 線性表的連結儲存結構及實現 迴圈連結串列

first 從單鏈表中某結點p出發如何找到其前驅? 將單鏈表的首尾相接,將終端結點的指標域由空指標改為指向頭結點,構成單迴圈連結串列,簡稱迴圈連結串列。 迴圈連結串列 空表和非空表的處理一致 附設頭結點 空迴圈連結串列 first 非空迴圈連結串列

first 迴圈連結串列——插入

演算法描述: s=new Node; s->data=x; s->next=p->next; p->next=s; 迴圈連結串列——插入 template void LinkList::Insert(int i, T x)if (j<i-1) throw “位置”; { else { p=first ; j=0; //工作指標p初始化s=new Node; while (p->next!=first && j<i-1)s->data=x; {s->next=p->next; p=p->next; //工作指標p後移p->next=s; j++;} }} 與單鏈表的插入操作相比,差別是什麼? 迴圈連結串列迴圈連結串列中沒有明顯的尾端 如何避免死迴圈迴圈條件: p!=NULLp!=first p->next!=NULLp->next!=first 迴圈連結串列

first 如何查詢開始結點和終端結點? 開始結點:first->next 終端結點:將單鏈表掃描一遍,時間為O(n) 迴圈連結串列帶尾指標的迴圈連結串列

開始結點:rear->next->next rear 終端結點:rear 一個儲存結構設計得是否合理,取決於基於該儲存結構的運算是否方便,時間效能是否提高。 雙鏈表

如何求結點p的直接前驅,時間效能? 為什麼可以快速求得結點p的後繼? 如何快速求得結點p的前驅?

雙鏈表 雙鏈表:在單鏈表的每個結點中再設定一個指向其前驅結點的指標域。 結點結構: prior data next data:資料域,儲存資料元素; prior:指標域,儲存該結點的前趨結點地址; next:指標域,儲存該結點的後繼結點地址。 雙鏈表 prior data next 定義結點結構: template struct DulNode { T data; DulNode *prior, *next; }; 啟示? 時空權衡——空間換取時間 雙鏈表的操作——插入 操作介面: void Insert(DulNode *p, T x);

s->prior=p; s->next=p->next; 注意指標修改的相對順序 p->next->prior=s; p->next=s; 雙鏈表的操作——插入 特殊情況:在空表中插入一個結點(在表尾插入一個結點) first 空的雙向連結串列 s first 非空的雙向連結串列 s->prior=p; s->next=p->next; p->next->prior=s; 統一插入操作: s->prior=p; s->next=p->next; if(p->next!=NULL) p->next=s; p->next->prior=s; p->next=s; 雙鏈表的操作——刪除 操作介面: T Delete(DulNode *p);

(p->prior)->next=p->next; (p->next)->prior=p->prior; 結點p的指標是否需要修改? delete p; 雙鏈表的操作——刪除 特殊情況:刪除表尾結點 first

(p->prior)->next=p->next; (p->next)->prior=p->prior; 統一刪除操作: (p->prior)->next=p->next; delete p; if(p->next!=NULL) (p->next)->prior=p->prior; delete p; 雙鏈表類的定義 template class DoubleLink template { struct DulNode private: { DulNode *first; T data; public: DulNode *prior; DoubleLink() ; DulNode *next; ~DoubleLink(); }; void Append(T data); void Display(); void Insert(int locate , T data); T Get(int locate); T Delete(int locate); }; 雙鏈表的構造-空表的構造 DoubleLink() ; template DoubleLink ::DoubleLink(){ first =new DulNode; first ->prior=NULL; first ->next=NULL; } 追加(頭插):void Append(T data); template void DoubleLink::Append(T data){ DulNode *s; s=new DulNode; if (s!=NULL) { s->data=data; s->next= first ->next; first ->next=s; s->prior= first; if (s->next!=NULL) s->next->prior=s; } else throw “記憶體空間不足”; return; } 遍歷 void Display(); template void DoubleLink::Display(){ DulNode *p; p=first->next; while(p !=NULL) { cout<data<<" "; p=p->next; } cout<<endl; }

2.3 線性表的連結儲存結構及實現 析構 ~DoubleLink(); template DoubleLink::~DoubleLink() { DulNode *p,*q; p=first; while(p!=NULL) { q=p->next; delete p; p=q; } } 順序表和連結串列的比較儲存分配方式比較 順序表採用順序儲存結構,即用一段地址連續的儲存單元依次儲存線性表的資料元素,資料元素之間的邏輯關係通過儲存位置來實現。 單鏈表採用連結儲存結構,即用一組任意的儲存單元存放線性表的元素。用指標來反映資料元素之間的邏輯關係。 時間效能比較 時間效能是指實現基於某種儲存結構的基本操作(即演算法)的時間複雜度。 按位查詢: 順序表的時間為O(1),是隨機存取; 單鏈表的時間為O(n),是順序存取。 插入和刪除: 順序表需移動表長一半的元素,時間為O(n); 單鏈表不需要移動元素,在給出某個合適位置的指標後,插入和刪除操作所需的時間僅為O(1)。

空間效能比較 空間效能是指某種儲存結構所佔用的儲存空間的大小。 定義結點的儲存密度: 資料域佔用的儲存量儲存密度= 整個結點佔用的儲存量 單鏈表中,如果資料域是整型變數int 則單鏈表結點的儲存密度是: = 50 % 空間效能比較 結點的儲存密度: 順序表中每個結點的儲存密度為1(只儲存資料元素),沒有 浪費空間; 單鏈表的每個結點的儲存密度<1(包括資料域和指標域),有指標的結構性開銷。 整體結構: 順序表需要預分配儲存空間,如果預分配得過大,造成浪費,若估計得過小,又將發生上溢; 單鏈表不需要預分配空間,只要有記憶體空間可以分配,單鏈表中的元素個數就沒有限制。 結論 ⑴若線性表需: •查詢多,插入和刪除少,或 •操作和元素在表中的位置密切相關宜採用順序表作為儲存結構;若線性表需頻繁插入和刪除時,則宜採用單鏈表做儲存結構。 ⑵若線性表中元素個數變化較大或未知時,最好使用單鏈表實現;如果事先知道線性表的大致長度,使用順序表的空間效率會更高。 總之,線性表的順序實現和連結串列實現各有其優缺點,不能籠統地說哪種實現更好,只能根據實際問題的具體需要,並對各方面的優缺點加以綜合平衡,才能最終選定比較適宜的實現方法。

靜態連結串列 靜態連結串列:用陣列來表示單鏈表,用陣列元素的下標來模擬單鏈表的指標。 陣列元素(結點)的構成: 資料域 指標域 data next data:儲存放資料元素; next:也稱遊標,儲存該元素的後繼在陣列的下標。 靜態連結串列 例:線性表(張,王,李,趙,吳)的靜態連結串列儲存 data next first 0 1first:靜態連結串列頭指標,為了方 2便插入和刪除操作,通常靜態鏈 3 李 錶帶頭結點; 4 趙 avail:空閒連結串列頭指標,空閒鏈 5 吳 表由於只在表頭操作,所以不帶 avail 6頭結點; 7 8 靜態連結串列 線上性表(張,王,李,趙,吳)中“王”之後插入“孫” data next data next 0 0 11 1 張 2 王 3 李 4 趙 5 吳 -1 7 8 -1 1 張 2 王 6 李 4 趙 5 吳 -1 孫 3 8 -1 2王之後 2

33 插入孫 44 55 6 6 77 88 靜態連結串列 線上性表(張,王,李,趙,吳)中刪除“趙” data next data next 0 0 11 1 張 2 王 3 李 4 趙 5 吳 -1 7 8 -1 1 張 2 王 3 李 5 趙 5 吳 -1 7 8 -1 22 3刪除趙 3 4 4 5摘鏈 5 6 6 77 88 靜態連結串列 線上性表(張,王,李,趙,吳)中刪除“趙” data next data next 0 0 11 1 張 2 王 3 李 4 趙 5 吳 -1 7 8 -1 1 張 2 王 3 李 5 6 吳 -1 7 8 -1 22 3刪除趙 3 4 4 5歸還空間 5 66 77 88 靜態連結串列 相對於順序表而言,靜態連結串列有什麼優點? 優點:在執行插入和刪除操作時,只需修改遊標,不需要移動表中的元素,從而改進了在順序表中插入和刪除操作需要移動大量元素的缺點。 缺點:沒有解決連續儲存分配帶來的表長難以確定的問題;靜態連結串列還需要維護一個空閒鏈;靜態連結串列不能隨機存取。 間接定址 間接定址:是將陣列和指標結合起來的一種方法,它將陣列中儲存資料元素的單元改為儲存指向該元素的指標。

0 1 2 i-1 n-1 Max-1 插入操作移動的不是元素而是指向元素的指標。當元素佔用的空間較多時,比順序表的插入操作快得多。

2.6 應用舉例 2.6.1 順序表應用舉例——大整數求和問題描述: 在用某種程式設計語言程式設計時,可能需要處理非常大或對運算 精度要求非常高的整數,這種大整數用該語言的基本資料型別無法表示。 例如,C++ 的長整型數的範圍為 -2147483648~2147483647 超過該範圍的整數無法表示怎麼辦? 藉助順序表解決。 Eg.9348594+993327765 清華大學出版社下標0 5 6 7 7 2 3 3 9 9

9 5 3 6 7 6 2 0 0 1 B9 C10

最低位(0)最高位

  1. 設計標誌位flag(int,初值為0),用來記錄計算過程中是否有進位
  2. 從陣列低端開始,將對應位置上的兩個數及flag的值相加,取結果的個位數,存入的c的對應位置上,將10位數上的值賦值給flag
  3. 重複進行計算,直到A或B計算完畢
  4. 處理A或B中剩餘的部分
  5. 計算結果的位數
  6. 設計標誌位flag(int,初值為0),用來記錄計算過程中是否有進位
  7. 從陣列低端開始,將對應位置上的兩個數及flag的值相加,取結果的個位數,存入的c的對應位置上,將10位數上的值賦值給flag
  8. 重複進行計算,直到A或B計算完畢
  9. 處理A或B中剩餘的部分
  10. 計算結果的位數 SeqList Add(SeqList A,SeqListB){//友員函式實現 int flag=0,i=0,m,n; SeqList C; m=B.Length () ; n=A.Length() ; while(i<n && i<m) { C.data[i]=(A.data[i]+B.data [i]+flag)%10; flag=(A.data[i]+B.data [i]+flag)/10; i++; }

清華大學出版社1. 設計標誌位flag(int,初值為0),用來記錄計算過程中是否有進位資料結構(C++版) 2. 從陣列低端開始,將對應位置上的兩個數及flag的值相加,取結果的個位數,存入的c的對應位置上,將10位數上的值賦值給flag 3. 重複進行計算,直到A或B計算完畢 4. 處理A或B中剩餘的部分 5. 計算結果的位數 for(;i<n;i++) { C.data[i]=(A.data[i]+flag)%10;flag=(A.data[i]+flag)/10; } for(;i<m;i++){ C.data[i]=(B.data[i]+flag)%10; flag=(B.data[i]+flag)/10; } int max; max=n; if(m>n) max=m; C.length =max+flag; //如果最後一位產生進位 if(flag==1) C.data [C.length-1 ]=1; return C; }

  1. 設計標誌位flag(int,初值為0),用來記錄計算過程中是否有進位
  2. 從陣列低端開始,將對應位置上的兩個數及flag的值相加,取結果的個位數,存入的c的對應位置上,將10位數上的值賦值給flag
  3. 重複進行計算,直到A或B計算完畢
  4. 處理A或B中剩餘的部分
  5. 計算結果的位數 SeqList Add1(SeqList A,SeqListB){ //非友員函式實現 int flag=0,i=1,m,n; SeqList C; m=B.Length () ; n=A.Length() ; while(i<=n && i<=m) { C.Insert(i,(A.Get(i)+B.Get(i)+flag)%10); flag=(A.Get(i)+B.Get(i)+flag)/10; i++; } 清華大學出版社1. 設計標誌位flag(int,初值為0),用來記錄計算過程中是否有進位資料結構(C++版)
  6. 從陣列低端開始,將對應位置上的兩個數及flag的值相加,取結果的個位數,存入的c的對應位置上,將10位數上的值賦值給flag
  7. 重複進行計算,直到A或B計算完畢
  8. 處理A或B中剩餘的部分
  9. 計算結果的位數 for(;i<=n;i++) { C.Insert(i,(A.Get(i)+flag)%10);flag=(A.Get(i)+flag)/10; } for(;i<=m;i++){ C.Insert(i,(B.Get(i)+flag)%10); flag=(B.Get(i)+flag)/10; } int max; max=n; if(m>n) max=m; if(flag==1) C.Insert(max+flag,1); return C; }

2.6 應用舉例 2.6.2 單鏈表的應用舉例——一元多項式求和在數學上,一個一元多項式Pn(x)可按升冪的形式寫成: P X P PX PX PXn( )= 0 + 1 + 2 2 + 3 3 ++PXn n 它實際上可以由n+1個係數唯一確定。因此,在計算機內,可以用一個線性表P來表示: P P P P= ( 0, 1, 2,,Pn) 假設Qm(x)是一個一元多項式,則它也可以用一個線性 表Q來表示,即 Q q q q= ( 0, 1, 2,,qm) 若 假 設 m<n , 則 兩 個 多 項 式 相 加 的 結 果 Rn(x)=Pn(x)+Qm(x),也可以用線性表R來表示: R p q p q p q= ( 0 + 0, 1 + 1, 2 + 2,, p q pm + m, m+1,, pn) 可以採用順序儲存結構來實現順序表的方法,使得多項式的相加的演算法定義十分簡單,即p[0]存係數p0,p[1]存係數 p1, …, p[n]存係數p n,對應單元的內容相加即可。 但是在通常的應用中,多項式的指數有時可能會很高並且變化 很大。例如: R x( )=1+5x10000 +7x20000 •若採用順序儲存,則需要20001個空間,而儲存的有用資料只有三個,這無疑是一種浪費。 •若只儲存非零係數項,則必須儲存相應的指數資訊。假設一元多項式Pn(x)=p1xe1+p2xe2+…+pmxem,其中pi是指數為ei的項的係數(且0≤e1≤e2≤…≤em=n), 若只存非零係數,則多項式中每一項由兩項構成(指數項和係數項),用線性表來表示,即  ((p e1 1, ),(p e2 2, ),,(p em m, )) 採用這樣的方法儲存,在最壞情況下,即n+1個係數都不為零, 則比只儲存係數的方法多儲存1倍的資料。對於非零係數多的多項式則不宜採用這種表示。 儲存結構的選擇 • 順序儲存 • 順序儲存的問題分析 • 鏈式儲存 建立一元多項式的鏈式儲存 (1)用單鏈表儲存多項式的結點結構如下:   struct Polynode  {  Node data;   Polynode *next;   struct Node { int coef; int exp; }; }; coef exp next data next 一元多項式單鏈表的結點結構: 多項式的相加多項式相加的過程:

  1. 兩個多項式中所有指數相同的項的對應係數相加, 若和不為零, 則構成“和多項式”中的一項;
  2. 所有指數不相同的項 均復抄到“和多項式”中。 多項式的相加 AH = 1 - 3x6 + 7x12 BH = - x4 + 3x6 - 9x10 14  pc pa AH.first BH.first CH.first

pc AH.first

CH.first

CH.first

CH.first

CH.first AH.first7 12 ∧ pc -9 10 8 14∧ pb CH.first CH.first

CH.first

CH.first 一元多項式相加中的三種情況 1 若pa->expexp,則結點pa所指的結點應是“和多項式”中的一項,令指標pa、pc後移; 2 若pa->exp>pb->exp,則結點pb所指的結點應是“和多項式”中的一項, 將結點pb插入在結點pa之前,且令指標 pa在原來的連結串列上,pb 、pc後移;  3 若pa->exp=pb->exp, 則將兩個結點中的係數相加, 釋放pb結點; 1 當和不為零時修改結點pa的係數域, pa、pc後移; 2 若和為零,釋放pa,pa後移。

pa,pb:當前比較的結點資料結構(C++版) 演算法 pc:剛剛插入c的結點, p:被刪結點

  1. 設定四個工作指標,pa, pb, pc, p,並對其進行初始化
  2. 當 pa!=NULL 並且pb!=NULL時,重複做以下工作
  3. if(pa.exp<pb.exp),將pa加入到C中,後移pa、pc;
  4. if(pa.exp>pb.exp),將pb加入到C中,後移pb、pc;
  5. if(pa.exp==pb.exp) ,pa、pb的係數相加,刪除pb,pb 後移
  6. 如果和為0,刪除pa, pa後移
  7. 否則,將和寫入到pa中,將pa加入到C中,pa、 pc後移;
  8. 如pa!=NULL,將pa鏈入C中,否則將pb鏈入C中; 核心程式碼 Void Add(LinkList &A, LinkList B){ Node *pa, *pb, *pc, *p; T a, b; //存放pa、pb兩個指標變數中儲存的係數 pc = A.GetHead(); //結果存放指標 p = B.GetHead(); pa = A.GetHead()->next; //pa指向ah pb = B.GetHead()->next; //pb指向bh delete p; //刪去bh的表頭結點 struct Node   struct Polynode   { {  int coef; Node data;  int exp; Polynode *next;   }; }; while ( pa != NULL && pb != NULL ) { a = pa->data; b = pb->data; if(a.exp==b.exp){ //pa->exp == pb->exp a.coef = a.coef + b.coef; //係數相加 p = pb; pb = pb->next; delete p; //刪去原pb所指結點 if (a.coef == 0) {//相加為零, 該項刪除 p = pa; pa = pa->next; delete p; } else { //相加不為零, 加入c鏈 pa->data = a; pc->next = pa; pc = pa; pa = pa->next; } } else if(a.exp>b.exp) { //pa->exp > pb->exp pc->next = pb; pc = pb; pb = pb->next; } else{ //pa->exp < pb->exp pc->next=pa; pc = pa; pa = pa->next; } } if (pa!=NULL ) pc->next=pa; //處理剩餘部分 else pc->next=pb; } • 對結果表示式的構造: – 從空表開始,依次將A或B中結點加入到C中,完成 C表的構造 • 教材中程式碼: – 將A看作是結果表示式的一部分,將B中的節點依次插入到A中,完成結果表示式的構造 – 存在的問題 • 未對指向節點前驅的指標進行及時的修改 不帶頭結點的單鏈表逆轉

A NULL 設定三個輔助變數 p:指向連結串列的頭結點,頭指標,初值為list.head q:指向p的後繼結點,初值為NULL, front: 逆轉之後的連結串列的頭指標,初值為NULL。 不帶頭結點的單鏈表逆轉演算法描述 „ 如果連結串列為空表,丟擲異常 „ 設定三個輔助變數p(指向連結串列的頭結點,頭指標,初值為 list.head),q(指向p的後繼結點,初值為null),front(逆轉之後的連結串列的頭指標,初值為null)。 „ 在連結串列不空的情況下,重複做以下工作: 用q記錄p的後繼(原連結串列的新的頭結點) „ p.next=front; //將p作為頭結點,鏈入逆轉之後的表中 分別修改兩個連結串列的頭指標 public void reverse() //將單鏈表逆轉 { Node *p= a->head, q=null, front=NULL; while (p!=NULL) p = q;

建立排序的單鏈表 構造連結串列,使得在插入過程中,依次將要插入的資料同連結串列中的資料進行比較,確定插入點進行插入. 插入過程: 尋找比插入值(element)大的第一個節點,查詢成功的情況下,進行插入。 如果查詢失敗,則新插入的節點為最後一個節點 first … b 帶頭結點的單鏈表 有序(升序)連結串列的插入 void LinkList::Insert(T x) while(p && x>p->data) { { Node *s,*p,*front; front=p; s=new Node; p=p->next; s->data=x; } front=first; p=first->next; s->next=p; front->next=s; return ; } first 1 2 … n b 帶頭結點的單鏈表 建立有序連結串列 void LinkList::create(T a[],int n) { for(int i=0,i<n;i++) Insert(a[i]); } first … b 帶頭結點的單鏈表 有序表的合併 • 有兩個連結串列LA和LB,其元素均為非遞減有序排列,編寫一個演算法,將它們合併成一個連結串列LC,要求LC也是非遞減有序排列 • 演算法思想: • 設表LC是一個空表,為使LC也是非遞減有序排列,可設兩個指標pa、pb、 pc分別指向表LA、LB和LC中的元素, • 若pa->data>pb->data,則先將pb插入到表LC中; • 若pa->data<=pb->data ,則先將pa插入到表LC中, • 如此進行下去,直到其中一個表被掃描完畢,然後再將未掃描完的表中剩餘的所有元素放到表LC中。

合併演算法

  1. 設定工作指標pa,pb,pc,並對其進行初始化
  2. 重複做以下工作,直到pa或pb為空
  3. 將pa的值和pb的值進行比較
  4. 如果pa->data>pb->data,
  5. pc->next=pb,pb=pb->next
  6. 如果pa->data<=pb->data,
  7. pc->next=pa,pa=pa->next
  8. Pc=pc->next
  9. 如果pa不空,
  10. 則將pa中剩餘的結點鏈入pc
  11. 否則,將pb中剩餘的結點鏈入pc LA… LB… template void merge(LinkList LA, LinkList LB){ Node *pa,*pb,*pc; pa=LA.first; pb=LB.first; pc=LA.first; pa=pa->next; pb=pb->next; while(pa&&pb) { if (pa->data>pb->data) { pc->next=pb;pc=pb;pb=pb->next;} else { pc->next=pa; pc=pa;pa=pa->next; } } if (pa) pc->next=pa; else pc->next=pb; } 本章總結

上機實驗 P169 順序表操作驗證 P174 單鏈表操作驗證(迴圈連結串列,雙鏈表)設計: 分別用順序表和單鏈表通過基本操作的組合來實現:假設利用兩個線性表LA和LB分別表示兩個集合A和 B(即:線性表中的資料元素即為集合中的成員),現要求修改集合A,使得A=A∪B ,即集合A儲存A和B的並集。 150