數據結構與算法之線性表單向循環鏈表
在上一篇《數據結構與算法之基礎篇》中,瞭解了
算法和數據結構
的一些概念問題,以及線性表順序存儲
和單鏈表
的創建、插入、刪除等一些操作。這一篇來了解一下線性表單向循環鏈表的一些操作。
1. 單向循環鏈表概念
1.1 單向循環鏈表
是單鏈表的一個變形,鏈表中最後一個節點的next域不再爲None,而是指向鏈表的頭節點。
1.2 鏈表結構和順序存儲的優缺點:
存儲分配方式
- 順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素;
- 單鏈表採用鏈式存儲結構,⽤用⼀一組任意的存儲單元存放線性表的元素
時間性能
- 查找
順序存儲 O(1)
單鏈表O(n)
- 插入和刪除
存儲結構需要平均移動一個表長一半的元素,時間O(n)
單鏈表查找某位置後的指針後,插⼊和刪除爲 O(1)
空間性能
- 順序存儲結構需要預先分配存儲空間,分太⼤大,浪費空間;分⼩小了了,發⽣生上溢出;
- 單鏈表不不需要分配存儲空間,只要有就可以分配, 元素個數也不不受限制;
2. 單向循環鏈表的操作
準備工作:定義相關變量
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存儲空間初始分配量 */
typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定,這裏假設爲int */
//定義結點
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
2.1. 單向循環鏈表的創建
單向循環鏈表的創建分爲2種情況:
- 第一次開始創建;
- 已經創建,往裏面新增數據
判斷是否第一次創建鏈表
YES
: 創建一個新結點,並使得新結點的next
指向自身,(*L)->next = (*L)
;NO
: 找鏈表尾結點,將尾結點的next = 新結點
,新結點的next = (*L)
;
而鏈表找尾,可以有兩種寫法,一種爲for循環
,一種爲定義一個變量記錄。
創建代碼示例:
// for循環找尾
Status CreateList(LinkList *L){
int item;
LinkList temp = NULL;
LinkList target = NULL;
printf("輸入節點的值,輸入0結束\n");
while(1)
{
scanf("%d",&item);
if(item==0) break;
//如果輸入的鏈表是空。則創建一個新的節點,使其next指針指向自己 (*head)->next=*head;
if(*L==NULL)
{
*L = (LinkList)malloc(sizeof(Node));
if(!L)exit(0);
(*L)->data=item;
(*L)->next=*L;
}
else
{
//輸入的鏈表不是空的,尋找鏈表的尾節點(for循環找尾),使尾節點的next=新節點。新節點的next指向頭節點
for (target = *L; target->next != *L; target = target->next);
temp=(LinkList)malloc(sizeof(Node));
if(!temp) return ERROR;
temp->data=item;
temp->next=*L; //新節點指向頭節點
target->next=temp;//尾節點指向新節點
}
}
return OK;
}
// 定義變量 r, 記錄尾
Status CreateList2(LinkList *L){
int item;
LinkList temp = NULL;
LinkList target = NULL;
LinkList r = NULL;
printf("請輸入新的結點, 當輸入0時結束!\n");
while (1) {
scanf("%d",&item);
if (item == 0) {
break;
}
//第一次創建
if(*L == NULL){
*L = (LinkList)malloc(sizeof(Node));
if(!*L) return ERROR;
(*L)->data = item;
(*L)->next = *L;
r = *L;
}else{
temp = (LinkList)malloc(sizeof(Node));
if(!temp) return ERROR;
temp->data = item;
temp->next = *L;
r->next = temp;
// 記錄尾
r = temp;
}
}
return OK;
}
2.2. 單向循環鏈表的遍歷
頭節點就有值,可以用do...while
void traverseLinkList(LinkList p)
{
//如果鏈表是空
if(p == NULL){
printf("打印的鏈表爲空!\n");
return;
}else{
LinkList temp;
temp = p;
do{
printf("%3d",temp->data);
temp = temp->next;
}while (temp != p);
printf("\n");
}
}
2.3. 單向循環鏈表的插入
單向循環列表的插入分爲兩種情況:
- 插入位置在首元節點(特殊處理)
- 創建
新節點temp
,並判斷是否創建成功,成功則賦值 - 找到鏈接最後的尾結點,即找尾。
- 將
新節點temp
的next
->頭結點
尾結點的next
指向新的頭結點
- 讓頭指針指向
temp
(臨時的新結點)
- 創建
2. 插入位置在其他位置
* 創建新節點temp
,並判斷是否創建成功,成功則賦值
* 找到插入的位置,如果超過鏈表長度,則自動插入隊尾
* 通過target
找到要插入位置的前一個結點(即插入結點的前驅), 讓target->next = temp
,
* 插入結點的前驅的next
指向新結點,新結點的next
指向target
原來的next
位置
//4.3 循環鏈表插入數據
Status ListInsert(LinkList *L, int place, int num){
LinkList temp ,target;
int i;
if (place == 1) { //如果插入的位置爲1, 特殊處理
//1. 創建新結點temp,並判斷是否創建成功,成功則賦值,否則返回ERROR;
temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) {
return ERROR;
}
temp->data = num;
//2. 找到鏈表最後的結點_尾結點,
for (target = *L; target->next != *L; target = target->next);
//3. 讓新結點的next 指向頭結點.
temp->next = *L;
//4. 尾結點的next 指向新的頭結點;
target->next = temp;
//5. 讓頭指針指向temp(臨時的新結點)
*L = temp;
}else { //如果插入的位置在其他位置;
//1. 創建新結點temp,並判斷是否創建成功,成功則賦值,否則返回ERROR;
temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) {
return ERROR;
}
temp->data = num;
//2. 先找到插入的位置,如果超過鏈表長度,則自動插入隊尾;
for ( i = 1,target = *L; target->next != *L && i != place - 1; target = target->next,i++) ;
//3. 新結點的next 指向target原來的next位置;
temp->next = target->next;
//4. 通過target找到要插入位置的前一個結點, 讓target->next = temp;
target->next = temp;
}
return OK;
}
2.4. 單向循環鏈表的刪除
單向循環列表的插入分爲兩種情況:
-
刪除第一個位置
-
刪除到只剩下首元結點了,則直接將
*L
鏈表置空 -
鏈表還有很多數據,但是刪除的是首結點
-
找到
尾結點 target
, 使得尾結點 next
指向頭結點
的下一個結點,即target->next = (*L)->next
; -
新結點
做爲頭結點
,釋放原來的頭結點
-
-
-
刪除其他位置
- 找到刪除結點前一個結點
target
- 定義變量
temp
,記錄刪除的節點,即:temp = target->next
- 使得
target->next
指向刪除位置的下一個結點 - 釋放需要刪除的結點temp
- 找到刪除結點前一個結點
Status LinkListDelete(LinkList *L,int place){
LinkList temp,target;
int i;
//temp 指向鏈表首元結點
temp = *L;
if(temp == NULL) return ERROR;
if (place == 1) {
//1>.如果刪除到只剩下首元結點了,則直接將*L置空;
if((*L)->next == (*L)){
(*L) = NULL;
return OK;
}
//2>.鏈表還有很多數據,但是刪除的是首結點;
//1. 找到尾結點, 使得尾結點 next 指向頭結點的下一個結點 target->next = (*L)->next;
for (target = *L; target->next != *L; target = target->next);
// 記錄頭結點
temp = *L;
//2. 新結點做爲頭結點,則釋放原來的頭結點
*L = (*L)->next;
target->next = *L;
free(temp);
}else { //如果刪除其他結點--其他結點
//1. 找到刪除結點前一個結點target
for(i=1,target = *L;target->next != *L && i != place -1;target = target->next,i++) ;
// 記錄要刪除的節點
temp = target->next;
// 使得刪除節點的前驅target->next 指向刪除節點的後繼
target->next = temp->next;
//3. 釋放需要刪除的結點temp
free(temp);
}
return OK;
}
2.5. 單向循環鏈表的簡單查詢
int findValue(LinkList L,int value){
int i = 1;
LinkList p;
p = L;
//尋找鏈表中的結點 data == value
while (p->data != value && p->next != L) {
i++;
p = p->next;
}
//當尾結點指向頭結點就會直接跳出循環,所以要額外增加一次判斷尾結點的data == value;
if (p->next == L && p->data != value) {
return -1;
}
return i;
}