【程式設計師面試高頻演算法】一篇文章搞定連結串列,附C語言原始碼,建議收藏!

Newton爱编程發表於2024-11-22

一個程式設計師對計算機底層原理的理解,決定了他職業生涯的上限。
圖片

單連結串列是面試中頻繁考察的重點之一。

今天,我們就來深入剖析單連結串列,涵蓋其定義、特點、優缺點、適用場景,以及一系列高頻演算法題(增刪改查、翻轉、找倒數第k個節點、判斷是否有環等)的解法,並附上C語言原始碼,讓你輕鬆掌握這一關鍵知識點。
‌單連結串列的定義‌

單連結串列是一種線性資料結構,由一系列節點組成,每個節點包含資料域和指向下一個節點的指標。這種結構透過指標將各個節點連線起來,形成一條鏈。

‌單連結串列的特點‌
動態性:連結串列長度不固定,可隨需求增加或減少節點。

節點不連續:連結串列節點在記憶體中無需連續儲存,有助於記憶體利用。

‌單連結串列的優缺點‌
優點

插入和刪除操作高效,特別是在連結串列頭部或中間位置。

適用於元素數量不固定的動態場景。

缺點

查詢效率低,無法直接透過索引訪問元素,需從頭節點順序遍歷。
‌單連結串列的使用場景‌
連結串列是非常重要的資料結構,應用場景廣泛。

適用於實現棧、佇列、樹等複雜資料結構。

在需要頻繁插入和刪除操作的場景中表現出色,如動態資料集合、快取淘汰策略等。

相較於陣列,連結串列在處理元素動態變化的場景時更具優勢;但在頻繁查詢元素的場景中,陣列更為合適。

單連結串列因其動態性和靈活性,在處理元素不固定且需要頻繁插入、刪除的場景中具有重要的應用價值。

程式碼實現

一、資料結構定義

// 定義單連結串列節點結構體
typedef struct ListNode {
    int val;
    struct ListNode* next;
} ListNode;

// 定義單連結串列結構體(包含頭節點)
typedef struct LinkedList {
    ListNode* head;
} LinkedList;

// 建立一個新節點
ListNode* createNode(int val) {
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->val = val;
    newNode->next = NULL;
    return newNode;
}

// 建立一個空連結串列
LinkedList* createLinkedList() {
    LinkedList* list = (LinkedList*)malloc(sizeof(LinkedList));
    list->head = NULL;
    return list;
}

二、增刪改查操作
插入操作

  1. 頭部插入
// 在連結串列頭部插入新節點
void insertAtHead(LinkedList* list, int val) {
    ListNode* newNode = createNode(val);
    newNode->next = list->head;
    list->head = newNode;
}
  1. 中間插入
// 在指定位置(假設位置從1開始計數)之後插入新節點
void insertAfterPosition(LinkedList* list, int position, int val) {
    ListNode* newNode = createNode(val);
    ListNode* current = list->head;
    int count = 1;
    while (current!= NULL && count < position) {
        current = current->next;
        count++;
    }
    if (current!= NULL) {
        newNode->next = current->next;
        current->next = newNode;
    } else {
        printf("Position out of range.\n");
    }
}
  1. 尾部插入
// 在連結串列尾部插入新節點
void insertAtTail(LinkedList* list, int val) {
    ListNode* newNode = createNode(val);
    if (list->head == NULL) {
        list->head = newNode;
        return;
    }
    ListNode* current = list->head;
    while (current->next!= NULL) {
        current = current->next;
    }
    current->next = newNode;
}

刪除操作

  1. 刪除頭部節點
// 刪除連結串列頭部節點
void deleteHead(LinkedList* list) {
    if (list->head!= NULL) {
        ListNode* temp = list->head;
        list->head = list->head->next;
        free(temp);
    } else {
        printf("List is empty.\n");
    }
}
  1. 刪除中間節點
// 刪除指定位置(假設位置從1開始計數)的節點
void deleteAtPosition(LinkedList* list, int position) {
    ListNode* current = list->head;
    ListNode* prev = NULL;
    int count = 1;
    while (current!= NULL && count < position) {
        prev = current;
        current = current->next;
        count++;
    }
    if (current!= NULL) {
        if (prev == NULL) {
            list->head = current->next;
        } else {
            prev->next = current->next;
        }
        free(current);
    } else {
        printf("Position out of range.\n");
    }
}
  1. 刪除尾部節點
// 刪除連結串列尾部節點
void deleteTail(LinkedList* list) {
    if (list->head == NULL) {
        printf("List is empty.\n");
        return;
    }
    ListNode* current = list->head;
    ListNode* prev = NULL;
    while (current->next!= NULL) {
        prev = current;
        current = current->next;
    }
    if (prev == NULL) {
        list->head = NULL;
    } else {
        prev->next = NULL;
    }
    free(current);
}

查詢操作

// 查詢連結串列中是否存在指定值
int searchList(LinkedList* list, int val) {
    ListNode* current = list->head;
    while (current!= NULL) {
        if (current->val == val) {
            return 1;
        }
        current = current->next;
    }
    return 0;
}

修改操作

// 修改指定位置(假設位置從1開始計數)節點的值
void modifyAtPosition(LinkedList* list, int position, int newVal) {
    ListNode* current = list->head;
    int count = 1;
    while (current!= NULL && count < position) {
        current = current->next;
        count++;
    }
    if (current!= NULL) {
        current->val = newVal;
    } else {
        printf("Position out of range.\n");
    }
}

三、演算法進階

翻轉連結串列

// 翻轉單連結串列
ListNode* reverseList(LinkedList* list) {
    ListNode* prev = NULL;
    ListNode* current = list->head;
    ListNode* next = NULL;
    while (current!= NULL) {
        next = current->next;
        current->next = prev;
        prev = current;
        current = next;
    }
    list->head = prev;
    return list->head;
}

找連結串列的倒數第 k 個節點

// 找到單連結串列的倒數第 k 個節點
ListNode* findKthFromEnd(LinkedList* list, int k) {
    ListNode* slow = list->head;
    ListNode* fast = list->head;
    // 讓快指標先走 k 步
    for (int i = 0; i < k; i++) {
        if (fast == NULL) {
            printf("k is larger than the length of the list.\n");
            return NULL;
        }
        fast = fast->next;
    }
    // 然後慢指標和快指標一起走,當快指標走到連結串列末尾時,慢指標就是倒數第 k 個節點
    while (fast!= NULL) {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

判斷單連結串列是否有環

// 判斷單連結串列是否有環
int hasCycle(LinkedList* list) {
    ListNode* slow = list->head;
    ListNode* fast = list->head;
    while (fast!= NULL && fast->next!= NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) {
            return 1; // 有環
        }
    }
    return 0; // 無環
}

希望這篇文章能對你有所幫助,記得收藏起來哦,方便隨時查閱和複習。

相關文章