【資料結構與演算法】雙向鏈表
鏈表的分類
- 單向、雙向
- 帶頭單鏈表、不帶頭鏈表
- 回圈、非回圈
在我們平時的學習中,最常用的兩種鏈表:
-
無頭單向非回圈鏈表

-
帶頭雙向回圈鏈表
head的是哨兵位頭結點,不存盤資料

下文主要講解第二種帶頭雙向鏈表
創建結構體
雙向鏈表中,每個結點除了包含下個節點的地址外,還包含前一個結點的地址,因此我們需要分別創建兩個指標next和prev
創建結構體代碼
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
鏈表初始化
方法一:要完成初始化,我們需要把phead的地址傳給相應的初始化函式,只有址傳遞才能改變phead的值,下面例子則是錯誤的:

下面我們來看看呼叫部分的phead和傳入函式后的phead的地址是否相同
呼叫部分的phead地址如下

傳入函式后的phead的地址如下

可見此時phead的地址不一樣,因此無法對傳入的phead指標變數進行修改,正確的操作應該是傳入phead的地址,如下圖

此時傳入的即為指標變數的地址,可以實作對傳入指標變數的修改(初始化)了

初始化函式代碼(方法一)
//初始化鏈表
void ListInit(ListNode** pphead)
{
*pphead = BuyListNode(0);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;
return pphead;
}
**方法二:**這種方法比較簡單,不用傳入二級指標,直接回傳初始化函式創建的頭結點賦值給phead即可完成初始化
ListNode* phead = ListInit();
初始化函式代碼(方法二)
//初始化鏈表
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
清理資料結點
-
指標cur指向當前要清理的結點,next用于保存下一個待清理結點的地址

-
cur==phead的時候的時候,結束清理,且對phead初始化,以確保頭結點繼續使用

phead->next = phead;
phead->prev = phead;
清理資料結點代碼
void ListClear(ListNode* phead)
{
assert(phead);
//清理所有資料結點,保留頭結點,可以繼續使用
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
phead->next = phead;
phead->prev = phead;
}
銷毀鏈表
上面我們講的是對鏈表的結點進行清理,但如果這個鏈表不再使用時,需要將頭結點也一起銷毀
銷毀鏈表代碼
//銷毀鏈表
void ListDestroy(ListNode* phead)
{
assert(phead);
ListClear(phead);
free(phead);
}
創建新結點
ListNode* BuyListNode(LTDataType x)
{
ListNode*node = (ListNode*)malloc(sizeof(ListNode));
//初始化
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
鏈表的列印
? 思路:
-
結束條件和單鏈表不一樣,單鏈表是在列印結點指向NULL時結束列印,但現在是回圈鏈表,不存在指向NULL的問題,因此,結束條件可改為當cur(進行結點列印的指標)等于phead時結束列印
-
頭結點phead是哨兵位結點,不儲存資訊,因此從phead的下一個指標開始列印
鏈表的列印代碼
assert(phead);
ListNode* cur = phead->next;
while (cur != phead) //結束條件
{
print("%d ", cur->data);
cur = cur->next;
}
尾插

-
進行尾插,首先要找到尾結點,由上圖可知雙鏈表頭結點的prev指標指向的就是尾結點
ListNode* tail = phead->prev; -
尾結點找到后,我們來插入新的結點,首先我們將新結點和尾結點進行鏈接,如下圖紅色箭頭

tail->next = newnode; newnode->prev = tail; -
頭結點與新結點鏈接

newnode->next = phead; phead->prev = newnode;
尾插代碼
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead); //斷言,頭指標不指向NULL
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
//進行結點的鏈接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾刪
思路:
-
確定尾結點
ListNode* tail = phead->prev; ListNode* tailPrev = tail->prev;
-
tailPrev指向的結點與頭結點進行鏈接
phead->prev = tailPrev; tailPrev->next = phead;
-
洗掉尾結點

尾刪代碼
//尾刪
void ListPopBack(ListNode* phead)
{
assert(phead);
if (phead->next == phead) //判斷是否只有哨兵位頭結點
{
return phead;
}
else
{
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
phead->prev = tailPrev;
tailPrev->next = phead;
free(tail);
return phead;
}
}
頭插
頭插是指在哨兵位頭結點的后面插入

