網站首頁 編程語言 正文
一.引入
在單鏈表中我們是把結點遍歷一遍后就結束了,為了使表處理更加方便靈活,我們希望通過任意一個結點出發都可以找到鏈表中的其它結點,因此我們引入了循環鏈表。
二.循環鏈表的定義
將單鏈表中終端結點的指針端由空指針改為頭結點,就使整個單鏈表形成了一個環,這種頭尾相接的單鏈表稱為循環鏈表。
簡單理解就是形成一個閉合的環,在環內尋找自己需要的結點。
三.單鏈表與循環鏈表對比
3.1圖示對比
單鏈表:
循環鏈表:
3.2代碼對比
單鏈表:
typedef struct Node{ //定義單鏈表結點類型
int data; //數據域,可以是別的各種數據類型
struct Node *next; //指針域
}LNode, *LinkList;
循環鏈表:
typedef int ELEMTYPE;
typedef struct Clist
{
ELEMTYPE data; //數據域 存放數據
struct Clist* next;
//指針域存放下一個節點的地址(尾結點的next保存頭結點的地址)
}Clist, * PClist;
四.循環鏈表的操作
循環鏈表是單鏈表中擴展出來的結構,所以有很多的操作是和單鏈表相同的,如求長度,查找元素,獲取一個元素,這里我們對循環鏈表進行初始化,創建,插入,刪除,銷毀的一系列操作。
4.1循環鏈表的初始化
單鏈表和循環鏈表初始化對比如下:
單鏈表 | 循環鏈表 |
---|---|
數據域不處理 | 數據域不處理 |
next域賦值為NULL | next域賦值為頭結點自身的地址 |
代碼如下:
void InitClist(struct Clist* plist)
{
//assert
//plist->data; // 數據域不處理
plist->next = plist;
}
4.2循環鏈表的建立
循環鏈表的建立是基于單鏈表所以也有頭插和尾插兩種方式。
4.2.1頭插法建立循環鏈表
頭插法建立循環鏈表的主要是這兩行代碼:
pnewnode->next=plist->next;
plist->next=pnewnode;
這里我們需要思考一下當鏈表為空時是否適用:這里明確告訴大家不管是否是空鏈表,這兩行代碼均可以使用,下面給大家用示意圖表示一下;
不是空鏈:
是空鏈:
代碼如下:
bool Insert_head(PClist plist, ELEMTYPE val)
{
//assert
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置 (頭插 不用找)
//3.插入
pnewnode->next = plist->next;
plist->next = pnewnode;
return true;
}
4.2.2尾插法建立循環鏈表
尾插法建立循環鏈表主要代碼如下:
for(p=plist;p->next!=plist;p=p->next);
代碼如下:
bool Insert_tail(PClist plist, ELEMTYPE val)
{
//assert
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置(通過帶前驅的for循環)
struct Clist* p = plist;
for (p; p->next != plist; p = p->next);
//此時 for循環執行結束 p指向尾結點
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
4.3循環鏈表的插入操作
與單向鏈表的插入不同,循環單鏈表插入時只能往表頭節點的后面插入,由初始結構可知,想完成插入操作,必須先找到要插入位置的前一個節點,然后修改相應指針即可完成操作。
bool Insert_pos(PClist plist, int pos, ELEMTYPE val)
{
assert(plist != NULL);
assert(pos >= 0 && pos <= Get_length(plist));
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置(通過帶前驅的for循環)
struct Clist* p = plist;
for (int i = 0; i < pos; i++)
{
p = p->next;
}
//此時 for循環執行結束 p指向待插入的合適位置
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
4.4循環鏈表的刪除操作
循環鏈表的刪除操作相當于單鏈表的刪除操作,主要分為以下三個步驟:
- 指針p指向待刪除的結點;
- 指針q指向待刪除結點的前一個結點;
- 跨越指向。
void Delete(list **pNode,int place) //刪除操作
{
list *temp,*target;
int i;
temp=*pNode;
if(temp==NULL) //首先判斷鏈表是否為空
{
printf("這是一個空指針 無法刪除\n");
return;
}
if(place==1) //如果刪除的是頭節點
{ //應當特殊處理,找到尾節點,使尾節點的next指向頭節點的下一個節點
// rear->next=(*head)->next;然后讓新節點作為頭節點,釋放原來的頭節點
for(target=*pNode;target->next!=*pNode;target=target->next);
temp=*pNode;
*pNode=(*pNode)->next;
target->next=*pNode;
free(temp);
}
else //刪除其他節點
{ //首先找出尾節點
for(i=1,target=*pNode;target->next!=*pNode&&i!=place-1;target=target->next,i++);
if(target->next==*pNode) //判斷要刪除的位置是否大于鏈表長度,若大于鏈表長度,
//特殊處理直接刪除尾節點
{
//找出尾節的前一個節點
for(target=*pNode;target->next->next!=*pNode;target=target->next);
temp=target->next; // 尾節點的前一個節點直接指向頭節點 釋放原來的尾節點
target->next=*pNode;
printf("數字太大刪除尾巴\n");
free(temp);
}
else
{
temp=target->next;// 刪除普通節點 找到要刪除節點的前一個節點target,
//使target指向要刪除節點的下一個節點 轉存刪除節點地址
target->next=temp->next; // 然后釋放這個節點
free(temp);
}
}
}
4.5循環鏈表的銷毀
循環鏈表這里有兩種銷毀方式:
4.5.1無限頭刪
void Destroy1(PClist plist)
{
//assert
while (plist->next != plist)
{
struct Clist* p = plist->next;
plist->next = p->next;
free(p);
}
plist->next = plist;
}
4.5.2兩個指針變量
void Destroy2(PClist plist)
{
//assert
struct Clist* p = plist->next;
struct Clist* q = NULL;
plist->next = plist;
while (p != plist)
{
q = p->next;
free(p);
p = q;
}
}
4.6其他操作
循環鏈表還有查找值,判空,求鏈表長度,清空等一系列操作,這些操作與單鏈表那的操作基本相同,這里不進行詳細贅述,在后面的完整代碼中會寫出來。
五.總結
循環鏈表相對于單鏈表來說會稍微復雜一點,主要思想還是和單鏈表差不多的,只不過是將鏈表的首位進行相連,但是正是因為首尾的相連,便于我們通過任意一個結點出發都可以找到鏈表中的其它結點。
六.全部代碼
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
//循環鏈表里邊很少出現NULL, nullptr
//初始化
void InitClist(struct Clist* plist)
{
//assert
//plist->data; // 數據域不處理
plist->next = plist;
}
//頭插
bool Insert_head(PClist plist, ELEMTYPE val)
{
//assert
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置 (頭插 不用找)
//3.插入
pnewnode->next = plist->next;
plist->next = pnewnode;
return true;
}
//尾插
bool Insert_tail(PClist plist, ELEMTYPE val)
{
//assert
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置(通過帶前驅的for循環)
struct Clist* p = plist;
for (p; p->next != plist; p = p->next);
//此時 for循環執行結束 p指向尾結點
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//按位置插
bool Insert_pos(PClist plist, int pos, ELEMTYPE val)
{
assert(plist != NULL);
assert(pos >= 0 && pos <= Get_length(plist));
//1.購買新節點
struct Clist* pnewnode = (struct Clist*)malloc(1 * sizeof(struct Clist));
assert(pnewnode != NULL);
pnewnode->data = val;//購買的新節點 處理完畢
//2.找到插入位置(通過帶前驅的for循環)
struct Clist* p = plist;
for (int i = 0; i < pos; i++)
{
p = p->next;
}
//此時 for循環執行結束 p指向待插入的合適位置
//3.插入
pnewnode->next = p->next;
p->next = pnewnode;
return true;
}
//頭刪
bool Del_head(PClist plist)
{
//assert
if (IsEmpty(plist))//不空 則至少存在一個有效值
{
return false;
}
//1.指針p指向待刪除節點
struct Clist* p = plist->next;
//2.指針q指向待刪除節點的前一個節點
//q 就是 頭結點 這里就不再額外處理
//3.跨越指向
plist->next = p->next;
free(p);
return true;
}
//尾刪
bool Del_tail(PClist plist)
{
//assert
if (IsEmpty(plist))//不空 則至少存在一個有效值
{
return false;
}
//1.指針p指向待刪除節點(尾刪的話,這里指向尾結點)
struct Clist* p = plist;
for (p; p->next != plist; p = p->next);
//此時 for指向結束 代表著p指向尾結點
//2.指針q指向倒數第二個節點
struct Clist* q = plist;
for (q; q->next != p; q = q->next);
//此時 for指向結束 代表著q指向p的上一個節點
//3.跨越指向
q->next = p->next;
free(p);
return true;
}
//按位置刪
bool Del_pos(PClist plist, int pos)
{
assert(plist != NULL);
assert(pos >= 0 && pos < Get_length(plist));
if (IsEmpty(plist))
{
return false;
}
//1.指針p指向待刪除節點
//2.指針q指向待刪除節點的上一個節點
//這里采用第二種方案找pq,先找q再找p
struct Clist* q = plist;
for (int i = 0; i < pos; i++)
{
q = q->next;
}
struct Clist* p = q->next;
//3.跨越指向
q->next = p->next;
free(p);
return true;
}
//按值刪除
bool Del_val(PClist plist, ELEMTYPE val)
{
//assert
struct Clist* p = Search(plist, val);
if (p == NULL)
{
return false;
}
struct Clist* q = plist;
for (q; q->next != p; q = q->next);
q->next = p->next;
free(p);
return true;
}
//查找(如果查找到,需要返回找到的這個節點的地址)
struct Clist* Search(struct Clist* plist, ELEMTYPE val)
{
//assert
for (struct Clist* p = plist->next; p != plist; p = p->next)
{
if (p->data == val)
{
return p;
}
}
return NULL;
}
//判空
bool IsEmpty(PClist plist)
{
//assert
return plist->next == plist;
}
//判滿(循環鏈表不需要這個操作)
//獲取長度
/*指針p從頭結點的下一個節點開始向后跑,如果p再次遇到了頭結點,
證明p走了一圈回來了,這是有效節點肯定已經遍歷結束*/
int Get_length(PClist plist)
{
//不帶前驅的for循環 跑一遍就好
int count = 0;
for (struct Clist* p = plist->next; p != plist; p = p->next)
{
count++;
}
return count;
}
//清空
void Clear(PClist plist)
{
//assert
Destroy1(plist);
}
//銷毀1(無限頭刪)
void Destroy1(PClist plist)
{
//assert
while (plist->next != plist)
{
struct Clist* p = plist->next;
plist->next = p->next;
free(p);
}
plist->next = plist;
}
//銷毀2(要用到兩個指針變量)
void Destroy2(PClist plist)
{
//assert
struct Clist* p = plist->next;
struct Clist* q = NULL;
plist->next = plist;
while (p != plist)
{
q = p->next;
free(p);
p = q;
}
}
//打印
void Show(struct Clist* plist)
{
//assert
for (struct Clist* p = plist->next; p != plist; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
原文鏈接:https://blog.csdn.net/weixin_56935264/article/details/123592599
相關推薦
- 2022-01-20 npm 報錯: npm ERR! code ERESOLVE , npm ERR! code E40
- 2022-06-08 報錯:No fallback instance of type class**解決辦法
- 2022-10-23 python操作SqlServer獲取特定表的所有列名(推薦)_python
- 2022-06-12 Linux中rm命令使用以及C/C++代碼實現_C 語言
- 2022-10-22 關于redis的延遲雙刪策略總結_Redis
- 2022-11-22 docker+Nginx部署前端項目的詳細過程記錄_docker
- 2022-12-24 C++中的模板類繼承和成員訪問問題_C 語言
- 2023-12-16 @Configuration(proxyBeanMethods = true)
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支