插入排序还是选择排序的变体?

时间:2017-01-21 21:29:51

标签: c++ algorithm sorting insertion-sort

我有一个代码段here。在少数情况下测试它,似乎工作正常。

在学习算法之后,我已经一次性编写代码用于插入排序,但是对于这是否真的是传统的插入排序有疑问?

我感觉它可能是选择排序的变异(调整版本),这是我混淆的原因。

具体来说,这是值得关注的领域:(给定a元素的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;
        }
    }      
} 

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

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:3)

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

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;
}

这两个代码片段的逻辑等效如下:
直到搜索到的索引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

答案 1 :(得分:3)

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

您的代码缺乏确定的步骤,一旦确定元素的位置,比较就可以停止,并且对排序序列的移位操作随后会为新元素创建一个洞。相反,您依靠比较循环为您执行该转换,即使不再需要比较,这也不是非常有效。

这可能看起来有点令人困惑,所以我会详细说明你的代码。

  • 每次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。< / p>

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 

请注意,轮播需要比较。

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

  • 对序列中已经排序的部分使用二进制搜索,以最大限度地减少比较。
  • 执行轮换时使用比较。

祝你好运。