0

0

深入理解链表插入排序:O(1)空间复杂度与节点重连机制

花韻仙語

花韻仙語

发布时间:2025-10-31 12:57:01

|

305人浏览过

|

来源于php中文网

原创

深入理解链表插入排序:o(1)空间复杂度与节点重连机制

本文深入探讨了链表插入排序的严格定义与实现。通过分析一个常见但非标准的实现,我们阐明了真正的链表插入排序应通过节点重连而非创建新节点来实现O(1)额外空间复杂度的原地排序。文章强调了理解算法核心机制对于高效编程的重要性。

引言:插入排序的核心原理

插入排序是一种简单直观的排序算法,其基本思想是将一个待排序的元素插入到已排序序列的正确位置。它迭代地从输入数据中“移除”一个元素,找到其在已排序列表中的位置,然后将其“插入”到该位置。这个过程重复进行,直到所有输入元素都被处理完毕。对于数组而言,这通常涉及元素的移动;而对于链表,其实现方式则有其独特之处。

链表插入排序的特性与挑战

在链表结构中实现插入排序,其关键在于如何处理节点的“移除”和“插入”操作。严格意义上的链表插入排序应当在O(1)的额外空间复杂度下完成,这意味着算法不应创建新的节点,而是通过修改现有节点的指针(即“重连”操作)来改变它们的顺序。如果实现中创建了新的链表或复制了节点数据,那么它虽然可能达到排序目的,但已偏离了传统意义上链表插入排序所追求的空间效率。

代码分析:一种基于新列表的排序实现

以下代码展示了一种将原始链表元素逐个插入到一个新创建的已排序链表中的方法:

public static List sort(List list) {        
    List sortedList = new List();                  // sortedList
    List.Iter curIndex = List.Iter.last(sortedList);  // terminated forward iterator        

    for(List.Iter iter = List.Iter.first(list); !iter.end(); iter.next()) {
        curIndex = List.Iter.last(sortedList);
        List.Node node = iter.key_data();  
        System.out.println("node: "+node.data);
        System.out.println("curIndex: "+curIndex.key_data().data);
        if (sortedList.empty()) {                
            sortedList.insAfter(curIndex, node.key, node.data);                
        }
        else if (curIndex.key_data().key >= node.key) {
            boolean hasPrev = true;
            while (hasPrev && curIndex.key_data().key >= node.key) {
                hasPrev = curIndex.prev();                    
            }                                
            sortedList.insAfter(curIndex, node.key, node.data);                        
        }
        else {                            
            boolean hasNext = true;
            while (hasNext && curIndex.key_data().key < node.key) {
                hasNext = curIndex.next();
            }
            sortedList.insAfter(curIndex, node.key, node.data);                
        }

    }
    return sortedList;
}

代码解析:

该实现通过以下步骤完成排序:

  1. 初始化一个空的 sortedList。
  2. 遍历原始 list 中的每一个节点。
  3. 对于每个从原始 list 中取出的节点,在 sortedList 中找到其合适的插入位置。
  4. 使用 sortedList.insAfter() 方法将该节点的键和数据插入到 sortedList 中。

优点:

  • 功能上实现了将一个无序列表转换为一个有序列表。
  • 逻辑相对直观,易于理解。

局限性:

秘塔AI搜索
秘塔AI搜索

秘塔AI搜索,没有广告,直达结果

下载
  • 非原地排序: 该方法并没有修改原始链表 list 的顺序,而是创建了一个全新的 sortedList。
  • 空间复杂度: 由于创建了一个新的链表并插入了所有节点(或其数据副本),其额外空间复杂度为 O(n),其中 n 是列表的元素数量。这与严格意义上链表插入排序所追求的 O(1) 额外空间不符。
  • 不符合严格定义: 严格的插入排序要求“移除”输入元素并“插入”到已排序部分。此代码并未从原始列表中“移除”节点,而是从原始节点中提取数据并在新列表中创建了新的数据项或节点。

“真正”的链表插入排序:节点重连机制

