首页/数据结构/线性表/线性表的应用 🔗 在 Obsidian 中打开
数据结构 · 线性表

线性表的应用

重要度 ⭐⭐⭐⭐ 线性表合并逆序查找应用题
速查
线性表经典应用:有序表合并(归并思想,O(m+n),比较次数最少 min(m,n)、最多 m+n-1);逆序(头插法/三指针,O(n));双指针找倒数第 k 个、中间结点(O(n) 一次遍历)。

核心概念

一、有序表合并(归并思想)

顺序表合并:将两个有序顺序表 A、B 合并为有序顺序表 C:

bool Merge(SqList A, SqList B, SqList *C) {
    if (A.length + B.length > MaxSize) return false;
    int i = 0, j = 0, k = 0;
    while (i < A.length && j < B.length) {
        if (A.data[i] <= B.data[j]) C->data[k++] = A.data[i++];
        else                        C->data[k++] = B.data[j++];
    }
    while (i < A.length) C->data[k++] = A.data[i++];
    while (j < B.length) C->data[k++] = B.data[j++];
    C->length = k;
    return true;
}

时间复杂度 O(m+n);空间复杂度 O(m+n)(需要新数组)。

链表合并(尾插法)

LinkList Merge(LinkList A, LinkList B) {
    LNode *p = A->next, *q = B->next;
    LNode *r = A;  // r 指向结果链表尾部
    while (p && q) {
        if (p->data <= q->data) { r->next = p; r = p; p = p->next; }
        else                    { r->next = q; r = q; q = q->next; }
    }
    r->next = p ? p : q;  // 接上剩余部分
    free(B);
    return A;
}

时间复杂度 O(m+n);空间复杂度 O(1)(原地合并)。

二、链表逆序(反转)

方法 1:头插法逆序——依次摘下结点,用头插法插入到头结点之后:

void Reverse(LinkList L) {
    LNode *p = L->next;
    L->next = NULL;
    while (p != NULL) {
        LNode *q = p->next;   // 保存后继
        p->next = L->next;    // 头插
        L->next = p;
        p = q;                // 继续下一个
    }
}

方法 2:三指针法(不带头结点):

LNode* Reverse(LNode *head) {
    LNode *prev = NULL, *curr = head, *next = NULL;
    while (curr != NULL) {
        next = curr->next;    // 保存后继
        curr->next = prev;    // 反转指针
        prev = curr;  curr = next;
    }
    return prev;  // 新的头结点
}

三、查找算法

链表查找倒数第 k 个结点(双指针法)

LNode *FindKth(LinkList L, int k) {
    LNode *fast = L->next, *slow = L->next;
    for (int i = 0; i < k; i++) {
        if (fast == NULL) return NULL;
        fast = fast->next;
    }
    while (fast != NULL) { fast = fast->next; slow = slow->next; }
    return slow;
}

查找中间结点(快慢指针):fast 走两步,slow 走一步,fast 到末尾时 slow 在中间。

判断链表是否有环(Floyd 判圈算法):fast 每次 2 步、slow 每次 1 步,相遇则有环。

四、链表分割

void Partition(LinkList L, ElemType x) {
    // 小于 x 的放 L1,大于等于 x 的放 L2,最后 L1->next = L2->next
    LNode *p = L->next;
    LNode *L1 = (LNode *)malloc(sizeof(LNode));  // 小于 x
    LNode *L2 = (LNode *)malloc(sizeof(LNode));  // 大于等于 x
    LNode *r1 = L1, *r2 = L2;
    while (p != NULL) {
        if (p->data < x) { r1->next = p; r1 = p; }
        else             { r2->next = p; r2 = p; }
        p = p->next;
    }
    r1->next = L2->next; r2->next = NULL;
    L->next = L1->next; free(L1); free(L2);
}

五、删除重复元素(有序链表去重)

