基本資料結構實現--雙連結串列
*概述
單連結串列節點中只有一個指向其後記的指標,使得單連結串列只能從頭節點依次順序地向後遍歷。要訪問某個節點的前驅節點,只能從頭
開始遍歷,在拿到某節點指標p後,訪問p的後繼節點的時間複雜度為O(1),訪問前驅節點的時間複雜度依舊是O(N)。
為了解決這個問題,引入了雙連結串列。在原本的單連結串列每個節點中加入了一個指向其前驅的節點prior。因此,在我們拿到某節點指標p
的前提下,想要對這個節點的前驅進行操作就可以在O(1)的時間內完成。請注意:一般情況下我們並沒有連結串列中所有節點的指標資訊,
而是僅僅儲存類似頭節點、尾節點的標誌性節點。
對於單連結串列,拿到某節點指標p後,其後所有節點都可以找到,而想要訪問前面的節點只能從頭節點開始一一查詢。而對於雙連結串列來說,
拿到某節點指標後既可以向後查詢,也可以向前查詢。
*雙連結串列結構型別的定義
一個雙連結串列的型別可以定義如下:
1 typedef struct DNode { 2 ElemType data; //節點資料域 3 DNode* next,* prior; //後繼指標、前驅指標 4 } * DLinkList;
此處,DNode 定義了一個雙連結串列的節點型別,DLinkList便定義了一個雙連結串列。DLinkList即是頭結點指標。請注意:要標記一個連結串列,只需
要儲存連結串列的頭節點指標就可以了(當然根據需求不同也可以儲存尾指標,甚至第i個節點的指標)。因此這裡DLinkList既是雙連結串列的頭指標,又
代表了“雙連結串列”這一資料型別。
*雙連結串列基本操作及其實現
(1) 初始化一個雙連結串列
1 void InitDLinkList(DLinkList& L) 2 { 3 L = new DNode; //C語言版:L = (DNode*)malloc(sizeof(DNode)); 4 L->data = 0; 5 L->next = NULL; 6 L->prior = NULL; 7 }
由於L指向的是連結串列的頭節點,這個資料域是不屬於連結串列的有效元素的,如果沒有他用,可以隨意賦值(這裡賦值0).
(2) 後插操作,在p指向的節點之後插入s指向的節點。插入成功返回true,失敗(如p不合法等)返回false。
1 bool InsertNextDNode( DNode* p,DNode* s ) 2 { 3 if( p == NULL || s == NULL ) 4 return false; 5 s->next = p->next; 6 //如果p是最後一個節點,p沒有下一個節點,自然不需要修改下一個結點的前驅指標 7 if( p->next != NULL ) 8 p->next->prior = s; 9 s->prior = p; 10 p->next = s; 11 return true; 12 }
注意這裡與單連結串列的不同。雙連結串列在插入、刪除操作時不僅要修改next指標,還要修改節點的prior指標。在後插時,
如果p是最後一個節點,則不需要修改p->next->prior:因為p沒有next。其他情況時需要修改p->next->prior為s。
(3) 刪除操作:刪除p的後繼節點。成功刪除返回true,失敗(如p不合法,或p沒有後繼節點等)返回false。
1 bool DeleteNextDNode( DNode* p ) 2 { 3 if( p == NULL ) 4 return false; 5 if( p->next == NULL ) 6 return false; 7 DNode* q = p->next; //q是p的後繼節點 8 //需要修改兩個指標:p的next以及q的後繼的prior 9 if( q->next != NULL ) 10 q->next->prior = p; 11 p->next = q->next; 12 delete q; //C語言版: free(q); 13 return true; 14 }
要注意:p是否是最後一個節點?p的下一個節點是否是最後一個節點?
(4) 按值查詢
1 DNode* LocateElem( DLinkList DL,ElemType e ) 2 { 3 DNode* p = DL->next; 4 while( p != NULL && p->data != e ) { 5 p = p->next; 6 } 7 return p; 8 }
(5) 按位序查詢
1 DNode* GetElem( DLinkList L, int i ) 2 { 3 if( i < 0 ) //認為頭節點是第0個節點,如果輸入i為0則返回頭結點的指標 4 return NULL; 5 int j = 0; 6 DNode* p = L; 7 while( j != i && p != NULL ) { 8 j++; 9 p = p->next; 10 } 11 return p; 12 }
按值查詢與按位序查詢在僅有頭指標的前提下和單連結串列中的實現沒有任何區別。
(6) 銷燬整個連結串列
在實現了刪除後繼節點的操作後,我們就可以使用這個操作,逐一刪除頭節點的後繼節點,從而達成銷燬整個
連結串列的目的。請注意:把頭結點看成第0個節點的話,每執行一次,相當於刪除了連結串列中的第一個節點,而刪
除前的第二個節點成為了新的第一個節點。實現程式碼如下:
1 void DestroyList( DLinkList& DL ) 2 { 3 while( DL->next != NULL ) 4 DeleteNextDNode(DL); 5 delete DL; 6 DL = NULL; 7 }
* 程式碼測試
以下測試先將0,2,4,6,8,10,12,14,16,18這10個數以後插的方式建立連結串列,然後嘗試找到值為18的節點並
將其刪除;接下來測試查詢不存在的,值為200的節點。然後嘗試用後插操作實現一個前插操作。(思想:
在p前插入s,先通過prior指標拿到p的前驅節點pp,這樣,就相當於對pp執行後插操作)。
1 /* 帶頭節點的雙連結串列 2 3 實現操作: 4 *1 刪除p的後繼節點 5 *2 後插操作:在p後插入s 6 *3 前插操作:在p前插入s 7 *4 按值查詢 8 *5 按位序查詢 9 *6 銷燬整個連結串列 10 */ 11 #include <iostream> 12 #include <cstdio> 13 using namespace std; 14 15 typedef int ElemType; 16 17 typedef struct DNode { 18 ElemType data; //節點資料域 19 DNode* next,* prior; //後繼指標、前驅指標 20 } * DLinkList; 21 22 //初始化一個雙連結串列 23 void InitDLinkList(DLinkList& L) 24 { 25 L = new DNode; //C語言版:L = (DNode*)malloc(sizeof(DNode)); 26 L->data = 0; 27 L->next = NULL; 28 L->prior = NULL; 29 } 30 31 //後插操作,在p指向的節點之後插入s指向的節點 32 bool InsertNextDNode( DNode* p,DNode* s ) 33 { 34 if( p == NULL || s == NULL ) 35 return false; 36 s->next = p->next; 37 //如果p是最後一個節點,p沒有下一個節點,自然不需要修改下一個結點的前驅指標 38 if( p->next != NULL ) 39 p->next->prior = s; 40 s->prior = p; 41 p->next = s; 42 return true; 43 } 44 45 //刪除操作:刪除p節點的後繼節點 46 //由於連結串列是帶頭結點的,因此使用這個函式也可以刪除表的第一個資料節點 47 bool DeleteNextDNode( DNode* p ) 48 { 49 if( p == NULL ) 50 return false; 51 if( p->next == NULL ) 52 return false; 53 DNode* q = p->next; //q是p的後繼節點 54 //需要修改兩個指標:p的next以及q的後繼的prior 55 if( q->next != NULL ) 56 q->next->prior = p; 57 p->next = q->next; 58 delete q; //C語言版: free(q); 59 return true; 60 } 61 62 //銷燬整個連結串列 63 void DestroyList( DLinkList& DL ) 64 { 65 while( DL->next != NULL ) 66 DeleteNextDNode(DL); 67 delete DL; 68 DL = NULL; 69 } 70 71 //列印整個連結串列 72 void Print( DLinkList DL ) 73 { 74 if( DL == NULL ) 75 return ; 76 DNode* p = DL->next; 77 while( p != NULL ) { 78 cout << p->data << endl; 79 p = p->next; 80 } 81 82 } 83 84 //按值查詢:找到表中第一個值等於e的元素,返回其指標,如果 85 //不存在值等於e的,返回NULL 86 DNode* LocateElem( DLinkList DL,ElemType e ) 87 { 88 DNode* p = DL->next; 89 while( p != NULL && p->data != e ) { 90 p = p->next; 91 } 92 return p; 93 } 94 95 //按位序查詢,找到表中第i個元素,返回其指標 96 DNode* GetElem( DLinkList L, int i ) 97 { 98 if( i < 0 ) //認為頭節點是第0個節點,如果輸入i為0則返回頭結點的指標 99 return NULL; 100 int j = 0; 101 DNode* p = L; 102 while( j != i && p != NULL ) { 103 j++; 104 p = p->next; 105 } 106 return p; 107 } 108 109 //嘗試使用後插操作實現一個前插操作:要在p指向的節點前插入s指向的節點, 110 //先拿到p的前驅節點的指標pp,在pp後插入s 111 bool InsertPriorNode( DNode* p,DNode* s ) 112 { 113 if( p == NULL || s == NULL ) 114 return false ; 115 //如果p的前驅為空,說明p是頭節點,顯然不能在頭節點前插入資料 116 if( p->prior == NULL ) 117 return false; 118 DNode* pp = p->prior; 119 InsertNextDNode(pp,s); 120 return true; 121 } 122 123 124 void test1() 125 { 126 //初始化 127 DLinkList DL; 128 InitDLinkList(DL); 129 //測試後插 130 for( int i=0; i<10; i++ ) { 131 DNode* temp = new DNode; 132 temp->data = 2*i; 133 temp->next = temp->prior = NULL; 134 InsertNextDNode(DL,temp); 135 } 136 Print(DL); 137 cout << "-------------------------" << endl; 138 //測試按值查詢和刪除 139 { 140 //嘗試找到值為18的節點並將其刪除 141 DNode* temp = LocateElem(DL,18); 142 if( temp != NULL ) 143 DeleteNextDNode(temp); 144 145 //嘗試查詢不存在的節點 146 temp = LocateElem(DL,200); 147 if( temp == NULL ) { 148 cout << "200不存在" << endl; 149 } 150 } 151 Print(DL); 152 cout << "--------------------------" << endl; 153 //測試前插操作 154 { 155 //嘗試在第一個元素及最後一個元素前插入值為985的元素 156 DNode* temp1 = GetElem(DL,1); 157 DNode* temp2 = GetElem(DL,9); //表中現在只有9個元素 158 cout << temp2->data << endl; 159 DNode* _9851 = new DNode; 160 _9851->data = 985; 161 _9851->next = _9851->prior = NULL; 162 DNode* _9852 = new DNode; 163 _9852->data = 985; 164 _9852->next = _9852->prior = NULL; 165 if( InsertPriorNode(temp1,_9851) && InsertPriorNode(temp2,_9852) ) 166 Print(DL); 167 } 168 //測試銷燬 169 { 170 DestroyList(DL); 171 cout << "123456" << endl; 172 Print(DL); 173 cout << "123456" << endl; 174 } 175 } 176 177 int main() 178 { 179 test1(); 180 return 0; 181 }