爲了彌補和克服上一節所述順序存儲結構所帶來的的不足,這一節討論線性表的另外一種存儲結構——鏈式存儲結構。鏈式存儲結構不要求邏輯上相鄰的數據元素在物理位置上也相鄰,經過 指針 來映射數據元素之間的邏輯關係。node
使用連式存儲結構時,每一個數據元素除了存儲自身的數據信息外,還須要存儲一個指示其直接後繼元素位置的信息,這兩部分組成一個 鏈結點。面試
當鏈表中每個鏈結點除了數據域外只設置一個指針域時,稱這樣的鏈表爲 線性鏈表 或 單鏈表。算法
一個鏈結點定義以下:函數
typedef struct node {
ElemType data;
struct node *link;
} LNode, *LinkList;
複製代碼
線性鏈表的基本操做都比較簡單,挑幾個重點說明以下。ui
書中沒有指定每一個數據元素的來源,這裏假設是由用戶輸入的,使用 scanf
函數。spa
/** * 1. 創建一個線性鏈表 */
LinkList create(int n) {
LinkList list = NULL, rear = NULL;
for (int i = 0; i < n; i++) {
int val;
scanf("%d", &val);
LNode *node = malloc(sizeof(LNode));
node -> data = val;
node -> link = NULL;
if (list == NULL) {
list = node;
} else {
rear -> link = node;
}
rear = node;
}
return list;
}
複製代碼
須要注意一下若是使用的是判斷 rear == NULL
,那麼 rear
在聲明的時候必須賦初值爲 NULL
,不然 rear
初始化時是一個垃圾值,不會進入判斷條件,使得下面調用 rear -> link
會拋出異常。指針
插入分了三種,往頭部插入、往尾部插入以及在某個結點後插入。後面兩種比較簡單,但注意第一種。C 語言是 按值傳遞,若是此處函數簽名不是 LinkList
的指針(LinkList*
),而直接使用 LinkList
,那麼在最後賦值時,list = newNode
是不會起做用的,由於修改的只是 形參的指針。code
/** * 5. 在非空線性鏈表第一個鏈結點前插入一個 item * * 注意:簽名必須是指針的指針 */
void insertLink1(LinkList *list, ElemType item) {
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
newNode -> link = *list;
*list = newNode;
}
/** * 6. 在非空線性鏈表的末尾插入一個 item */
void insertLink2(LinkList list, ElemType item) {
LinkList rear = list;
while (rear -> link != NULL) {
rear = rear -> link;
}
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
newNode -> link = NULL;
rear -> link = newNode;
}
/** * 7. 在線性鏈表指針 p 後面插入一個 item */
void insertLink3(LinkList list, LinkList q, ElemType item) {
LinkList newNode = malloc(sizeof(LNode));
newNode -> data = item;
if (list == NULL) {
newNode -> link = NULL;
list = newNode;
} else {
newNode -> link = q -> link;
q -> link = newNode;
}
}
複製代碼
線性鏈表的反轉是一個比較經典的面試題,實際上實現起來也比較簡單,主要須要理清思路。blog
實際上須要維護的指針有三個:
cur
)prev
)prevPrev
)當每一步進行反轉時,實際上反轉的是將 prev
指向 prevPrev
,由於 cur
指針的 link
是不能動的,不然你就找不到下一個鏈結點了。
因此思路是(while
循環中的四行代碼):
prevPrev
)日後走prev
)日後走cur
)日後走prev
與 prevPrev
/** * 13. 線性鏈表的反轉 */
void invert(LinkList *list) {
LinkList cur = *list, prev = NULL, prevPrev = NULL;
while (cur != NULL) {
prevPrev = prev;
prev = cur;
cur = cur -> link;
prev -> link = prevPrev;
}
*list = prev;
}
複製代碼
由於鏈表是單向的,沒法直接從後往前尋找倒數第 N 個節點。要使時間複雜度爲 O(n)
,即一次遍歷實現,能夠基於以下思路:
使用兩個指針,其中一個指針比另外一個領先 N 個節點,而後兩個節點同時日後移動,當前面的指針到達結尾時,後面的指針則到達了倒數第 N 個位置。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode* pioneer = head;
struct ListNode* prev = head;
for (int i = 0; i < n; i++) {
pioneer = pioneer -> next;
}
if (pioneer == NULL) {
prev = prev -> next;
return prev;
}
while (pioneer -> next != NULL) {
prev = prev -> next;
pioneer = pioneer -> next;
}
prev -> next = prev -> next -> next;
return head;
}
複製代碼