void DeleteDuplicate(LinkList L) {
    LNode *p = L->next;
    while (p->next != NULL) {
        if (p->data == p->next->data) {
            LNode *q = p->next; p->next = q->next; free(q);
        } else { p = p->next; }
    }
}

手算示例

例 1:合并两个有序链表

A:1 → 3 → 5 → 7  B:2 → 4 → 6 → 8

比较 1 和 2,取 1 → C: [1]
比较 3 和 2,取 2 → C: [1, 2]
比较 3 和 4,取 3 → C: [1, 2, 3]
比较 5 和 4,取 4 → C: [1, 2, 3, 4]
比较 5 和 6,取 5 → C: [1, 2, 3, 4, 5]
比较 7 和 6,取 6 → C: [1, 2, 3, 4, 5, 6]
比较 7 和 8,取 7 → C: [1, 2, 3, 4, 5, 6, 7]
B 剩余 8 → C: [1, 2, 3, 4, 5, 6, 7, 8]

例 2:头插法逆序

原链表:L → [1] → [2] → [3] → NULL

  1. $p = [1]$,摘下 [1],头插:L → [1] → NULL
  2. $p = [2]$,摘下 [2],头插:L → [2] → [1] → NULL
  3. $p = [3]$,摘下 [3],头插:L → [3] → [2] → [1] → NULL

结果:3 → 2 → 1(逆序完成)

例 3:快慢指针找中间结点

链表:1 → 2 → 3 → 4 → 5

初始:fast=1, slow=1
第1步:fast=3, slow=2
第2步:fast=5, slow=3
第3步:fast=NULL,结束 → slow=3 即中间结点

常见考法

考法 1 · 合并两个有序序列合并,比较次数最少 min(m,n) 次,最多 m+n-1 次
考法 2不带头结点的单链表逆序,写出核心代码(三指针法)。
考法 3用 O(n) 时间找链表中间结点 / 倒数第 k 个结点(双指针)。
考法 4 · 综合删除链表中值为 x 的元素 / 分离奇偶元素(按值分割)。

易错点

易错清单
  1. 合并时相等元素处理:通常取 A 中元素(稳定性)。
  2. 逆序时保存后继:头插法必须先保存 p->next
  3. 快慢指针边界:偶数个结点时中间有两个,注意题目要求。
  4. 带头结点 vs 不带头结点:算法实现差异大,看清题目。
  5. 尾插法最后置 NULL:$r\text{->next} = NULL$。

核心结论

操作时间复杂度空间复杂度
有序表合并O(m+n)顺序表 O(m+n),链表 O(1)
链表逆序O(n)O(1)
查找中间结点O(n)O(1)
查找倒数第 k 个O(n)O(1)
判断有环O(n)O(1)
有序链表去重O(n)O(1)

记忆卡片

合并两个有序表的时间/空间?
时间 O(m+n);空间:顺序表 O(m+n)、链表 O(1) 原地合并。
头插法逆序的核心?
先保存后继 q = p->next,再头插,避免断链。
找倒数第 k 个结点?
fast 先走 k 步,然后 fast 和 slow 一起走,fast 到末尾时 slow 在倒数第 k 个。
合并比较次数范围?
最少 min(m,n),最多 m+n-1。

交互动画 · 有序表合并 & 快慢指针找中间

A:1 3 5 7  B:2 4 6 8  C:归并结果 1 3 5 7 2 4 6 8 i 指针 → 0 / 1 / 2 / 3(A 行)  j 指针 → 0 / 1 / 2 / 3(B 行)
有序表合并:比较 A[i] 与 B[j],取较小者放入 C
点击「合并 · 下一步」逐步归并,或「中间结点 · 走一步」演示快慢指针
归并比较次数最少 min(m,n)、最多 m+n-1;快慢指针可 O(n) 一次遍历找中间或倒数第 k 个结点。

相关知识点

singly-linked-list-implementation sequential-list-implementation doubly-and-circular-linked-list

↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。