• 【不三不四的脑洞】一个梦所引发关于排序算法的思考


    前一段时间,我做了一个梦

    一个漆黑的深夜里,我正在躲避日本皇军和伪满军人的追捕,突然发现前方有一个很大的古刹,虽然有点阴森,但是随着追兵的步步逼近,我也别无选择,只能咬咬牙打算藏身在里面。

    在这里插入图片描述

    我翻墙便进了寺庙,在里头转啊转啊,突然发现天空出现了几只小蝙蝠(因为离得远所以看着小),而且有越来越多的趋势,逐渐漫天都是,而且越变越大(开始往下降、准备着陆)。

    在这里插入图片描述

    我躲在大柱子后面观察其中先着陆的几只,大的有将近一人高,而且乍看之下有些人形。

    在这里插入图片描述

    直觉告诉我,此地不宜久留,我尽量回避它们的眼神,不敢直视它们的眼睛(依稀记得 Discovery 之类的探索频道有说过,盯着动物看是一种挑衅行为),但是情急之下我还是得强装镇定的往前继续走,心中一边默念 “佛祖保佑!佛祖保佑!佛门净地,不能杀生,希望它们对人不感兴趣,不会吃人。。。”。
    在这里插入图片描述

    我往前走着走着,不知经过了多久,忽然发现前方有几个蝙蝠队伍,它们有序聚集在各自对应的小窗户前。我凑近仔细观察,发现原来是它们正在从中领取一种食物。

    突然背后有什么东西拍了拍我的肩膀(着实吓了我一跳),我惊恐地回头一看,原来是位面容清秀的小沙弥,他微微一笑告诉我:“施主莫慌,看您是生面孔,想必是第一次来敝寺。您所见的这些巨蝙蝠是敝寺的守护,性格温顺,不轻易伤人,如若有邪物想进犯本寺,它们就会倾巢而出赶走邪物,那些追捕你的军队,已经被它们所吓跑,施主不必多虑。此外,它们所吃的东西叫 ‘尸净’,是它们的最爱。”

    说着,他便拿出一块来,我凑近一看,那个食物看起来是一种小小的带有人形的果冻(胶状体),但是通体晶莹,有多种颜色。

    说着这位小沙弥就邀请我进了他们的后厨。进屋一看,我被眼前的一切所 震惊 了。。。他们的炊具 —— 竟然是一个 马桶型 的大锅,里头烧着满满的一锅杂烩,各种各样的颜色,但是看不出是什么食材。

    小沙弥继续介绍说,其实他们也不用往里头加任何食材,只要加 “水” 熬制七七四十九个小时就能形成晶莹剔透的胶状物质。“是什么神奇的水吗?” 我好奇地问道。小沙弥说:“是洗干净尸体的水”。。。(听到此处,我心中不自觉一阵翻江倒海。。。

    在这里插入图片描述
    越怕什么越来什么,说话间,小沙弥便热情地用大勺盛了一碗给我,虽然心中万般不情愿,但是无奈盛情难却,我又脸皮薄不好意思拒绝,所以。。。正当我在考虑先吃哪一碗的时候突然灵机一动,岔开话题,询问小沙弥他们这样给蝙蝠发食物是否有遇到过什么效率问题。小沙弥说:“的确是有一个问题,虽然这些蝙蝠很有素质,能够依序排队领取食物,但是每只蝙蝠的食量却不一样,有多有少,要是能够对它们依据食量提前进行排序就好了!

    小沙弥还提醒了我有额外要求

    • 时间复杂度:nlogn
    • 空间复杂度:常量

    听到这里,我突然醒了,但是本着对小沙弥负责的态度(绝对不是为了蹭 CSDN 的礼物),我还是敏感地发觉这是一个 LeetCode 148 相关的排序问题

    所以

    如何才能满足上述要求对蝙蝠进行排序?

    在这里插入图片描述

    首先,通过查阅相关资料,常见的几种排序算法的性能如下

    方法容器时间复杂度空间复杂度
    插入排序数组/链表O(n^2)O(1)
    堆排序数组/链表/链表O(nlogn)/O(nlogn)/O(n^2logn)O(1)/O(n)/O(1)
    快速排序数组/链表O(nlogn)~O(n^2)O(logn)~O(n)
    归并排序(自上而下)数组/链表O(nlogn)/O(nlogn)O(n+logn)/O(logn)
    归并排序(自下而上)数组/链表O(nlogn)/O(nlogn)O(n)/O(1)

    从上表可知,堆排序在 数组 情况下、归并排序(自下而上)在 链表 情况下都满足以上条件。我这里着重介绍一下 归并排序(自下而上)

    • 代码和详细注释如下
    /// @note 代码原作者: Huahua
    /// 详细注释:ShaderJoy
    
    /// @note 一只蝙蝠结点
    struct ListNode{
        int val;        ///< 蝙蝠食量
        ListNode *next; ///< 后一只蝙蝠
        ListNode(int x): val(x), next(NULL){}
    };
    
    class Solution {
    public:
      ListNode* sortList(ListNode* head) {
        /// @note 完成状态,只剩 0 或者 1 只蝙蝠
        if (!head || !head->next) return head;
        
        int len = 1;
        ListNode* cur = head;
        while (cur = cur->next) ++len; ///< 首先统计队列中有多少只蝙蝠
        
        ///@note 工具结点
        ListNode dummy(0); 
        dummy.next = head; ///< 它的后面保存的是头蝙蝠
        ListNode* l;
        ListNode* r;
        ListNode* tail;
        
        /// @note n 表示每个分组的蝙蝠个数
        /// 每次迭代后 n 都会翻倍
        /// 保证执行 log(len) 次
        for (int n = 1; n < len; n <<= 1) {      
          cur = dummy.next; ///< 当前处理的队头蝙蝠(已经部分排序的头)
          tail = &dummy; ///< 队尾
          while (cur) {
            l = cur;
            r = split(l, n);            ///< 这两行代码将当前列表分割两次,结果是 l 和 r 都是 n 个蝙蝠
            cur = split(r, n);          ///< 而此时 cur 指向了 l 和 r 的后面
            auto merged = merge(l, r);  ///< 将两个列表进行合并(并从小到大排序)
            tail->next = merged.first;  ///< 合并且有序的 head 接到 tail 的后面
            tail = merged.second;       ///< 合并且有序的 tail 成为新的 tail
          }
        }      
        return dummy.next;
      }
    private:
      /// @note 将列表分成两部分,前 n 个元素和其余元素。
      /// 返回其余部分的头。
      ListNode* split(ListNode* head, int n) {    
        /// @note 往后走 n 步
        while (--n && head)
          head = head->next;
        /// @note 把 rest 保存此时的 head 后方(如果 head 存在的话)
        ListNode* rest = head ? head->next : nullptr;
        /// @note 断开 head 后方
        if (head) head->next = nullptr;
        return rest;
      }
      
      /// @note 合并两个列表,返回合并后列表的头和尾。
      pair<ListNode*, ListNode*> merge(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);
        ListNode* tail = &dummy;
        while (l1 && l2) {
          /// @note 将小的放在 l1,大的放在 l2 (所以 while 循环中始终只要操作 l1 和 tail)
          if (l1->val > l2->val) swap(l1, l2);
          tail->next = l1;   ///< 保存 l1 为 tail 的下一位
          l1 = l1->next;     ///< l1 后移一位(继续和 l2 比较)
          tail = tail->next; ///< tail 也后移一位
        }
        tail->next = l1 ? l1 : l2; ///< 善后 l1, l2
        while (tail->next) tail = tail->next; ///< 并让 tail 指向合并后队列的尾部
        return {dummy.next, tail};
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 程序运行结果如下(以 4 只蝙蝠为例)

    在这里插入图片描述

  • 相关阅读:
    Vue:状态管理pinia
    [oeasy]python0011 - python虚拟机的本质_cpu架构_二进制字节码_汇编语言
    NetCoreAPI操作Excel表格
    2流高手速成记(之三):SpringBoot整合mybatis/mybatis-plus实现数据持久化
    T1029:计算浮点数相除的余(信息学一本通C++)
    案例分析背诵点
    RabbitMQ简介(一)
    Java中的泛型(Generics)
    【力扣-1300打卡】
    如何利用AI技术优化独立站客服系统?听听专家怎么说!
  • 原文地址:https://blog.csdn.net/panda1234lee/article/details/126186692