前言
引入雙向鏈表:關(guān)于單鏈表的問題與討論
單鏈表存在的毛病:
-
因?yàn)閱捂湵?strong>只能單向遍歷鏈表,
-
對于前插這個操作,單鏈表必須得找到所需前插節(jié)點(diǎn)位置的前一個,那么這時就得從頭指針重新遍歷一次鏈表,會造成時間復(fù)雜度大大增加。
-
沒有頭節(jié)點(diǎn)(哨兵位)無法刪除首節(jié)點(diǎn)
這些都大大提高了時間復(fù)雜度 [ 關(guān)于算法的時間復(fù)雜度與空間復(fù)雜度 這一專題,我在之前寫的一篇專題中有詳細(xì)的講解,有需要的可以點(diǎn)擊鏈接了解一下 算法的時間復(fù)雜度與空間復(fù)雜度 ]
【注意:不要下意識覺得鏈表就一定有哨兵位,可以有,也可以沒有!】
正是因?yàn)閱捂湵碇荒?單向遍歷 這一特性所帶來各種的麻煩,前人設(shè)計出了雙向鏈表。
一、雙向鏈表的特性簡概
-
特性:
- 雙向
- 循環(huán)
正是因?yàn)橛羞@兩個特性,促成了雙向鏈表很多優(yōu)勢:
- 不需要像單鏈表那樣從 頭節(jié)點(diǎn) 完整遍歷一邊鏈表,才能找到尾節(jié)點(diǎn)。
雙向鏈表:直接 phead->prev 找到尾節(jié)點(diǎn)(雙向、循環(huán) 的特性)。 - 且找到需要處理的節(jié)點(diǎn),還需要從頭節(jié)點(diǎn)再遍歷一次鏈表,只為找到該節(jié)點(diǎn)的前一個節(jié)點(diǎn),才能對該節(jié)點(diǎn)進(jìn)行處理。
雙向鏈表:pos->prev 前一節(jié)點(diǎn)
雙向鏈表 邏輯樣例圖
代碼實(shí)現(xiàn)
//類型聲明
typedef int LTDataType; //數(shù)據(jù)類型重命名
typedef struct ListNode //結(jié)構(gòu)體類型聲明
{ //兩頭的指針變量 儲存雙向兩旁結(jié)構(gòu)體的地址
struct ListNode* prev; //保存前一個節(jié)點(diǎn)的指針
LTDataType data;
struct ListNode* next; //保存后一個節(jié)點(diǎn)的指針
}ListNode;
二、雙鏈表的增刪查改【C 代碼實(shí)現(xiàn)】
(一)創(chuàng)建文件
- List.h (雙向鏈表雙向鏈表的類型定義、接口函數(shù)聲明、引用的頭文件)
- List.c (雙向鏈表接口函數(shù)的實(shí)現(xiàn))
- test.c (主函數(shù)、測試順序表各個接口功能)
(二)List.h
1. 頭文件聲明
#pragma once //防止頭文件重復(fù)包含
//頭文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
2. 雙向結(jié)構(gòu)體類型聲明
//類型聲明
typedef int LTDataType; //數(shù)據(jù)類型重命名
typedef struct ListNode //結(jié)構(gòu)體類型聲明
{
struct ListNode* prev; //兩頭的指針變量 儲存雙向兩旁結(jié)構(gòu)體的地址
LTDataType data;
struct ListNode* next;
}ListNode;
(三)List.c
1.創(chuàng)建返回雙向鏈表的頭結(jié)點(diǎn).
圖解
phead 的含義 = pointer to head
// 創(chuàng)建返回鏈表的頭結(jié)點(diǎn).
ListNode* ListCreate() {
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
//結(jié)構(gòu)體指針phead 存的是malloc為新結(jié)構(gòu)體開辟內(nèi)存后 的返回的該新節(jié)點(diǎn)的指針
assert(malloc);
return phead; //phead傳的是phead指針的內(nèi)容=head地址 =>返回結(jié)構(gòu)體地址
}
2. 雙向鏈表的初始化
// 雙向鏈表的初始化
void ListInit(ListNode* phead) { //也用phead接受傳過來的head的地址
assert(phead);
phead->prev = phead->next;
phead->data = 0; //加深對指針的理解
phead->next = phead->prev; //直接用head【記?。?. **名 直接用的是內(nèi)容** 明白這點(diǎn) 對于指針的理解就輕松很多】
//2. -> 只能對指針使用 且不支持二級指針解引用*后得到一級指針的形式
// (如:ListNode** pphead **pphead->data (x)好像不行 去試一下 )
【關(guān)于指針注意的點(diǎn)的講解】
}
【關(guān)于指針注意的點(diǎn)的講解】 :加深對指針的理解
- 直接用head => 名 直接用的是內(nèi)容 明白這點(diǎn) 對于指針的理解就輕松很多
- -> 只能對指針使用 且不支持二級指針解引用后得到一級指針的形式
(如:ListNode* pphead
**pphead->data (x) 好像不行 去試一下 )
3.創(chuàng)建返回新節(jié)點(diǎn)
// 創(chuàng)建返回新節(jié)點(diǎn)
ListNode* BuynewNode(x) {
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
return newNode;
}
4.雙向鏈表尾插
// 雙向鏈表尾插
void ListPushBack(ListNode* phead,LTDataType x) {
ListNode* newNode = BuynewNode(x); //1M的空間可創(chuàng)建出一千多萬個指針變量
ListNode* tail = phead->prev; //多創(chuàng)建指針變量 自己也標(biāo)的看的清楚 增加代碼的可讀性
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
}
5.雙向鏈表頭插
// 雙向鏈表頭插
void ListPushFront(ListNode* phead, LTDataType x) {
ListNode* newNode = BuynewNode(x);
ListNode* first = phead->next; //第一個節(jié)點(diǎn)
newNode->next = first;
first->prev = newNode;
phead->next = newNode;
newNode->prev = phead;
}
6.雙向鏈表尾刪
// 雙向鏈表尾刪
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListNode* tailPrev = phead->prev->prev;
ListNode* tail = phead->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;
}
7.雙向鏈表頭刪
// 雙向鏈表頭刪
void ListPopFront(ListNode* phead) {
assert(phead);
assert(phead->next != phead);
ListNode* newNext = phead->next->next;
ListNode* Next = phead->next; //新建指針變量 保存好要free掉的節(jié)點(diǎn)的地址
free(Next); //就不用怕后續(xù)改變各節(jié)點(diǎn)之間的指針關(guān)系時把該節(jié)點(diǎn)的地址弄丟了
Next = NULL;
phead->next = newNext;
newNext->prev = phead;
}
8.雙向鏈表查找
// 雙向鏈表查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
ListNode* Head = phead->next; //設(shè)置兩個指針變量,一個從頭開始遍歷,一個從后遍歷
ListNode* Back = phead->prev;
while (Head!=Back) {
if (Head->data = x)
return Head;
else if (Back->data = x)
return Back;
Head = Head->next;
Back = Back->prev;
}
return NULL;
}
9.雙向鏈表在pos的前面進(jìn)行插入
// 雙向鏈表在pos的前面進(jìn)行插入
void ListInsert(ListNode* pos, LTDataType x) {
ListNode* posPrev = pos->prev;
ListNode* newNode = BuynewNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
★10. 雙向鏈表刪除pos位置的節(jié)點(diǎn)
// 雙向鏈表刪除pos位置的節(jié)點(diǎn) //若傳過來的pos=phead->next =>作頭刪作用
void ListErase(ListNode* pos) {
ListNode* posPrev = pos->prev; //若傳過來的pos=phead(由于雙向鏈表具有循環(huán)的特性) =>作尾刪作用
ListNode* posNext = pos->next;
//也正是由于雙向鏈表具有循環(huán)的特性,即使鏈表中只有一個節(jié)點(diǎn)也能很好的運(yùn)行
posPrev->next = posNext; //圖解
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
Erase函數(shù)以后 頭刪和尾刪也可以這樣寫文章來源:http://www.zghlxwxcb.cn/news/detail-719938.html
10.1 雙向鏈表尾刪【ListErase版本】
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListErase(phead);
}
10.2 雙向鏈表頭刪【ListErase版本】
// 雙向鏈表頭刪【ListErase版本】
void ListPopFront(ListNode* phead) {
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListErase(phead->next);
}
11.雙向鏈表打印
11.1 遞歸實(shí)現(xiàn)
// 雙向鏈表打印 遞歸實(shí)現(xiàn)
void ListPrint(ListNode* phead) {
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead) {
printf("%d <=>", cur->data);
cur = cur->next;
ListPrint(cur);
}
printf("\n");
}
11.2 非遞歸實(shí)現(xiàn)
// 雙向鏈表打印 非遞歸實(shí)現(xiàn)
void ListPrint(ListNode* phead) {
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead) {
cur = phead->next;
printf("%d <=>", cur->data);
}
}
12. 雙向鏈表銷毀
// 雙向鏈表銷毀
void ListDestory(ListNode* phead) {
ListNode* cur = phead->next;
while (cur!=phead) {
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(phead);
phead = NULL;
}
三、完整代碼
碼源 我已上傳至gitee 有需要的可點(diǎn)擊后方鏈接 雙向鏈表的增刪查改 碼源 文章來源地址http://www.zghlxwxcb.cn/news/detail-719938.html
1.List.h
#pragma once //防止頭文件重復(fù)包含
#include<stdio.h> //頭文件
#include<stdlib.h>
#include<assert.h>
// 帶頭+雙向+循環(huán)鏈表增刪查改實(shí)現(xiàn)
//類型聲明
typedef int LTDataType; //數(shù)據(jù)類型重命名
typedef struct ListNode //結(jié)構(gòu)體類型聲明
{
struct ListNode* prev; //兩頭的指針變量 儲存雙向兩旁結(jié)構(gòu)體的地址
LTDataType data;
struct ListNode* next;
}ListNode;
// 創(chuàng)建返回雙向鏈表的頭結(jié)點(diǎn).
ListNode* ListCreate();
// 雙向鏈表的初始化
void ListInit(ListNode* phead);
// 創(chuàng)建返回新節(jié)點(diǎn)
ListNode* BuynewNode(LTDataType x);
// 雙向鏈表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 雙向鏈表頭插
void ListPushFront(ListNode* phead, LTDataType x);
// 雙向鏈表尾刪
void ListPopBack(ListNode* phead);
// 雙向鏈表頭刪
void ListPopFront(ListNode* phead);
// 雙向鏈表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 雙向鏈表在pos的前面進(jìn)行插入
void ListInsert(ListNode* pos, LTDataType x);
// 雙向鏈表刪除pos位置的節(jié)點(diǎn)
void ListErase(ListNode* pos);
// 雙向鏈表打印
void ListPrint(ListNode* phead);
// 雙向鏈表銷毀
void ListDestory(ListNode* phead);
2.List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
// 創(chuàng)建返回鏈表的頭結(jié)點(diǎn).
ListNode* ListCreate() {
ListNode* phead = (ListNode*)malloc(sizeof(ListNode)); //結(jié)構(gòu)體指針phead 存的是malloc為新結(jié)構(gòu)體開辟內(nèi)存后 的返回的該新節(jié)點(diǎn)的指針
assert(malloc);
return phead; //phead傳的是phead指針的內(nèi)容=head地址 =>返回結(jié)構(gòu)體地址
}
// 雙向鏈表的初始化
void ListInit(ListNode* phead) { //也用phead接受傳過來的head的地址
assert(phead);
phead->prev = phead->next;
phead->data = 0; //加深對指針的理解
phead->next = phead->prev; //直接用head【記住:1. 名 直接用的是內(nèi)容 明白這點(diǎn) 對于指針的理解就輕松很多】
//2. -> 只能對指針使用 且不支持二級指針解引用*后得到一級指針的形式
// (如:ListNode** pphead **pphead->data (x)好像不行 去試一下 ) 【關(guān)于指針注意的點(diǎn)的講解】
}
// 創(chuàng)建返回新節(jié)點(diǎn)
ListNode* BuynewNode(x) {
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
return newNode;
}
// 雙向鏈表尾插
void ListPushBack(ListNode* phead,LTDataType x) {
ListNode* newNode = BuynewNode(x); //1M的空間可創(chuàng)建出一千多萬個指針變量
ListNode* tail = phead->prev; //多創(chuàng)建指針變量 自己也標(biāo)的看的清楚 增加代碼的可讀性
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
}
// 雙向鏈表頭插
void ListPushFront(ListNode* phead, LTDataType x) {
ListNode* newNode = BuynewNode(x);
ListNode* first = phead->next; //第一個節(jié)點(diǎn)
newNode->next = first;
first->prev = newNode;
phead->next = newNode;
newNode->prev = phead;
}
// 雙向鏈表尾刪
void ListPopBack(ListNode* phead) {
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListNode* tailPrev = phead->prev->prev;
ListNode* tail = phead->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;
}
// 雙向鏈表頭刪
void ListPopFront(ListNode* phead) {
assert(phead);
assert(phead->next != phead);
ListNode* newNext = phead->next->next;
ListNode* Next = phead->next; //新建指針變量 保存好要free掉的節(jié)點(diǎn)的地址
free(Next); //就不用怕后續(xù)改變各節(jié)點(diǎn)之間的指針關(guān)系時把該節(jié)點(diǎn)的地址弄丟了
Next = NULL;
phead->next = newNext;
newNext->prev = phead;
}
// 雙向鏈表查找
ListNode* ListFind(ListNode* phead, LTDataType x) {
ListNode* Head = phead->next; //設(shè)置兩個指針變量,一個從頭開始遍歷,一個從后遍歷
ListNode* Back = phead->prev;
while (Head!=Back) {
if (Head->data = x)
return Head;
else if (Back->data = x)
return Back;
Head = Head->next;
Back = Back->prev;
}
return NULL;
}
// 雙向鏈表在pos的前面進(jìn)行插入
void ListInsert(ListNode* pos, LTDataType x) {
ListNode* posPrev = pos->prev;
ListNode* newNode = BuynewNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
// 雙向鏈表刪除pos位置的節(jié)點(diǎn) //若傳過來的pos=phead->next =>作頭刪作用
void ListErase(ListNode* pos) {
ListNode* posPrev = pos->prev; //若傳過來的pos=phead(由于雙向鏈表具有循環(huán)的特性) =>作尾刪作用
ListNode* posNext = pos->next;
//也正是由于雙向鏈表具有循環(huán)的特性,即使鏈表中只有一個節(jié)點(diǎn)也能很好的運(yùn)行
posPrev->next = posNext; //圖解
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
//Erase函數(shù)以后 頭刪和尾刪也可以這樣寫
// 雙向鏈表尾刪【ListErase版本】
void ListPopBack(ListNode* phead) {
//assert(phead);
//assert(phead->next != phead);
//ListNode* tailPrev = phead->prev->prev;
//ListNode* tail = phead->prev;
//free(tail);
//tail = NULL;
//tailPrev->next = phead;
//phead->prev = tailPrev;
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListErase(phead);
}
// 雙向鏈表頭刪【ListErase版本】
void ListPopFront(ListNode* phead) {
//assert(phead);
//assert(phead->next != phead);
//ListNode* newNext = phead->next->next;
//ListNode* Next = phead->next;
//free(Next);
//Next = NULL;
//phead->next = newNext;
//newNext->prev = phead;
assert(phead);
assert(phead->next != phead); //確保鏈表不為空,有東西可刪,及時報錯
ListErase(phead->next);
}
// 雙向鏈表打印 遞歸實(shí)現(xiàn)
void ListPrint(ListNode* phead) {
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead) {
printf("%d <=>", cur->data);
cur = cur->next;
ListPrint(cur);
}
printf("\n");
}
// 雙向鏈表打印 非遞歸實(shí)現(xiàn)
void ListPrint(ListNode* phead) {
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead) {
cur = phead->next;
printf("%d <=>", cur->data);
}
}
// 雙向鏈表銷毀
void ListDestory(ListNode* phead) {
ListNode* cur = phead->next;
while (cur!=phead) {
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
free(phead);
phead = NULL;
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
//測試ListPushBack 、ListPrint 的功能
void test1() {
ListNode* phead = ListCreate(); //返回head的地址
ListInit(phead);
ListPushBack(phead,1); //測試ListPushBack
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPrint(phead); //測試ListPrint
}
void test2() {
ListNode* phead = ListCreate(); //返回head的地址
ListInit(phead);
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPushBack(phead, 5);
ListPushBack(phead, 6);
ListPrint(phead);
ListNode* pos = ListFind(phead,3); //測試ListFind
ListErase(pos); //測試ListErase
ListPopFront(phead); //測試ListPopFront
ListPopBack(phead); //測試ListPopBack
ListPrint(phead);
pos = ListFind(phead, 2);
ListInsert(pos,8);
ListPrint(phead);
}
//測試ListDestory
void test3() {
ListNode* phead = ListCreate(); //返回head的地址
ListInit(phead);
ListPushBack(phead, 1);
ListPushBack(phead, 2);
ListPushBack(phead, 3);
ListPushBack(phead, 4);
ListPushBack(phead, 5);
ListPushBack(phead, 6);
ListPrint(phead);
ListNode* pos = ListFind(phead, 3);
ListErase(pos);
ListPopFront(phead);
ListPopBack(phead);
ListPrint(phead);
pos = ListFind(phead, 2);
ListInsert(pos, 8);
ListPrint(phead);
ListDestory(phead);
}
int main() {
test1();//測試ListPushBack 、ListPrint
test2();//測試
test3();//測試ListDestory
}```
到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)】雙向鏈表的增刪查改(C 代碼實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!