循環(huán)鏈表是一種特殊的鏈式存儲結構,其中最后一個節(jié)點的指針域指向頭部節(jié)點,從而形成了一個閉環(huán)。這種結構使得從任意節(jié)點出發(fā)都能夠訪問到列表中的所有節(jié)點。循環(huán)鏈表的操作通常比單鏈表更便捷,因為它們不需要額外的存儲空間,只需要稍微調整鏈表的鏈接方式。
數(shù)據(jù)結構
存儲結構
```c
typedef struct LNode {
ElemType 數(shù)據(jù);
struct LNode *next;
} LNode, *LinkList;
```
基本操作
構造空表
```c
void InitList(LinkList *L)
{
// 操作結果:構造一個空的線性表L
*L = (LinkList)malloc(sizeof(struct LNode));
if (!*L) // 存儲分配失敗
exit(OVERFLOW);
(*L)->next = *L; // 指針域指向頭結點
}
```
銷毀表
```c
void DestroyList(LinkList *L)
{
// 操作結果:銷毀線性表L
LinkList q, p = (*L)->next; // p指向頭結點
while (p != *L) // 沒到表尾
{
q = p->樂華七子NEXT;
free(p);
p = q;
}
free(*L);
*L = NULL;
}
```
清除表
```c
void ClearList(LinkList *L)
{
// 初始條件:線性表L已存在。操作結果:將L重置為空表
LinkList p, q;
*L = (*L)->next; // L指向頭結點
p = (*L)->next; // p指向第一個結點
while (p != *L) // 沒到表尾
{
q = p->樂華七子NEXT;
free(p);
p = q;
}
(*L)->next = *L; // 頭結點指針域指向自身
}
```
判斷表是否為空
```c
Status ListEmpty(LinkList L)
{
// 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
if (L->next == L) // 空
return TRUE;
else
return FALSE;
}
```
計算表長度
```c
int ListLength(LinkList L)
{
// 初始條件:L已存在。操作結果:返回L中數(shù)據(jù)元素個數(shù)
int i = 0;
LinkList p = L->next; // p指向頭結點
while (p != L) // 沒到表尾
{
i++;
p = p->next;
}
return i;
}
```
獲取元素
```c
Status GetElem(LinkList L, int i, ElemType *e)
{
// 當?shù)趇個元素存在時,其值賦給e并返回OK,否則返回ERROR
int j = 1; // 初始化,j為計數(shù)器
LinkList p = L->next->next; // p指向第一個結點
if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
return ERROR;
while (j < i)
{
/* 順指針向后查找,直到p指向第i個元素 */
p = p->next;
j++;
}
*e = p->數(shù)據(jù); // 取第i個元素
return OK;
}
```
查找元素
```c
int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType))
{
// 初始條件:線性表L已存在,compare()是數(shù)據(jù)元素判定函數(shù)
// 操作結果:返回L中第1個與e滿足關系compare()的數(shù)據(jù)元素的位序。
// 若這樣的數(shù)據(jù)元素不存在,則返回值為0
int i = 0;
LinkList p = L->next->next; // p指向第一個結點
while (p != L->next)
{
i++;
if (compare(p->數(shù)據(jù), e)) // 滿足關系
return i;
p = p->next;
}
return 0;
}
```
獲取前驅元素
```c
Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e)
{
// 初始條件:線性表L已存在
// 操作結果:若cur_e是L的數(shù)據(jù)元素,且不是第一個,則用pre_e返回它的前驅,
// 否則操作失敗,pre_e無定義
LinkList q, p = L->next->next; // p指向第一個結點
q = p->樂華七子NEXT;
while (q != L->next) // p沒到表尾
{
if (q->數(shù)據(jù) == cur_e)
{
*pre_e = p->data;
return TRUE;
}
p = q;
q = q->next;
}
return FALSE; // 操作失敗
}
```
獲取后繼元素
```c
Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e)
{
// 初始條件:線性表L已存在
// 操作結果:若cur_e是L的數(shù)據(jù)元素,且不是最后一個,則用next_e返回它的后繼,
// 否則操作失敗,next_e無定義
LinkList p = L->next->next; // p指向第一個結點
while (p != L) // p沒到表尾
{
if (p->數(shù)據(jù) == cur_e)
{
*next_e = p->樂華七子NEXT>data;
return TRUE;
}
p = p->next;
}
return FALSE; // 操作失敗
}
```
插入元素
```c
Status ListInsert(LinkList *L, int i, ElemType e)
{
// 改變L
// 在L的第i個位置之前插入元素e
LinkList p = (*L)->next, s; // p指向頭結點
int j = 0;
if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
return ERROR;
while (j < i - 1)
{
p = p->next;
j++;
}
s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
s->數(shù)據(jù) = e; // 插入L中
s->next = p->next;
p->next = s;
if (p == *L) // 改變尾結點
*L = s;
return OK;
}
```
刪除元素
```c
Status ListDelete(LinkList *L, int i, ElemType *e)
{
// 改變L
// 刪除L的第i個元素,并由e返回其值
LinkList p = (*L)->next, q; // p指向頭結點
int j = 0;
if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
return ERROR;
while (j < i - 1)
{
p = p->next;
j++;
}
q = p->next; // q指向待刪除結點
p->next = q->next;
*e = q->數(shù)據(jù);
if (*L == q) // 刪除的是表尾元素
*L = p;
free(q); // 釋放待刪除結點
return OK;
}
```
遍歷表
```c
void ListTraverse(LinkList L, void(*vi)(ElemType))
{
// 初始條件:L已存在。操作結果:依次對L的每個數(shù)據(jù)元素調用函數(shù)vi()
LinkList p = L->next->next; // p指向首元結點
while (p != L->next) // p不指向頭結點
{
vi(p->數(shù)據(jù));
p = p->next;
}
printf("\n");
}
```
應用問題
Josephu問題:據(jù)傳,猶太歷史學家Josephus曾講述過這樣一個故事:在羅馬人占領喬塔帕特后,39名猶太人與Josephus及其朋友躲進了一個山洞,他們決定以一種自殺的方式結束生命,即41個人圍成一圈,從第一個人開始報數(shù),每報到第三個人就要自殺,以此類推,直到所有人都死去。然而,Josephus和他的朋友并不愿意這樣做,他們計劃通過巧妙地安排自己的位置來幸免于難。Josephus將自己和朋友分別安排在第16個和第31個位置,最終成功逃脫了這場死亡游戲。
如何使用循環(huán)鏈表解決Josephu問題呢?
類型分類
循環(huán)鏈表可以分為兩類:單循環(huán)鏈表和多重鏈的循環(huán)鏈表。前者是在單鏈表的基礎上,將終端結點的指針域設置為指向表頭結點;后者則是將表中結點鏈接在多個環(huán)上。
特點
循環(huán)鏈表具有以下特點:
- 不需要額外的存儲空間,僅通過對鏈表鏈接方式進行微小改動,就能使其處理變得更加便捷靈活。
- 在循環(huán)鏈表上實現(xiàn)某些特定的運算會更為容易,比如在單循環(huán)鏈表上實現(xiàn)將兩個線性表連接成一個新的線性表的運算,其執(zhí)行時間僅為常數(shù)級別,而如果在單鏈表或頭指針表示的單循環(huán)表上進行同樣的操作,則需要遍歷整個鏈表,執(zhí)行時間會隨鏈表長度呈線性增長。
實際應用
循環(huán)鏈表的實際應用非常廣泛,特別是在計算機科學領域。例如,在操作系統(tǒng)中,進程調度隊列就可以使用循環(huán)鏈表來實現(xiàn),這樣能夠快速地定位隊列中的任意進程,并對其進行調度。此外,循環(huán)鏈表還被用于實現(xiàn)各種數(shù)據(jù)結構,如哈希表、堆棧和隊列等。
參考資料 >
循環(huán)鏈表與雙鏈表 .CSDN博客 .2024-08-29
鏈表.知乎.2024-08-29
《循環(huán)鏈表》 .CSDN博客 .2024-08-29