CatBase 排序算法实战,从内置 sorted 到手写冒泡

排序是程序员的必修课,也是检验一门新语言的最好切口。

原因很实在。一个排序算法下来,列表、循环、函数、比较运算四样东西全用上了。这四样是日常编码里出现频率最高的基础设施,排序写顺了,这门语言就算摸到门了。

前三篇我们写了 Hello World、配置体检工具、性能测试。这篇换个姿势,用算法来练手。

先看现成的答案

CatBase 手册列出的内置函数名里,有 sorted 和 reversed。排序这件事,语言本身给了现成的答案:

def main(args: list[str]) {
    nums: list[int] = [5, 3, 8, 1, 9, 2]
    print("原始: ", nums, "\n")
    print("排序后: ", sorted(nums), "\n")
}

说句实用的建议:生产环境请直接用内置的 sorted()。它底层是成熟的排序实现,比你手写的任何版本都快,也比你手写的任何版本都难写错。

那为什么还要手写?三个理由。面试要考,原理要懂,练手要真。手写一遍冒泡排序,你对「比较」和「交换」的理解会深一个层次,这个层次在看框架源码时会回来帮你。

下面三个算法,全部只用手册里已确认的语法写成。

冒泡排序

原理一句话:反复比较相邻的两个数,大的往后挪,每一轮把当前最大的数送到队尾。

def bubble_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 0
    while i < n {
        j: int = 0
        while j < n - 1 - i {
            if nums[j] > nums[j + 1] {
                tmp: int = nums[j]
                nums[j] = nums[j + 1]
                nums[j + 1] = tmp
            }
            j = j + 1
        }
        i = i + 1
    }
    return nums
}

代码里用到的语法,逐个对一遍手册:while 循环、if 比较、len()、列表索引读写、整数加减,全部有官方示例支撑。

注意 n - 1 - i 这个细节。每跑完一轮,队尾就固定一个最大值,所以内层循环的上界每轮减 1。少写这个优化,结果一样对,只是多做一些无用比较。

选择排序

原理换了个思路:每一轮在未排序区间里找出最小值,直接放到区间开头。

def selection_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 0
    while i < n {
        min_idx: int = i
        j: int = i + 1
        while j < n {
            if nums[j] < nums[min_idx] {
                min_idx = j
            }
            j = j + 1
        }
        if min_idx > i {
            tmp: int = nums[i]
            nums[i] = nums[min_idx]
            nums[min_idx] = tmp
        }
        i = i + 1
    }
    return nums
}

它和冒泡排序的交换次数完全不是一个量级。冒泡可能每轮都在换,选择排序一轮最多换一次。数据搬运成本高的场景,这个差别很可观。

顺带一个写法上的取舍:判断「最小值是否就是当前位置」时,这里用 min_idx > i,没用 !=。因为 min_idx 从 i 出发只会越找越大,两者相等就意味着没发生替换,一个 > 就够了。

插入排序

原理最接近人整理扑克牌:左手是排好的部分,每次把新牌插到该在的位置。

def insertion_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 1
    while i < n {
        key: int = nums[i]
        j: int = i - 1
        while j >= 0 and nums[j] > key {
            nums[j + 1] = nums[j]
            j = j - 1
        }
        nums[j + 1] = key
        i = i + 1
    }
    return nums
}

三个算法里,插入排序是唯一在「数据基本有序」时表现优异的。接近有序的数据跑它,时间复杂度会往 O(n) 靠。这也是为什么很多成熟排序实现,在数据量变小之后会切到插入排序。

一起跑一遍

写三个不如验一次。把四个方案摆到同一个乱序列表上,看结果是否一致:

def bubble_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 0
    while i < n {
        j: int = 0
        while j < n - 1 - i {
            if nums[j] > nums[j + 1] {
                tmp: int = nums[j]
                nums[j] = nums[j + 1]
                nums[j + 1] = tmp
            }
            j = j + 1
        }
        i = i + 1
    }
    return nums
}

def selection_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 0
    while i < n {
        min_idx: int = i
        j: int = i + 1
        while j < n {
            if nums[j] < nums[min_idx] {
                min_idx = j
            }
            j = j + 1
        }
        if min_idx > i {
            tmp: int = nums[i]
            nums[i] = nums[min_idx]
            nums[min_idx] = tmp
        }
        i = i + 1
    }
    return nums
}

def insertion_sort(nums: list[int]) -> list[int] {
    n: int = len(nums)
    i: int = 1
    while i < n {
        key: int = nums[i]
        j: int = i - 1
        while j >= 0 and nums[j] > key {
            nums[j + 1] = nums[j]
            j = j - 1
        }
        nums[j + 1] = key
        i = i + 1
    }
    return nums
}

def main(args: list[str]) {
    base: list[int] = [5, 3, 8, 1, 9, 2, 7, 4, 6]

    l1: list[int] = [5, 3, 8, 1, 9, 2, 7, 4, 6]
    l2: list[int] = [5, 3, 8, 1, 9, 2, 7, 4, 6]
    l3: list[int] = [5, 3, 8, 1, 9, 2, 7, 4, 6]

    print("原始数据  : ", base, "\n")
    print("内置 sorted: ", sorted(base), "\n")
    print("冒泡排序  : ", bubble_sort(l1), "\n")
    print("选择排序  : ", selection_sort(l2), "\n")
    print("插入排序  : ", insertion_sort(l3), "\n")
}

编译运行:

./bin/catbasecc sort.cat
./sort

四行排序结果应该完全一致。这就是验证算法正确性最朴素的办法:跟一个你信得过的实现对答案。

复杂度对比

算法 平均时间 最坏时间 额外空间 稳定性
冒泡排序 O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n²) O(1) 稳定
内置 sorted O(n log n) O(n log n) O(n) 稳定

三个手写版本的时间复杂度都是 O(n²)。数据量上千之后,和 O(n log n) 的差距会拉开到肉眼可见。所以手写是为了理解,不是为了上岗。

几句实话

这篇有两个地方需要你本地验证,我把话说在前面。

第一,变量索引赋值和嵌套循环,手册里还没有官方示例。常量索引赋值 nums[0] = 10 和变量索引读取 args[i] 都有示例,两者组合成的 nums[j] = x 本文用得很密,但手册确实没写。这是全文唯一需要你跑一遍确认的点。

第二,手册里没有递归的示例,所以快排和归并排序这篇没写。等官方文档确认了递归,下一篇补上。

另外 sorted() 只是手册列出的内置函数名,签名没有细写,具体用法以你本地版本为准。

版本还是 0.6,标准库第 7 章尚未补全。遇到跑不通的代码,把报错丢进官方微信群,也算帮他们补文档了。

写在最后

手写排序这件事,性价比高得离谱。三个算法加起来不到 80 行,换来的是对列表、循环、函数、比较运算的完整理解。

你还记得第一次写冒泡排序是跟谁学的吗?评论区聊聊。


*参考来源:CatBase 官网 catbase-lang.com、GitHub 仓库 DoRobotAi/catbase-lang README 与 doc/catbase.md。复杂度表格为算法通用结论。

posted @ 2026-10-17 15:06  努力,努力再努力  阅读(28)  评论(1)    收藏  举报