插入排序与选择排序

时间:2013-04-03 22:04:09

标签: algorithm sorting insertion-sort selection-sort

我想了解插入排序和选择排序之间的区别。

它们似乎都有两个组件:未排序列表和排序列表。它们似乎都从未排序的列表中取出一个元素并将其放入适当位置的排序列表中。我已经看到一些网站/书籍说选择排序是通过一次交换一个而插入排序只是找到正确的位置并插入它。但是,我看到其他文章说了些什么,说插入排序也交换了。因此,我很困惑。有没有规范来源?

22 个答案:

答案 0 :(得分:155)

选择排序:

给定一个列表,取当前元素并将其与当前元素右侧的最小元素交换。 Selection Sort

插入排序:

给定一个列表,获取当前元素并将其插入列表的适当位置,每次插入时调整列表。它类似于在纸牌游戏中安排牌。 Insertion Sort

时间复杂性选择排序始终为n(n - 1)/2,而插入排序具有更好的时间复杂度,因为其最差情况复杂度为n(n - 1)/2。通常,它将采用较小或相等的比较n(n - 1)/2

来源:http://cheetahonfire.blogspot.com/2009/05/selection-sort-vs-insertion-sort.html

答案 1 :(得分:50)

插入排序和选择排序都有一个外部循环(在每个索引上)和一个内部循环(在索引子集上)。内循环的每次传递都会将排序区域扩展一个元素,代价是未排序区域,直到它用完了未排序的元素。

不同之处在于内循环的作用:

  • 在选择排序中,内部循环位于未排序元素上。每个过程选择一个元素,并将其移动到最终位置(在已排序区域的当前末尾)。

  • 在插入排序中,内循环的每次传递都会遍历排序的元素。排序的元素将被移位,直到循环找到插入下一个未排序元素的正确位置。

因此,在选择排序中,排序的元素在输出顺序中找到,并在找到它们后保持不变。相反,在插入排序中,未排序的元素保持不变,直到按输入顺序消耗,而排序区域的元素不断移动。

就交换而言:选择排序在内循环的每次传递中进行一次交换。插入排序通常会在内循环之前将要插入的元素保存为temp ,为内循环留出空间将排序后的元素向上移动一次,然后将temp复制到之后的插入点。

答案 2 :(得分:18)

这可能是因为您将链接列表的排序说明与数组的排序说明进行比较。但我无法确定,因为你没有引用你的消息来源。

理解排序算法的最简单方法通常是获得算法的详细描述(不是模糊的东西,比如“这种使用交换。某处。我不是说在哪里”),得到一些扑克牌(5-10应该足够简单的排序算法),并手动运行算法。

选择排序:扫描未排序的数据,查找剩余的最小元素,然后将其交换到排序数据后面的位置。重复直到完成。如果对列表进行排序,则不需要将最小元素交换到位,您可以将列表节点从其旧位置移除并将其插入新位置。

插入排序:将元素紧跟在排序数据之后,扫描排序数据以找到放置它的位置,并将其放在那里。重复直到完成。

插入排序 可以在“扫描”阶段使用交换,但不是必须的,除非您正在排序数据类型的数组,否则它不是最有效的方式:(a)不能移动,只能复制或交换; (b)复制比交换更昂贵。如果插入排序确实使用swap,它的工作方式是你同时搜索放置新元素的位置,通过重复地将新元素与紧接在它之前的元素交换,只要它之前的元素比它大。一旦你到达一个不大的元素,你就找到了正确的位置,然后转到下一个新元素。

答案 3 :(得分:9)

两种算法的逻辑非常相似。它们在数组的开头都有一个部分排序的子数组。唯一的区别是他们如何搜索要放入排序数组的下一个元素。

  • 插入排序在正确的位置插入下一个元素;

  • 选择排序选择最小元素并与当前项目交换;

此外,插入排序是稳定的,而不是选择排序

我在python中实现了两者,并且值得注意它们的相似之处:

def insertion(data):
    data_size = len(data)
    current = 1
    while current < data_size:
        for i in range(current):
            if data[current] < data[i]:
                temp = data[i]
                data[i] = data[current]
                data[current] = temp

        current += 1

    return data

只需进行少量更改,就可以进行选择排序算法。

