插入排序
插入排序
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。
插入排序的基本思想是:每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
分类
包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序)。属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) 。
直接插入排序
例如,已知待排序的一组纪录是:
60,71,49,11,24,3,66
假设在排序过程中,前3个纪录已按关键码值递增的次序重新排列,构成一个有序序列:
49,60,71
将待排序纪录中的第4个纪录(即11)插入上述有序序列,以得到一个新的含4个纪录的有序序列。首先,应找到11的插入位置,再进行插入。可以讲11放入数组的第一个单元r[0]中,这个单元称为监视哨,然后从71起从右到左查找,11小于71,将71右移一个位置,11小于60,又将60右移一个位置,11小于49,又再将49右移一个位置,这时再将11与r[0]的值比较,11≥r[0],它的插入位置就是r[1]。假设11大于第一个值r[1]。它的插入位置应该在r[1]和r[2]之间,由于60已经右移了,留出来的位置正好留给11.后面的纪录依照同样的方法逐个插入到该有序序列中。若纪录数n,续进行n-1趟排序,才能完成。
直接插入排序的算法思路:
(1) 设置监视哨r[0],将待插入纪录的值赋值给r[0];
(2) 设置开始查找的位置j;
(3) 在数组中进行搜索,搜索中将第j个纪录后移,直至r[0].key≥r[j].key为止;
(4) 将r[0]插入r[j+1]的位置上。
折半插入排序(二分插入排序)
将直接插入排序中寻找A[i]的插入位置的方法改为采用折半比较,即可得到折半插入排序算法。在处理A[i]时,A[0]……A[i-1]已经按关键码值排好序。所谓折半比较,就是在插入A[i]时,取A[i-1/2]的关键码值与A[i]的关键码值进行比较,如果A[i]的关键码值小于A[i-1/2]的关键码值,则说明A[i]只能插入A[0]到A[i-1/2]之间,故可以在A[0]到A[i-1/2-1]之间继续使用折半比较;否则只能插入A[i-1/2]到A[i-1]之间,故可以在A[i-1/2+1]到A[i-1]之间继续使用折半比较。如此担负,直到最后能够确定插入的位置为止。一般在A[k]和A[r]之间采用折半,其中间结点为A[k+r/2],经过一次比较即可排除一半纪录,把可能插入的区间减小了一半,故称为折半。执行折半插入排序的前提是文件纪录必须按顺序存储。[2]
折半插入排序的算法思想:
算法的基本过程:
(1)计算 0 ~ i-1 的中间点,用 i 索引处的元素与中间值进行比较,如果 i 索引处的元素大,说明要插入的这个元素应该在中间值和刚加入i索引之间,反之,就是在刚开始的位置 到中间值的位置,这样很简单的完成了折半;
(2)在相应的半个范围里面找插入的位置时,不断的用(1)步骤缩小范围,不停的折半,范围依次缩小为 1/2 1/4 1/8 .......快速的确定出第 i 个元素要插在什么地方;
(3)确定位置之后,将整个序列后移,并将元素插入到相应位置。
3 希尔排序法
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
各组内的排序通常采用直接插入法。由于开始时s的取值较大,每组内记录数较少,所以排序比较快。随着不断增大,每组内的记录数逐步增多,但由于已经按排好序,因此排序速度也比较快。
设计步骤
算法设计有很多方法。插入排序使用的是增量(incremental)方法;在排好子数组A[1..j-1]后,将A[j]插入,形成排好序的子数组A[1..j];
步骤
⒈从有序数列和无序数列{a2,a3,…,an}开始进行排序;
⒉处理第i个元素时(i=2,3,…,n),数列{a1,a2,…,ai-1}是已有序的,而数列{ai,ai+1,…,an}是无序的。用ai与ai-1,a i-2,…,a1进行比较,找出合适的位置将ai插入;
⒊重复第二步,共进行n-i次插入处理,数列全部有序。
思路
假定这个数组的序是排好的,然后从头往后,如果有数比当前外层元素的值大,则将这个数的位置往后挪,直到当前外层元素的值大于或等于它前面的位置为止.这具算法在排完前k个数之后,可以保证a[1…k]是局部有序的,保证了插入过程的正确性.
描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
⒈ 从第一个元素开始,该元素可以认为已经被排序
⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
⒌ 将新元素插入到下一位置中
⒍ 重复步骤2~5
如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。
C语言
示例代码为C语言,输入参数中,需要排序的数组为array[ ],起始索引为first(数组有序部分最后一个的下标,或者直接标 0),终止索引为last(数组元素的最后一个的下标)。示例代码的函数采用insert-place排序,调用完成后,array[]中从first到last处于升序排列。
voidinsertion_sort(intarray[],intfirst,intlast) { inti,j; inttemp; for(i=first+1;i<last;i++) { temp=array[i]; j=i-1; //与已排序的数逐一比较,大于temp时,该数移后 while((j>=0)&&(array[j]>temp)) { array[j+1]=array[j]; j--; } //存在大于temp的数 if(j!=i-1) {
array[j+1]=temp;} }
} } 这个更好: voidinsert_sort(int*array,unsignedintn) { inti,j; inttemp; for(i=1;i<n;i++) { temp=*(array+i); for(j=i;j>0&&*(array+j-1)>temp;j--) { *(array+j)=*(array+j-1); } *(array+j)=temp; } }
c++版本
#include<iterator> template<typenamebiIter> voidinsertion_sort(biIterbegin,biIterend) { typedeftypenamestd::iterator_traits<biIter>::value_typevalue_type; biIterbond=begin; std::advance(bond,1); for(;bond!=end;std::advance(bond,1))
{ value_typekey=*bond; biIterins=bond; biIterpre=ins; std::advance(pre,-1); while(ins!=begin&&*pre>key)
{ *ins=*pre; std::advance(ins,-1); std::advance(pre,-1); } *ins=key; } }
PHP版本
functioninsertSort($arr){ for($i=1;$i<count($arr);$i++){ $tmp=$arr[$i]; $key=$i-1; while($key>=0&&$tmp<$arr[$key]){ $arr[$key+1]=$arr[$key]; $key--; } if(($key+1)!=$i) $arr[$key+1]=$tmp; } return$arr; }
Java版本
/** *插入排序 *@paramarr *@return */ private static int[] insertSort(int[]arr){ if(arr == null || arr.length < 2){ return arr; } for(inti=1;i<arr.length;i++){ for(intj=i;j>0;j--){ if(arr[j]<arr[j-1]){ //TODO: int temp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=temp; }else{ //接下来是无用功 break; } } } return arr; }
运行软件:eclipse
C#版本
classProgram { staticvoidMain(string[]args) { InsertionSort(); } ///<summary> ///插入排序法 ///</summary> privatestaticvoidInsertionSort() { Console.WriteLine("插入排序法"); inttemp=0; int[]arr={23,44,66,76,98,11,3,9,7}; Console.WriteLine("排序前的数组:"); foreach(intiteminarr) { Console.Write(item+","); } Console.WriteLine(); varlength=arr.Length; for(inti=1;i<length;i++) { for(intj=i;j>0;j--) { if(arr[j]>arr[j-1]) { temp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=temp; } } //每次排序后数组 PrintResult(arr); } Console.ReadKey(); } ///<summary> ///打印结果 ///</summary> ///<paramname="arr"></param> privatestaticvoidPrintResult(IEnumerable<int>arr) { foreach(intiteminarr) { Console.Write(item+","); } Console.WriteLine(); } }
Pascal版本
procedure insertsort(var R:filetype); //对R[1..N]按递增序进行插入排序,R[0]是监视哨 begin for I := 2 To n do //依次插入R[2],...,R[n] begin R[0]:=R[I];j:=l-1; while R[0] < R[J] Do //查找R[I]的插入位置 begin R[j+1]:=R[j]; //将大于R[I]的元素后移 j:=j-1; end; R[j+1]:=R[0] ; //插入R[I] end; end; //InsertSort (运行软件:Free Pascal IDE 2.6.4)
Ruby版本
def insertion_sort(array) array.each_with_index do |element, index| next if index == 0 #第一个元素默认已排序 j = index - 1 while j >= 0 && array[j] > element array[j + 1] = array[j] j -= 1 end array[j + 1] = element end array end
Scala版本
definsertSort(ilist:Array[Int])
{ for(i<-1untililist.length)
{ for(j<-(0untili).reverse)
{ if(ilist(j+1)<ilist(j))
{ valtmp=ilist(j+1) ilist(j+1)=ilist(j) ilist(j)=tmp } } } }
算法复杂度
如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。[3]