資料結構——連結串列
阿新 • • 發佈:2021-12-05
鎮樓圖
Pixiv:飛者
〇、連結串列構建前的細節問題
順序表構建時一定需要一個確定的長度嗎?
順序表也可以採用類似連結串列的方式增加節點,也就是每新增一個節點就開闢一塊記憶體(realloc)
這樣順序表可以不必增加len引數
是否帶頭節點?
在連結串列中通常要帶頭節點指明整個結構。
如果不帶,在涉及到表頭的操作時則需要修改首元素地址增加額外不必要的負擔
是否迴圈?
和順序表一樣,可做可不做
單鏈表還是雙鏈表?
連結串列的索引關係是通過指標來完成的
單鏈表只有一個指標會使得索引關係具有單向性,即除表頭表尾每個節點都只有後繼而無前驅
雙鏈表存在兩個指標會使得索引關係具有雙向性,即除表頭表尾每個節點都有後繼和前驅
(個人觀點:順序表是除表頭表尾都有前驅後繼,不過從圖的觀點來看,順序表可以認為是一種完全圖)
選擇什麼引數?
這裡我推薦加上cnt當前長度的引數
一、單鏈表
typedef struct{
int value;
}element,*Element;
typedef struct __llnode{
element data;//資料域
struct __llnode *next;//指標域
}*llist_head,*llist_node,llist_size;
typedef struct{ llist_head head; int cnt; }llist; /*一般情況得到上面的即可 不過我增加了額外引數需要進一步封裝
基本操作
(1)建立單鏈表
void llist_create(llist &L){
//作用:建立連結串列
L.cnt = 0;
L.head = (llist_head)malloc(sizeof(llist_size));
L.head->next = NULL;
}
(2)輸入/輸出
void llist_input(llist &L,int n){ //作用:輸入n個數據 if(n < 0)return; llist_node p = L.head; L.cnt += n; while(p->next)p = p->next; while(n--){ llist_node newnode = new llist_size; newnode->next = NULL; p->next = newnode; scanf("%d ",&newnode->data.value); p = p->next; } }
void llist_output(llist &L){
//作用:輸出連結串列
llist_node p = L.head->next;
while(p){
printf("%d ",p->data.value);
p = p->next;
}
printf("\n");
}
(3)判斷單鏈表是否為空
bool llist_ifempty(llist &L){
//作用:判斷連結串列是否為空
return (L.head->next) ? true : false ;
}
(4)獲取單鏈表長度(假設不存在cnt引數)
int llist_length(llist &L){
//作用:獲取連結串列當前長度
int count = 0;
llist_node p = L.head->next;
while(p){
count++;
p = p->next;
}
return count;
}
(5)前/後.插入位序為i的引數
void llist_insertpre1(llist &L,int pos,element e){
//作用:前插入元素e
if(pos < 1 || pos > L.cnt+1){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
//一:pos-1將前插變為後插
while(pos---1)p = p->next;
llist_node newnode = (llist_node)malloc(sizeof(llist_size));
newnode->data = e;
newnode->next = p->next;
p->next = newnode;
L.cnt++;
}
void llist_insertpre2(llist L,int pos,element e){
//作用:前插入元素e
if(pos < 1 || pos > L.cnt){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
/*二:假設得到的就是當前元素p
然後需要前插newnode元素
可以交換p和newnode的資料域
這樣就變成了如何在newnode後插p的問題
在已知p指標資訊的情況下
該演算法時間複雜度降低為O(1)
不過缺點是pos範圍從原來的[1,n+1]
變為[1,n](n > 0)或Ø(n = 0)
*/
while(pos--)p = p->next;
llist_node newnode = (llist_node)malloc(sizeof(llist_size));
newnode->data = p->data;
p->data = e;
newnode->next = p->next;
p->next = newnode;
L.cnt++;
}
void llist_insertpost(llist &L,int pos,element e){
//作用:後插入元素e
if(pos < 0 || pos > L.cnt){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
for(int i = 0;i < pos;i++)p = p->next;
llist_node newnode = (llist_node)malloc(sizeof(llist_size));
newnode->data = e;
newnode->next = p->next;
p->next = newnode;
L.cnt++;
}
(6)刪除位序為i的元素
void llist_delete(llist L,int pos){
//作用:刪除元素e
if(pos < 1 || pos > L.cnt){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head,d = L.head->next;
while(pos---1){
p = p->next;
d = d->next;
}
p->next = d->next;
free(d);
L.cnt--;
}
/*還有一種演算法可以只得到要刪除元素的前一位p
然後只需要刪除p的後繼即可,無需前驅
其他操作
(1)反轉單鏈表
void llist_reverse(llist &L){
//最簡單的是直接遞迴逆向輸出(不過多少不會有人接受這種演算法)
//這裡採用改變索引關係得到
llist_node p1 = L.head;
llist_node p2 = L.head->next;
llist_node t = NULL;
while(p2){
t = p2->next;
p2->next = p1;
p1 = p2;
p2 = t;
}
L.head->next->next = NULL;
L.head->next = p1;
}
(2)合併兩個有序連結串列
void llist_union(llist_node &p1,llist_node &p2,llist &L3){
/*這個演算法我已經在順序表中闡述過了
不過本次採用遞迴思路
下一次佇列時我會闡述n次選擇思想
*/
//注:L3必須為空連結串列
if(!p1 && !p2)return;
if(!p1){
llist_insertpost(L3,L3.cnt,p2->data);
llist_union(p1,p2->next,L3);
}else if(!p2){
llist_insertpost(L3,L3.cnt,p1->data);
llist_union(p1->next,p2,L3);
}else{
if(p1->data.value > p2->data.value){
llist_insertpost(L3,L3.cnt,p1->data);
llist_union(p1->next,p2,L3);
}else{
llist_insertpost(L3,L3.cnt,p2->data);
llist_union(p1,p2->next,L3);
}
}
}
二、做迴圈處理的連結串列
這裡不再使用程式碼演示,僅僅談我對於迴圈處理的看法
迴圈處理即最後一個元素的指標不指向NULL,而是指向頭節點或第一個節點
大幅提高遍歷的靈活性,可以從任意節點開始遍歷得到整個表,如約瑟夫環問題
涉及到與表尾與表頭的處理上有所變化,比如判斷空表、是否達到表尾等都有所變化
不過一般情況是不需要做迴圈處理的
三、雙鏈表
單鏈表在索引時只具有單向性,在部分操作上稍麻煩(如刪除元素時若不做特殊處理就無法得到前驅的尷尬)
雙鏈表在索引上具有雙向性,比如得知某一節點就能推出前驅和後繼
typedef struct{
int value;
}element,*Element;
typedef struct __llnode{
element data;//資料域
struct __llnode *prior,*next;//指標域
}*llist_head,*llist_node,llist_size;
typedef struct{
llist_head head;
int cnt;
}llist;
基本操作
(1)建立雙鏈表
void llist_create(llist &L){
L.cnt = 0;
L.head = (llist_head)malloc(sizeof(llist_size));
L.head->prior = NULL;
L.head->next = NULL;
}
(2)輸入/輸出
void llist_input(llist &L,int n){
//作用:輸入n個數據
if(n < 0)return;
llist_node p = L.head;
L.cnt += n;
while(p->next)p = p->next;
while(n--){
llist_node newnode = new llist_size;
newnode->next = NULL;
newnode->prior = p;
p->next = newnode;
scanf("%d ",&newnode->data.value);
p = p->next;
}
}
(3)判斷是否為空
bool llist_ifempty(llist &L){
//作用:判斷連結串列是否為空
return (L.head->next) ? true : false ;
}
(4)前/後插入
void llist_insertpre(llist &L,int pos,element e){
//作用:前插入元素e
if(pos < 1 || pos > L.cnt+1){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
while(pos--)p = p->next;
llist_node newnode = new llist_size;
newnode->data = e;
newnode->prior = p->prior;
newnode->next = p;
p->prior->next = newnode;
p->prior = newnode;
L.cnt++;
}
void llist_insertpost(llist &L,int pos,element e){
//作用:後插入元素e
if(pos < 0 || pos > L.cnt){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
for(int i = 0;i < pos;i++)p = p->next;
llist_node newnode = (llist_node)malloc(sizeof(llist_size));
newnode->data = e;
newnode->prior = p;
newnode->next = p->next;
p->next->prior = newnode;
p->next = newnode;
L.cnt++;
}
(5)刪除
void llist_delete(llist L,int pos){
//作用:刪除元素e
if(pos < 1 || pos > L.cnt){
printf("錯誤:超出範圍");
return;
}
llist_node p = L.head;
while(pos--)p = p->next;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
L.cnt--;
}
參考教程
參考書籍
《資料結構與演算法分析 C語言描述》