一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
0x07 双链表查找(DListFind)
💬 DList.h
DLNode* DListFind(DLNode* pHead, DLNodeDataType x);
💬 DList.c
DLNode* DListFind(DLNode* pHead, DLNodeDataType x) {
assert(pHead); //防止pHead为空
DLNode* cur = pHead->next;
while(cur != pHead) {
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
🔑 解读:很简单,和打印的思路一样。只需要创建一个 cur 从第一个有效节点(pHead->next)开始遍历链表就可以了。如果 cur->data 和传入的 x,即需要查找的值相同就返回 cur,cur 是带有值和地址的。如果整个链表都走完了还没有找到相同的值,就返回 NULL 。
💬 Test.c
没什么测的,懒得测了。自己测去吧。
(划掉)
0x08 双链表pos位置之前插入(DListInsert)
💬 DList.h
void DListInsert(DLNode* pos, DLNodeDataType x);
🔑 解读:根据性质,这里甚至都不需要哨兵位,我们只需要传入 pos 和待插入的值 x 即可。
💬 DList.c
void DListInsert(DLNode* pos, DLNodeDataType x) {
assert(pos != NULL); // 防止传入的pos为空
DLNode* new_node = CreateNewNode(x);
DLNode* posPrev = pos->prev; //pos前驱
//思路草图: posPrev newnode(待插目标) pos
posPrev->next = new_node;
new_node->prev = posPrev;
new_node->next = pos;
pos->prev = new_node;
}
🔑 解读:
① 防止 pos 传入为空。
② 首先创建新节点。因为是在 pos 位置之前插入,为了方便,我们先标出 pos 的前驱指针 posPrev,随后我们画出草图:
思路草图:posPrev newnode(待插目标) pos
将 posPrev 与 new_node 相互链接起来:
posPrev->next = new_node;
new_node->prev = posPrev;
将 new_node 与 pos 相互链接起来:
new_node->next = pos;
pos->prev = new_node;
⚡ 此时,DListInsert 就大功告成了!它就是双向链表最核心的两个接口之一,我们可以将这个万能的 "pos位置之前插入" 复用到我们刚才写的尾插和头插那里,即 DListPushBack 和 DListPushFront 中:
// 尾插(NEW)
void DListPushBack(DLNode* pHead, DLNodeDataType x) {
assert(pHead != NULL); //防止pHead为空
/*
DLNode* tail = pHead->prev; //创建尾指针
DLNode* new_node = CreateNewNode(x); //创建新节点
//思路草图: pHead tail new_node(尾插目标)
tail->next = new_node;
new_node->prev = tail;
new_node->next = pHead;
pHead->prev = new_node;
*/
DListInsert(pHead, x);
}
🔑 解读:传入 pHead 进入 DListInsert 中,可以找到尾部节点,即可完成尾插。
// 头插(NEW)
void DListPushFront(DLNode* pHead, DLNodeDataType x) {
assert(pHead != NULL); //防止pHead为空
/*
DLNode* new_node = CreateNewNode(x); //创建新节点
DLNode* pHeadNext = pHead->next; //提出第一个节点
//思路草图: pHead new_node(头插目标) First
pHead->next = new_node;
new_node->prev = pHead;
new_node->next = pHeadNext;
pHeadNext->prev = new_node;
*/
DListInsert(pHead->next, x);
}
🔑 解读:头插,将 pHead->next 传入 DListInsert 中即可完成头插。
0x09 双链表删除pos位置(DListEarse)
💬 DList.h
void DListEarse(DLNode* pos);
💬 DList.c
void DListEarse(DLNode* pos) {
assert(pos != NULL); //防止传入的pos为空
DLNode* posPrev = pos->prev; //标出pos的前驱指针
DLNode* posNext = pos->next; //标出pos的后继指针
//思路草图: posPrev pos(待删目标) posNext
posPrev->next = posNext;
posNext->prev = posPrev;
//释放
free(pos);
pos = NULL
}
🔑 解读:
① 防止 pos 传入为空。
② 既然要删除 pos 位置的节点,我们先标出 pos 的前驱指针 posPrev 和 pos 的后继指针 posNext ,随后我们画出草图:
思路草图: posPrev pos(待删目标) posNext
之后我们开始 "缝针" ,将 posPrev 与 posNext 相互链接起来:
posPrev->next = posNext;
posNext->prev = posPrev;
最后再将 pos 释放即可。
⚡ 此时,DListEarse 也搞定了!我们可以将这个万能的 "pos位置删除" 复用到之前写的尾删和头删那里,即 DListPopBack 和 DListPopFront 中:
// 尾删(NEW)
void DListPopBack(DLNode* pHead) {
assert(pHead != NULL); //防止pHead为空
assert(pHead->next != pHead); //防止链表为空
/*
//思路草图: pHead tailPrev tail(待删目标)
DLNode* tail = pHead->prev;
DLNode* tailPrev = tail->prev;
//释放
free(tail);
//链接
tailPrev->next = pHead;
pHead->prev = tailPrev;
*/
DListEarse(pHead->prev);
}
🔑 解读:尾删,将 pHead->prev 传入 DListEarse 中即可完成尾删。
// 头删(NEW)
void DListPopFront(DLNode* pHead) {
assert(pHead != NULL); // 防止pHead为空
assert(pHead->next != pHead); // 防止链表为空
/*
//思路草图: pHead pHeadNext(待删目标) pHeadNextNext
DLNode* pHeadNext = pHead->next;
DLNode* pHeadNextNext = pHeadNext->next;
//链接
pHead->next = pHeadNextNext;
pHeadNextNext->prev = pHead;
//释放
free(pHeadNext);
*/
DListEarse(pHead->next);
}
🔑 解读:头删,将 pHead->next 传入 DListEarse 中即可完成头删。
四、总结
🔺 所以,双向链表严格来说只需要快速地实现 insert 和 earse 这两个接口就可以搞定了。为什么会这么简单?别问,问就是结构的优势!
如果以后让你快速实现一个双向链表,你把 "pos位置之前插入" 和 "删除pos位置" 这两个接口写好,头尾的插入和删除直接复用就可以搞定了。
五、完整代码
最后附上完整代码(包含测试用地代码)
💬 DList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int DLNodeDataType; // DLNodeDataType == int
typedef struct DoubleListNode {
DLNodeDataType data; // 用来存放结点的数据
struct DoubleListNode* next; // 指向后继节点的指针
struct DoubleListNode* prev; // 指向前驱节点的指针
} DLNode; // 重命名为DLNode
DLNode* DListInit();
void DListPrint(DLNode* pHead);
void DListPushBack(DLNode* pHead, DLNodeDataType x);
void DListPushFront(DLNode* pHead, DLNodeDataType x);
void DListPopBack(DLNode* pHead);
void DListPopFront(DLNode* pHead);
DLNode* DListFind(DLNode* pHead, DLNodeDataType x);
void DListInsert(DLNode* pos, DLNodeDataType x);
void DListEarse(DLNode* pos);
💬 DList.c
#include "DList.h"
DLNode* DListInit() {
//哨兵位头节点
DLNode* pHead = (DLNode*)malloc(sizeof(DLNode));
pHead->next = pHead;
pHead->prev = pHead;
return pHead;
}
DLNode* CreateNewNode(DLNodeDataType x) {
//动态内存开辟一块 DLNode 大小的空间给 new_node
DLNode* new_node = (DLNode*)malloc(sizeof(DLNode));
//检查malloc
if (new_node == NULL) {
printf("malloc failed!\n");
exit(-1);
}
//放置数据
new_node->data = x;
//初始化
new_node->next = NULL;
new_node->prev = NULL;
//返回
return new_node;
}
void DListPrint(DLNode* pHead) {
assert(pHead != NULL); //防止pHead为空
DLNode* cur = pHead->next; //因为pHead存的不是有效数据,所以要从pHead的下一个节点开始
while(cur != pHead) {
printf("%d ", cur->data); //打印
cur = cur->next;
}
printf("\n"); //换行
}
void DListPushBack(DLNode* pHead, DLNodeDataType x) {
assert(pHead != NULL); //防止pHead为空
DListInsert(pHead, x);
}
void DListPushFront(DLNode* pHead, DLNodeDataType x) {
assert(pHead != NULL); //防止pHead为空
DListInsert(pHead->next, x);
}
void DListPopBack(DLNode* pHead) {
assert(pHead != NULL); //防止pHead为空
assert(pHead->next != pHead); //防止链表为空
DListEarse(pHead->prev);
}
void DListPopFront(DLNode* pHead) {
assert(pHead != NULL); // 防止pHead为空
assert(pHead->next != pHead); // 防止链表为空
DListEarse(pHead->next);
}
DLNode* DListFind(DLNode* pHead, DLNodeDataType x) {
assert(pHead != NULL); //防止pHead为空
DLNode* cur = pHead->next;
while(cur != pHead) {
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
// 在pos位置之前插入
void DListInsert(DLNode* pos, DLNodeDataType x) {
assert(pos != NULL); // 防止传入的pos为空
DLNode* new_node = CreateNewNode(x);
DLNode* posPrev = pos->prev; //pos前驱
//思路草图:posPrev newnode(待插目标) pos
posPrev->next = new_node;
new_node->prev = posPrev;
new_node->next = pos;
pos->prev = new_node;
}
// 删除pos位置
void DListEarse(DLNode* pos) {
assert(pos != NULL); //防止传入的pos为空
DLNode* posPrev = pos->prev; //标出pos的前驱指针
DLNode* posNext = pos->next; //标出pos的后继指针
//思路草图: posPrev pos(待删目标) posNext
posPrev->next = posNext;
posNext->prev = posPrev;
//释放
free(pos);
pos = NULL;
}
💬 Test.c
#include "DList.h"
void TestList1() {
DLNode* pList = DListInit();
DListPushBack(pList, 1);
DListPushBack(pList, 2);
DListPushBack(pList, 3);
DListPushBack(pList, 4);
DListPrint(pList);
}
void TestList2() {
DLNode* pList = DListInit();
DListPushFront(pList, 10);
DListPushFront(pList, 20);
DListPushFront(pList, 30);
DListPushFront(pList, 40);
DListPrint(pList);
}
void TestList3() {
DLNode* pList = DListInit();
DListPushBack(pList, 1);
DListPushBack(pList, 2);
DListPushBack(pList, 3);
DListPushBack(pList, 4);
DListPrint(pList);
DListPopBack(pList);
DListPrint(pList);
}
void TestList4() {
DLNode* pList = DListInit();
DListPushBack(pList, 1);
DListPushBack(pList, 2);
DListPushBack(pList, 3);
DListPushBack(pList, 4);
DListPrint(pList);
DListPopFront(pList);
DListPrint(pList);
}
int main() {
TestList1();
// TestList2();
// TestList3();
// TestList4();
return 0;
}
参考资料:
Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .
百度百科[EB/OL]. []. baike.baidu.com/.
声明:本章的 DoubleListNode 源代码参考自C++的STL库!
📌 笔者:王亦优
📃 更新: 2021.11.15
❌ 勘误: 无
📜 声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!