上篇博客已经对链表的种类进行了简略介绍。详细讲解了如何实现 单链表,感兴趣的可以去看看。现在进行 带头双向循环链表(习惯称为 双向链表)的实现
双向链表的的实现
- 与单链表一样,双向链表也是对增删查改这些功能进行实现
双向链表节点结构定义
- 带头双向循环链表:带头,双向,循环就是这个链表的特点,他与单链表就是两个极端,一个十分简陋,一个应有尽有。但双向链表的实现可要比单链表简单许多,这都多亏了他的结构特点
- 个人建议写双向链表时一定要画图,这样会清晰很多
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;//尾
LTDataType data;
struct ListNode* next;//头
}LTNode;
- 该结构包括了一个前驱指针指向前一个节点
- 数据域
- 指向下一个节点的next指针
打印数据
- 为了方便观察,还是先实现一个打印函数。
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!= phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
}
- 由于结构不同,不能再像单链表这样来遍历链表了。观察上图可以看到head是链表的头,完成一次循环后会回到head,所以pcur!= phead,便是控制循环的条件
初始化
- 带头即malloc出一个节点,这个节点作为头节点(也称为哨兵位),数据域不存储有效数据(这里默认设置为-1但,是无效数据)。
- 这时只有一个头节点,把他们都指向头节点,再返回即完成初始化。
LTNode* LTInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
if (phead == NULL)
{
perror("malloc");
return 1;
}
phead->data = -1;
phead->next = phead->prev = phead;
return phead;
}
插入数据
- 这里我们也进行头插和尾插的操作。
- 频繁插入数据就需要频繁申请节点,所以依旧封装一个ListBuyNode函数来申请节点并把数据放入节点,最后返回。
LTNode* ListBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc");
return 1;
}
node->data = x;
node->next = node->prev = NULL;
return node;
}
- 还需要注意到是prev指针和next指针指向的是哪里,弄清楚这个就很好理解插入操作了
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->next = phead->next;
node->prev = phead;
phead->next->prev = node;
phead->next = node;
}
- 使用assert断言来确保传过来的是有效的地址
- 插入数据时我们一般先对新节点进行连接,再去处理前一个和后一个节点
- 实在不理解时就去画图!!!画图后会发现清晰明了
尾插
void LTPushback(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->prev = phead->prev;
node->next = phead;
phead->prev->next = node;
phead->prev = node;
}
- 看图操作清晰明了,操作顺序依旧先对新节点进行操作,再处理其他节点
删除数据
头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
- 删除数据必须确保有数据,所以用assert断言一下phead->next != phead
- 头删比较简单,只需处理要删除节点del的下一个节点的prev指针和头节点的next指针即可
尾删
void LTPopBack(LTNode* phead)
{
assert(phead&&phead->next!=phead);
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
- 尾删需要处理头节点的prev指针和删除节点前一个节点的next指针
指定位置插入数据
- 要找到指定的位置,通过LTFind函数实现。
- 遍历链表,找到指定的数据则返回其地址,否则返回空
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
- 这里我们只实现了指定位置后插入数据,你也可以自己实现指定位置之前。
void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->prev = pos;
node->next = pos->next;
pos->next->prev = node;
pos->next = node;
}
- 依旧是先对node节点操作,再对pos节点的next指针和pos节点的下一个节点的prev操作
指定位置删除数据
void LTErase(LTNode* phead, LTNode* pos)
{
assert(phead && phead->next != phead);
LTNode* del = pos;
del->prev->next = del->next;
del->next->prev = del->prev;
free(del);
del = NULL;
}
- 删除指定位置数据也简单,只对del节点的上一个节点的next指针和del节点的下一个节点的prev指针操作即可
销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);//需要手动置空哨兵位
phead = NULL;
}
- 由于链表节点是在堆区开辟的,为防止内存泄漏,需要及时释放
- 用next指针保存下一个节点,再释放当前节点,不断遍历直到释放完
- 最后手动置空一下头节点
总结
带头双向循环链表因为结构的不同,比单链表更为容易实现,而且在尾插操作是可以直接找到尾节点,不需要遍历链表来找尾,在时间复杂度上更胜一筹。而单链表在OJ题上用得很多,也十分重要。
自此数据结构中最常用的单向不带头不循环(单链表)和带头双向循环链表(双向链表)的实现就完成了,会了这两种最常用的链表,剩余六种也是湿湿碎啦。希望这两篇博客对大家有所帮助。
完整代码
- 分为三个文件,也可以自己合成一个文件。
List.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;//尾
LTDataType data;
struct ListNode* next;//头
}LTNode;
LTNode* LTInit();//初始化链表
void LTPrint(LTNode* phead);//打印链表
void LTPushback(LTNode* phead, LTDataType x);//尾插
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTInser(LTNode* phead, LTNode* pos, LTDataType x);//指定位置之后插入
void LTErase(LTNode* phead, LTNode* pos);//删除指定位置
void LTDestory(LTNode* phead);//销毁链表
List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
LTNode* LTInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
if (phead == NULL)
{
perror("malloc");
return 1;
}
phead->data = -1;
phead->next = phead->prev = phead;
return phead;
}
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!= phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
}
LTNode* ListBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc");
return 1;
}
node->data = x;
node->next = node->prev = NULL;
return node;
}
void LTPushback(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->prev = phead->prev;
node->next = phead;
phead->prev->next = node;
phead->prev = node;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->next = phead->next;
node->prev = phead;
phead->next->prev = node;
phead->next = node;
}
void LTPopBack(LTNode* phead)
{
assert(phead&&phead->next!=phead);
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
assert(phead);
LTNode* node = ListBuyNode(x);
node->prev = pos;
node->next = pos->next;
pos->next->prev = node;
pos->next = node;
}
void LTErase(LTNode* phead, LTNode* pos)
{
assert(phead && phead->next != phead);
LTNode* del = pos;
del->prev->next = del->next;
del->next->prev = del->prev;
free(del);
del = NULL;
}
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);//需要手动置空哨兵位
phead = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void LTtest()
{
//LTNode*phead= LTInit();
//LTPushback(phead, 0);
//LTPushback(phead, 1);
//LTPushback(phead, 2);
//LTPushFront(phead, 0);
//LTPopBack(phead);
//LTPopFront(phead);
//LTNode* find = LTFind(phead, 0);
//LTInser(phead, find, 5);
//LTPrint(phead);
//
//LTDestory(phead);
}
int main()
{
LTtest();
return 0;
}