雙向鏈表和雙向循環鏈表
1. 雙向鏈表
1.1 概念
雙向鏈表顧名思義,就是鏈表由單向的鏈變成了雙向鏈。 使用這種數據結構,可以不再拘束於單鏈表的單向創建於遍歷等操作,大大減少了在使用中存在的問題。
在單鏈表中,每個結點有一個數據域,還有一個指針域,數據域用來存儲相關數據,而指針域則負責鏈表之間的“聯繫”。 而在雙向鏈表中,每個需要有兩個指針域,一個負責向後連接,一個負責向前連接。
一般在創建雙線鏈表時,可以根據個人喜好是否添加一個頭結點來指向首元節點。添加頭節點時,在對雙向鏈接做插入刪除等一列操作時,不需要考慮首元節點的特殊情況。
非空的雙向鏈表帶頭結點:
非空的雙向鏈表不帶頭結點:
1.2 雙向鏈接的表結構
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存儲空間初始分配量 */
typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定,這裏假設爲int */
//定義節點
typedef struct Node {
ElemType data; // s數據域
struct Node *prior; // 向前的指針
struct Node *next; // 向後的指針
}Node;
typedef struct Node * LinkList;
1.2 雙向鏈接的創建和打印
在創建雙向鏈表時,初始化一個頭節點來標誌鏈表的信息,代碼如下:
// 創建
Status creatLinkList(LinkList *L) {
// *L 指向頭結點
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) return ERROR;
(*L)->prior = NULL;
(*L)->data = -1; // 設置一個特殊值
(*L)->next = NULL;
return OK;
}
//遍歷
void show(LinkList L){
// 由頭結點L 找到首元節點
LinkList temp = L->next;
if (temp == NULL) {
printf("打印的雙向鏈表爲空!\n");
return;
}
while (temp) {
printf("%d ",temp->data);
temp = temp->next;
}
printf("\n");
}
1.3 雙向鏈接的插入
關鍵步驟:
1. 找到插入位置B節點的前一個節點p
2. 創建新節點 temp,將C節點的 next 指向 p 的 next
3. 將 p 的 next,即插入位置的 B 的 prior 指向 temp
4. 將 p->next 更新成新創建的temp
5. 將新創建的 temp 的前驅 prior 指向 p
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){
printf("插入的位置超過鏈表本身的長度!\n");
return ERROR;
}
//6. 判斷插入位置是否爲鏈表尾部;
// 因爲有頭節點,所以對插入位置爲1時,不用進行特殊處理
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;
}
1.4 雙向鏈接的刪除
1> 刪除指定位置上的結點
關鍵步驟:
1.找到刪除位置的前一個節點 p,
2.創建臨時指針delTemp 指向要刪除的結點,並將要刪除的結點的data 賦值給*e,帶回
3.將 p 的 next 指向被刪除節點的 next
4.將被刪除節點的 next 的 prior 指向 p
5.釋放被刪除的節點
代碼:
Status ListDelete(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.創建臨時指針delTemp 指向要刪除的結點,並將要刪除的結點的data 賦值給*e,帶回到main函數
LinkList delTemp = p->next;
*e = delTemp->data;
//5. p->next 等於要刪除的結點的下一個結點
p->next = delTemp->next;
//6. 判斷被刪除結點的下一個結點不爲空,則將將要刪除的下一個結點的前驅指針賦值p;
if (delTemp->next != NULL) {
delTemp->next->prior = p;
}
//7.刪除delTemp結點
free(delTemp);
return OK;
}
2> 刪除指定指定的元素
關鍵步驟:
1.遍歷雙向循環鏈表
2.判斷當前節點 p 的 data 是否等於指定的元素,相等,則p 爲被刪除的節點
3.將刪除節點 p 的 prior 指向 p 的 next
4.被刪除結點 p 的後繼結點的前驅指針指向p 的 prior
5.釋放被刪除的節點
代碼:
Status LinkListDeletVAL(LinkList *L, int data){
LinkList p = *L;
//1.遍歷雙向循環鏈表
while (p) {
//2.判斷當前結點的數據域和data是否相等,若相等則刪除該結點
if (p->data == data) {
//將刪除節點 p 的 prior 指向 p 的 next
p->prior->next = p->next;
//被刪除結點 p 的後繼結點的前驅指針指向p 的 prior
if(p->next != NULL){
p->next->prior = p->prior;
}
//釋放被刪除結點p
free(p);
//退出循環
break;
}
//沒有找到該結點,則繼續移動指針p
p = p->next;
}
return OK;
}
1.5 雙向鏈接的元素查找和更新
1> 元素查找
關鍵步驟:
1.創建變量i,記錄位置,然後開始遍歷雙向循環鏈表
2.判斷當前節點 p 的 data 是否等於指定的元素,
3.相等,記錄位置,並返回
4.不相等,i++, p = p->next
5.遍歷完成而沒查找到,則返回-1,代表沒有該元素
代碼:
int selectElem(LinkList L,ElemType elem){
LinkList p = L->next;
// 創建變量i,記錄位置
int i = 1;
while (p) {
// 判斷當前節點 p 的 data 是否等於指定的元素,
if (p->data == elem) {
// 相等,記錄位置,並返回
return i;
}
// 不相等,i++, p = p->next
i++;
p = p->next;
}
// 返回-1,代表沒有該元素
return -1;
}
2> 節點更新
關鍵步驟:
1.定義變量 p,指向首元節點,
2.遍歷找到要更新元素的節點
3.更新元素
代碼:
Status replaceLinkList(LinkList *L,int index,ElemType newElem){
// 定義變量 p,指向首元節點,
LinkList p = (*L)->next;
// 遍歷找到要更新元素的節點
for (int i = 1; i < index; i++) {
p = p->next;
}
// 更新元素
p->data = newElem;
return OK;
}
2. 雙向循環鏈表
2.1 概念
雙向循環鏈表(默認有一個頭結點作爲輔助節點) 的節點和 雙向鏈表 的節點是一樣的。唯一的不同是,雙向循環鏈表 的最後一個節點的後繼指向頭結點,頭節點的前驅指向尾結點。
循環結束的條件是:
- 雙向循環鏈表:節點的後繼不等於頭結點,即:
p-next != *L
- 雙向鏈表:尾節點的後繼不等於
NULL
,即:p-next != NULL
2.2 雙向循環鏈表的創建和遍歷
1> 創建
在創建雙向循環鏈表時基本和雙向鏈表的創建一樣,初始化一個頭節點來標誌鏈表的信息,不同的是雙向循環鏈表的next
指向自己。
代碼如下:
//創建
Status creatLinkList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) {
return ERROR;
}
(*L)->next = (*L);
(*L)->prior = (*L);
(*L)->data = -1; // 設置一個特殊值
return OK;
}
//遍歷
Status show(LinkList L){
if (L == NULL) {
printf("打印的雙向循環鏈表爲空!\n\n");
return ERROR;
}
printf("雙向循環鏈表內容: ");
LinkList p = L->next;
while (p != L) {
printf("%d ",p->data);
p = p->next;
}
printf("\n\n");
return OK;
}
2.3 雙向循環鏈表的插入
主要步驟:
1.創建新節點 temp,對 temp 的數據域賦值,
2.找到插入位置的前一個節點 p
3.將新節點 temp 的 prior 指向 p
4.將新節點 temp 的 next,指向 p 的 next
5.將 p 的 next 指向新節點 temp
6.判斷 temp 是不是最後一個節點。是,則頭節點的 prior 指向 temp;
不是,則temp節點的下一個結點的前驅指向 temp 結點
代碼:
Status ListInsert(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;
//11. temp 是不是最後一個節點。
if (*L != temp->next) {
//不是,則temp節點的下一個結點的前驅指向 temp 結點
temp->next->prior = temp;
}else{
// 是,則頭節點的 prior 指向 temp;
(*L)->prior = temp;
}
return OK;
}
2.4 雙向循環鏈表的刪除
主要步驟:
1.判斷是否刪除到只剩頭結點,是,直接置空
2.找到要刪除的節點 p,並將 data 賦值給參數傳回被刪除的data
3.修改被刪除結點的前驅結點的後繼指針,即:將 p 的 prior 的 next 指向 p 的 next
4.修改被刪除結點的後繼結點的前驅指針,即:將 p 的 next 的 prior 指向 p 的 prior
5.釋放刪除的節點
代碼:
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;
}
// 找到要刪除的結點
while (i < index) {
temp = temp->next;
i++;
}
// 給e賦值要刪除結點的數據域
*e = temp->data;
// 修改被刪除結點的前驅結點的後繼指針
temp->prior->next = temp->next;
// 修改被刪除結點的後繼結點的前驅指針
temp->next->prior = temp->prior;
// 刪除結點temp
free(temp);
return OK;
}