俗話說得好,不懂鏈表的程序員,不配稱爲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;
}
總結:
循環鏈表和單項鍊表“本事同根生”, 所以,只要懂得了單向鏈表,也就間接的懂得了循環鏈表,因爲在之前的文章中已經詳細的講解了單向鏈表,所以在循環鏈表中就大概的掠過了,,文章開頭有單向鏈表的詳細用法鏈接,有興趣的小夥伴可以去看看,學習學習!