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;
}

如果鏈表只有頭節點的話,就可以直接進行插入了,否則,需要遍歷到尾節點處,才能插入。
在這裏插入圖片描述


頭插法

一樣也有兩種情況,不過他們的代碼實現都是一樣的。

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

總結:
循環鏈表和單項鍊表“本事同根生”, 所以,只要懂得了單向鏈表,也就間接的懂得了循環鏈表,因爲在之前的文章中已經詳細的講解了單向鏈表,所以在循環鏈表中就大概的掠過了,,文章開頭有單向鏈表的詳細用法鏈接,有興趣的小夥伴可以去看看,學習學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章