【问题标题】:Insertion sort or a variation of selection sort?插入排序还是选择排序的变体?
【发布时间】:2017-06-06 16:28:25
【问题描述】:

我有一个代码 sn-p here。测试了几个案例,似乎工作正常。

学习算法后,我一口气写了插入排序的代码,但是有一个问题,这是否真的是传统的插入排序?

我感觉这可能是选择排序的变体(调整版本),这是我感到困惑的原因。

具体来说这是关注的领域:(给定数组a of n 元素)

for(i=1;i<n;i++){
    for(j=0;j<i;j++){
        if(a[i] < a[j]){
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }      
} 

此外,这种方法的比较或交换次数是否更多/更少?

提前感谢您的帮助。

【问题讨论】:

    标签: c++ algorithm sorting insertion-sort


    【解决方案1】:

    您的问题最直接的答案是是的,它是插入排序。这是一个非常低效的插入排序,但它仍然是插入排序。

    您的代码缺少确定性步骤,即一旦确定了元素的位置,比较就可以停止,然后对排序序列进行移位操作,从而为新元素留下一个洞。相反,您依靠比较循环为您执行该转换,即使不再需要比较也是如此,这不是很有效。

    这可能看起来有点令人困惑,所以我将针对您的代码进行详细说明。

    • i 每次迭代的潜在客户元素最初是 a[i]
    • 您对序列中已排序的部分进行线性枚举,寻找a[i] 所属的位置
    • 一旦找到该位置(除非它已经是它所属的位置),您将 a[i] 与当前驻留在目标中的元素 a[j] 交换。
    • 从那时起,a[i] 的原始值现在就在序列中,但是...
    • 对于排序序列的其余部分,交换比较保证会针对存储在a[i] 中的任何值触发为真(提示:为什么要这样做?),因为之前成功的值已经排序。因此,a[i] 不断被排序序列中的下一个值替换,直到它最终保持最大值,即根据定义它所属的位置。

    因此,是的,这就是插入排序。它在整体开始时维护一个排序序列,该序列随着每次主要迭代而不断扩展。并且对于每次主要迭代,前景元素都被“插入”,并且尾随元素被向下移动以形成可用的孔来执行此操作。

    ...这种方法的比较或交换次数更多/更少?

    相当需要与您的方法进行更多比较。每次迭代都保证线性 O(n) 复杂度,并且有n 次迭代。因此,保证您的比较具有 O(N^2) 复杂度,这是高效排序算法的困扰。不只是最坏的情况; 保证


    C++ 插入排序

    也就是说,考虑一下

    template<typename Iter>
    void insertion_sort(Iter first, Iter last)
    {
        for (Iter it = first; it != last; ++it)
            std::rotate(std::upper_bound(first, it, *it), it, std::next(it));
    }
    

    如果您刚开始使用 C++,这可能是 seems like Greek(对希腊人没有冒犯),但它使用了两种使其非常高效的基本算法:std::upper_boundstd::rotate

    std::upper_bound 对排序序列进行操作。利用这一点,它可以利用 二分搜索 算法来定位排序序列中严格大于潜在值 (*it) 的第一个元素。因此,寻找单个潜在客户的插入点是O(logN),远远优于O(n)的线性搜索。

    一旦知道插入点,std::rotate 就会使用插入点的迭代器将元素放置到位。它有效地做到了这一点:

    0 1 2 3 5 6 4
            ^ ^ *  these will be rotated right one element
    
    0 1 2 3 5 6 
                4
    
    0 1 2 3   5 6 
            4
    
    0 1 2 3 4 5 6 
    

    请注意,旋转需要没有比较。

    显然,这个模板解决方案不是有人会为一些补救算法课程提交的。但我希望它能给您一些关于插入排序如何通过以下方式最小化其比较的想法:

    • 对序列中已排序的部分使用二分搜索来减少比较。
    • 在执行轮换时使用 no 比较。

    祝你好运。

    【讨论】:

    • 谢谢您,先生,出色的回答和全面有用的解释。
    【解决方案2】:

    此代码是一个插入排序实现。看一下内部循环:

    for(j=0;j<i;j++){
        if(a[i] < a[j]){
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    } 
    

    或简化:

    for(j = 0; j < i ; j++)
        if(a[i] < a[j])
            swap(a[i], a[j]);
    

    现在我们知道子数组a[0:i - 1] 已经从外循环的上一次运行中排序。这是查找索引n 的逻辑高尔夫版本,我们需要在该索引处插入a[i],并将索引在[n, i - 1] 范围内的所有元素推高一个索引:

    while(j < i && a[j] <= a[i])
        j++;
    
    //insert a[i] at the appropriate index
    int tmp = a[j];
    a[j] = a[i];
    
    //push all elements starting from index n one index further
    for(; j < i; j++){
        int swap = tmp;
        tmp = a[j];
        a[j] = swap;
    }
    

    这两个sn-ps代码的逻辑等价如下:
    直到搜索到的索引na[i] 的插入索引),都不会发生交换。现在我们换掉a[i]a[n]。从那时起,a[i] 将等同于上述代码中的tmp-变量。由于数组的其余部分仍按索引i - 1 排序,因此我们现在将每个元素与其前一个元素交换,该元素当前存储在索引i 处。绝对是一些漂亮的高尔夫插入排序。

    外循环只是标准

    for(i = 0; i < array_length; i++)
         insert a[i] at appropriate position
    

    【讨论】:

    • 非常感谢,这个答案真的很有帮助:)
    • @Aditya 花了我相当长的时间来解决这个问题。不错的高尔夫。很高兴帮助:)
    猜你喜欢
    • 1970-01-01
    • 2013-03-25
    • 2023-03-20
    • 1970-01-01
    • 2015-04-25
    • 2013-04-02
    • 2018-10-29
    • 1970-01-01
    • 2021-12-25
    相关资源
    最近更新 更多