def selection(data):
    data_size = len(data)
    current = 0
    while current < data_size:
        for i in range(current, data_size):
            if data[i] < data[current]:
                temp = data[i]
                data[i] = data[current]
                data[current] = temp

        current += 1

    return data

答案 4 :(得分:9)

选择排序
假设存在以特定/随机方式写入的数字数组,并且可以说我们要按升序排列。因此,一次取一个数字并用最小的编号代替它们。在列表中可用。通过执行此步骤,我们最终将获得所需的结果。

enter image description here



插入排序
请牢记类似的假设,但唯一的区别是这次我们一次选择一个数字并将其插入预先排序的部分中,从而减少了比较因此效率更高。

enter image description here

答案 5 :(得分:4)

简而言之,我认为选择排序首先搜索数组中的最小值,然后进行交换,而插入排序采用一个值并将其与保留给它的每个值(在它后面)进行比较。如果值较小,则交换。然后,再次比较相同的值,如果它小于它后面的值,则再次交换。 我希望这是有道理的!

答案 6 :(得分:4)

简而言之,

选择排序:从未排序的数组中选择第一个元素,并将其与剩余的未排序元素进行比较。它与冒泡排序类似,但不是交换每个较小的元素,保持最新的元素索引更新并在每个循环结束时交换它

插入排序:它与Selection sort相反,它从未排序的子数组中选取第一个元素并将其与已排序的子数组进行比较,然后插入找到的最小元素并将其全部移位从右边到第一个未排序元素的已排序元素。

答案 7 :(得分:3)

我还要再试一次:考虑几乎排序的数组的幸运案例会发生什么。

排序时,数组可以被认为有两个部分:左侧 - 排序,右侧 - 未排序。

插入排序 - 选择第一个未排序的元素并尝试在已排序的部分中找到它的位置。由于您从右向左搜索,很可能会发生您要比较的第一个排序元素(最左边的部分,最左边的部分)小于拾取的元素,因此您可以立即继续使用下一个未排序的元素。

选择排序 - 选择第一个未排序的元素并尝试找到整个未排序部分的最小元素,并在需要时交换这两个元素。问题是,由于右边的部分是未排序的,你必须每次都考虑每个元素,因为你不可能确定是否存在比拾取的元素更小的元素。

顺便说一句,这正是heapsort在选择排序方面的改进 - 由于heap,它能够更快地找到最小的元素。

答案 8 :(得分:3)

选择排序:当您开始构建已排序的子列表时,该算法确保排序的子列表始终完全排序,不仅根据其自身的元素而且还根据完整的数组(即已排序和未排序的子列表)。因此,从未分类的子列表中找到的新的最小元素将仅附加在已排序的子列表的末尾。

插入排序:算法再次将数组分成两部分,但这里元素从第二部分中拾取并插入到第一部分的正确位置。这永远不能保证第一部分按照完整数组进行排序,尽管在最后一遍中每个元素都处于正确的排序位置。

答案 9 :(得分:3)

这两种排序算法的选择取决于所使用的数据结构。

使用数组时,请使用选择排序(尽管为什么,何时可以使用qsort?)。 使用链接列表时,请使用插入排序。

这是因为:

  • 链表遍历比数组更昂贵。
  • 插入列表比数组便宜得多。

Insertion sort将新值注入到排序后的段的中间。因此,需要将数据“推回”。但是,当您使用链接列表时,通过扭曲2个指针,可以有效地将整个列表向后推。在数组中,必须执行n-i交换才能将值推回,这可能会非常昂贵。

选择排序始终追加到末尾,因此在使用数组时不会出现此问题。因此,数据无需“推回”。

答案 10 :(得分:2)

这两种算法通常都是这样的

第1步: 从未排序列表中取出下一个未排序的元素,然后

第2步: 把它放在排序列表中的正确位置。

其中一个步骤对于一种算法更容易,反之亦然。

插入排序: 我们获取未排序列表的第一个元素,将其放在排序列表中,某处。我们知道在哪里采用下一个元素(未排序列表中的第一个位置),但是需要一些工作才能找到放置它的位置(某处)。第1步很简单。

