【问题标题】:What is an appropriate sort algorithm for an embedded system?什么是适合嵌入式系统的排序算法?
【发布时间】:2026-01-22 18:45:01
【问题描述】:

我正在为嵌入式系统开发软件,我需要实现一个排序例程,但我在选择最佳解决方案时遇到了麻烦。我的要求如下:

  1. 由于这是一个内存非常有限的系统,空间复杂性是主要因素。
  2. 由于要排序的元素数量通常很少,而且排序只是偶尔发生,因此时间复杂度不一定是主要因素。
  3. 稳定的算法是我的应用程序的要求。
  4. 因为这是一个嵌入式系统,所以代码大小是一个因素。
  5. 无法保证数据最初会按近乎排序的顺序排列。

我考虑过以下算法:

  • 冒泡排序(是的,尽管我很惭愧地说)
  • gnome 排序
  • 插入排序
  • 就地合并排序(尽管在我看来,链表比数组更理想?)

虽然答案(对于我的确切情况)很可能是“呃,呃,没关系,我们关心的都使用冒泡排序”,但这个答案并不是很有用。 一般来说,哪些排序算法对嵌入式系统有用?

【问题讨论】:

  • 当你说小时,你的意思是多小? 〜10,〜100,〜1,000〜10,000?为了公平起见,请在您的系统上测试每个经典的 O(n^2) 就地排序算法,看看哪一个表现最好。
  • 第二个问题:你真的需要在你的应用程序中自动排序吗?除非您自动显示整个排序列表,否则您可能比使用排序进行预处理做得更好。
  • 对少量元素使用插入排序。它编码简单、稳定且能胜任。
  • 堆排序很好,只是它不稳定。我已将它用于中型嵌入式系统上的 2000 条目电话簿。

标签: algorithm sorting embedded


【解决方案1】:

插入排序也很好,它在实践中运行得很快,稳定且就地。它与 gnome 排序非常相关,在实践中更快,但是对于 gnome 排序,代码更小,并且占用的辅助空间更少(不是大 O,区别在于常数)

编辑:是的,我搞砸了一点,把它们颠倒过来了——我可能不应该在喝咖啡之前回答问题。它之前说插入​​排序的代码和空间比 gnome 排序少

