带头+双向+循环链表
- 1. 项目头文件
- 2. 具体实现功能
- 2.1 双向链表的初始化
- 2.2 双向链表尾插
- 2.3 双向链表头插
- 2.4 双向链表尾删
- 2.5 双向链表头删
- 2.6 双向链表查找
- 2.7 双向链表在pos的前面进行插入
- 2.8 双向链表删除pos位置的节点
- 2.9 双向链表打印
- 2.10 双向链表销毁
我们上篇博客进行了单链表的实现,这次就实现一下双链表:
1. 项目头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
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, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
2. 具体实现功能
2.1 双向链表的初始化
ListNode* ListCreate()
{
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL)
{
perror("malloc");
return NULL;
}
phead->_data = -1;
phead->_next = phead;
phead->_prev = phead;
return phead;
}
创建一个哨兵位头结点,前后都指向自己,再返回链表的头结点。
2.2 双向链表尾插
ListNode* BuyNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc");
return NULL;
}
newnode->_next = NULL;
newnode->_prev = NULL;
newnode->_data = x;
return newnode;
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyNode(x);
ListNode* tail = pHead->_prev;
newnode->_prev = tail;
tail->_next = newnode;
newnode->_next = pHead;
pHead->_prev = newnode;
}
和单链表一样,添加操作时都需要创建一个新的结点,但与单链表不同的是,此时不必再判断是否为空(因为有了哨兵位头结点),并且尾插时找尾结点也不用依次遍历,直接找头结点的前驱。
2.3 双向链表头插
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyNode(x);
ListNode* first = pHead->_next;
pHead->_next = newnode;
newnode->_prev = pHead;
newnode->_next = first;
first->_prev = newnode;
}
此时,也不用判断是否为空了(因为有了哨兵位头结点)。
2.4 双向链表尾删
bool LTEmpty(ListNode* pHead)
{
return pHead->_next == pHead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(!LTEmpty(pHead));
ListNode* tail = pHead->_prev;
ListNode* tailPrev = tail->_prev;
pHead->_prev = tailPrev;
tailPrev->_next = pHead;
free(tail);
}
尾删时,直接断言哨兵位不能为空。
2.5 双向链表头删
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(!LTEmpty(pHead));
ListNode* first = pHead->_next;
ListNode* second = first->_next;
free(first);
pHead->_next = second;
second->_prev = pHead;
}
头删也一样,直接断言哨兵位不能为空。
2.6 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->_next;
while (cur != pHead)
{
if (cur->_data == x)
{
return cur;
}
cur = cur->_next;
}
return NULL;
}
需要注意的是,开始是从哨兵位头结点后一个结点开始遍历的,结束的条件是当其和哨兵位头结点相同时,则循环结束。所以这整个链表的设计是非常之巧妙的,带有工学之美的。
2.7 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->_prev;
ListNode* newnode = BuyNode(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = newnode;
}
在进行插入时,需要与查找函数相互配合。我们把查找到的结点返回给插入函数的pos。
2.8 双向链表删除pos位置的节点
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* posPrev = pos->_prev;
ListNode* posNext = pos->_next;
posPrev->_next = posNext;
posNext->_prev = posPrev;
free(pos);
}
删除也是一样的,跟查找函数进行配合。
2.9 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->_next;
printf("哨兵位<=>");
while (cur != pHead)
{
printf("%d<=>",cur->_data);
cur = cur->_next;
}
printf("\n");
}
2.10 双向链表销毁
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
ListNode* cur = pHead->_next;
while (cur != pHead)
{
ListNode* next = cur->_next;
free(cur);
cur = next;
}
free(pHead);
}
打印和销毁也非常简单,就不细说了。总体而言,带头+双向+循环链表的实现比单链表简单多了。
代码参考: https://gitee.com/yujin518/test_c/tree/master/test_3_17