《數據結構教程》2.3 線性鏈表

爲了彌補和克服上一節所述順序存儲結構所帶來的的不足,這一節討論線性表的另外一種存儲結構——鏈式存儲結構。鏈式存儲結構不要求邏輯上相鄰的數據元素在物理位置上也相鄰,經過 指針 來映射數據元素之間的邏輯關係。node

使用連式存儲結構時,每一個數據元素除了存儲自身的數據信息外,還須要存儲一個指示其直接後繼元素位置的信息,這兩部分組成一個 鏈結點面試

當鏈表中每個鏈結點除了數據域外只設置一個指針域時,稱這樣的鏈表爲 線性鏈表單鏈表算法

C 語言實現

鏈結點

一個鏈結點定義以下:函數

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;
  }
}
複製代碼

經典算法題

線性鏈表的反轉

leetcode 206. 反轉鏈表cdn

線性鏈表的反轉是一個比較經典的面試題,實際上實現起來也比較簡單,主要須要理清思路。blog

實際上須要維護的指針有三個:

  • 當前遍歷到的鏈結點(cur
  • 上一個鏈結點(prev
  • 上上個鏈結點(prevPrev

當每一步進行反轉時,實際上反轉的是將 prev 指向 prevPrev,由於 cur 指針的 link 是不能動的,不然你就找不到下一個鏈結點了

因此思路是(while 循環中的四行代碼):

  1. 上上個結點(prevPrev)日後走
  2. 上個結點(prev)日後走
  3. 當前結點(cur)日後走
  4. 反轉 prevprevPrev
/** * 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 個節點

leetcode 19. 刪除鏈表的倒數第N個節點

由於鏈表是單向的,沒法直接從後往前尋找倒數第 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;
}
複製代碼
相關文章
相關標籤/搜索