【讨论】:

    【解决方案2】:

    不要为冒泡排序感到羞耻,它有它的位置。如果您的数据集较小,则很容易编写代码,并且如果操作正确(永远不要交换相等的元素),则很稳定。

    如果您的数据大部分是通过在每次传递时交替方向进行排序的,它也可能会非常快。我知道你说它最初不是接近排序的,我说的是如果你排序然后坚持下去,它变成这样的可能性。在任何一种情况下,如果数据集的大小很小,那么它是否完全未排序并不重要。

    尤其是如果,正如您在对另一个答案的评论中提到的那样,您的数据集大小约为 11。缺少明确的排序算法设计是故意可怕的,任何算法都可以轻松地足够快地处理这样的大小。

    如果您的环境不提供稳定的排序,考虑到您的约束和属性,我会选择冒泡排序。


    事实上,使用以下程序和time 实用程序,我发现用于qsort 和冒泡排序的CPU 时间只有在元素计数达到10,000 时才会开始产生影响。

    即便如此,冒泡排序也只用了不到半秒。除非你每秒要进行很多次排序,否则这无关紧要。

    #include <stdio.h>
    #include <stdlib.h>
    
    static int data[10000];
    #define SZDATA (sizeof (*data))
    #define NMDATA (sizeof (data) / sizeof (*data))
    
    int compfn (const void *a, const void *b) {
        if (*((int*)a) > *((int*)b))
            return 1;
        if (*((int*)a) < *((int*)b))
            return -1;
        return 0;
    }
    
    int main (void) {
        int i, tmp, swapped, count;
    
        for (i = 0; i < NMDATA; i++)
            data[i] = (i * 3) % 11;
    
        if (0) {
            qsort (data, NMDATA, SZDATA, compfn);
        } else {
            swapped = 1;
            count = NMDATA;
            while (swapped) {
                swapped = 0;
                for (i = 1; i < count; i++) {
                    if (data[i] < data[i-1]) {
                        tmp = data[i];
                        data[i] = data[i-1];
                        data[i-1] = tmp;
                        swapped = 1;
                    }
                }
                count --;
            }
        }
    
        //for (i = 0; i < NMDATA; i++)
            //printf ("%10d\n", data[i]);
    
        return 0;
    }
    

    通过改变data 数组和if (0) 语句的大小,我得到了以下结果(以毫秒为单位),每种情况运行五次:

    Data size | qsort | bubble
    ----------+-------+-------
          100 |    61 |     76
              |    76 |     76
              |    77 |     61
              |    61 |     60
              |    61 |     61  avg qsort = 67, bubble = 67
    
         1000 |    77 |     93
              |    61 |     45
              |    76 |     77
              |    77 |     76
              |    76 |     77  avg qsort = 73, bubble = 74
              |       |
        10000 |    92 |    414
              |    77 |    413
              |    61 |    413
              |    76 |    405
              |    61 |    421  avg qsort = 73, bubble = 413
    

    我怀疑使用小数据集的快速冒泡排序之所以如此,是因为缺少函数调用。

    要摆脱这一点的是,对于较小的数据集,算法的效率通常并不重要,因为像 big-O 这样的东西通常会随着数据集变大而相关。

    但是,此测试是在 my 环境中完成的,您的环境可能会有很大差异。我建议在您的环境中执行相同的测量 - 实施冒泡排序,并且仅在必要时才考虑使用更复杂的算法。


    根据评论者的建议,我使用srand(42) 重新运行测试,然后使用rand() 填充数组元素。在这种情况下,冒泡排序的结果仅略差一点,10,000 个元素的 642 对 413 毫秒和 1,000 个元素的 82 对 74 毫秒。

    考虑到问题中的限制(元素数量少、排序不频繁、稳定性要求、空间复杂度低),我仍然更喜欢冒泡排序的简单性,而不是任何更复杂的算法。

    不过,请记住我之前的建议:您需要在自己的自己的环境中计时。结果可能大不相同。您可以使用我提供的代码作为基线。说真的,如果您选择的任何方法都花费不到一秒钟的时间,那么对于您的目的来说,它可能已经绰绰有余了。

    【讨论】:

    • @Mystical:至于 571,这不是排序算法的一部分,它只是为了获取“随机”数据,所以没关系 - 如果你愿意,可以使用真正的随机数据。我只是为了让 qsort 和 bubble 拥有相同的数据。
    • 在 i=100 的情况下,这不是大量的交换,您只需从列表的开头移动几个元素。使用 i=10000 也不正确,因为您有 lot 相同的元素。我是说你的测量非常有偏见而且毫无用处。关于插入排序:我已经赞成插入排序答案,该建议是针对您的,因此您建议使用正确的替代方法:/顺便说一句:您可以使用常量初始化随机生成器。
    • 我不同意:冒泡排序的唯一位置是在教室里,在“没人应该使用的幼稚算法”的标题下。
    • 那你们应该写自己的答案,而不是堵塞评论系统。我不会再回复了——无休止的争论不是 cmets 的本意。
    • 我很高兴有人支持冒泡排序。在某些(尽管是奇异的)情况下,冒泡排序可能是一个很棒的——甚至是最好的——算法,而且它的简单性(在某些情况下)比它通常得到的赞誉更多。无论如何,我不明白为什么 OP 不应该对其性能进行基准测试以进行比较……结果可能令人惊讶。
    【解决方案3】:

    系统是否嵌入并不重要。重要的是您列出的因素:代码大小限制、可用内存、所需速度和元素数量。

    根据您的概述,冒泡排序是一种完全可以接受的解决方案:它体积小、可预测、易于使用内存,并且非常易于实现和调试。我在 1980 年代初看到了一个证明,得出的结论是,对于 n ≤ 11,冒泡排序是时间最优的。现代快速排序可能会稍微改变这一点,但我怀疑收支平衡可能会大大减少。

    要使不稳定的排序算法稳定,请添加一个包含原始位置的排序键。仅当主键是平局时才参考辅助键。

    【讨论】:

    • 这正好是我可能要处理的元素数量。感谢您的信息!
    【解决方案4】:

    删除“在嵌入式系统上”并将问题更改为“一般来说,什么排序算法有用”。接下来,试试吧!到目前为止你尝试了什么,内存消耗是多少,执行时间是多少,代码大小是多少?

    您应该在嵌入式或桌面上执行这些实验并提出这些问题。没有一般的答案,因为没有一般的要求。每个解决方案都有它的位置,具体取决于需求。一种尺寸适合所有人,没有人适合。真正的问题是您的要求是什么,然后您担心实施和满足这些要求。甚至需要排序,这可以用不同的方式解决吗?这种排序在您的整体绩效数字中的哪个位置?帐篷里的其他长杆是什么?

    就像在桌面上一样,对它们进行编码并尝试它们,看看你想出了什么。没有理由因为提到冒泡排序而感到尴尬。如果您的要求没有性能或大小数字冒泡排序好,每个人都可以阅读和理解代码,简单,可维护,不依赖库或库版本等。没有一件坏事可以说(如果有无性能要求)。

    【讨论】:

      【解决方案5】:

      Sorting in Embedded Systems,Nigel Jones 关于 Embedded Gurus 的文章值得一看。

      他得出结论,shell 排序是他根据代码大小和性能选择的排序算法。

      【讨论】:

      • 当然是相关的,但是shell排序不稳定,所以我不能使用它。
      【解决方案6】:

      嵌入式系统的限制因目标平台和应用程序要求而异,这通常是竞争因素之间的权衡。您需要考虑的因素可能包括:

      • 确定的执行时间
      • 确定性内存使用情况
      • 最小化执行时间
      • 最小化内存使用
      • 代码大小

      例如,您可能有一个算法平均使用很少的内存,但内存量与排序中的项目数不是线性的。然后,您最好使用一种算法,即一个平均值使用更多内存,但内存使用量更有限或线性。同样对于执行速度,非确定性算法依赖于初始顺序,而其他算法对初始顺序的依赖性或不变性较小。

      在这种情况下,您可能会优先考虑内存使用问题而不是执行速度。

      所以答案是没有单一的答案,这取决于您的具体要求和限制。这个useful comparison table 应该用来帮助选择最适合您的算法。

      综上所述,为了加快开发速度,尝试编译器标准库提供的标准 qsort() 函数总是值得的。如果这对您有用并且满足您的限制条件,则无需进一步的工作。

      【讨论】: