資料結構中的線性離散儲存-連結串列
在上節,我們已經瞭解到了線性儲存中的連續儲存,我們還把這種儲存結構叫做順序表,或者陣列。並且知道線性連續儲存存在以下優缺點:
- 優點:能實現快速追加和存取元素
- 缺點:插入元素或刪除元素都要移動大量的原有元素
在本節,我們將一起來了解《資料結構》中研究的另一種線性資料結構-離散儲存,我們也可以把線性的離散儲存叫做連結串列。連結串列的基本結構如下圖:
如果你沒有閱讀過本系列的前面部門文章,建議您通過以下連結先閱讀之前的內容:
- 1.從線性連續儲存開始,重新認識《資料結構》 https://blog.jkdev.cn/index.php/archives/327/
一、連結串列的實現過程
1.定義連結串列節點
和順序表相比,連結串列的儲存結構在實現插入、刪除時,不需要移動大量的元素。但不容易實現隨機存取元素線性表中第i個元素的操作。所以連結串列適用於需要經常進行插入和刪除的操作的線性表,如飛機航班乘客表。
首先我們定義一個02-LinkList.cpp
檔案,需要引入基本的c語言標頭檔案,並且定義連結串列節點的結構體
# include <stdio.h> // 標準io頭部,包含printf函式 # include <malloc.h> // 包含malloc函式,在mac電腦上,改為sys/malloc.h # include <stdlib.h> // 包含exit函式 typedef struct Node { int data; // 資料域 struct Node *pNext; // 指標域 } NODE, *PNODE; // NODE 等價於 struct Node, *PNODE 等價於* Node
2.建立連結串列
接下來我們定義建立連結串列的函式
PNODE create_list(void) { int len; // 存放節點的有效個數 int val; //存放使用者輸入的臨時存入的節點的值 // 分配一個不存在任何資料的頭節點 PNODE pHead = (PNODE)malloc(sizeof(NODE)); if (NULL == pHead) { printf("記憶體分配失敗!\n"); exit(-1); } // 初始狀態下,連結串列尾節點和頭節點指向同一個記憶體(即頭節點就是尾節點),而指標域為NULL PNODE pTail = pHead; pTail->pNext = NULL; printf("請輸入您需要生成的連結串列節點的個數:len="); scanf("%d", &len); for (int i = 0; i < len; i++) { printf("請輸入第%d個節點的值", i + 1); scanf("%d", &val); PNODE pNew = (PNODE)malloc(sizeof(NODE)); if (NULL == pNew) { printf("分配失敗,程式終止!\n"); exit(-1); } // 分配成功,給新節點賦值 pNew->data = val; // 讓連結串列尾節指標域點指向最新的節點,實現增加新節點 pTail->pNext = pNew; // 新節點的指標域為NULL pNew->pNext = NULL; // 最後,再讓尾節點指向新節點。 pTail = pNew; } // 連結串列建立完成後,返回頭節點 return pHead; }
3.遍歷連結串列元素
從頭節點開始,如果連結串列節點的指標域不為NULL,即輸出資料
void traverse_list(PNODE pHead)
{
// 把第一個節點賦給變數p
PNODE p = pHead->pNext;
while (NULL != p)
{
// p 不為NULL,代表有資料,則輸出p的資料於
printf("%d ", p->data);
// 輸出p的資料域之後,讓p指向下一個節點
p = p->pNext;
}
printf("\n");
return;
}
4.判斷連結串列是否為空和計算連結串列長度
如果連結串列頭節點的指標域為空,則連結串列是空連結串列。長度的計算則通過遍歷連結串列來計算,如下程式碼
// 判斷連結串列是否為空
bool is_empty(PNODE pHead)
{
if (NULL == pHead->pNext)
{
return true;
}
else
{
return false;
}
}
// 計算連結串列的長度
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while (NULL != 0)
{
++len;
p = p->pNext;
}
return len;
}
5.連結串列排序
接下來,我們根據從小到大的資料域值對連結串列節點進行排序。連結串列的排序和順序表類似,我們使用兩個節點變數用於臨時儲存對比中的兩個節點,如下程式碼
// 連結串列排序
void sort_list(PNODE pHead)
{
int i, j, t;
int len = length_list(pHead);
// 定義p和q兩個節點變數,用於臨時存放交換節點
PNODE p, q;
// 讓p指向當前節點
for (i = 0, p = pHead->pNext; i < len; i++, p = p->pNext)
{
// 讓q指向下一個節點
for (j = i + 1, q = p->pNext; i < len; j++, q = q->pNext)
{
// 用當前節點和下一個節點進行對比,如果當前節點的資料域大於下一個節點,就將資料進行交換
if (p->data > q->data)
{
t = p->data;
p->data = q->data;
q->data = t;
}
}
}
}
6.插入新節點
在接下來的插入和刪除操作中,我們記連結串列的索引為position,position從0開始。首先,在連結串列的position位置插入節點,該節點的值是val,程式碼如下
// 插入節點
bool insert_list(PNODE pHead, int position, int val)
{
int len = length_list(pHead);
if (len < 0 || position > len)
{
return false;
}
int i = 0;
PNODE p = pHead;
// 使用while迴圈,使p變數指向position節點的前一個節點
while (NULL != p->pNext && i < position)
{
p = p->pNext;
++i;
}
// 程式執行到這裡,p已經指向position節點的前一個節點,position節點是否為空無所謂
// 插入過程1:分配新節點
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("動態分配記憶體失敗失敗");
exit(-1);
}
// 插入過程2:將傳入的值賦給新節點的資料域
pNew->data = val;
// 插入過程3:用變數q臨時儲存position節點
PNODE q = p->pNext;
// 插入過程4:將position節點的前一個節點指向新節點
p->pNext = pNew;
// 插入過程5:再將新節點指向posion節點
pNew->pNext = q;
return true;
}
7.刪除節點
刪除節點和插入節點操作類似。區別在於,插入節操作在找到position節點後,動態分配新空間並插入到原連結串列的position位置,刪除節點操作則在找到position節點之後,釋放position節點的空間,再把原position旁邊兩個不相連的節點連線起來。
// 刪除節點
bool delete_list(PNODE pHead, int position, int *pVal)
{
int len = length_list(pHead);
if (len < 0 || position > len)
{
return false;
}
int i = 0;
PNODE p = pHead;
// 使用while迴圈,使p變數指向position節點的前一個節點
while (NULL != p->pNext && i < position)
{
p = p->pNext;
++i;
}
// 如果position節點為NULL,返回false
if (NULL == p->pNext)
{
return false;
}
// 程式執行到這裡,p已經指向position節點的前一個節點,並且position節點是存在的
// 刪除過程1,讓q變數指向position節點
PNODE q = p->pNext;
// 刪除過程2,將position節點的資料賦給pVal
*pVal = q->data;
// 刪除過程3,讓position節點的前一個節點指向position節點的下一個
p->pNext = p->pNext->pNext;
// 刪除過程4,釋放position節點指向的記憶體,並讓q變數指向NULL
free(q);
q = NULL;
return true;
}
二、測試與驗證
1.測試
所有測試程式碼如下
#include <stdio.h>
#include <sys/malloc.h>
#include <stdlib.h>
// 定義連結串列節點
typedef struct Node
{
int data; // 資料域
struct Node *pNext; // 指標域
} NODE, *PNODE; // NODE 等價於 struct Node, *PNODE 等價於* Node
// 建立一個非迴圈的單鏈表
PNODE create_list(void)
{
int len; // 存放節點的有效個數
int val; //存放使用者輸入的臨時存入的節點的值
// 分配一個不存在任何資料的頭節點
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if (NULL == pHead)
{
printf("記憶體分配失敗!\n");
exit(-1);
}
// 初始狀態下,連結串列尾節點和頭節點指向同一個記憶體(即頭節點就是尾節點),而指標域為NULL
PNODE pTail = pHead;
pTail->pNext = NULL;
printf("請輸入您需要生成的連結串列節點的個數:len=");
scanf("%d", &len);
for (int i = 0; i < len; i++)
{
printf("請輸入第%d個節點的值:", i + 1);
scanf("%d", &val);
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("分配失敗,程式終止!\n");
exit(-1);
}
// 分配成功,給新節點賦值
pNew->data = val;
// 讓連結串列尾節指標域點指向最新的節點,實現增加新節點
pTail->pNext = pNew;
// 新節點的指標域為NULL
pNew->pNext = NULL;
// 最後,再讓尾節點指向新節點。
pTail = pNew;
}
// 連結串列建立完成後,返回頭節點
return pHead;
}
// 遍歷連結串列
void traverse_list(PNODE pHead)
{
// 把第一個節點賦給變數p
PNODE p = pHead->pNext;
while (NULL != p)
{
// p 不為NULL,代表有資料,則輸出p的資料於
printf("%d ", p->data);
// 輸出p的資料域之後,讓p指向下一個節點
p = p->pNext;
}
printf("\n");
return;
}
// 判斷連結串列是否為空
bool is_empty(PNODE pHead)
{
if (NULL == pHead->pNext)
{
return true;
}
else
{
return false;
}
}
// 計算連結串列的長度
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while (NULL != p)
{
++len;
p = p->pNext;
}
return len;
}
// 連結串列排序
void sort_list(PNODE pHead)
{
int i, j, t;
int len = length_list(pHead);
// 定義p和q兩個節點變數,用於臨時存放交換節點
PNODE p, q;
// 讓p指向當前節點
for (i = 0, p = pHead->pNext; i < len - 1; i++, p = p->pNext)
{
// 讓q指向當前節點的下一個節點
for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
{
// 用當前節點和下一個節點進行對比,如果當前節點的資料域大於下一個節點,就將資料進行交換
if (p->data > q->data)
{
t = p->data;
p->data = q->data;
q->data = t;
}
}
}
}
// 插入節點
bool insert_list(PNODE pHead, int position, int val)
{
int len = length_list(pHead);
if (len < 0 || position > len)
{
return false;
}
int i = 0;
PNODE p = pHead;
// 使用while迴圈,使p變數指向position節點的前一個節點
while (NULL != p->pNext && i < position)
{
p = p->pNext;
++i;
}
// 程式執行到這裡,p已經指向position節點的前一個節點,position節點是否為空無所謂
// 插入過程1:分配新節點
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("動態分配記憶體失敗失敗");
exit(-1);
}
// 插入過程2:將傳入的值賦給新節點的資料域
pNew->data = val;
// 插入過程3:用變數q臨時儲存position節點
PNODE q = p->pNext;
// 插入過程4:將position節點的前一個節點指向新節點
p->pNext = pNew;
// 插入過程5:再將新節點指向posion節點
pNew->pNext = q;
return true;
}
// 刪除節點
bool delete_list(PNODE pHead, int position, int *pVal)
{
int len = length_list(pHead);
if (len < 0 || position > len)
{
return false;
}
int i = 0;
PNODE p = pHead;
// 使用while迴圈,使p變數指向position節點的前一個節點
while (NULL != p->pNext && i < position)
{
p = p->pNext;
++i;
}
// 如果position節點為NULL,返回false
if (NULL == p->pNext)
{
return false;
}
// 程式執行到這裡,p已經指向position節點的前一個節點,並且position節點是存在的
// 刪除過程1,讓q變數指向position節點
PNODE q = p->pNext;
// 刪除過程2,將position節點的資料賦給pVal
*pVal = q->data;
// 刪除過程3,讓position節點的前一個節點指向position節點的下一個
p->pNext = p->pNext->pNext;
// 刪除過程4,釋放position節點指向的記憶體,並讓q變數指向NULL
free(q);
q = NULL;
return true;
}
int main()
{
// 建立連結串列,定義長度為6,輸入1、5、6、4、3、2
PNODE pHead = create_list();
// 遍歷元素:輸出 1 5 6 4 3 2
traverse_list(pHead);
// 連結串列排序
sort_list(pHead);
// 遍歷元素:輸出 1 2 3 4 5 6
traverse_list(pHead);
// 在索引為0的位置新增元素7
insert_list(pHead, 0, 7);
// 遍歷元素:輸出 7 1 2 3 4 5 6
traverse_list(pHead);
// 在索引為5的位置刪除元素,並輸出刪除的元素
int val;
delete_list(pHead, 5, &val);
// 遍歷元素,輸出:
traverse_list(pHead);
// 列印刪除的元素
printf("被刪除的元素資料域是%d\n", val);
return 0;
}
2.結果
程式編譯與執行的結果如下:
pan@pandeMBP ds % g++ 02-LinkList.cpp
pan@pandeMBP ds % ./a.out
請輸入您需要生成的連結串列節點的個數:len=6
請輸入第1個節點的值:1
請輸入第2個節點的值:5
請輸入第3個節點的值:6
請輸入第4個節點的值:4
請輸入第5個節點的值:3
請輸入第6個節點的值:2
1 5 6 4 3 2
1 2 3 4 5 6
7 1 2 3 4 5 6
7 1 2 3 4 6
被刪除的元素資料域是5
本文原創首發自wx訂閱號:極客開發中up,禁止轉載