C++ 入门算法,新手必看之:循环“链表”(二)

俗话说得好,不懂链表的程序员,不配称为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;

初始化后,形成空循环链表,如上图。


尾插法

它有两种情况:

  1. 循环链表中只有头节点
  2. 循环链表中有其他节点

情况不同,他处理的方式也不同。

// 尾插法
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;
}

如果链表只有头节点的话,就可以直接进行插入了,否则,需要遍历到尾节点处,才能插入。
在这里插入图片描述

扫描二维码关注公众号,回复: 10129489 查看本文章

头插法

一样也有两种情况,不过他们的代码实现都是一样的。

// 头插法
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;
}

总结:
循环链表和单项链表“本事同根生”, 所以,只要懂得了单向链表,也就间接的懂得了循环链表,因为在之前的文章中已经详细的讲解了单向链表,所以在循环链表中就大概的掠过了,,文章开头有单向链表的详细用法链接,有兴趣的小伙伴可以去看看,学习学习!

发布了50 篇原创文章 · 获赞 46 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/105026894