跳转至

AB22-将带头结点双向链表中值最小的结点移动到链表最前面

代码实现

C
/**
 * 将带头结点双向链表中值最小的结点移动到链表最前面
 * @param L 指向双向链表头结点的指针
 * @return 移动成功返回true,最小值已在首位或链表为空返回false
 */
bool move(DinkList L) {
    // 边界条件检查:空链表或只有一个头结点
    if (L == NULL || L->next == NULL) {
        return false;
    }

    // p指向当前结点的前驱(从头结点开始)
    DinkList p = L;

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

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

    // 如果最小值结点已经在第一个位置,则无需移动
    if (pos == L) {
        return false;  // 没有发生移动
    }

    // 将最小值结点移动到链表最前面
    DinkList r = pos->next;        // r指向要移动的最小值结点

    // 1. 从原位置断开最小值结点
    pos->next = r->next;           // 前驱结点指向最小值结点的后继
    // 防止最后一个结点值最小的情况
    if (r->next != NULL) {
        r->next->prev = pos;       // 后继结点的前驱指向原前驱结点
    }

    // 2. 将最小值结点插入到链表最前面
    r->next = L->next;             // 最小值结点指向原第一个数据结点
    L->next->prev = r;             // 原第一个数据结点的前驱指向最小值结点
    L->next = r;                   // 头结点指向最小值结点
    r->prev = L;                   // 最小值结点的前驱指向头结点

    return true;  // 移动成功
}

算法执行流程图解

移动过程示例:

Text Only
原链表:L→[ ]↔[5]↔[2]↔[8]↔[3]↔[7]↔NULL

查找最小值过程:
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=[7], 7<2? 否
p=[7]: p->next=NULL, 结束

找到:pos=[5](最小值结点[2]的前驱)

移动操作:
移动前:L→[ ]↔[5]↔[2]↔[8]↔[3]↔[7]↔NULL
              ↑    ↑    ↑
             pos   r   r->next

移动步骤:
1. pos->next = r->next  → [5]→[8]
2. r->next->prev = pos  → [8]←[5]
3. r->next = L->next    → [2]→[5]
4. L->next->prev = r    → [5]←[2]
5. L->next = r          → 头→[2]
6. r->prev = L          → [2]←头

移动后:L→[ ]↔[2]↔[5]↔[8]↔[3]↔[7]↔NULL

关键操作详细图解

从原位置断开结点:

Text Only
断开前:
L→[ ]↔[A]↔[最小值]↔[B]↔[C]↔NULL
     ↑        ↑        ↑
    pos      r      r->next

断开操作:
1. pos->next = r->next
   L→[ ]↔[A]→[B]↔[C]↔NULL    [最小值]→[B]↔[C]↔NULL

2. r->next->prev = pos (如果r->next存在)
   L→[ ]↔[A]↔[B]↔[C]↔NULL    [最小值]→[B]←[A] [C]↔NULL

插入到链表最前面:

Text Only
插入前:
L→[ ]↔[A]↔[B]↔[C]↔NULL    [最小值]→[B]←[A] [C]↔NULL

插入操作:
1. r->next = L->next
   [最小值]→[A]↔[B]↔[C]↔NULL

2. L->next->prev = r
   [最小值]↔[A]↔[B]↔[C]↔NULL

3. L->next = r
   L→[ ]→[最小值]↔[A]↔[B]↔[C]↔NULL

4. r->prev = L
   L↔[ ]↔[最小值]↔[A]↔[B]↔[C]↔NULL

特殊情况处理

最后一个结点值最小:

Text Only
链表:L→[ ]↔[5]↔[8]↔[3]↔[2]↔NULL

移动前:L→[ ]↔[5]↔[8]↔[3]↔[2]↔NULL
                          ↑    ↑
                         pos   r

移动操作:
1. pos->next = r->next = NULL
   L→[ ]↔[5]↔[8]↔[3]↔NULL

2. r->next = L->next = [5]
   [2]→[5]↔[8]↔[3]↔NULL

3. L->next->prev = r = [2]
   [2]↔[5]↔[8]↔[3]↔NULL

4. L->next = r = [2]
   L→[ ]→[2]↔[5]↔[8]↔[3]↔NULL

5. r->prev = L = [ ]
   L↔[ ]↔[2]↔[5]↔[8]↔[3]↔NULL

复杂度分析

时间复杂度:O(n)

  • 查找时间:需要遍历整个链表一次,O(n)
  • 移动时间:常数时间O(1)
  • 总体时间复杂度O(n)

空间复杂度:O(1)

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

优化版本:更清晰的逻辑和变量命名

C
/**
 * 优化版本:更清晰的逻辑和变量命名
 */
bool move_Optimized(DinkList L) {
    // 边界检查
    if (L == NULL || L->next == NULL) {
        return false;
    }

    // 查找最小值结点的前驱
    DinkList current = L;
    DinkList min_prev = L;

    while (current->next != NULL) {
        if (current->next->data < min_prev->next->data) {
            min_prev = current;
        }
        current = current->next;
    }

    // 如果最小值已在第一个位置,无需移动
    if (min_prev == L) {
        return false;
    }

    // 执行移动操作
    DinkList min_node = min_prev->next;

    // 从原位置删除
    min_prev->next = min_node->next;
    if (min_node->next != NULL) {
        min_node->next->prev = min_prev;
    }

    // 插入到最前面
    min_node->next = L->next;
    L->next->prev = min_node;
    L->next = min_node;
    min_node->prev = L;

    return true;
}

双向链表移动与单向链表对比

操作 单向链表 双向链表
指针操作数 4个 6个
实现复杂度 相对简单 较复杂
安全性 需要更多边界检查 prev指针提供额外信息
调试难度 中等 较高

算法特点

核心思想: 1. 查找最小值:通过一次遍历找到最小值结点 2. 双向断链:正确处理prev和next指针 3. 双向连接:重新建立新的双向连接关系

关键技巧: - 边界处理:特别注意最后一个结点的特殊情况 - 指针操作顺序:避免断链和野指针 - 双向维护:同时维护前驱和后继指针

优势: - 双向链表的prev指针提供了更多的操作便利性 - 删除和插入操作更加直观 - 可以进行双向遍历验证

易错点: 1. 空指针检查:移动最后一个结点时r->next为NULL 2. 指针操作顺序:必须先断开再连接 3. 双向一致性:确保prev和next指针同时正确

这种算法展示了双向链表操作的复杂性和优势,是数据结构学习中的重要案例。