本人錄製技術視頻地址:https://edu.csdn.net/lecturer/1899 歡迎觀看。
一、鏈表的基本概念
這一節講解一下C語言中的鏈表,並且只講解單鏈表。在單鏈表中,每一個節點包含一個指向鏈表下一個節點的指標。鏈表最後一個節點的指標欄位的值為NULL,提示鏈表後面不再有其他節點。在你找到鏈表的第一個節點後,指標就可以帶你訪問剩餘的所有節點。為了記住鏈表的起始位置,可以使用根指標(root pointer)。根指標指向鏈表的第一個節點。根指標只是一個指標,它不包含任何資料。示意圖如下:
所以,我們可以根據鏈表的結構,建立對應的結構體:
typedef struct NODE { struct NODE *link; int value;} Node; 1. 每個節點中儲存了下一個節點的地址,用 *link儲存。
2. 每個節點中又有自己的值,儲存在value中。
在上面的圖中,這些節點相鄰在一起,這是為了顯示鏈表所提供的邏輯順序。事實上,鏈表中的節點可能分佈於記憶體中的各個地方。各節點在物理上是否相鄰並沒有什麼關係,因為程式始終用鏈(指標)從一個節點移動到另一個節點。
二、 單鏈表的插入
假設我們有一個新值12,想把它插入到上圖的鏈表中。思路是非常清晰的,從鏈表的起始位置開始,跟隨指標直到找到第一個值大於12的節點,然後把這個新值插入到那個節點之前的位置。但是前一個節點的指標欄位必須進行修改以實現這個插入,但是我們已經越過了前一個節點,無法返回去。所以我們可以始終儲存一個指向鏈表當前節點之間的那個節點的指標。
所以分析可知,代碼中需要三個指標變數,用來指向不同的節點。
1. Node *current, 用來指向當前的節點。
2. Node *previous, 用來指向前一個節點。
3. Node *new, 用來指向新分配的節點。
下面,我使用流程圖來分析插入過程。
在插入之前的狀態(current指向第一個節點):
現在current->value 是5, 小於12。所以previous和current的指標都需要向前移動一個,如下圖:
現在current->value 是10, 還是小於12。所以previous和current的指標都需要向前移動一個,如下圖:
此時,current->value的值是15,大於12。我們需要建立新的節點,準備插入:
把這個節點插入到鏈表中需要兩個步奏:
1. 使新節點指向將成為鏈表下一個節點的節點,也就是我們所找到的第一個值大於12的那個節點。代碼就是
new->link = current;
效果圖如下:
2. 讓previous指標所指向的節點指向這個新節點。代碼就是
previous->link = new;
效果圖如下:
這樣,單鏈表的基本插入操作就算完成了,代碼如下:
int insert(Node *current, int new_value) { Node *previous; Node *new; // 尋找正確的插入位置,方法是按序訪問鏈表,直到到達一個其值大於或者等於新值的節點。 while (current->value < new_value) { previous = current; current = current->link; } // 分配新插入的節點 new = (Node *)malloc(sizeof(Node)); if (new == NULL) { // 分配失敗 return 0; } new->value = new_value; // 把新節點插入到鏈表中 new->link = current; previous->link = new; return 1;}
但是,代碼還是有缺陷的,我們現在考慮邊緣值的問題。
1. 插入的值,大於鏈表中值的最大值。
2. 插入的值,小於鏈表中值的最小值。
先來分析第一種情況,假設插入的值是20。while迴圈執行到最後,current->value的值是15,執行current = current->link; 後, current 則為NULL,然後又嘗試執行while語句中的判斷,而此時current為NULL,執行current->value 則報錯了。所以我們需要對判斷條件進行限制。代碼如下:
while (current != NULL && current->value < new_value)
這樣,插入20之後的流程圖如下:
再來分析第二種情況,假設插入的值是3. 在while迴圈的條件永遠不成立了,因為current指標預設就是指向第一個節點的,我們無法操作root,也就是不可以操作根指標。
為了在鏈表的起始位置插入一個節點,函數必須修改根指標。所以我們需要定義一個指向根指標的指標。所以這個指標是指向指標的指標。我們可以定義為 Node **rootp;
所以我們可以修改代碼如下:
int insert(Node **rootp, int new_value) { Node *current; Node *previous; Node *new; // 得到指向第一個節點的指標 current = *rootp; previous = NULL; // 尋找正確的插入位置,方法是按序訪問鏈表,直到到達一個其值大於或者等於新值的節點。 while (current != NULL && current->value < new_value) { previous = current; current = current->link; } // 分配新插入的節點 new = (Node *)malloc(sizeof(Node)); if (new == NULL) { // 分配失敗 return 0; } new->value = new_value; // 把新節點插入到鏈表中 new->link = current; if (previous == NULL) { *rootp = new; } else { previous->link = new; } return 1;}
修改代碼之後,初始流程圖如下:
插入含有值3的這個節點後的流程圖如下: