單鏈表(C語言實現)學習記錄
# 單鏈表(C語言實現)學習記錄
## 概念
### 連結方式儲存
連結方式儲存的線性表簡稱為連結串列(Linked List)。
連結串列的具體儲存表示為:
- 用一組任意的儲存單元來存放線性表的結點(這組儲存單元既可以是連續的,也可以是不連續的)。
- 連結串列中結點的邏輯次序和物理次序不一定相同。為了能正確表示結點間的邏輯關係,在儲存每個結點值的同時,還必須儲存指示其後繼結點的地址(或位置)資訊(稱為指標(pointer)或鏈(link))。
鏈式儲存是最常用的儲存方式之一,它不僅可用來表示線性表,而且可用來表示各種非線性的資料結構。
### 單鏈表
單鏈表是一種鏈式存取
的資料結構,用一組地址任意的儲存單元存放線性表中的資料元素。
連結串列中的資料是以結點來表示的,每個結點的構成為:
元素(資料元素的映象,通常稱為“資料域”) + 指標(指示後繼元素儲存位置,通常稱為“指標域”)。
元素就是儲存資料的儲存單元,指標就是連線每個結點的地址資料。
如圖1所示,資料域data--存放結點值的資料域;指標域next--存放結點的直接後繼的地址(位置)的指標域(鏈域)。
連結串列通過每個結點的鏈域將線性表的n個結點按其邏輯順序連結在一起的,每個結點只有一個鏈域的連結串列稱為單鏈表(Single Linked List)。
圖1 連結串列結點的結構
### 頭指標pHead、頭結點pHeadNode、首元結點p1Node和終端結點(尾結點)pTailNode
- 頭結點pHeadNode:
有時,在連結串列的第一個結點之前會額外增設一個結點,結點的資料域一般不存放資料(有些情況下也可以存放連結串列的長度等資訊),此結點被稱為頭結點。
若頭結點的指標域為空(NULL),表明連結串列是空表(如圖2 所示)。頭結點對於連結串列來說,不是必須的,在處理某些問題時,給連結串列新增頭結點會使問題變得簡單。
圖2 空連結串列
- 頭指標pHead:
永遠指向連結串列中第一個結點的位置(如果連結串列有頭結點,頭指標指向頭結點;否則,頭指標指向首元結點)。
- 頭結點和頭指標的區別:
頭指標是一個指標,頭指標指向連結串列的頭結點或者首元結點;頭結點是一個實際存在的結點,它包含有資料域和指標域。兩者在程式中的直接體現就是:頭指標只宣告而沒有分配儲存空間,頭結點進行了宣告並分配了一個結點的實際實體記憶體。
單鏈表中可以沒有頭結點,但是不能沒有頭指標!
單鏈表中每個結點的儲存地址是存放在其前趨結點next域中。
開始結點無前趨,故應設頭指標pHead指向開始結點。
連結串列由頭指標唯一確定,單鏈表可以用頭指標的名字來命名。
- 首元結點p1Node:
連結串列中第一個元素所在的結點,如果存在頭結點則它是頭結點後邊的第一個結點。如圖 3 所示。
圖3 非空連結串列
- 終端結點(尾結點)pTailNode:
終端結點(尾結點)無後繼,故終端結點的指標域為空,即NULL。
## 單鏈表的定義
C語言使用結構體來定義單鏈表:
//定義結點資料域的型別
typedef char DataType;
//定義結點
typedef struct Node{
DataType data;//資料域
struct Node *next;//指標域
}Node;
//Node和SinglyLinkedList是不同名字的同一個型別(命名的不同是為了概念上更明確)
typedef struct Node SinglyLinkedList;
//顯示定義SinglyLinkedList型別的指標變數*pHead表示它是單鏈表的頭指標
SinglyLinkedList *pHead;
## 單鏈表的建立
### 初始化
帶頭結點的單鏈表的初始化就是建立一個頭結點,給他分配儲存空間。並將頭結點的指標域指向NULL。
/**
* 初始化單鏈表,建立一個帶頭結點的空連結串列
* @return 連結串列頭指標
*/
SinglyLinkedList *InitSinglyLinkedList()
{
// 申請儲存空間可使用malloc()函式實現,需設立一申請單元指標,這裡是頭指標pHead,
// 但malloc()函式得到的指標並不是指向結構體的指標,因此,需使用強制型別轉換,
// 將其轉換成結構體型指標。
pHead = (SinglyLinkedList *)malloc(sizeof(SinglyLinkedList));
// 剛開始時,連結串列還沒建立,是一空連結串列,pHead結點的next指標為NULL。
pHead->next = NULL;
return pHead;
}
單鏈表是使用者不斷申請儲存單元和改變連結關係而得到的一種特殊資料結構,將連結串列的左邊稱為鏈頭,右邊稱為鏈尾。
帶頭結點的單鏈表的建立有頭插法、尾插法兩種方法。
- 頭插法
頭插法建單鏈表是將連結串列右端看成固定的,連結串列不斷向左延伸而得到的。頭插法最先得到的是尾結點。如圖 4 所示:
圖4 頭插法
由於連結串列的長度是隨機的,故用一個for迴圈來控制連結串列中結點個數。
申請儲存空間可使用malloc()函式實現,需設立一申請單元指標,但malloc()函式得到的指標並不是指向結構體的指標,需使用強制型別轉換,將其轉換成結構體型指標。
剛開始時,連結串列還沒建立,是一空連結串列,pHead指標為NULL。
連結串列建立的過程是申請空間、得到資料、建立連結的迴圈處理過程。
頭插法實現程式碼如下:
/**
* 頭插法建立帶頭結點的單鏈表
* 如:pHead-->d-->c-->b-->a-->NULL [逆序]
* @param pHead 連結串列頭指標
* @param pData 要插入資料的指標
* @param dataCount 要插入資料的數量
* @return 插入後連結串列的頭指標
*/
SinglyLinkedList *
CreateListFrHead (SinglyLinkedList *pHead, DataType *pData, int dataCount)
{
//建立一個搜尋結點,用於遍歷連結串列
SinglyLinkedList *pCurrent = pHead;
for(int i = 0; i < dataCount; i++)
{
// 建立新結點pInsertNode用於存放要插入的資料
SinglyLinkedList *pInsertNode = (SinglyLinkedList *)malloc(sizeof(SinglyLinkedList));
pInsertNode->data = pData[i];
// 將pInsertNode插在原結點之前,前驅結點之後
// 因為每個結點的地址都是存放在其前驅結點的指標域next中
pInsertNode->next = pCurrent->next; //原結點之前
pCurrent->next = pInsertNode; //前驅節點結點之後
}
return pHead;
}
- 尾插法
若將連結串列的左端固定,連結串列不斷向右延伸,這種建立連結串列的方法稱為尾插法。如圖 5 所示:
圖5 尾插法
尾插法建立連結串列時,頭指標固定不動,故必須設立一個搜尋指標,向連結串列右邊延伸,則整個演算法中應設立三個連結串列指標,即頭指標pHead、搜尋指標pCurrent、申請單元指標pInsertNode。尾插法最先得到的是頭結點。
尾插法實現程式碼如下:
/**
* 尾插法建立帶頭結點的單鏈表
* 例如:pHead-->a-->b-->c-->d-->NULL [順序]
* @param pHead 連結串列頭指標
* @param pData 要插入的資料指標
* @param dataCount 要插入的資料數量
* @return 插入後的連結串列頭指標
*/
SinglyLinkedList * CreateListFrTail(SinglyLinkedList *pHead, DataType *pData,
int dataCount) {
//建立搜尋指標pCurrent用於遍歷連結串列
SinglyLinkedList *pCurrent = pHead;
//遍歷連結串列
for (int i = 0; i < dataCount; i++) {
//建立新結點pInsertNode用於儲存要插入的資料
SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
sizeof(SinglyLinkedList));
pInsertNode->data = pData[i];
//將pInsertNode插入pCurrent之後
pCurrent->next = pInsertNode;
//pCurrent始終指向尾結點
pCurrent = pInsertNode;
}
//插入完成後,尾結點的next域置為NULL
pCurrent->next = NULL;
return pHead;
}
完整的測試程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定義結點資料域的型別
typedef char DataType;
//定義結點
typedef struct Node {
DataType data; //資料域
struct Node *next; //指標域
} Node;
//定義SinglyLinkedList型別變量表示單鏈表
//注意:Node和SinglyLinkedList是不同名字的同一個型別(命名的不同是為了概念上更明確)
typedef struct Node SinglyLinkedList;
//顯示定義SinglyLinkedList型別的指標變數*pHead表示它是單鏈表的頭指標
SinglyLinkedList *pHead;
/**
* 初始化單鏈表,建立一個帶頭結點的空連結串列
* @return 連結串列頭指標
*/
SinglyLinkedList *InitSinglyLinkedList();
/**
* 判斷連結串列是否為空
* @param pHead 連結串列頭指標
* @return 1為空,0不為空
*/
int IsEmpty(SinglyLinkedList *pHead);
/**
* 頭插法建立帶頭結點的單鏈表
* 如:pHead-->d-->c-->b-->a [逆序]
* @param pHead 連結串列頭指標
* @param pData 要插入資料的指標
* @param dataCount 要插入資料的數量
* @return 插入後連結串列的頭指標
*/
SinglyLinkedList *CreateSinglyLinkedListFrHead(SinglyLinkedList *pHead, DataType *pData,
int dataCount);
/**
* 尾插法建立帶頭結點的單鏈表
* 例如:pHead-->a-->b-->c-->d-->NULL [順序]
* @param pHead 連結串列頭指標
* @param pData 要插入的資料指標
* @param dataCount 要插入的資料數量
* @return 插入後的連結串列頭指標
*/
SinglyLinkedList * CreateSinglyLinkedListFrTail(SinglyLinkedList *pHead, DataType *pData,
int dataCount);
/**
* 輸出連結串列的長度
* @param pHead 連結串列頭指標
* @return 連結串列中結點個數
*/
int SinglyLinkedListLength(SinglyLinkedList *pHead);
/**
* 輸出連結串列中的資料
* @param pHead 連結串列頭指標
*/
void DispSinglyLinkedList(SinglyLinkedList *pHead);
int main(void) {
//帶頭結點的單鏈表初始化
pHead = InitSinglyLinkedList();
//輸出連結串列
DispSinglyLinkedList(pHead);
//建立如下單鏈表
DataType *pData = "abcdefg";
//頭插法建立連結串列
pHead = CreateSinglyLinkedListFrHead(pHead, pData, strlen(pData));
//尾插法建立連結串列
// pHead = CreateSinglyLinkedListFrTail(pHead, pData, strlen(pData));
printf("連結串列長度為:%d\n", SinglyLinkedListLength(pHead));
//輸出連結串列
DispSinglyLinkedList(pHead);
//釋放儲存空間
free(pHead);
return EXIT_SUCCESS;
}
/**
* 初始化單鏈表,建立一個帶頭結點的空連結串列
* @return 連結串列頭指標
*/
SinglyLinkedList *InitSinglyLinkedList() {
// 申請儲存空間可使用malloc()函式實現,需設立一申請單元指標,這裡是頭指標pHead,
// 但malloc()函式得到的指標並不是指向結構體的指標,因此,需使用強制型別轉換,
// 將其轉換成結構體型指標。
pHead = (SinglyLinkedList *) malloc(sizeof(SinglyLinkedList));
// 剛開始時,連結串列還沒建立,是一空連結串列,pHead結點的next指標為NULL。
pHead->next = NULL;
return pHead;
}
/**
* 判斷連結串列是否為空
* @param pHead 連結串列頭指標
* @return 1為空,0不為空
*/
int IsEmpty(SinglyLinkedList *pHead)
{
return (pHead->next == NULL);
}
/**
* 頭插法建立帶頭結點的單鏈表
* 如:pHead-->d-->c-->b-->a-->NULL [逆序]
* @param pHead 連結串列頭指標
* @param pData 要插入資料的指標
* @param dataCount 要插入資料的數量
* @return 插入後連結串列的頭指標
*/
SinglyLinkedList *CreateSinglyLinkedListFrHead(SinglyLinkedList *pHead, DataType *pData,
int dataCount) {
//建立一個搜尋結點,用於遍歷連結串列
SinglyLinkedList *pCurrent = pHead;
for (int i = 0; i < dataCount; i++) {
// 建立新結點pInsertNode用於存放要插入的資料
SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
sizeof(SinglyLinkedList));
pInsertNode->data = pData[i];
// 將pInsertNode插在原結點之前,前驅結點之後
// 因為每個結點的地址都是存放在其前驅結點的指標域next中
pInsertNode->next = pCurrent->next; //原結點之前
pCurrent->next = pInsertNode; //前驅節點結點之後
}
return pHead;
}
/**
* 尾插法建立帶頭結點的單鏈表
* 例如:pHead-->a-->b-->c-->d-->NULL [順序]
* @param pHead 連結串列頭指標
* @param pData 要插入的資料指標
* @param dataCount 要插入的資料數量
* @return 插入後的連結串列頭指標
*/
SinglyLinkedList * CreateSinglyLinkedListFrTail(SinglyLinkedList *pHead, DataType *pData,
int dataCount) {
//建立搜尋指標pCurrent用於遍歷連結串列
SinglyLinkedList *pCurrent = pHead;
//遍歷連結串列
for (int i = 0; i < dataCount; i++) {
//建立新結點pInsertNode用於儲存要插入的資料
SinglyLinkedList *pInsertNode = (SinglyLinkedList *) malloc(
sizeof(SinglyLinkedList));
pInsertNode->data = pData[i];
//將pInsertNode插入pCurrent之後
pCurrent->next = pInsertNode;
//pCurrent始終指向尾結點
pCurrent = pInsertNode;
}
//插入完成後,尾結點的next域置為NULL
pCurrent->next = NULL;
return pHead;
}
/**
* 輸出連結串列中的資料
* @param pHead 連結串列頭指標
*/
void DispSinglyLinkedList(SinglyLinkedList *pHead) {
if (IsEmpty(pHead))
{
printf("連結串列為空!\n");
return;
}
//建立搜尋結點pCurrent用於遍歷連結串列
//因為頭結點中不存放資料,所以需要跳過頭結點
SinglyLinkedList *pCurrent = pHead->next;
//遍歷連結串列
while (pCurrent != NULL) {
printf("%c ", pCurrent->data);
pCurrent = pCurrent->next;
}
printf("\n");
}
/**
* 輸出連結串列的長度
* @param pHead 連結串列頭指標
* @return 連結串列中結點個數
*/
int SinglyLinkedListLength(SinglyLinkedList *pHead)
{
int ListLength = 0;
SinglyLinkedList *pCurrent = pHead;
while (pCurrent->next != NULL)
{
ListLength++;
pCurrent = pCurrent->next;
}
return ListLength;
}
未完待續。。。