雙向鏈表:函數
是在單鏈表的每一個節點中,再設置一個指向其前驅結點的指針域。工具
因此在雙向鏈表中的結點有兩個指針域,一個指向直接後繼,另外一個指向直接前驅。性能
雙向循環鏈表:ui
與雙向鏈表相比,雙向循環鏈表的尾結點的next
指向頭結點,頭結點的prior 指向尾結點,造成一個循環。以下圖所示:spa
建立代碼以下:指針
#define ElemType int;
typedef struct DulNode {
ElemType data;
struct DulNode *prior /*直接前驅指針*/ struct DulNode *next /*直接後繼指針*/ } DulNode, *DuLinkList;
複製代碼
建立雙向鏈表的步驟以下:code
L
,以及結點*L
p
temp
,並進行數值給定p
與temp
進行雙線連接連接
temp
是 p
的後繼p
是 temp
的前驅temp
賦值給p
,p
依然爲鏈表的尾結點。// ① 建立*L 指向頭結點
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) return ERROR;
(*L)->prior = NULL;
(*L)->next = NULL;
(*L)->data = -1;
//② 新建一個尾結點 p
LinkList p = *L;
for(int i=0; i < 10;i++){
// ③ 建立1個臨時的結點
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->prior = NULL;
temp->next = NULL;
temp->data = i;
// ④ 爲新增的結點創建雙向鏈表關係
// 1 temp 是p的後繼
p->next = temp;
// 2 temp 的前驅是p
temp->prior = p;
// ⑤ p 要記錄最後的結點的位置,方便下一次插入
p = p->next;
}
複製代碼
向雙向鏈表種添加結點的步驟和單向鏈表添加結點相似,只是多了一步連接前驅的工做。cdn
步驟以下:blog
temp
p
,指向鏈表的頭結點p
向後移,找到插入位置 i
的結點
p
爲鏈表尾部,只需作 p
與temp
首尾相連i
結點後,分兩步對p
與temp
進行首尾相連
p
的next
的 prior 指向 目標結點temp
next
指向 p
的 next
p
的next
指向目標結點 temp
temp
的 prior
指向 p
**注意:**這裏的第四步中,第1,2兩步必須先於3,4兩步執行,不然先將p
與 temp
,關聯上,會致使原p
的next
丟失,成爲野指針。源碼
畫個圖表示一下流程:
Status ListInsert(LinkList *L, int i, ElemType data){
//1. 插入的位置不合法 爲0或者爲負數
if(i < 1) return ERROR;
//2. 新建結點
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->data = data;
temp->prior = NULL;
temp->next = NULL;
//3.將p指向頭結點!
LinkList p = *L;
//4. 找到插入位置i直接的結點
for(int j = 1; j < i && p;j++)
p = p->next;
//5. 若是插入的位置超過鏈表自己的長度
if(p == NULL){
return ERROR;
}
//6. 判斷插入位置是否爲鏈表尾部;
if (p->next == NULL) {
p->next = temp;
temp->prior = p;
}
else
{
//1️⃣ 將p->next 結點的前驅prior = temp
p->next->prior = temp;
//2️⃣ 將temp->next 指向原來的p->next
temp->next = p->next;
//3️⃣ p->next 更新成新建立的temp
p->next = temp;
//4️⃣ 新建立的temp前驅 = p
temp->prior = p;
}
return OK;
}
複製代碼
刪除鏈表中的結點分兩種:刪除指定位置結點和刪除指定元素的結點。其思路都是一致的,遍歷鏈表中的元素,找到指定元素,並進行刪除。主要流程與刪除單向鏈表的邏輯相似,只是多了一個移除前驅結點的操做。
刪除結點的通俗理解:就比如員工離職前,必要的一步就是工做交接,告訴你們接下來工做時誰來接手,鍋該由誰來背,公司才能正常運行;不然你一拍屁股刪庫跑路了,公司可就熱鬧了,你們都抓瞎了,這樣就亂套了。
流程以下:
p
,指向鏈表的頭結點p
指向刪除位的前一個temp
指向要刪除的結點,並把該結點 data
賦值給返回的 *e
next
指向 temp
的後一個結點temp
的下一結點的 prior
指向工具結點p
temp
總結一下,核心操做就兩步:
next
指給下一個prior
指給上個結點有圖有真相:
貼一下實操的代碼以下:
Status DeleteVeryNode(LinkList *L, int i, ElemType *e){
int k = 1;
LinkList p = (*L);
//1.判斷雙向鏈表是否爲空,若是爲空則返回ERROR;
if (*L == NULL) {
return ERROR;
}
//2. 將指針p移動到刪除元素位置前一個
while (k < i && p != NULL) {
p = p->next;
k++;
}
//3.若是k>i 或者 p == NULL 則返回ERROR
if (k>i || p == NULL) {
return ERROR;
}
//4.建立臨時指針temp 指向要刪除的結點,並將要刪除的結點的data 賦值給*e,帶回到main函數
LinkList temp = p->next;
*e = temp->data;
//5. p->next 等於要刪除的結點的下一個結點
p->next = temp->next;
//6. 若是刪除結點的下一個結點不爲空,則將將要刪除的下一個結點的前驅指針賦值p;
if (temp->next != NULL) {
temp->next->prior = p;
}
//7.刪除temp結點
free(temp);
return OK;
}
複製代碼
刪除指定元素的結點,會更簡單,只須要遍歷循環,找到相應的結點後,依次對前驅點和後繼點進行從新配置。
步驟以下:
L
, 將p
指向首結點。L
,判斷給定元素 data
與 p-> data
是否相等,相等即找到目標結點prior
,指向目標的上一個結點。p
貼一下實操的代碼以下:
Status DeleteDefinedNode(LinkList *L, int data){
// 1. 建立鏈表,以及頭結點 p
LinkList p = *L;
//1.遍歷雙向循環鏈表
while (p) {
//2.判斷當前結點的數據域和data是否相等,若相等則刪除該結點
if (p->data == data) {
//修改被刪除結點的前驅結點的後繼指針
p->prior->next = p->next;
//修改被刪除結點的後繼結點的前驅指針
if(p->next != NULL){
p->next->prior = p->prior;
}
//釋放被刪除結點p
free(p);
//退出循環
break;
}
//沒有找到該結點,則繼續移動指針p
p = p->next;
}
return OK;
}
複製代碼
L
, 將p
指向首結點。L
,判斷給定元素 data
與 p-> data
是否相等,
p
移動到下一個結點int selectElem(LinkList L,ElemType elem){
LinkList p = L->next;
int i = 1;
while (p) {
if (p->data == elem) {
return i;
}
i++;
p = p->next;
}
return -1;
}
複製代碼
更新結點,只須要遍歷循環鏈表,找到序號內的結點,將其數據域 data
替換爲新的數據
Status replaceLinkList(LinkList *L,int index,ElemType newElem){
LinkList p = (*L)->next;
for (int i = 1; i < index; i++) {
p = p->next;
}
p->data = newElem;
return OK;
}
複製代碼
雙向循環鏈表的建立步驟,與雙向鏈表相似。差異在於:多了將尾結點的 next
指向 頭結點,而頭結點的 prior
指向 尾結點。
具體步驟:
L
,以及結點*L
,使得其前驅和後繼都指向本身p
temp
,並進行數值給定p
與temp
進行雙線連接連接
temp
是 p
的後繼p
是 temp
的前驅temp
的後繼是 p
p
的前驅是新建的 temp
temp
賦值給p
,p
依然爲鏈表的尾結點,方便下次插入新結點。實現源碼以下:
Status creatCircularLinkList(LinkList *L){
// 1 建立空鏈表
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) {
return ERROR;
}
(*L)->next = (*L);
(*L)->prior = (*L);
// 指定一個尾結點爲 p
LinkList p = *L;
for(int i=0; i < 10;i++){
//1.建立1個臨時的結點
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->data = i;
//2.爲新增的結點創建雙向鏈表關係
//① temp 是p的後繼
p->next = temp;
//② temp 的前驅是p
temp->prior = p;
//③ temp的後繼是*L
temp->next = (*L);
//④ p 的前驅是新建的temp
p->prior = temp;
//⑤ p 要記錄最後的結點的位置,方便下一次插入
p = p->next;
}
return OK;
}
複製代碼
與雙向鏈表類似,區別在於雙向循環鏈表因爲有首位域,在找到指定位置後,須要先將插入結點的prior
和 next
與 先後創建關係,以後再考慮前結點的 next
連接,最後考慮的是 目標結點的next
具體步驟以下:
temp
p
,指向鏈表的頭結點p
向後移,找到插入位置 i
的結點
p
爲next
指向 頭結點跳出i
結點後,分兩步對p
與temp
進行首尾相連
temp
的 prior
指向 p
temp
的 next
指向 p
的 next
p
的next
指向目標結點 temp
temp
是不是尾結點
prior
指向 temp
temp
的 prior
指向 p
Status LinkCircularListInsert(LinkList *L, int index, ElemType e){
//1. 建立指針p,指向雙向鏈表頭
LinkList p = (*L);
int i = 1;
//2.雙向循環鏈表爲空,則返回error
if(*L == NULL) return ERROR;
//3.找到插入前一個位置上的結點p
while (i < index && p->next != *L) {
p = p->next;
i++;
}
//4.若是i>index 則返回error
if (i > index) return ERROR;
//5.建立新結點temp
LinkList temp = (LinkList)malloc(sizeof(Node));
//6.temp 結點爲空,則返回error
if (temp == NULL) return ERROR;
//7.將生成的新結點temp數據域賦值e.
temp->data = e;
//8.將結點temp 的前驅結點爲p;
temp->prior = p;
//9.temp的後繼結點指向p->next;
temp->next = p->next;
//10.p的後繼結點爲新結點temp;
p->next = temp;
//若是temp 結點不是最後一個結點
if (*L != temp->next) {
//11.temp節點的下一個結點的前驅爲temp 結點
temp->next->prior = temp;
}else{
(*L)->prior = temp;
}
return OK;
}
複製代碼
雙向循環鏈表的結點刪除,比雙向鏈表的簡單,由於鏈表首尾相連的特性,不須要考慮是否爲尾結點或者頭結點。
流程以下:
p
,指向鏈表的頭結點p
指向刪除位的前一個temp
指向要刪除的結點,並把該結點 data
賦值給返回的 *e
next
指向 temp
的後一個結點temp
總結一下,核心操做就兩步:
next
指給下一個prior
指給上個結點Status LinkListDelete(LinkList *L,int index,ElemType *e){
int i = 1;
LinkList temp = (*L)->next;
if (*L == NULL) {
return ERROR;
}
//①.若是刪除到只剩下首元結點了,則直接將*L置空;
if(temp->next == *L){
free(*L);
(*L) = NULL;
return OK;
}
//1.找到要刪除的結點
while (i < index) {
temp = temp->next;
i++;
}
//2.給e賦值要刪除結點的數據域
*e = temp->data;
//3.修改被刪除結點的前驅結點的後繼指針
temp->prior->next = temp->next;
//4.修改被刪除結點的後繼結點的前驅指針
temp->next->prior = temp->prior;
//5. 刪除結點temp
free(temp);
return OK;
}
複製代碼
與雙向鏈表相同,更新結點,只須要遍歷循環鏈表,找到序號內的結點,將其數據域 data
替換爲新的數據
代碼參見 2.5
順序結構的線性表與鏈式結構的線性表比較起來,
從空間性能上比較:
存儲空間分配
存儲密度的大小
存儲密度 =
時間性能比較:
雙向鏈表比起單向鏈表來講,增長了prior
這一指針域,能夠在查找和刪除時,極其迅速的操做,無須作更多的判斷,極大地提升了運算效率。同時在操做時,須要注意指針修改順序,以避免造成野指針。