俗话说得好,不懂链表的程序员,不配称为C/C++程序员。
继上一篇博客“单向链表”之后,现在给大家分享第二篇:循环链表。
循环链表是建在单向链表之上的,所以,学习了单向链表再来学习循环链表,就游刃有余的了。
不懂单向链表的朋友可以点击下面链接去学习!
https://blog.csdn.net/cpp_learner/article/details/105015219
言归正传,循环链表,就是和单向链表一模一样,只是,单向链表最后一个节点的next指向NULL,而循环链表最后一个节点的next指向头节点,形成循环链表。
因为循环链表和单向链表很像,所以他们的用法都是差不多一模一样,只是结算判断不同,单向链表是最后一个节点的next等于NULL时停止,而循环链表则不能这样判断,循环链表的是最后一个节点的next为头节点时结束。
空链表:
自己指向自己,形成循环链表。
循环链表的定义和初始化
// 定义循环链表
typedef struct Link {
int date; // 链表中的数据
struct Link* next; // 下一个节点地址
}LinkList, LinkNode;
// 初始化循环链表
bool inItLoopLink(LinkList*& List) { // 参数一:循环链表的头节点指针的引用
List = new LinkList; // 分配内存
List->next = List; // 头节点的next指向本身,形成环路
return true;
}
和单向链表有稍微的区别。
单向链表:List->next = NULL;
循环链表:List->next = List;
初始化后,形成空循环链表,如上图。
尾插法
它有两种情况:
- 循环链表中只有头节点
- 循环链表中有其他节点
情况不同,他处理的方式也不同。
// 尾插法
bool loopLinkInsertBack(LinkList*& List, LinkNode* Node) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
if (p->next == List) { // 第一种情况:循环链表中只有头节点
Node->next = List; // 待插入节点的next指向头节点
List->next = Node; // 头节点的next指向待插入节点
} else { // 第二种情况:循环链表中有其他节点
while (p->next != List) p = p->next; // 找到尾节点,p指向它
Node->next = p->next; // 待插入节点的next指向头节点
p->next = Node; // 前尾节点的next指向待插入节点
}
return true;
}
如果链表只有头节点的话,就可以直接进行插入了,否则,需要遍历到尾节点处,才能插入。
头插法
一样也有两种情况,不过他们的代码实现都是一样的。
// 头插法
bool loopLinkInsertFront(LinkList*& List, LinkNode *Node) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
Node->next = List->next; // 待插入节点的next指向头节点的下一个节点
List->next = Node; // 头节点的next指向待插入节点
return true;
}
任意位置插入
需要找到待插入位置的前一个节点才能插入。
// 任意位置插入
bool loopLinkInsert(LinkList*& List, LinkNode* Node, int i) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点;参数三:插入位置
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点节点
int j = 0; // 用于循环判断,因为从头节点开始便利,所以j赋值0
while (p->next != List && j < i - 1) { // 找出待插入位置的前一个节点,p指向它
p = p->next;
j++;
}
// 执行到这一步,如果j != i-1,说明i值不合法
if (j != i - 1) return false; // i值不合法的情况:i > n || i <= 0
// 当执行到这一步,说明p已经指向待插入位置的前一个节点处
Node->next = p->next; // 待插入节点的next指向p的下一个节点
p->next = Node; // p的next指向待插入节点
return true;
}
注意看while循环和if判断。
获取循环链表中指定位置的值
// 获取循环链表中指定位置的值
bool gainLoopLinkValue(LinkList*& List, int i, int& e) { // 参数一:链表的头节点指针的引用;参数二:查找的位置;参数三:获取它的值
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p != List && j < i) { // 找到i位置的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一,返回false;i<=0,触发条件二,返回false
if (p == List || j > i) return false; // i值不合法的情况:i > n || i <= 0
e = p->date;
return true;
}
查找该值在链表中节点的位置
// 查找该值在链表中节点的位置
bool loopLinkNodeLocation(LinkList*& List, int e, int &i) { // 参数二:链表中查找的值;参数三:返回查找到的节点位置
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p != List) { // 当p等List时,说明已经遍历一遍了。也就结束循环
if (p->date == e) { // 判断当前节点的值是否等于需要查找的值
i = j; // 相等,则将j标记的当前节点位置赋值给i返回
return true;
}
j++;
p = p->next;
}
i = 0; // 执行到这一步,说明没有找到,i赋值0返回
return false;
}
修改指定位置节点的值
// 修改指定位置节点的值
bool loopLinkAlterValue(LinkList*& List, int i, int e) { // 参数二:节点的位置;参数三:修改的值
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next; // 定义临时节点指向头节点的下一个节点
int j = 1; // 用于找到节点位置
while (p != List && j < i) { // 找到节点位置,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0,触发条件二
if (p == List || j > i) return false; // i值不合法的情况:i > n || i <= 0
// 执行到这一步说明已经找到了需要修改值的节点,p指向它
p->date = e; // 将e值赋值给p的date
return false;
}
删除链表中的一个节点
情况一:根据节点的位置删除
// 删除链表中的一个节点:1.根据节点的位置删除
bool loopLinkDelete_nodeLocation(LinkList*& List, int i) { // 参数二:待删除节点的位置
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
LinkList* q; // 该节点用于指向待删除节点的上一个节点
int j = 0; // 循环判断
while (p->next != List && j < i - 1) { // 找到待删除节点的上一个节点
p = p->next;
j++;
}
// 如果找到了,则j肯定是等于i-1的,否则就是没有该位置的节点,返回false
if (j != i - 1) return false; // i值不合法的情况:i > n || i <= 0
q = p->next; // q 指向待删除节点
p->next = q->next; // 待删除节点的上一个节点的next指向待删除节点的下一个节点
delete q; // 释放待删除节点的内存
return true;
}
情况二:根据节点的值删除
// 删除链表中的一个节点:2.根据节点的值删除
bool loopLinkDelete_nodeValue(LinkList*& List, int e) {
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
LinkList* q; // 该节点用于指向待删除节点的上一个节点
// 此条循环找到待删除节点的前一个节点,p指向它
while (p->next != List && p->next->date != e) p = p->next;
// 如果p的next指向头节点,说明p现在指向尾节点,没有找到与e值相等的节点,返回false
if (p->next == List) return false;
q = p->next; // q 指向待删除节点
p->next = q->next; // 待删除节点的前一个节点的next指向待删除节点的下一个节点
delete q; // 释放待删除节点的内存
return true;
}
输出循环链表
// 输出循环链表
void loopLinkPrint(LinkList*& List) {
if (!List || List->next == List) {
cout << "循环链表为空或循环链表没有其他节点!" << endl;
return;
}
LinkList* p = List->next; // 定义临时节点指向头节点的下一个节点
while (p != List) { // 当节点不等于头节点时,执行循环
cout << p->date << "\t";
p = p->next;
}
cout << endl;
}
销毁循环链表
// 销毁循环链表
bool loopLinkDestroy(LinkList*& List) {
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next; // 临时节点指向头节点的下一个节点
LinkList* q = NULL; // 临时节点,用于指向待释放节点的下一个节点
while (p != List) { // 此while循环用于将链表遍历一遍
q = p->next; // q指向待释放节点的下一个节点
delete p; // 释放p
p = q; // 将q赋值给p
}
delete List; // 循环结束后,还剩下头节点,把头节点释放掉
List = p = q = NULL; // 将他们都指向NULL
return true;
}
好了,搞完上面的代码后,我们现在来做一个小项目:
有 10 个小朋友按编号顺序 1,2,。。。,10 顺时针方向围成一圈。从 1 号开始顺时针方向 1,2,。。。,9 报数,凡报数 9 者出列(显然,第一个出圈为编号 9 者)。
最后一个出圈者的编号是多少?第 5 个出圈者的编号是多少?
代码:
#include <iostream>
#include <Windows.h>
using namespace std;
// 定义循环链表
typedef struct Link {
int date; // 链表中的数据
struct Link* next; // 下一个节点地址
}LinkList, LinkNode;
// 初始化循环链表
bool inItLoopLink(LinkList*& List) { // 参数一:循环链表的头节点指针的引用
List = new LinkList; // 分配内存
List->next = List; // 头节点的next指向本身,形成环路
return true;
}
// 尾插法
bool loopLinkInsertBack(LinkList*& List, LinkNode* Node) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
if (p->next == List) { // 第一种情况:循环链表中只有头节点
Node->next = List; // 待插入节点的next指向头节点
List->next = Node; // 头节点的next指向待插入节点
} else { // 第二种情况:循环链表中有其他节点
while (p->next != List) p = p->next; // 找到尾节点,p指向它
Node->next = p->next; // 待插入节点的next指向头节点
p->next = Node; // 前尾节点的next指向待插入节点
}
return true;
}
// 头插法
bool loopLinkInsertFront(LinkList*& List, LinkNode *Node) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
Node->next = List->next; // 待插入节点的next指向头节点的下一个节点
List->next = Node; // 头节点的next指向待插入节点
return true;
}
// 任意位置插入
bool loopLinkInsert(LinkList*& List, LinkNode* Node, int i) { // 参数一:循环链表的头节点指针的引用;参数二:待插入节点;参数三:插入位置
if (!List || !Node) { // 合法性检查
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点节点
int j = 0; // 用于循环判断,因为从头节点开始便利,所以j赋值0
while (p->next != List && j < i - 1) { // 找出待插入位置的前一个节点,p指向它
p = p->next;
j++;
}
// 执行到这一步,如果j != i-1,说明i值不合法
if (j != i - 1) return false; // i值不合法的情况:i > n || i <= 0
// 当执行到这一步,说明p已经指向待插入位置的前一个节点处
Node->next = p->next; // 待插入节点的next指向p的下一个节点
p->next = Node; // p的next指向待插入节点
return true;
}
// 获取循环链表中指定位置的值
bool gainLoopLinkValue(LinkList*& List, int i, int& e) { // 参数一:链表的头节点指针的引用;参数二:查找的位置;参数三:获取它的值
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p != List && j < i) { // 找到i位置的节点,p指向它
p = p->next;
j++;
}
// i>n,触发条件一,返回false;i<=0,触发条件二,返回false
if (p == List || j > i) return false; // i值不合法的情况:i > n || i <= 0
e = p->date;
return true;
}
// 查找该值在链表中节点的位置
bool loopLinkNodeLocation(LinkList*& List, int e, int &i) { // 参数二:链表中查找的值;参数三:返回查找到的节点位置
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next;
int j = 1;
while (p != List) { // 当p等List时,说明已经遍历一遍了。也就结束循环
if (p->date == e) { // 判断当前节点的值是否等于需要查找的值
i = j; // 相等,则将j标记的当前节点位置赋值给i返回
return true;
}
j++;
p = p->next;
}
i = 0; // 执行到这一步,说明没有找到,i赋值0返回
return false;
}
// 修改指定位置节点的值
bool loopLinkAlterValue(LinkList*& List, int i, int e) { // 参数二:节点的位置;参数三:修改的值
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next; // 定义临时节点指向头节点的下一个节点
int j = 1; // 用于找到节点位置
while (p != List && j < i) { // 找到节点位置,p指向它
p = p->next;
j++;
}
// i>n,触发条件一;i<=0,触发条件二
if (p == List || j > i) return false; // i值不合法的情况:i > n || i <= 0
// 执行到这一步说明已经找到了需要修改值的节点,p指向它
p->date = e; // 将e值赋值给p的date
return false;
}
// 删除链表中的一个节点:1.根据节点的位置删除
bool loopLinkDelete_nodeLocation(LinkList*& List, int i) { // 参数二:待删除节点的位置
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
LinkList* q; // 该节点用于指向待删除节点的上一个节点
int j = 0; // 循环判断
while (p->next != List && j < i - 1) { // 找到待删除节点的上一个节点
p = p->next;
j++;
}
// 如果找到了,则j肯定是等于i-1的,否则就是没有该位置的节点,返回false
if (j != i - 1) return false; // i值不合法的情况:i > n || i <= 0
q = p->next; // q 指向待删除节点
p->next = q->next; // 待删除节点的上一个节点的next指向待删除节点的下一个节点
delete q; // 释放待删除节点的内存
return true;
}
// 删除链表中的一个节点:2.根据节点的值删除
bool loopLinkDelete_nodeValue(LinkList*& List, int e) {
if (!List || List->next == List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List; // 定义临时节点指向头节点
LinkList* q; // 该节点用于指向待删除节点的上一个节点
// 此条循环找到待删除节点的前一个节点,p指向它
while (p->next != List && p->next->date != e) p = p->next;
// 如果p的next指向头节点,说明p现在指向尾节点,没有找到与e值相等的节点,返回false
if (p->next == List) return false;
q = p->next; // q 指向待删除节点
p->next = q->next; // 待删除节点的前一个节点的next指向待删除节点的下一个节点
delete q; // 释放待删除节点的内存
return true;
}
// 输出循环链表
void loopLinkPrint(LinkList*& List) {
if (!List || List->next == List) {
cout << "循环链表为空或循环链表没有其他节点!" << endl;
return;
}
LinkList* p = List->next; // 定义临时节点指向头节点的下一个节点
while (p != List) { // 当节点不等于头节点时,执行循环
cout << p->date << "\t";
p = p->next;
}
cout << endl;
}
// 销毁循环链表
bool loopLinkDestroy(LinkList*& List) {
if (!List) {
cout << "链表为空!" << endl;
return false;
}
LinkList* p = List->next; // 临时节点指向头节点的下一个节点
LinkList* q = NULL; // 临时节点,用于指向待释放节点的下一个节点
while (p != List) { // 此while循环用于将链表遍历一遍
q = p->next; // q指向待释放节点的下一个节点
delete p; // 释放p
p = q; // 将q赋值给p
}
delete List; // 循环结束后,还剩下头节点,把头节点释放掉
List = p = q = NULL; // 将他们都指向NULL
return true;
}
// 小项目
bool test(LinkList*& List, int index) { // 参数二:出列标记数
if (!List || List->next == List) { // 合法性检查
cout << "循环链表为空或循环链表没有其他节点!" << endl;
return false;
}
if (index < 1) {
cout << "数据无意义!" << endl;
return false;
}
int i = 0, j = 0;
int times = 0, num = 0;
LinkList* p, * q;
p = List;
do {
// 出列编号
i += index; // index, 2*index, 3*index, ..., n*index
while (p->next) { // 死循环
if (p->next != List) j++; // 数数,跳过头节点不数
if (j >= i) break; // 当j等于需要出列的编号时,结束循环,p也就指向了需要出列节点的前一个节点
p = p->next;
}
times++; // 指定出列的第几个编号
q = p->next; // q 指向带出列的节点
num = q->date; // 出列编号赋值给num
if (times == 5) {
cout << "出列第五个节点的编号为:" << num << endl;
}
/*cout << "当前出列编号:" << q->date << ",下一个出列编号:"
<< p->date << ", 下一个开始数的编号:" << q->next->date << endl;*/
p->next = q->next; // 待出列节点的前一个节点的next指向待出列节点的后一个节点
delete q; // 释放待出列节点的内存
} while (List->next != List); // 循环条件
cout << "最后一个出圈的编号为:" << num << endl;
return true;
}
int main2(void) {
LinkList* list = NULL;
LinkNode* node = NULL;
// 初始化循环链表
if (inItLoopLink(list)) {
cout << "循环链表初始化成功!" << endl;
} else {
cout << "循环链表初始化失败!" << endl;
}
// 尾插法
int i = 0;
while ((++i) <= 10) {
LinkNode* p = new LinkNode;
p->date = i;
if (loopLinkInsertBack(list, p)) {
cout << "尾插法插入成功!" << endl;
} else {
cout << "尾插法插入失败!" << endl;
}
}
// 输出循环链表中的值
loopLinkPrint(list);
// 修改指定位置节点的值
if (loopLinkAlterValue(list, 5, 999)) {
cout << "修改第一个节点的值成功!" << endl;
loopLinkPrint(list);
}
else {
cout << "修改第一个节点的值失败!" << endl;
loopLinkPrint(list);
}
// 任意位置插入
int n = 0, e = 0;
cout << "请输入任意位置插入的个数:";
cin >> n;
cout << "请输入插入位置和插入的元素:";
while (n-- > 0) {
cin >> i >> e;
node = new LinkNode;
node->date = e;
if (loopLinkInsert(list, node, i)) {
cout << "插入成功!" << endl;
loopLinkPrint(list);
} else {
cout << "插入失败!" << endl;
loopLinkPrint(list);
}
}
// 头插法
cout << "请输入头插入的个数:";
cin >> n;
cout << "请输入头插入的元素:";
while (n-- > 0) {
cin >> e;
node = new LinkNode;
node->date = e;
if (loopLinkInsertFront(list, node)) {
cout << "插入成功!" << endl;
loopLinkPrint(list);
} else {
cout << "插入失败!" << endl;
loopLinkPrint(list);
}
}
// 获取循环链表中指定位置的值
if (gainLoopLinkValue(list, 5, e)) {
cout << "获取第五个位置的值成功,值为:" << e << endl;
} else {
cout << "获取第五个位置的值失败!" << endl;
}
// 查找该值在链表中节点的位置
if (loopLinkNodeLocation(list, 5, i)) {
cout << "查找成功,值为5的节点位置在:" << i << endl;
} else {
cout << "查找失败!" << endl;
}
// 删除链表中的一个节点:1.根据节点的位置删除
if (loopLinkDelete_nodeLocation(list, 1)) {
cout << "删除第一个位置的节点成功!" << endl;
loopLinkPrint(list);
} else {
cout << "删除第一个位置的节点失败!" << endl;
loopLinkPrint(list);
}
// 删除链表中的一个节点:2.根据节点的值删除
if (loopLinkDelete_nodeValue(list, 3)) {
cout << "删除值为三的节点成功!" << endl;
loopLinkPrint(list);
} else {
cout << "删除值为三的节点失败!" << endl;
loopLinkPrint(list);
}
if (test(list, 9)) {
cout << "出圈完成!" << endl;
} else {
cout << "出圈失败!" << endl;
}
// 销毁链表
if (loopLinkDestroy(list)) {
cout << "循环链表销毁成功!" << endl;
} else {
cout << "循环链表销毁失败!" << endl;
}
loopLinkPrint(list);
system("pause");
return 0;
}
总结:
循环链表和单项链表“本事同根生”, 所以,只要懂得了单向链表,也就间接的懂得了循环链表,因为在之前的文章中已经详细的讲解了单向链表,所以在循环链表中就大概的掠过了,,文章开头有单向链表的详细用法链接,有兴趣的小伙伴可以去看看,学习学习!