文章目录
双向循环链表的基本操作
双向循环链表介绍
双向链表的结点:指针域1+数据域+指针域2
指针域1:指向该结点前面一个结点
指针域2:指向该结点后面一个结点
头文件
#include<iostream>
#include<assert.h>
using std::cin;
using std::cout;
using std::endl;
双向链表的基本结构
typedef int Datatype; //声明数据类型
typedef struct ListNode
{
Datatype data; //存储数据
struct ListNode* next; //指向下一个节点
struct ListNode* prev; //指向上一个节点
}ListNode, * pListNode;
首先声明数据类型为Datatype,在接下来双链表的基本操作中,将用Datatype代表int。
如果后续存储的数据类型需要改变为其他的(如: float,double等)
只需要修改:typedef int Datatype;大大减少了工作量
next记录后继节点,prev记录前驱节点
将结构体重命名为ListNode和 * pListNode,后续声明该结构体指针时,无需用 struct ListNode * p,可以直接pListNode p,
p就为该结构体的指针
(本文后续将采用此中方法)
初始化双链表
初始化链表,即malloc一个哨兵节点,该节点的next指向自己,prev也指向自己,且哨兵节点中不存储值
ListNode* Init_List()
{
pListNode s = (pListNode)malloc(sizeof(ListNode));
if (!s) //判断malloc是否成功,若失败,判断成立,返回NULL
{
perror("Init_List_malloc_error!");
return NULL;
}
s->next = s;
s->prev = s;
return s;
}
这里初始化哨兵节点,将哨兵节点的next和prev都指向自身,此时链表为空
顺便提一句: malloc可能会分配失败(但这种情况为少数),若分配失败则会返回NULL;这时 !s 的结果为 1,也就是malloc失败,执行报错提示,并返回NULL;
双链表判空
int Empty_List(pListNode L)
{
if (!L)
{
perror("ListNode*_is_NULL");
return -1;
}
return L->next == L;
}
若传入节点的next等于该节点本身,说明该双链表为空
L->next==L成立,返回1,否则不为空,返回0;
下图为只有一个哨兵位的双链表,即空表,判空条件有如下两种
双链表尾插
void Insert_End(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_End_L_NULL");
return;
}
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
NewNode->next = L;
NewNode->prev = L->prev;
L->prev->next = NewNode;
L->prev = NewNode;
}
同样先检查传入的链表,以及malloc的节点是否为空,若没有问题,再将节点的值赋值为x,以及新节点前后指针的修改。
双链表头插
void Insert_Head(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_Head_L_NULL");
return;
}
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
NewNode->next = L->next;
NewNode->prev = L;
L->next->prev = NewNode;
L->next = NewNode;
}
创建一个新节点
pListNode Buy_newNode(Datatype x)
{
pListNode newnode = (pListNode)malloc(sizeof(ListNode));
if (!newnode)
{
perror("malloc_error!");
return NULL;
}
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
这个Buy_newNode函数用来动态分配一个新的ListNode节点,并检查是否创建成功。
有了这个函数,可以减少我们的工作量
比如:
之前在头插和尾插中,我们都是先malloc一个新节点,检查是否分配成功,并为这个NewNode赋值。
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
现在我们只需要
pListNode NewNode = Buy_newNode(x);
也就是说,我们的头插和尾插可以做如下优化:
//尾插
void Insert_End(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_End_L_NULL");
return;
}
pListNode NewNode = Buy_newNode(x);
/*
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
*/
NewNode->data = x;
NewNode->next = L;
NewNode->prev = L->prev;
L->prev->next = NewNode;
L->prev = NewNode;
}
//头插
void Insert_Head(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_Head_L_NULL");
return;
}
pListNode NewNode = Buy_newNode(x);
/*pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
*/
NewNode->next = L->next;
NewNode->prev = L;
L->next->prev = NewNode;
L->next = NewNode;
}
如此,不仅减少了工作量,也减轻了代码的冗余。
在某一节点前插入一个新节点
void Insert_List(pListNode pos, Datatype x)
{
assert(pos);
pListNode pos_prev = pos->prev;
pListNode newNode = Buy_newNode(x);
//pos_prev newNode pos
pos_prev->next = newNode;
newNode->prev = pos_prev;
newNode->next = pos;
pos->prev = newNode;
}
Insert_List函数能够创建一个新的节点,并为该节点赋值为x,同时将新创建的节点插入至节点L之前。
这个代码十分强劲,能够同时完成创建并插入。
看到这里的小伙伴不知有没有想到,之前的头插和尾插也是创建节点并赋值,再将新节点进行插入。
这两者的模式十分相同,那么我们是否又可以对之前的代码进行优化?
答案是肯定的。
看代码:
//尾插
void Insert_End(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_End_L_NULL");
return;
}
Insert_List ( L->prev,x );
}
//头插
void Insert_Head(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_Head_L_NULL");
return;
}
Insert_List ( L->next,x );
}
之前的两个庞然大物,现在直接变得精小细致。
删除当前节点
int Delete_(pListNode s)
{
if (!s)
{
perror("Delete_L_NULL");
return 0;
}
s->prev->next = s->next;
s->next->prev = s->prev;
free(s);
s = NULL;
return 1;
该函数能够删除当前传入的指针所指向的节点。
双链表头删
void Delete_Head(pListNode L)
{
assert(L);//判断当前指针是否为空指针
assert(!Empty_List(L)); //判断链表是否为空
Delete_(L->next);
}
assert排除完异常情况后,就可以用删除节点函数Delete_(s)
因为头删就是删除哨兵节点的下一个节点,只要调用Delete函数并传入L->next即可
双链表尾删
void Delete_End(pListNode L)
{
assert(L);
assert(!Empty_List(L));
Delete_(L->prev); //删除某一位置的元素
}
同理,尾删就是删除哨兵节点的前一个节点,传入L->prev即可
双链表打印
打印双链表中的元素
void Printf_List(pListNode L)
{
if (!L)
{
perror("Printf_L_NULL");
return;
}
pListNode s = L->next;
cout << "表中元素为:";
while (s != L)
{
cout << s->data << " ";
s = s->next;
}
cout << endl << "打印完毕!" << endl;
}
注意:循环停止的条件为当前节点的next节点如果等于哨兵节点,则说明链表已经全部遍历完,这里的等于指的是指针值相等,而不是节点中存储的数据值相等
查找
pListNode Find_List(pListNode L, Datatype x) //按值查找
{
if (!L)
{
perror("Find_L_NULL");
return NULL;
}
pListNode s = L->next;
while (s != L)
{
if (s->data == x)
{
return s;
}
else
{
s = s->next;
}
}
if (s = L)
{
cout << "Can't find " << x << " in List!" << endl;
}
return NULL;
}
按值查找,若在链表中找到匹配的值,则返回该节点的地址,否则返回NULL;
销毁双链表
int Destory_List(pListNode L)
{
if (!L)
{
perror("Destory_L_NULL");
return 0;
}
pListNode s = L->next, r = s;
while (s != L)
{
s = s->next;
free(r);
r = s;
}
free(L);
L = NULL;
return 1;
}
先令指针s指向哨兵节点的后一个节点,依次释放节点,直至指针s与哨兵节点相等,最后别忘了释放头节点。
注意: 这里不能先释放哨兵节点再依次循环删除其他节点,即s必须指向哨兵节点的下一个节点,因为删除了哨兵节点,再循环,等到运行到链表最后一个节点时,s->next指向的空间已经释放了,运行会异常
代码总览
typedef int Datatype; //声明数据类型
typedef struct ListNode
{
Datatype data; //存储数据
struct ListNode* next; //指向下一个节点
struct ListNode* prev; //指向上一个节点
}ListNode, * pListNode;
ListNode* Init_List()
{
pListNode s = (pListNode)malloc(sizeof(ListNode));
if (!s) //判断malloc是否成功,若失败,判断成立,返回NULL
{
perror("Init_List_malloc_error!");
return NULL;
}
s->next = s;
s->prev = s;
return s;
}
int Empty_List(pListNode L)
{
if (!L)
{
perror("ListNode*_is_NULL");
return -1;
}
return L->next == L;
}
void Insert_End(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_End_L_NULL");
return;
}
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
NewNode->next = L;
NewNode->prev = L->prev;
L->prev->next = NewNode;
L->prev = NewNode;
}
void Insert_Head(ListNode* L, Datatype x)
{
if (!L)
{
perror("Inser_Head_L_NULL");
return;
}
pListNode NewNode = (ListNode*)malloc(sizeof(ListNode));
if (!NewNode)
{
perror("Insert_End_malloc");
return;
}
NewNode->data = x;
NewNode->next = L->next;
NewNode->prev = L;
L->next->prev = NewNode;
L->next = NewNode;
}
//创建一个新节点
pListNode Buy_newNode(Datatype x)
{
pListNode newnode = (pListNode)malloc(sizeof(ListNode));
if (!newnode)
{
perror("malloc_error!");
return NULL;
}
newnode->data = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
void Insert_List(pListNode pos, Datatype x)
{
assert(pos);
pListNode pos_prev = pos->prev;
pListNode newNode = Buy_newNode(x);
//pos_prev newNode pos
pos_prev->next = newNode;
newNode->prev = pos_prev;
newNode->next = pos;
pos->prev = newNode;
}
int Delete_(pListNode s)
{
if (!s)
{
perror("Delete_L_NULL");
return 0;
}
s->prev->next = s->next;
s->next->prev = s->prev;
free(s);
s = NULL;
return 1;
}
//双链表头删
void Delete_Head(pListNode L)
{
assert(L);//判断当前指针是否为空指针
assert(!Empty_List(L)); //判断链表是否为空
Delete_(L->next);
}
//双链表尾删
void Delete_End(pListNode L)
{
assert(L);
assert(!Empty_List(L));
Delete_(L->prev); //删除某一位置的元素
}
void Printf_List(pListNode L)
{
if (!L)
{
perror("Printf_L_NULL");
return;
}
pListNode s = L->next;
cout << "表中元素为:";
while (s != L)
{
cout << s->data << " ";
s = s->next;
}
cout << endl << "打印完毕!" << endl;
}
//查找
pListNode Find_List(pListNode L, Datatype x) //按值查找
{
if (!L)
{
perror("Find_L_NULL");
return NULL;
}
pListNode s = L->next;
while (s != L)
{
if (s->data == x)
{
return s;
}
else
{
s = s->next;
}
}
if (s = L)
{
cout << "Can't find " << x << " in List!" << endl;
}
return NULL;
}
//销毁双链表
int Destory_List(pListNode L)
{
if (!L)
{
perror("Destory_L_NULL");
return 0;
}
pListNode s = L->next, r = s;
while (s != L)
{
s = s->next;
free(r);
r = s;
}
free(L);
L = NULL;
return 1;
}