选择排序: 我们从未排序列表中取元素某处,然后将其放在排序列表的最后位置。我们需要找到下一个元素(它很可能不在未排序列表的第一个位置,而是某处)然后将其放在排序列表的末尾。第2步很简单

答案 11 :(得分:2)

插入排序的内部循环经过已经排序的元素(与选择排序相反)。这样,它可以在找到正确位置时中止内部循环。这意味着:

  1. 在一般情况下,内部循环只会遍历一半的元素。
  2. 如果几乎​​对数组进行了排序,则内部循环将被更早终止。
  3. 如果已对数组进行排序,则内部循环将立即中止,从而使插入排序的复杂度呈线性。

选择排序必须始终遍历所有内部循环元素。这就是为什么插入排序优先于选择排序的原因。但是,另一方面,选择排序的元素交换要少得多,这在某些情况下可能更重要。

答案 12 :(得分:1)

基本上插入排序的工作方式是一次比较两个元素,选择排序从整个数组中选择最小元素并对其进行排序。

概念上插入排序通过比较两个元素继续对子列表进行排序,直到整个数组被排序,而选择排序选择最小元素并将其交换到第一个位置第二个最小元素到第二个位置,依此类推。

插入排序可以显示为:

    for(i=1;i<n;i++)
        for(j=i;j>0;j--)
            if(arr[j]<arr[j-1])
                temp=arr[j];
                arr[j]=arr[j-1];
                arr[j-1]=temp;

选择排序可以显示为:

    for(i=0;i<n;i++)
        min=i;
        for(j=i+1;j<n;j++)
        if(arr[j]<arr[min])
        min=j;
        temp=arr[i];
        arr[i]=arr[min];
        arr[min]=temp;

答案 13 :(得分:1)

尽管选择排序和插入排序的时间复杂度相同,即n(n-1)/ 2。平均性能插入排序更好。在具有随机30000个整数的i5 cpu上进行了测试,选择排序的平均时间为1.5s,而插入排序的平均时间为0.6s。

答案 14 :(得分:0)

插入排序和选择排序都在前面有一个排序列表,在末尾有未排序列表,算法的作用也很相似:

  1. 从未排序的列表中获取一个元素
  2. 将其放入已排序列表

区别是:

  • 插入排序提取未排序列表的第一个元素,然后在排序列表中进行比较和交换以确保该元素到达正确的位置,这主要是在步骤2中进行的插入
auto insertion_sort(vector<int>& vs)
{
  for(int i=1; i < vs.size(); ++i)
  {
    for(int j=i; j > 0; --j)
    {
      if(vs[j] < vs[j-1]) swap(vs[j], vs[j-1]);
    }
  }
  return vs;
}
  • 选择排序比较并标记未排序列表的最小元素,然后将其与未排序列表的第一个元素交换,实际上将此元素作为已排序列表的一部分-努力是主要是在步骤1中进行选择
auto selection_sort(vector<int>& vs)
{
  for(int i = 0; i < vs.size(); ++i)
  {
    int iMin = i;
    for(int j=i; j < vs.size(); ++j)
    {
      if(vs[j] < vs[iMin]) iMin = j;
    }
    swap(vs[i], vs[iMin]);
  }
  return vs;
}

答案 15 :(得分:0)

我想从上面用户@thyago 摊位的几乎出色的答案中添加一点改进。在 Python 中,我们可以进行一行交换。下面的 selection_sort 也已通过将当前元素与右侧的最小元素交换来修复。

在插入排序中,我们将从第二个元素开始运行外循环,并在当前元素的左侧进行内循环,将较小的元素向左移动。

def insertion_sort(arr):
    i = 1
    while i < len(arr):
        for j in range(i):
            if arr[i] < arr[j]:
                arr[i], arr[j] = arr[j], arr[i]
    i += 1

在选择排序中,我们也运行外循环,但不是从第二个元素开始,而是从第一个元素开始。然后内循环将当前 + i 元素循环到数组末尾以找到最小元素,我们将与当前索引交换。

def selection_sort(arr):
    i = 0
    while i < len(arr):
        min_idx = i
        for j in range(i + 1, len(arr)):
            if arr[min_idx] > arr[j]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    i += 1

答案 16 :(得分:0)

