目录
一 前言
当你面试的时候,面试官让你十分钟之内写一个链表,你心里会不会非常害怕,不用怕看完这篇文章你会觉得手到擒来。
二 带头双向循环链表的介绍和具体实现
2.1 带头双向循环链表的介绍
带头双向链表是一种具有双向链接性的线性数据结构。与传统的单链表相比,它每个节点都包含两个指针字段:一个指向前一个节点,一个指向后一个节点。此外,它还引入了一个头指针,用于方便操作链表。下面将详细介绍一种特殊的链表结构——带头双向链表,它在带头双向链表常用于需要频繁插入、删除操作的场景,特别是双向迭代以及快速访问前驱和后继元素的情况可以展现出独特的优雅之处。
2.2 带头双向循环链表的结构
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
next:指针域用于指向当前节点的直接后继节点;
prev:指针域用于指向当前节点的直接前驱节点;
data:数据域用于存储数据元素。
2.3 带头双向循环链表的初始化和头结点的创建
// 创建并返回链表的头结点.
LTNode* ListCreate()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
2.4 判断带头双向循环链表是否为空
//判断带头双向循环链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
2.5 带头双向循环链表的打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
2.6 带头双向循环链表的头插法
//带头双向循环链表的头插法
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
2.7 带头双向循环链表的尾插法
//带头双向循环链表的尾插法
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
2.8 带头双向循环链表的头删法
//带头双向链表的头删法
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
}
2.9 带头双向循环链表的尾删法
//带头双向链表的尾删法
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
2.10 带头双向循环链表的查找
//带头双向链表的查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.11 在pos的前面的节点进行插入
//在pos的前面的节点进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
2.12 删除pos位置的节点
//删除pos位置的节点
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
2.13 带头双向链表的销毁
//带头双向链表的销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
看到这里你心里或许会想这些代码我对这段代码足够熟练且打字快才能十分钟之内完成,下面将对带头双向链表的插入和删除的优化,保证你正常打字速度十分钟也可以轻松拿捏。
对带头双向链表的插入和删除的优化
//带头双向链表的尾插法
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
//带头双向链表的头插法
void LTPushFront(LTNode* phead, LTDataType x)
{
LTInsert(phead->next, x);
}
//带头双向链表的尾删法
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->prev);
}
//带头双向链表的头删法
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->next);
}
上面的简单的几行代码就可以实现对带头双向循环链表的插入和删除,所以我们可以先写在pos位置之前进行插入和删除pos位置的代码,看到这里是不是觉得十分钟之内完成链表也是简简单单。
三 完整代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPrint(LTNode* phead)
{
assert(phead);
printf("guard<==>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
void LTPushFront(LTNode* phead, LTDataType x)
{
LTInsert(phead->next, x);
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->next);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
int main()
{
// 创建链表头结点
LTNode* pHead = LTInit();
// 尾部插入元素
LTPushBack(pHead, 1);
LTPushBack(pHead, 2);
LTPushBack(pHead, 3);
// 头部插入元素
LTPushFront(pHead, 4);
LTPushFront(pHead, 5);
// 打印链表
LTPrint(pHead);
// 查找节点
ListNode* node = LTFind(pHead, 2);
if (node != NULL)
{
printf("Found: %d\n", node->data);
}
// 删除节点
LTErase(node);
// 打印链表
LTPrint(pHead);
// 销毁链表
LTDestroy(pHead);
return 0;
}
四 总结
如果觉得对你有帮助的话,欢迎关注,点赞,收藏,转发,非常感谢!
如有错误和不妥的地方,欢迎在评论区中或者私信指出。