线性表文档之循环单链表
Posted 二木成林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表文档之循环单链表相关的知识,希望对你有一定的参考价值。
循环单链表
定义
概念
循环单链表是在单链表的基础上,将链表最后一个结点的 next
指针域指向了链表的第一个结点(如果单链表是带头结点的则最后一个结点的 next
指针域指向头结点;如果单链表是不带头结点的则最后一个结点的 next
指针域指向开始结点)。
结构体
循环单链表的链表结点同单链表的结点一样,都是由一个数据域和指针域组成。其中数据域存储当前节点的数据值,而指针域存储当前节点的后继节点的地址。如图所示:
循环单链表结点结构体代码如下:
/**
* 循环单链表的节点
*/
typedef struct CLNode
/**
* 链表节点的数据域
*/
int data;
/**
* 链表节点的指针域,指向后继节点
*/
struct CLNode *next;
CLNode;
特点
单链表有的特点,循环单链表也有:
- 链式存储线性表时,不需要使用地址连续的存储元素,即不要求逻辑上相邻的元素在物理位置上也相邻。
- 单链表由于是通过『链』建立起的数据元素之间的逻辑关系,插入和删除操作不需要移动元素,只需要修改链结点指针域的指向。
- 因为单链表的元素是离散地分布在存储空间中,所以单链表不能随机存取,如果要找到某个数据元素,最坏情况下需要遍历整个单链表。
- 单链表存储数据不需要大量连续存储空间,但单链表结点除了存储数据值之外,还附加有指针域,就存在浪费存储空间的缺点。
循环单链表独有的特点:
- 最后一个结点的指针域指向链表的第一个结点(如果是带头结点的循环单链表则指向头结点;如果是不带头结点的循环单链表则指向开始结点)。
- 循环单链表可以实现从任一结点出发访问链表中的任何结点,而单链表只能从任一结点出发后访问这个结点本身及其之后的所有结点。
基本操作
注:如无特殊说明,下面关于链表的所有操作都是基于带头结点的链表。完整代码请参考:
概述
注:下面都是 C 语言代码,所以如果要对链表进行删除或新增操作,链表参数都是双指针。如果要使用 C++ 的引用则改成
*&
。一般如果是考研建议使用&
引用,避免双指针。
循环单链表的常见操作如下:
void init(CLNode **list)
:初始化带头结点的循环单链表。其中list
是未初始化的循环单链表。CLNode *createByHead(CLNode **list, int nums[], int n)
:通过头插法常见循环单链表。其中list
是未初始化的循环单链表;nums
是待批量插入到循环单链表中的数组;n
是数组长度。返回创建成功的循环单链表。CLNode *createByTail(CLNode **list, int nums[], int n)
:通过尾插法常见循环单链表。其中list
是未初始化的循环单链表;nums
是待批量插入到循环单链表中的数组;n
是数组长度。返回创建成功的循环单链表。int insert(CLNode **list, int i, int ele)
:在循环单链表第i
个位置插入值为ele
的新结点。其中list
是循环单链表;i
是指定结点位置,从 1 开始;ele
是待插入元素值。如果插入成功则返回 1,否则返回 0。void insertFirst(CLNode **list, int ele)
:在循环单链表的头部插入新元素。其中list
是循环单链表;ele
是新元素值。void insertLast(CLNode **list, int ele)
:在循环单链表的尾部插入新元素。其中list
是循环单链表;ele
是新元素值。int removeByNum(CLNode **list, int i, int *ele)
:删除循环单链表中第i
个位置的结点,并将被删结点的值保存到ele
中。其中list
是循环单链表;i
是循环单链表中的结点序号,从 1 开始;ele
用来保存被删结点的数据值。如果删除成功则返回 1,否则返回 0 表示删除失败。void removeByEle(CLNode **list, int ele)
:删除循环单链表中第一个等于指定值ele
的结点。其中list
是循环单链表;ele
是指定值。void removeFirst(CLNode **list, int *ele)
:删除循环单链表第一个结点。其中list
是循环单链表;ele
用来保存被删第一个结点的数据值。void removeLast(CLNode **list, int *ele)
:删除循环单链表最后一个结点。其中list
是循环单链表;ele
用来保存被删的最后一个结点的数据值。CLNode *findByNum(CLNode *list, int i)
:查找循环单链表中第i
个结点。其中list
是循环单链表;i
是结点序号,从 1 开始。如果查找成功则返回该结点,否则返回NULL
表示查找失败。CLNode *findByEle(CLNode *list, int ele)
:查找循环单链表中第一个值等于ele
的结点。其中list
是循环单链表;ele
是指定值。如果查找成功则返回该结点,否则返回NULL
表示查找失败。int size(CLNode *list)
:获取循环单链表的长度,即结点个数。其中list
是循环单链表。返回循环单链表的结点个数。int isEmpty(CLNode *list)
:判断循环单链表是否为空。其中list
是循环单链表。如果循环单链表为空则返回 1,否则返回 0。void clear(CLNode **list)
:清空循环单链表。其中list
是循环单链表。void print(CLNode *list)
:打印循环单链表所有结点。其中list
是循环单链表。
init
初始化循环单链表。注意带头结点的循环单链表和不带头结点的循环单链表初始化不一样。
实现步骤:
- 如果是带头结点的循环单链表,则创建头结点,为其分配空间,并将头结点的
next
指针域指向头结点本身。 - 如果是不带头结点的循环单链表,则将头指针
head
指向NULL
。
实现代码如下:
/**
* 对循环单链表进行初始化
* @param list 循环单链表
*/
void init(CLNode **list)
// 为链表的头节点分配空间
*list = (CLNode *) malloc(sizeof(CLNode));
// 指定头节点的指针域,将其指向自身,表示是循环单链表,而普通单链表是指向 null
(*list)->next = *list;// 注意,指向头结点本身
createByHead
通过头插法批量插入元素然后创建一个非空循环单链表。所谓的头插法就是每次插入一个新元素都是插入在第一个结点的位置,无论循环单链表是否带有头节点。
以 nums=[1, 2, 3, 4, 5]; n=5
为例创建循环单链表:
实现步骤:
- 对链表进行初始化。注意,是带头结点的循环单链表。
- 循环遍历数组中的每个元素,然后根据数组元素创建单链表结点,创建新节点时将数组元素值赋给结点数据域,将新节点的指针域指向
NULL
。 - 将创建的新节点的
next
指针域指向单链表的第一个节点,然后将单链表的头结点的next
指针指向新节点。
实现代码如下:
/**
* 通过头插法创建循环单链表
* @param list 已经初始化后的循环单链表
* @param nums 待插入到单链表中的数据数组
* @param n 数组长度
* @return 创建成功的循环单链表
*/
CLNode *createByHead(CLNode **list, int nums[], int n)
// 1.初始化循环单链表,即创建循环单链表的头结点。也可以直接调用 init 函数来初始化
// 1.1 为头结点分配空间
*list = (CLNode *) malloc(sizeof(CLNode));
// 1.2 修改头结点的 next 指针,将其指向自己,而普通单链表是指向 null
(*list)->next = *list;// 注意,指向头结点本身
// 2.遍历 nums 数组中所有元素,将其添加到链表中,完成链表的创建
for (int i = 0; i < n; i++)
// 2.1 创建新节点
// 2.1.1 给新节点分配空间
CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
// 2.1.2 指定新节点的数据域
newNode->data = nums[i];
// 2.1.3 将新节点的指针域置为 null,指向空
newNode->next = NULL;
// 2.2 将新节点插入到链表中,但是插入到头结点的后面
// 2.2.1 将新节点的 next 指针指向链表第一个节点,此时完成了新节点和链表第一个节点链接
newNode->next = (*list)->next;
// 2.2.2 将链表头节点的 next 指针指向新节点,此时完成了链表头节点和新节点的链接
(*list)->next = newNode;
return *list;
createByTail
通过尾插法创建循环单链表。所谓的尾插法就是每次将新节点插入到链表的尾部。
以 nums=[1, 2, 3, 4, 5]; n=5
为例使用尾插法创建循环单链表:
实现步骤:
- 初始化单链表。
- 设置一个变量来记录单链表的尾结点
tailNode
,初始为单链表的头结点。 - 循环遍历数组中的每个元素,然后根据数组元素创建单链表结点,创建新节点时将数组元素值赋给结点数据域,将新节点的指针域指向
NULL
。 - 将尾结点
tailNode
的next
指针指向新结点newNode
,然后将新结点newNode
的next
指针指向循环单链表的头结点head
,最后将新结点newNode
记录为新的尾节点tailNode
。
实现代码如下:
/**
* 通过尾插法创建循环单链表
* @param list 循环单链表
* @param nums 待插入到单链表中的数据数组
* @param n 数组长度
* @return 创建成功的循环单链表
*/
CLNode *createByTail(CLNode **list, int nums[], int n)
// 1.初始化循环单链表,即创建循环单链表的头结点。也可以直接调用 init 函数来初始化
// 1.1 为头结点分配空间
*list = (CLNode *) malloc(sizeof(CLNode));
// 1.2 修改头结点的 next 指针,将其指向自己,而普通单链表是指向 null
(*list)->next = *list;// 注意,指向头结点本身
// 保存链表的尾节点,初始为链表的头节点,即空链表的尾节点就是链表的头节点
CLNode *tailNode = *list;
// 2.循环数组 nums 中所有数据,插入到单链表中
for (int i = 0; i < n; i++)
// 2.1 创建新节点
// 2.1.1 给新节点分配空间
CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
// 2.1.2 指定新节点的数据域
newNode->data = nums[i];
// 2.1.3 将新节点的指针域置为 null,指向空
newNode->next = NULL;
// 2.2 将新节点追加到链表的尾部
// 2.2.1 将链表原尾节点的 next 指针指向新节点,即让新节点成为链表的尾节点
tailNode->next = newNode;
// 2.2.2 然后将新节点(即链表新的尾节点)指向链表的头节点
newNode->next = *list;
// 2.2.3 然后将 tailNode 指向新节点(即新的尾节点)
tailNode = newNode;
// 3.返回创建成功的链表
return *list;
insert
在循环单链表的第 i
个位置插入值为 ele
的新结点。以 list=[1, 2, 3, 4, 5]; i=3; ele=66
为例如图:
实现步骤:
- 参数校验,位置
i
的范围必须在[1, length]
。否则超出范围就判定插入失败。 - 声明一个变量
node
保存循环单链表中的结点,初始为链表的开始结点;声明一个变量pre
保存结点node
的前驱结点,为了插入。 - 通过遍历循环单链表(注意循环结束条件),找到第
i
个结点node
和 第i-1
个结点pre
。 - 然后创建一个新结点
newNode
,为其分配数据域和指针域。将新结点newNode
插入到第i-1
个结点pre
和第i
个结点node
之间。 - 最后完成插入,返回 1 表示插入成功。
实现代码如下:
/**
* 向链表的第 i 个位置插入新节点
* 注意,如果是空链表(已经初始化),插入第 1 个节点会失败
* @param list 循环单链表
* @param i 序号,从 1 开始
* @param ele 新节点的数据值
* @return 如果插入成功则返回 1,否则返回 0
*/
int insert(CLNode **list, int i, int ele)
// 0.参数校验
if (i < 0 || i > size(*list))
return 0;
// 1.声明一些变量
// 1.1 链表的第一个节点
CLNode *node = (*list)->next;// 链表的第一个节点
// 1.2 保存前驱节点,初始为链表的头节点
CLNode *pre = *list;// 保存前驱节点
// 1.3 计数器,记录当前节点是链表的第几个节点
int count = 0;
// 注意,使用下面的方式插入第 1 个节点时,如果链表为空则会失败
// 找到第 i 个节点的前驱节点(即第 i-1 个节点),因为插入节点必须知道它的前驱节点
// 2.遍历链表所有节点,找到第 i 个节点,然后插入新节点
while (node != *list)
// 2.1 计数器加 1,表示已经迭代一个节点了
count++;
// 2.2 找到第 i 个节点,那么就可以插入新节点了
if (count == i)
// 2.2.1 创建新节点
// 2.2.1.1 给新节点分配空间
CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
// 2.2.1.2 给新节点的数据域赋值
newNode->data = ele;
// 2.2.1.3 给新节点的指针域赋为 null
newNode->next = NULL;
// 2.2.2 将新节点插入到链表中
// 2.2.2.1 将第 i-1 个节点的 next 指针指向新节点
pre->next = newNode;
// 2.2.2.2 将新节点的 next 指针指向原第 i 个节点
newNode->next = node;
// 2.2.3 结束循环,跳出程序
break;
// 2.3 保存当前节点为前驱节点
pre = node;
// 2.4 继续下一个节点
node = node->next;
return 1;
insertFirst
在循环单链表的开始结点位置插入新结点。
实现步骤:
- 创建新结点
newNode
,为新结点分配存储空间,并指明数据域和指针域。 - 然后将新结点插入到链表的头部。
实现代码如下:
/**
* 在循环单链表的头部插入新节点
* @param list 循环单链表
* @param ele 待插入的元素
*/
void insertFirst(CLNode **list, int ele)
// 1.创建新节点
// 1.1 为新节点分配空间
CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
// 1.2 为新节点的数据域指定内容
newNode->data = ele;
// 1.3 将新节点的指针域指向 null
newNode->next = NULL;
// 2.将新节点插入到链表的头部,成为新的链表第一个节点
// 2.1 将新节点的 next 指针指向原链表的第一个节点
newNode->next = (*list)->next;
// 2.2 将头节点的 next 指针指向新节点,即让新节点成为链表的第一个节点
(*list)->next = newNode;
insertLast
在循环单链表的尾部插入新结点。
实现步骤:
- 找到循环单链表的尾结点
tailNode
。 - 然后创建新结点
newNode
为其分配存储空间,并指定数据域和指针域。最后将新结点newNode
插入到尾结点tailNode
的后面,注意新尾结点的next
指针必须指向链表的头结点,才能构成一个循环。
实现代码如下:
/**
* 向循环单链表末尾追加一个新节点
* @param list 循环单链表
* @param ele 新节点的数据值
*/
void insertLast(CLNode **list, int ele)
// 1.先找到链表的最后一个节点,即尾节点
// 1.1 链表的第一个节点
CLNode *node = (*list)->next;
// 1.2 通过循环找到链表的最后一个节点,注意这里循环结束的条件是 node.next!=list,在循环结束之后,node 就是链表的尾节点
while (node->next != *list)
node = node->next;
// 2.创建新节点然后插入到链表的尾部
// 2.1 创建新节点
// 2.1.1 为新节点分配空间
CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
// 2.1.2 为新节点的数据域指定内容
newNode->data = ele;
// 2.1.3 将新节点的指针域指向 null
newNode->next = NULL;
// 2.2 将新节点追加到循环单链表的末尾
// 2.2.1 将链表的原尾节点的 next 指针指向新节点,已经完成了新节点链接到链表中
newNode->next = node->next;// 其实 node->next 就是链表的头结点,所以 newNode->next=*list 也是可以的
// 2.2.2 将新节点的 next 指针指向链表的头节点,完成了尾节点与头节点的链接
node->next = newNode;
removeByNum
删除循环单链表第 i
个结点,并将被删结点的值保存到 ele
中。以 list=[11, 22, 33, 44, 55]; i=3
为例如图所示:
实现步骤:
- 参数校验,序号
i
必须在[1, length]
范围内,否则参数不合法则返回 0 表示删除失败。 - 声明两个变量
pre
和node
,其中node
记录单链表中的结点,从链表第一个结点开始(即头结点的后继结点);而pre
记录结点node
的前驱结点。 - 通过循环遍历找到第
i
个结点node
和第i-1
个结点pre
。 - 最后删除第
i
个结点(即pre->next=node->next
),用ele
保存被删结点的值,然后调用free
函数释放结点空间。
实现代码如下:
/**
* 删除循环单链表中第 i 个节点
* @param list 循环单链表
* @param i 指定序号,从 1 开始
* @param ele 保存被删除节点的数据域
* @return 如果删除成功则返回 1,否则返回 0
*/
int removeByNum(CLNode **list, int i, int *ele)
// 0.参数校验
if (i < 0 || i > size(*list))
return 0;
// 1.声明一些变量
// 1.1 链表的第一个节点
CLNode *node = (*list)->next;
// 1.2 保存前驱节点,初始为链表的头节点
CLNode *pre = *list;
// 1.3 计数器,记录当前是链表的第几个节点
int count = 0;
// 2.遍历链表,寻找第 i 个节点,通过循环计数的方式来找到
while (node != *list)
// 2.1 计数器加 1
count++;
// 2.2 找到待删除节点,即比较计数器与参数值是否相等,如果相等则删除节点
if (count == i)
// 2.2.1 删除 node 节点,即将第 i-1 个节点的 next 指针指向第 i+1 个节点,这样就删除了第 i 个节点
pre->next = node->next;
// 2.2.2 保存被删除节点的数据域值
*ele = node->data;
// 2.2.3 释放节点空间
free(node);
// 2.2.4 删除成功则返回 1
return 1;
// 2.3 保存当前节点为前驱节点
pre = node;
// 2.4 继续下一个节点
node = node->next;
return 0;
removeByEle
删除循环单链表中第一个值为 ele
的结点,并用 ele
来保存被删结点的数据值。以 list=[11, 22, 33, 44, 55]; ele=33
为例如图所示:
实现步骤:
- 声明一个变量
node
来记录循环单链表中的结点,直到找到第i
个结点,从链表的第一个结点(即头结点的后继结点)开始;再声明一个变量pre
来记录node
结点的前驱结点,便于删除结点。 - 遍历循环,找到循环单链表中第一个值为
ele
的结点,如果找到则删除该结点。
实现代码如下:
/**
* 根据值删除循环单链表元素
* @param list 循环单链表
* @param ele 被删除节点的数据域值
*/
void removeByEle(CLNode **list, int ele)
// 0.声明一些变量
// 0.1 链表的第一个节点,其实主要用于遍历链表
CLNode *node = (*list)->next;
// 0.1 保存值等于 ele 的节点的前驱节点
CLNode *pre = *list;
// 1.循环链表,查找节点值等于 ele 的节点,然后进行删除
while (node != *list)
// 1.1 如果找到值等于 ele 的节点
if (node->data == ele)
// 1.1.1 删除 node 节点,即将第 i-1 个节点的 next 指针指向第 i+1 个节点,这样就删除了第 i 个节点
pre->next = node->next;
// 1.1.2 释放节点空间
free(node);
// 1.1.3 跳出循环
break;
// 1.2 保存当前节点为前驱节点
pre = node;
// 1.3 继续链表的下一个节点
node = node->next;
remvoeFirst
删除循环单链表中的开始结点,并用 ele
保存开始结点的数据值。以 list=[11, 22, 33, 44, 55]
为例如图所示:
实现步骤:
- 即将头结点的
next
指针指向开始结点node
的后继结点。
注:代码待完善,当循环单链表为空的时候虽然不会报错,但是由于会释放头结点空间,所以导致循环单链表结构出问题。
实现代码如下:
/**
* 删除链表的第一个节点
* @param list 循环单链表
* @param ele 被删除的节点的数据域值
*/
void removeFirst(CLNode **list, int *ele)
// 链表的第一个节点
CLNode *node = (*list)->next;
// 删除第一个节点,即将链表的头节点的 next 指针指向原链表的第二个节点(即让原链表第二个节点成为新的链表第一个节点)
(*list)->next = node->next;
// 保存被删除节点的数据域值
*ele = node->data;
// 释放节点空间
free(node);
removeLast
删除循环单链表的尾结点,并用 ele
保存被删结点的数据值。以 list=[11, 22, 33, 44, 55]
为例如图所示:
实现步骤:
- 声明两个变量
pre
和node
,其中node
记录循环单链表中的结点,初始为循环单链表的开始结点(即头结点的后继结点);而pre
记录node
结点的前驱结点。其实node
就是为了最后记录循环单链表的尾结点,pre
记录尾结点的前驱结点,便于删除。 - 通过遍历循环,找到循环单链表的尾结点
node
和尾结点的前驱结点pre
。 - 删除循环单链表的尾结点
node
,用ele
保存被删结点的数据值,然后释放存储空间。
实现代码如下:
/**
* 删除循环单链表的尾节点
* @param list 循环单链表
* @param ele 被删除节点的数据域值
*/
void removeLast(CLNode **list, int *ele)
// 0.声明一些变量
// 0.1 链表的第一个节点
CLNode *node = (*list)->next;
// 0.2 记录尾节点的前驱节点
CLNode *pre = *list;
// 1.找到链表的尾节点和尾节点的前驱节点
while (node->next != *list) // 注意循环结束的条件是 node.next!=list,当循环结束之后 node 就是链表的尾节点
// 1.1 保存当前节点为前驱节点
pre = node;
// 1.2 继续链表的下一个节点
node = node->next;
// 2.删除链表的最后一个节点
// 2.1 删除节点 node
pre->next = node->next;// 其实等价于 pre->next=*list
// 2.2 保存被删除节点的数据域值
*ele = node->data;
// 2.3 释放节点空间
free(node);
findByNum
查找循环单链表中第 i
个结点。以 list=[11, 22, 33, 44, 55]; i=3
为例如图所示:
实现步骤:
- 参数校验,序号
i
的范围必须在[1, length]
范围内。否则参数不合法,则返回NULL
。 - 声明一个变量
count
来记录已经遍历到第几个结点了,直到遇到count==i
,则表示找到第i
个结点,然后返回。
实现代码如下:
/**
* 通过序号在链表中检索节点
* @param list 循环单链表
* @param i 序号,从 1 开始
* @return 如果找到第 i 个节点则返回,否则返回 NULL
*/
CLNode *findByNum(CLNode *list, int i)
// 0.参数校验
if (i < 1 || i > size(list))
return NULL;
// 1.声明一些变量
// 1.1 链表的第一个节点
CLNode *node = list->next;
// 1.2 计数器,记录当前正在遍历的节点是链表的第几个节点
int count = 0;
// 2.遍历链表所有节点,找到第 i 个节点
while (node != list)
// 2.1 计数器加 1,表示已经迭代了一个节点
count++;
// 2.2 比较计数器的值与参数值是否相等,如果相等则表示找到了第 i 个节点则返回该节点
if (count == i)
return node;
// 2.3 继续下一个节点
node = node->next;
retu以上是关于线性表文档之循环单链表的主要内容,如果未能解决你的问题,请参考以下文章