它们的共同点是它们都使用分区来区分数组的排序部分和未排序的部分。

区别在于,通过选择排序,可以确保在将元素添加到已排序分区中时,数组的已排序部分不会改变。

之所以这样,是因为选择会搜索未排序集的最小值,然后将其添加到排序集的最后一个元素之后,从而使排序集增加1。

另一方面,

Insertion只关心关心遇到的下一个元素,它是数组未排序部分中的第一个元素。 它将使用此元素并将其简单地放入排序集中的适当位置。

对于仅部分排序的数组,插入排序通常通常是一个更好的选择,因为您正在浪费时间来寻找最小值。

结论:

选择排序通过在未排序部分中找到最小元素,将元素逐渐添加到末尾。

插入排序会将未排序部分中找到的第一个元素传播到排序部分中的任何地方。

答案 17 :(得分:0)

插入排序在交换该选择方面做得更多。这是一个示例:

enter image description here

答案 18 :(得分:0)

插入排序不交换内容。即使它使用了temp变量,使用temp var的要点是,当我们发现索引中的值与前一个索引的值相比较小时,我们将较大的值移到较小值的位置会写东西的索引。然后,我们使用temp var替换先前的索引。 示例:10、20、30、50、40。 迭代1:10、20、30、50、50。[温度= 40] 迭代2:10,20,30,40(临时值),50。 因此,我们只需在某个变量的所需位置插入一个值即可。

但是,当我们考虑选择排序时,我们首先找到具有较低值的索引,然后将该值与第一个索引中的值交换,并继续重复交换直到所有索引都得到排序。这与两个数字的传统交换完全相同。 例如:30,20,10,40,50。 迭代1:10、20、30、40、50。此处temp var仅用于交换。

答案 19 :(得分:0)

一个简单的解释如下:

给出:未排序的数组或数字列表。

问题陈述:按升序对数字列表/数组进行排序,以了解选择排序和插入排序之间的区别。

插入排序:为了便于理解,您从上到下查看列表。我们将第一个元素视为初始最小值。现在,我们的想法是,我们线性遍历该列表/数组的每个索引,以找出在任何索引处是否有其他元素的值小于初始最小值。如果找到这样的值,我们只交换它们索引处的值,即假设15是索引1的最小初始值,并且在线性遍历索引期间,我们遇到了一个数值较小的数字,例如索引9处为7现在,将索引9处的值7与具有15作为其值的索引1交换。该遍历将继续与当前索引的值进行比较,其余索引将交换为较小的值。这种情况一直持续到列表/数组的倒数第二个索引为止,因为最后一个索引已经排序,并且没有值可用于检查数组/列表之外的值。

选择排序:让我们假设列表/数组的第一个索引元素已排序。现在,从第二个索引处的元素开始,将其与先前的索引进行比较,以查看该值是否较小。遍历可以可视化为两个部分,已排序和未排序。对于列表/数组中的给定索引,将可视化从未排序到已排序的比较检查。  假设您的索引1的值为19,索引3的值为10。我们考虑从未排序到已排序(即从右到左)的遍历。因此,假设我们必须对索引3进行排序。从右到左比较时,我们看到它的值小于索引1。一旦确定,我们就将索引3的这个数字10放在具有值19的索引1的位置。索引1的原始值19向右移了一位。对于列表/数组中的每个元素,这种遍历一直持续到最后一个元素。

我没有添加任何代码,因为问题似乎在于了解遍历方法的概念。

答案 20 :(得分:-1)

选择 - 选择特定项目(最低)和            将它与i(没有迭代)元素交换。            (即,第一,第二,第三.......)            因此,将排序列表放在一边。

插入 - 首先与第二次比较            比较第三和第二&amp;第一            比较第四,第三,第二&amp;第一......

a link where all sortings are compared

答案 21 :(得分:-1)

用外行的话来说,(可能是获得对问题的高度理解的最简单方法)

冒泡排序类似于站在一条线上并尝试按高度排序。你一直与旁边的人交换,直到你在正确的地方。这从左侧开始(或根据实现方式向右),并且一直切换,直到每个人都被排序。

然而,在选择排序中,您所做的与安排一手牌相似。你看看卡片,拿最小的卡片,将它一直放在左边,依此类推。