引言:
双链表是一种常用的数据结构,它可以在O(1)的时间复杂度下实现插入、删除和查找操作。双链表中的每个节点都包含一个指向前一个节点和后一个节点的指针,这样可以方便地在任意位置进行插入和删除操作。
在本篇博客中,我们将介绍带头循环双链表的实现,并给出相应的代码示例。
代码实现
首先,我们需要定义一个节点结构体,包含数据和指向前后节点的指针。使新创建的节点存储数据x,前后指针制空。
//创建节点
ListNode* BuyListNode(LTDataType x) {
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL) {
perror("fail malloc");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
接下来,我们需要实现一些基本的操作。
1.1.创建链表:创建一个带头节点的空链表。
// 创建返回链表的头结点.
ListNode* ListCreate() {
ListNode* head = BuyListNode(0);
head->data = NULL;
head->next = head;
head->prev = head;
}
1.2.销毁链表:释放链表中的所有节点。
// 双向链表销毁
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
while (cur) {
pHead->next = cur->next;
cur->next->prev = cur->prev;
free(cur);
cur = cur->next;
}
}
1.3.打印链表:按顺序打印链表中的所有节点。
// 双向链表打印
void ListPrint(ListNode* pHead) {
assert(pHead);
printf("head<->");
ListNode* cur = pHead->next;
while (cur != pHead) {
printf("%d<->", cur->data);
cur = cur->next;
}
printf("\n");
}
完成一些基础操作后,接下来就是关于双链表的增删改查
2.1. 尾插节点:在链表的末尾插入一个新节点。原理如图所示:
思路:创建一个名为newnode的节点,将它的前指针指向尾节点tail,后指针指向头节点head,而head的前指针、tail的后指针指向newnode,如此便形成了尾插操作。代码的基本逻辑就是这样。
来看代码:
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
ListNode* tail = pHead->next;
while (tail->next != pHead) {
tail = tail->next;
}
newnode->next = pHead;
newnode->prev = tail;
tail->next = newnode;
pHead->prev = newnode;
//复用
//ListInsert(pHead, x);
}
2.2.尾删节点:删除链表的末尾节点。原理如图所示:
思路:将原本指向tail的指针指向tail的前一个节点,再将其借助free函数释放掉。
来看代码:
// 双向链表尾删
void ListPopBack(ListNode* pHead) {
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->next;
while (tail->next!=pHead) {
tail = tail->next;
}
tail->prev->next = pHead;
pHead->prev = tail->prev;
//复用
//ListErase(pHead->prev);
}
2.3. 头插节点:在链表的头部插入一个新节点。原理如图所示:
原理: 创建一个名为newnode的节点,将它的前指针指向头节点head,后指针指向下一个节点,而head的后指针、下一个节点的前指针指向newnode,如此便形成了前插操作。代码的基本逻辑就是这样。
来看代码 :
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuyListNode(x);
newnode->prev = pHead;
newnode->next = pHead->next;
pHead->next = newnode;
pHead->prev->prev = newnode;
//复用
//ListInsert(pHead->next, x);
}
2.4. 头删节点:删除链表的头部节点。
原理: 将head的后指针指向下下的节点,下下个节点的前指针指向head借助free函数释放原本头指针后节点指向的节点。
来看代码 :
// 双向链表头删
void ListPopFront(ListNode* pHead) {
assert(pHead);
ListNode* pNext = pHead->next;
pHead->next = pNext->next;
pNext->next->prev = pNext->prev;
free(pNext);
//复用
//ListErase(pHead->next);
}
然后我们来看怎么实现查找、删除及插入 :
3.1.查找节点:在链表中查找特定的节点。
根据输入的数值x,我们创建一个指针pos,遍历一遍链表,就能确定链表里面的值有没有x了。这里大家需要注意:这是一个循环链表,要怎么实现只遍历一遍呢?这里就需要一个条件了,就是pos的下一个不等于头节点。遍历一遍,如果找得到,返回节点;找不到,返回false。
来看代码:
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
assert(pHead->next != NULL);
ListNode* pos = pHead->next;
while (pos->next!=pHead) {
if (pos->data == x) {
return pos;
}
pos=pos->next;
}
return false;
}
3.2.在指定位置插入节点:在链表中的指定位置插入一个新节点。
原理:创建一个新节点newnode,标记指定位置的前一个节点为nPrev,nPrev的后指针、指定节点的前指针指向newnode,newnode的前指针指向nPrev,后指针指向指定位置的节点。可以在前面函数中复用。
来看代码:
// 双向链表在pos位置进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* nPrev;
nPrev = pos->prev;
newnode->prev = nPrev;
newnode->next = pos;
}
3.3. 删除指定位置的节点:删除链表中的指定位置的节点。
原理:将指定位置的前节点和后节点标记,让前节点的后指针指向后节点,后节点的前指针指向前节点,再借助free函数释放指定位置的节点即可。可以在前面函数中复用。
来看代码:
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* posPrev,* posNext;
posPrev = pos->prev;
posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//创建节点
ListNode* BuyListNode(LTDataType x) {
ListNode* node = (ListNode*)malloc(sizeof(ListNode*));
if (node == NULL) {
perror("fail malloc");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
// 创建返回链表的头结点.
ListNode* ListCreate() {
ListNode* head = BuyListNode(0);
head->data = NULL;
head->next = head;
head->prev = head;
}
// 双向链表销毁
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
while (cur) {
pHead->next = cur->next;
cur->next->prev = cur->prev;
free(cur);
cur = cur->next;
}
}
// 双向链表打印
void ListPrint(ListNode* pHead) {
assert(pHead);
printf("head<->");
ListNode* cur = pHead->next;
while (cur != pHead) {
printf("%d<->", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
ListNode* tail = pHead->next;
while (tail->next != pHead) {
tail = tail->next;
}
newnode->next = pHead;
newnode->prev = tail;
tail->next = newnode;
pHead->prev = newnode;
//复用
//ListInsert(pHead, x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead) {
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->next;
while (tail->next!=pHead) {
tail = tail->next;
}
tail->prev->next = pHead;
pHead->prev = tail->prev;
//复用
//ListErase(pHead->prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuyListNode(x);
newnode->prev = pHead;
newnode->next = pHead->next;
pHead->next = newnode;
pHead->prev->prev = newnode;
//复用
//ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead) {
assert(pHead);
ListNode* pNext = pHead->next;
pHead->next = pNext->next;
pNext->next->prev = pNext->prev;
free(pNext);
//复用
ListErase(pHead->next);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
assert(pHead->next != NULL);
ListNode* pos = pHead->next;
while (pos->next!=pHead) {
if (pos->data == x) {
return pos;
}
}
return false;
}
// 双向链表在pos位置进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* nPrev;
nPrev = pos->prev;
newnode->prev = nPrev;
newnode->next = pos;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* posPrev,* posNext;
posPrev = pos->prev;
posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
总结:
以上就是双链表的实现代码和相关操作的说明。双链表具有灵活性和高效性,适用于需要频繁插入和删除操作的场景。希望本篇博客对你理解双链表的实现有所帮助。如果有任何疑问或建议,请留言讨论。谢谢!