思路:
-
定義first指標用于儲存原鏈表中phead的后一個結點

-
新結點與phead鏈接

phead->next = newnode; newnode->prev = phead; -
新結點與first鏈接,頭插結束

newnode->next = first;
first->prev = newnode;
頭插代碼
//頭插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* first = phead->next;
ListNode* newnode = BuyListNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
頭刪
把哨兵位結點的后一個結點洗掉

-
創建兩個指標first和second
ListNode* first = phead->next; ListNode* second = first->next;
-
phead與second鏈接,洗掉first
phead->next = second; second->prev = phead; free(first);
頭刪代碼
//頭刪
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next!=phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
鏈表的查找
查找只要對鏈表遍歷即可,邏輯和列印鏈表相似,直接上代碼
鏈表的查找代碼
//查找
ListNode* ListFind(ListNode* phead, int x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
插入(任意結點)
要在給定結點的前面插入(例如在d2前插入),就要先得到這個給定結點的位置,那么用查找函式確定這個結點的位置pos

后面的插入和前面所提到到兩種插入大同小異,這里就不贅述了
插入(任意結點)代碼
//插入(任意位置)
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newnode = BuyListNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
洗掉(任意結點)

洗掉(任意結點)代碼
//洗掉(任意位置)
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
雙鏈表完整代碼
List.h檔案
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
//void ListInit(ListNode** phead);
ListNode* ListInit();
void ListClear(ListNode* phead);
void ListDestroy(ListNode* phead);
ListNode* BuyListNode(LTDataType x);
void ListPrint(ListNode* phead);
void ListPushBack(ListNode* phead, LTDataType x);
void ListPopBack(ListNode* phead);
void ListPushFront(ListNode* phead, LTDataType x);
void ListPopFront(ListNode* phead);
ListNode* ListFind(ListNode* phead, int x);
void ListInsert(ListNode* pos, LTDataType x);
void ListErase(ListNode* pos);
List.c檔案
#include "List.h"
//初始化鏈表
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
//創建新結點
ListNode* BuyListNode(LTDataType x)
{
ListNode*node = (ListNode*)malloc(sizeof(ListNode));
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
//清理資料結點
void ListClear(ListNode* phead)
{
assert(phead);
//清理所有資料結點,保留頭結點,可以繼續使用
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
phead->next = phead;
phead->prev = phead;
}
//銷毀鏈表
void ListDestroy(ListNode* phead)
{
assert(phead);
ListClear(phead);
free(phead);
}
//列印
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead); //斷言,頭指標不指向NULL
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
//進行結點的鏈接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//尾刪
void ListPopBack(ListNode* phead)
{
assert(phead);
if (phead->next == phead) //判斷是否只有哨兵位頭結點
{
return phead;
}
else
{
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
phead->prev = tailPrev;
tailPrev->next = phead;
free(tail);
return phead;
}
}
//頭插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* first = phead->next;
ListNode* newnode = BuyListNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
//頭刪
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next!=phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
//查找
ListNode* ListFind(ListNode* phead, int x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//插入(任意位置)
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newnode = BuyListNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
//洗掉(任意位置)
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
test.c
#include "List.h"
void TestList1()
{
ListNode* phead = ListInit();
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPrint(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPopBack(phead);
ListPrint(phead);
ListPushFront(phead, 1);
ListPushFront(phead, 2);
ListPushFront(phead, 3);
ListPushFront(phead, 4);
ListPushFront(phead, 5);
ListPrint(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPopFront(phead);
ListPrint(phead);
ListDestroy(phead);
}
void TestList2()
{
ListNode* phead = ListInit();
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPrint(phead);
ListNode* pos = ListFind(phead, 3);
ListInsert(pos, 30);
ListPrint(phead);
pos = ListFind(phead, 3);
ListErase(pos);
ListPrint(phead);
ListDestroy(phead);
}
void main()
{
//TestList1();
TestList2();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/384257.html
標籤:其他
上一篇:剖析Linux-行程控制
