如果人生会有很长,愿有你的荣耀永不散场。——《全职高手》
一 . 循环单链表
循环单链表是单链表的另一种形式,其结构特点是,链表中最后一个结点的指针域不再是结束标记,而是指向整个链表的第一个结点,从而使链表形成一个环。
和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种。带头结点的循环单链表实现插入和删除操作较为方便,且更为常用。
单链表的特点是,从链表头到链表尾操作比较方便,但无法从链表尾到链表头操作;与单链表相比,循环单链表的长处是从链表尾到链表头操作比较方便。
当要处理的元素序列具有环形结构特点时,适合采用循环单链表。
带头结点循环单链表的操作实现方法和带头结点单链表的操作实现方法类同,差别只是以
下两点。
1.在初始化函数中,把语句(*head) ->next-NULL改为(*head) ->next=*head,即把带头结点的循环单链表设置成如图(空链表)所示的形式。
2.在其他函数中,循环判断条件 p->next != NULL和p->next->next != NULL 中的NULI
改为头指针 head即可。详细代码这里就不多做赘述。
二 . 双向循环链表
(一)双向链表
双向链表与单链表不同,在双向链表中,每个结点除后继指针域外还有一个前驱指针域。
但又和单链表类同,双向链表也有带头结点结构和不带头结点结构两种,带头结点的双向链表更为常用。
当然,双向链表也有循环和非循环两种结构,在我们运用的过程中循环结构的双向链表更为常用。
这里讨论的是带头结点的双向环链表
(二)双向循环链表的实现
1. 定义
在双向循环链表中,每个结点包括三个域,分别是 data 域、next 域和 prev 域,其中data域为数据域,next 域为指向后继结点的指针域,prev域为指向前驱结点的指针域。
下图为链表的结点结构。
双向循环链表的定义如下
typedef struct ListNode {
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LT;
2.1 准备工作
首先我们要创建三个文件:
头文件:BoubleList.h : 用来包含一些必须的头文件,定义结构体,声明一些相关函数。
源文件:BoubleList.c : 用来实现函数具体功能,如:增,删,查,改。
Test.c : 用来测试函数功能。
2.2 申请新结点
尾新结点申请一份新内存空间,并将新结点指向next和prev,置空。
//申请新结点
LT* BuyListNode()
{
LT* newNode = (LT*)malloc(sizeof(LT));
newNode->next = NULL;
newNode->prev = NULL;
//返回新结点
return newNode;
}
2.3 链表初始化
链表初始化即:将链表变为下雨所示结构。
//初始化
LT* ListInit()
{
LT* phead = NULL;
//生成哨兵结点
phead = BuyListNode();
//只有一个结点,这个结点自己成环
phead->next = phead;
phead->prev = phead;
//返回头结点
return phead;
}
2.4 打印链表
在我们打印链表之前我们要先判断该链表是否初始化,若已初始化就遍历链表并打印,当走过了一个循环之后就停止打印。
//打印链表
LT* ListPrint(LT* phead)
{
//断言,如果未初始化,报错
assert(phead != NULL);
//从有效头结点开始走,一直到哨兵位结束——即一个循环之后
LT* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
2.5 插入
由于尾插和头插可以直接通过指定位置插入数据的函数实现,所以我们这里先实现指定位置插入数据的函数,然后再将尾插与头插进行实现。
我们先生成一个新结点,然后记录后一个结点的位置,并新结点的next指向pos的后一个结点,新结点的prev指向pos,并且将后一个结点的prev指向新结点,再将pos结点的next指向新结点,最后存储数据即可。
//指定位置插入
void ListInsert(LT* phead, LT* pos, LTDataType x)
{
//断言,如果未初始化,报错
assert(phead != NULL);
//生成新结点
LT* newnode = BuyListNode();
//记录后一个结点
LT* beforenode = pos->next;
//新结点的next指向pos的后一个结点
newnode->next = beforenode;
//新结点的prev指向pos
newnode->prev = pos;
//后一个结点的prev指向新结点
beforenode->prev = newnode;
//pos结点的next指向新结点
pos->next = newnode;
//存储数据
newnode->data = x;
}
尾插:第一个函数是用指定位置函数实现的,第二个是直接实现,头插代码也是一样的
如果要直接实现尾插,以下是基本思路
1.生成新结点。
2.尾结点的next指向新结点。
3.新结点的prev指向原尾结点,next指向头结点。
4.头结点的prev指向新结点。
//尾插
void ListPushBack(LT* phead, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//复用指定插入
ListInsert(phead, phead->prev, x);
}
void ListPushBack(LT* phead, LTDataType x)
{
assert(phead);
LT* tail = phead->prev;
LT* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
头插:
基本思路:
1 记录原有效头的结点。
2 原有效头的prev指向新有效头。
3 新有效头的prev指向哨兵位,next指向原有效头。
4 哨兵位的next指向新有效头。
//头插
void ListPushFront(LT* phead, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//复用指定插入
ListInsert(phead, phead, x);
}
void ListPushFront(LT* phead, LTDataType x)
{
assert(phead);
LT* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
2.6: 删除
与插入一样头删与尾删也可以由指定位置删除实现。
//指定删除
void ListErase(LT* phead, LT* pos)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵
assert(pos != phead);
//记录要删除的结点的后一个结点
LT* beforepos = pos->next;
//记录待删除结点的前一个结点
LT* aheadpos = pos->prev;
//后一个结点的prev指向待删除结点的前一个结点
beforepos->prev = aheadpos;
//前一个结点的next指向待删除结点的后一个结点
aheadpos->next = beforepos;
//释放结点
free(pos);
}
尾删:
基本思路:
1 记录尾部的前一个结点。
2 头指针的prev指向新的尾结点。
3 新的尾结点的next指向头结点。
4 注意不要删除掉头结点。
//尾部删除
void LsitPopBack(LT* phead)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵结点
assert(phead != phead->prev);
//复用指定删除
ListErase(phead, phead->prev);
}
void ListPopBack(LT* phead)
{
assert(phead);
assert(phead->next != phead);
LTe* tail = phead->prev;
LT* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
头删:
基本思路:
1 记录要删除的原有效头结点。
2 新有效头结点的prev指向哨兵位。
3 哨兵位的next指向新有效头结点。
4 注意不要删除哨兵位。
//头删
void ListPopFront(LT* phead)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵位
assert(phead->next != phead);
//复用指定删除
ListErase(phead, phead->next);
}
void ListPopFront(LT* phead)
{
assert(phead);
assert(phead->next != phead);
LT* first = phead->next;
LT* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
2.7: 查找结点
查找结点位置可以和打印链表一样从第一个有效结点开始遍历链表,找到了就返回结点位置,没找到就返回NULL。
LT* ListPopFind(LT* phead, LTDataType x)
{
//断言,如果未初始化,报错
assert(phead != NULL);
//和打印一样从第一个有效结点开始遍历链表
LT* cur = phead->next;
while (cur != phead)
{
//查找到返回结点位置
if (cur->data == x)
return cur;
cur = cur->next;
}
//查找失败,返回null
return NULL;
}
2.8:销毁链表
遍历链表,释放上一个结点的内存即可。最后释放哨兵结点
void ListDestory(LT* phead)
{
assert(phead);
LT* cur = phead->next;
while (cur != phead)
{
LT* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
(三)最终代码
BoubleList.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//重定义,方便更改存储数据的类型
typedef int LTDataType;
typedef struct ListNode {
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LT;
//申请新结点
LT* BuyListNode();
//链表初始化
LT* ListInit();
//打印链表
LT* ListPrint(LT* phead);
//尾部插入
void ListPushBack(LT* phead, LTDataType x);
//尾部删除
void LsitPopBack(LT* phead);
//头部插入
void ListPushFront(LT* phead, LTDataType x);
//头部删除
void ListPopFront(LT* phead);
//查找
LT* ListPopFind(LT* phead, LTDataType x);
//指定插入
void ListInsert(LT* phead, LT* pos, LTDataType x);
//指定删除
void ListErase(LT* phead, LT* pos);
BoubleList.c
#define _CRT_SECURE_NO_WARNINGS
#include "BoubleList.h"
//申请新结点
LT* BuyListNode()
{
LT* newNode = (LT*)malloc(sizeof(LT));
//新结点的prev,next最好置空
newNode->next = NULL;
newNode->prev = NULL;
//返回新结点
return newNode;
}
//初始化
LT* ListInit()
{
LT* phead = NULL;
//生成哨兵结点
phead = BuyListNode();
//只有一个结点,这个结点自己成环
phead->next = phead;
phead->prev = phead;
//返回头结点
return phead;
}
//打印
LT* ListPrint(LT* phead)
{
//如果未初始化,报错
assert(phead != NULL);
//从有效头结点开始走,一直到哨兵位结束
LT* cur = phead->next;
while (cur != phead)
{
//打印
printf("%d ", cur->data);
//迭代
cur = cur->next;
}
printf("NULL");
printf("\n");
}
//尾插
void ListPushBack(LT* phead, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//复用指定插入
ListInsert(phead, phead->prev, x);
}
//尾部删除
void LsitPopBack(LT* phead)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵结点
assert(phead != phead->prev);
//复用指定删除
ListErase(phead, phead->prev);
}
//头部插入
void ListPushFront(LT* phead, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//复用指定插入
ListInsert(phead, phead, x);
}
//头部删除
void ListPopFront(LT* phead)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵位
assert(phead->next != phead);
//复用指定删除
ListErase(phead, phead->next);
}
//查找,返回结点位置
LT* ListPopFind(LT* phead, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//和打印一样从第一个有效结点开始遍历
LT* cur = phead->next;
while (cur != phead)
{
//查找到返回结点位置
if (cur->data == x)
return cur;
//迭代
cur = cur->next;
}
//查找失败,返回null
return NULL;
}
//指定插入(后)
void ListInsert(LT* phead, LT* pos, LTDataType x)
{
//如果未初始化,报错
assert(phead != NULL);
//生成新结点
LT* newnode = BuyListNode();
//记录后一个结点
LT* beforenode = pos->next;
//新结点的next指向pos的后一个结点
newnode->next = beforenode;
//新结点的prev指向pos
newnode->prev = pos;
//后一个结点的prev指向新结点
beforenode->prev = newnode;
//pos结点的next指向新结点
pos->next = newnode;
//存储数据
newnode->data = x;
}
//指定删除
void ListErase(LT* phead, LT* pos)
{
//如果未初始化,报错
assert(phead != NULL);
//不能删除哨兵
assert(pos != phead);
//记录要删除的结点的后一个结点
LT* beforepos = pos->next;
//记录待删除结点的前一个结点
LT* aheadpos = pos->prev;
//后一个结点的prev指向待删除结点的前一个结点
beforepos->prev = aheadpos;
//前一个结点的next指向待删除结点的后一个结点
aheadpos->next = beforepos;
//释放结点
free(pos);
}
Test.c
#define _CRT_SECURE_NO_WARNINGS
#include "BoubleList.h"
void text1()
{
//初始化
LT* SL = ListInit();
//尾部插入
ListPushBack(SL, 2);
ListPushBack(SL, 7);
ListPushBack(SL, 10);
//打印
ListPrint(SL);
//头部插入
ListPushFront(SL, 8);
ListPushFront(SL, 112);
//尾部删除
LsitPopBack(SL);
//打印
ListPrint(SL);
//头部删除
ListPopFront(SL);
ListPopFront(SL);
ListPopFront(SL);
//指定插入
ListInsert(SL, ListPopFind(SL, 8), 76);
ListPushBack(SL, 100);
ListPrint(SL);
ListErase(SL, ListPopFind(SL, 100));
ListPrint(SL);
}
void text2()
{
LT* SL = ListInit();
ListPushFront(SL, 8);
ListPushFront(SL, 112);
ListPushBack(SL, 5);
ListPushBack(SL, 8);
ListPushFront(SL, 777);
LsitPopBack(SL);
ListPrint(SL);
}
int main()
{
text1();
//text2();
return 0;
}
三. 结语
OK!我们的双向循环链表的实现就到这里了,感谢大家!