文章目录
关于带表头的单链表的实现->带表头的单链表
一、双向(带头)循环链表的作用
典型的面恶心善的数据结构
乍一看,似乎结构在几种链表里最复杂,然而其实际的实现并没有困难到哪里去,反而因为其优秀的双向循环结构可以支持 O(1) 时间复杂度的情况下找到前驱结点,双向链表在多数情况下的插入、删除等操作都要比单链表简单、高效。
单独存储数据相较于其他链表也是更优的选择
二、基本的实现
1.基本的函数声明(DList.h)
基本的增删查改,与创建与摧毁链表
- 增【头插,尾插(可用更普遍的前插,后插来代替)】
- 删【头删,尾删,根据数据删除】
- 查(配合插入使用)
- 改 (修改节点存储数据,本处简写)
- 创建表头节点 不存储数据
- 摧毁 释放整条链表(包括表头),防止内存泄漏
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
//本链表为双向带头链表
#pragma once
typedef int LTDataType;//存储的数据类型,方便统一修改
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
//创建节点
ListNode* BuyNode(LTDataType x);
// 双向链表销毁
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 ListErase(ListNode* pos);
//看链表除了表头是否为空
bool IsEmpty(ListNode* pHead);
// 双向链表在pos的前面进行插入
void ListInsertFront(ListNode* pos, LTDataType x);
//后插
void ListInsertBack(ListNode* pos, LTDataType x);
//修改
void ListModify(ListNode* pos, LTDataType x);
2.表头/节点的创建
两函数并无本质区别,但表头不用于存储数据,所以加以区别
另外,创建循环链表的节点时切记将新节点的next prev都指向自身,作为最小的循环单元参与程序以避免意想不到的NULL指针
ListNode* ListCreate() {
ListNode* Head=(ListNode*)malloc(sizeof(ListNode));
if (Head == NULL)
{
perror("malloc error:");
exit(-1);
}
Head->next = Head;//形成循环
Head->prev = Head;
return Head;
}
ListNode* BuyNode(LTDataType x) {//创建节点
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL)
{
perror("malloc error:");
exit(-1);
}
node->next = node;//形成循环
node->prev = node;
node->data = x;
return node;
}
3.判断链表是否为空
实质在判断链表中是否存在两个及以上节点(包括表头)
无论传址是否为表头都不影响判断
bool IsEmpty(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
if (cur != pHead)
return false;
else
return true;
}
更加简便的写法:
bool IsEmpty(ListNode* pHead) {
assert(pHead);
return pHead->next==pHead;
}
4.插入(头插 尾插 前插 后插)
表头的存在为插入的前提判断带来了方便。尾插头插不用考虑链表为空时的特殊情况。
头插,尾插只是插入的特殊情况,往往都可以使用普遍的插入函数来代替实现;写在此处只是为了方便更好的理解
- 头插
void ListPushFront(ListNode* pHead, LTDataType x) {//头插
assert(pHead); //若不是有表头的双向链表 则可以不用断言 但此时尾插头插代码里必须加入判断链表是否为空 进行分支执行
ListNode* newnode = BuyNode(x);
pHead->next->prev = newnode;
newnode->next = pHead->next;//具体代码实现
newnode->prev = pHead;
pHead->next = newnode;
//ListInsertBack(pHead,x);//利用后插实现
//ListInsertFront(pHead->next, x);//利用前插实现
}
- 尾插
void ListPushBack(ListNode* pHead, LTDataType x) {//尾插
assert(pHead);
ListNode* newnode = BuyNode(x);//注释同头插
pHead->prev->next = newnode;//直接使用前节点
newnode->prev = pHead->prev;//具体代码实现
newnode->next = pHead;
pHead->prev = newnode;
//ListInsertFront(pHead,x);//利用前插实现尾插
//ListInsertBack(pHead->prev, x);//后插实现尾插
}
- 前插
void ListInsertFront(ListNode* pos, LTDataType x) {//插到前面
assert(pos);
ListNode* newnode = BuyNode(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
}
- 尾插
void ListInsertBack(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyNode(x);
pos->next->prev = newnode;
newnode->next = pos->next;
pos->next = newnode;
newnode->prev = pos;
}
5.删除(头删 尾删 普遍的删除)
- 头删
void ListPopFront(ListNode* pHead) {
assert(pHead);
if (IsEmpty(pHead))
return;
else
{
ListNode* front = pHead->next->next;
free(pHead->next);//同尾删
pHead->next = front;
front->prev = pHead;
}
//ListErase(pHead->next);
}
- 尾删
void ListPopBack(ListNode* pHead){//尾删
assert(pHead);
if (IsEmpty(pHead))//空链表 不再删除
return;
else
{
ListNode* end = pHead->prev->prev;
end->next = pHead;
free(pHead->prev);//释放该指针指向的地址,该指针任然存在
pHead->prev = end;
}
//ListErase(pHead->prev);
}
- 通用的删除
void ListErase(ListNode* pos) {
assert(pos);
if (IsEmpty(pos))//pos为表头时 不执行;非表头时,不可能为空; 加上是为了可以替换尾删 头删
return;
else
{
ListNode* prev0 = pos->prev;
ListNode* next0 = pos->next;
prev0->next = next0;
next0->prev = prev0;
free(pos);
}
}
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;
}
printf("未查询到该数据:%d", x);
return NULL;
}
- 修改(实际使用往往不仅仅存储一个数据,而是用结构体存储一类数据,此处为简写)
//void ListModify(ListNode* pos, LTDataType x) {//修改节点数据
// assert(pos);
// pos->data = x;
//}
7.销毁链表(手动释放内存)
在某一结构确定不再使用时,手动释放内存极为重要。 虽然在程序结束后系统会自动释放内存,但在如服务器等长期运行的环境中,程序往往不会或很久才会停止一次,此时未进行手动的释放内存很容易导致严重的内存泄漏
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
ListNode* f=NULL ;
while (cur != pHead)
{
f = cur;
cur = cur->next;
free(f);
}
free(pHead);//不要忘记释放表头 如链表为空,cur一开始就==pHead
}
三、源码汇总
#include"DList.h"
//本链表为双向带头链表
ListNode* ListCreate() {
ListNode* Head=(ListNode*)malloc(sizeof(ListNode));
if (Head == NULL)
{
perror("malloc error:");
exit(-1);
}
Head->next = Head;//形成循环
Head->prev = Head;
return Head;
}
ListNode* BuyNode(LTDataType x) {//创建节点
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL)
{
perror("malloc error:");
exit(-1);
}
node->next = node;//形成循环
node->prev = node;
node->data = x;
return node;
}
bool IsEmpty(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
if (cur != pHead)
return false;
else
return true;
}
void ListPrint(ListNode* pHead)
{
assert(pHead);
printf(" >= Head =>");
ListNode* cur=pHead->next;
while (cur != pHead)
{
printf("%d =>", cur->data);//如果LTDataType 改变,printf输出时打印的数据类型也要改变
cur = cur->next;
}
printf("\n");
}
void ListPushBack(ListNode* pHead, LTDataType x) {//尾插(默认有表头,或头结点不为NULL)
assert(pHead); //若不是有表头的双向链表 则可以不用断言 但尾插头插代码里必须加入判断链表是否为空 进行分支执行
ListNode* newnode = BuyNode(x);
pHead->prev->next = newnode;//直接使用前节点
newnode->prev = pHead->prev;//具体代码实现
newnode->next = pHead;
pHead->prev = newnode;
//ListInsertFront(pHead,x);//利用前插实现尾插
//ListInsertBack(pHead->prev, x);//后插实现尾插
}
void ListPopBack(ListNode* pHead){//尾删
assert(pHead);
if (IsEmpty(pHead))//空链表 不再删除
return;
else
{
ListNode* end = pHead->prev->prev;
end->next = pHead;
free(pHead->prev);//释放该指针指向的地址,该指针任然存在
pHead->prev = end;
}
//ListErase(pHead->prev);
}
void ListPushFront(ListNode* pHead, LTDataType x) {//头插(默认有表头,或头结点不为NULL)
assert(pHead);//注释同尾插
ListNode* newnode = BuyNode(x);
pHead->next->prev = newnode;
newnode->next = pHead->next;//具体代码实现
newnode->prev = pHead;
pHead->next = newnode;
//ListInsertBack(pHead,x);//利用后插实现
//ListInsertFront(pHead->next, x);//利用前插实现
}
void ListPopFront(ListNode* pHead) {
assert(pHead);
if (IsEmpty(pHead))
return;
else
{
ListNode* front = pHead->next->next;
free(pHead->next);//同尾删
pHead->next = front;
front->prev = pHead;
}
//ListErase(pHead->next);
}
ListNode* ListFind(ListNode* pHead, LTDataType x) {//最简单形式,只找到第一个相同数据的节点
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
return cur;
cur = cur -> next;
}
printf("未查询到该数据:%d", x);
return NULL;
}
void ListInsertFront(ListNode* pos, LTDataType x) {//插到前面
assert(pos);
ListNode* newnode = BuyNode(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
}
void ListInsertBack(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyNode(x);
pos->next->prev = newnode;
newnode->next = pos->next;
pos->next = newnode;
newnode->prev = pos;
}
void ListErase(ListNode* pos) {
assert(pos);
if (IsEmpty(pos))//pos为表头时 不执行;非表头时,不可能为空; 加上是为了可以替换尾删 头删
return;
else
{
ListNode* prev0 = pos->prev;
ListNode* next0 = pos->next;
prev0->next = next0;
next0->prev = prev0;
free(pos);
}
}
//void ListModify(ListNode* pos, LTDataType x) {//修改节点数据
// assert(pos);
// pos->data = x;
//}
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->next;
ListNode* f=NULL ;
while (cur != pHead)
{
f = cur;
cur = cur->next;
free(f);
}
free(pHead);//不要忘记释放表头 如链表为空,cur一开始就==pHead
}