一个符合严格定义的链表插入排序,尤其是对于双向链表,应该通过“重连”现有节点来实现,从而达到 O(1) 的额外空间复杂度。其核心思想是:

  1. 维护已排序部分: 假设链表头部或某个特定点开始的子链表是已排序的。最初,这个已排序部分可能只包含链表的第一个节点(或为空)。
  2. 逐个“取出”节点: 从未排序的剩余部分中取出一个节点。这里的“取出”意味着要修改其前后节点的指针,使其暂时脱离原链表。
  3. 寻找插入位置: 在已排序的子链表中从头开始(或从尾部开始,取决于具体策略)遍历,找到该节点应插入的正确位置。
  4. “插入”并重连: 将取出的节点插入到找到的位置,这涉及到修改四个指针:
    • 被插入节点的前驱节点的 next 指针。
    • 被插入节点的后继节点的 prev 指针。
    • 被插入节点自身的 prev 和 next 指针。

概念性步骤示例(单向链表简化):

假设我们有一个头节点 head,并且 sortedHead 指向已排序部分的头。

// 假设 head 是原始链表的头,sortedHead 是已排序链表的头
// 初始时,sortedHead = head; head = head.next; sortedHead.next = null; (将第一个元素作为已排序部分)

while (head != null) {
    ListNode current = head;
    head = head.next; // 从原链表中取出 current 节点

    // 在 sortedHead 中找到 current 的插入位置
    // 如果 current 比 sortedHead 小,则插入到 sortedHead 之前
    if (current.val < sortedHead.val) {
        current.next = sortedHead;
        sortedHead = current;
    } else {
        // 遍历 sortedHead 找到插入点
        ListNode search = sortedHead;
        while (search.next != null && current.val >= search.next.val) {
            search = search.next;
        }
        // 插入 current
        current.next = search.next;
        search.next = current;
    }
}
// 最终 sortedHead 就是完全排序的链表

对于双向链表,操作会更复杂,需要额外处理 prev 指针的修改。关键在于,整个过程不涉及 new ListNode(),所有操作都是对现有节点指针的修改。

注意事项与总结

软件开发中,理解算法的严格定义和其不同实现之间的差异至关重要。虽然前述的 sort 方法能够产生一个排序后的列表,但它在空间效率上牺牲了传统链表插入排序的优势。在评估和选择排序算法时,应根据具体需求进行权衡:

  • 是否需要原地排序? 如果需要修改原始列表且不占用额外空间,则必须采用节点重连的方式。
  • 空间复杂度要求? 如果内存资源紧张,O(1) 额外空间的实现是首选。
  • 实现复杂度? 节点重连的实现通常比创建新列表的实现更为复杂,尤其是在处理双向链表时。

综上所述,虽然有很多方法可以“排序”一个列表,但只有那些通过修改现有节点指针、实现 O(1) 额外空间复杂度的算法,才更符合严格意义上的链表插入排序。理解这些细微差别,有助于我们编写出更高效、更符合规范的代码。

相关专题

更多
sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

387

2023.09.04

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

404

2023.08.14

C++ 高级模板编程与元编程
C++ 高级模板编程与元编程

本专题深入讲解 C++ 中的高级模板编程与元编程技术,涵盖模板特化、SFINAE、模板递归、类型萃取、编译时常量与计算、C++17 的折叠表达式与变长模板参数等。通过多个实际示例,帮助开发者掌握 如何利用 C++ 模板机制编写高效、可扩展的通用代码,并提升代码的灵活性与性能。

10

2026.01.23

php远程文件教程合集
php远程文件教程合集

本专题整合了php远程文件相关教程,阅读专题下面的文章了解更多详细内容。

29

2026.01.22

PHP后端开发相关内容汇总
PHP后端开发相关内容汇总

本专题整合了PHP后端开发相关内容,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

php会话教程合集
php会话教程合集

本专题整合了php会话教程相关合集,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

宝塔PHP8.4相关教程汇总
宝塔PHP8.4相关教程汇总

本专题整合了宝塔PHP8.4相关教程,阅读专题下面的文章了解更多详细内容。

13

2026.01.22

PHP特殊符号教程合集
PHP特殊符号教程合集

本专题整合了PHP特殊符号相关处理方法,阅读专题下面的文章了解更多详细内容。

11

2026.01.22

PHP探针相关教程合集
PHP探针相关教程合集

本专题整合了PHP探针相关教程,阅读专题下面的文章了解更多详细内容。

8

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.8万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号