跳转至

AB23-找出链表内最小值并不断输出,并将结点从链表中删除,直到链表为空, 再删除表头结点

代码实现

C
/**
 * 对带头结点的循环单链表执行操作:
 * 反复找出链表内最小值并输出,然后删除该结点,
 * 直到链表为空,最后删除头结点
 * @param L 指向循环链表头结点的指针
 */
void del_min(LinkList L) {
    // 边界条件检查:空链表(只有头结点自循环)
    if (L == NULL || L->next == L) {
        if (L) free(L);  // 释放头结点
        return;
    }

    // 循环直到链表为空(只剩头结点自循环)
    while (L->next != L) {
        // p指向当前结点的前驱(从头结点开始)
        LinkList p = L;

        // pos指向最小值结点的前驱结点
        LinkList pos = L;

        // 遍历循环链表查找最小值结点
        while (p->next != L) {
            // 如果当前结点的值小于已知最小值
            if (p->next->data < pos->next->data) {
                pos = p;  // 更新最小值结点的前驱
            }
            p = p->next;  // 继续向后查找
        }

        // 找到最小值结点,输出其值
        printf("%d ", pos->next->data);

        // 准备删除最小值结点
        LinkList u = pos->next;  // u指向要删除的最小值结点

        // 删除操作:绕过最小值结点
        pos->next = u->next;     // 前驱结点指向最小值结点的后继

        // 释放最小值结点的内存
        free(u);
    }

    // 释放头结点
    free(L);
}

循环链表结构图解

循环链表特点:

Text Only
1
2
3
4
5
6
7
8
普通链表:L→[ ]→[1]→[2]→[3]→NULL
                   尾结点指向NULL

循环链表:L→[ ]→[1]→[2]→[3]→ ┐
         ↑                  │
         └──────────────────┘
        尾结点指向头结点,形成环

循环链表遍历终止条件:

Text Only
1
2
3
4
while (p != L) {  // 当回到头结点时停止
    // 处理结点
    p = p->next;
}

算法执行流程图解

执行过程示例:

Text Only
初始循环链表:L→[ ]→[5]→[2]→[8]→[3]→ ┐
              ↑                     │
              └─────────────────────┘

第1轮:
查找最小值:2
输出:2
删除后:L→[ ]→[5]→[8]→[3]→ ┐
         ↑                │
         └────────────────┘

第2轮:
查找最小值:3
输出:3
删除后:L→[ ]→[5]→[8]→┐
         ↑           │
         └───────────┘

第3轮:
查找最小值:5
输出:5
删除后:L→[ ]→[8]→┐
         ↑       │
         └───────┘

第4轮:
查找最小值:8
输出:8
删除后:L→[ ]→ ┐    (L->next == L,循环结束)
         ↑    │
         └────┘

最终:释放头结点

查找和删除详细图解

查找最小值过程:

Text Only
链表:L→[ ]→[5]→[2]→[8]→[3]→--┐
      ↑    ↑    ↑    ↑    ↑   │
      L   p1   p2   p3   p4   │
      pos                     │
      └───────────────────────┘

查找过程:
p=L: p->next=[5], pos->next=[5]
p=[5]: p->next=[2], 2<5? 是 → pos=[5]
p=[2]: p->next=[8], 8<2? 否
p=[8]: p->next=[3], 3<2? 否
p=[3]: p->next=L, 结束

删除操作过程:

Text Only
删除前:
L→[ ]→[5]→[2]→[8]→[3]→-┐
      ↑    ↑    ↑      │
     pos   u   u->next │
      └────────────────┘

删除操作:
1. pos->next = u->next  → [5]→[8]
2. free(u)              → 释放[2]

删除后:
L→[ ]→[5]→[8]→[3]→-┐
      ↑    ↑       │
     pos u->next   │
      └────────────┘

复杂度分析

时间复杂度:O(n²)

  • 外层循环:执行n次(每个结点删除一次)
  • 内层查找:第i次循环需要查找n-i+1个结点
  • 总比较次数:n + (n-1) + (n-2) + ... + 1 = n(n+1)/2
  • 总体时间复杂度O(n²)

空间复杂度:O(1)

  • 额外空间:只使用了常数个指针变量:pposu
  • 无辅助空间:没有使用额外的数据结构
  • 算法额外空间复杂度O(1)

优化版本:添加边界检查和更清晰的逻辑

C
/**
 * 优化版本:添加边界检查和更清晰的逻辑
 */
void del_min_Optimized(LinkList L) {
    // 边界检查
    if (L == NULL) return;

    // 检查是否为空循环链表
    if (L->next == L) {
        free(L);
        return;
    }

    while (L->next != L) {
        LinkList current = L;
        LinkList min_prev = L;

        // 查找最小值结点
        while (current->next != L) {
            if (current->next->data < min_prev->next->data) {
                min_prev = current;
            }
            current = current->next;
        }

        // 输出并删除最小值结点
        printf("%d ", min_prev->next->data);
        LinkList min_node = min_prev->next;
        min_prev->next = min_node->next;
        free(min_node);
    }

    free(L);
}

优化版本:带计数功能的版本

C
/**
 * 带计数功能的版本
 */
int del_min_with_count(LinkList L) {
    if (L == NULL) return 0;

    int count = 0;

    while (L->next != L) {
        LinkList p = L;
        LinkList pos = L;

        while (p->next != L) {
            if (p->next->data < pos->next->data) {
                pos = p;
            }
            p = p->next;
        }

        printf("%d ", pos->next->data);
        LinkList u = pos->next;
        pos->next = u->next;
        free(u);
        count++;
    }

    free(L);
    return count;
}

与其他链表类型的对比

特性 普通单链表 循环单链表
终止条件 p->next == NULL p->next == L
空表判断 L->next == NULL L->next == L
遍历复杂度 O(n) O(n)
实现难度 简单 中等

算法特点

核心思想: 1. 循环查找:不断在循环链表中查找最小值 2. 有序输出:按递增顺序输出所有元素 3. 逐步删除:找到一个删除一个 4. 完全释放:最终释放所有结点内存

关键技巧: - 循环遍历:使用p->next != L作为终止条件 - 前驱指针:便于删除操作 - 边界处理:正确处理空循环链表的情况

应用场景: - 需要有序输出并释放内存的场景 - 优先级队列的简单实现 - 一次性处理的临时数据结构

与普通链表版本的区别: 1. 终止条件:从p->next == NULL改为p->next == L 2. 空表判断:从L->next == NULL改为L->next == L 3. 循环特性:利用循环链表的环形结构

这种算法虽然时间复杂度较高,但充分利用了循环链表的特性,是处理循环链表的经典算法之一。