插入排序法包括:直接插入法(稳定),希尔排序法(不稳定),二分插入排序(稳定),链表插入排序
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外,而第二部分就只包含这一个元素。在第一部分排序后,再把这个最后元素插入到此刻已是有序的第一部分里的位置。
1. 从第一个元素开始,该元素可以认为已经被排序
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5. 将新元素插入到下一位置中
6. 重复步骤2
直接插入排序法的C++代码如下:元素由低到高排序
#include<iostream> using namespace std; void InsertSort(int *a,int length) { int i,j; for(i=1;i<length;i++) { int key=a[i]; for(j=i-1;j>=0;j--) { if(key>a[j]) break; else { a[j+1]=a[j]; a[j]=key; } } } } int main() { int b[10]={4,6,2,3,5,1,7,9,8,10}; InsertSort(b,10); for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; return 0; }
以下代码为直接插入排序法利用STL容器和模板实现的:
#include<iostream> #include<vector> using namespace std; template<typename Type> void InsertionSort(vector<Type>&a) { int n=a.size(); vector<Type>::iterator it,is; for(it=a.begin()+1;it!=a.end();it++) { is=it; Type num=*is; while(is!=a.begin()&&num<*(is-1)) { *is=*(is-1); is--; } *is=num; } } void InsertSort(int *a,int length) { int i,j; for(i=1;i<length;i++) { int key=a[i]; for(j=i-1;j>=0;j--) { if(key>a[j]) break; else { a[j+1]=a[j]; a[j]=key; } } } } int main() { int b[10]={4,6,2,3,5,1,7,9,8,10}; vector<int>a(b,b+10); InsertionSort(a); vector<int>::iterator it; for(it=a.begin();it!=a.end();it++) { cout<<*it<<" "; } cout<<endl; InsertSort(b,10); for(int i=0;i<10;i++) { cout<<b[i]<<" "; } cout<<endl; return 0; }
希尔排序(Shell Sort)
是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
#include<iostream> using namespace std; void HealSort(int *a,int length) { int b[1000]={0}; int k=length/2; int i,j,temp; while(k>0) { for(i=0;i<length-k;i++) { j=k+i; while(j>=k) { if(a[j]<a[j-k]) { temp=a[j]; a[j]=a[j-k]; a[j-k]=temp; j=j-k; } else break; } } k=k/2; } } int main() { int a[10]={15,6,28,89,61,92,43,24,47,510}; HealSort(a,10); for(int i=0;i<10;i++) { cout<<a[i]<<" "; } cout<<endl; return 0; }
二分插入排序
直接插入排序时后面的元素从后向前逐个比较寻找插入位置,有时候没有必要这样做,因为此时需要寻找合适插入位置的当前这个元素前面的子序列已经有序,如果使用二分查找插入位置往往可以减少平均搜索长度。对于一个待排序的随机序列而言,用二分插入排序取代直接插入排序,尽管总的移动元素次数不可能减少,但是可能减少总的平均比较次数。二分插入排序的平均时间复杂度为O(n2),最坏情况下的时间复杂度为(n2),当待排序序列已经有序时,排序时间复杂度为O(nlogn)。空间复杂度为O(1)。二分插入排序是否稳定依赖于具体实现。
假设一个序列已经有序,此时我们需要向这个序列里插入一个新的值,那么我们如何找到合适的位置呢?
首先跟序列最中间的那个元素比较,如果比最中间的这个元素小,则插入位置在它的左边,否则在它的右边。以当前最中间位置为分割点,如果在左边,则当前最中间位置是待搜索子序列的终点,如果在右边,右边邻接的元素将是待搜索子序列的起点。按照这种原则,继续寻找下一个中间位置,并继续这种过程,直到找到合适的插入位置为止。
问题是何谓合适的插入位置?如果序列中有一个元素与当前待插入的元素值相等,那么插入位置应该选在该元素的前面还是后面呢?选在后面则二分插入排序稳定,选在前面则二分插入排序不稳定。如果序列中有多个元素与当前待插入的元素值相等,插入位置选在哪里呢?选在最后一个的后面则二分插入排序稳定,其它情况均不稳定。这里的“前面”位置和“后面”位置通常被称为上界和下界。
还有,对数组二分插入排序比较简单,那么能对链表进行二分插入排序吗?理论上没有什么问题,如果希望代码复用程度高的话,链表需要提供迭代器。问题关键不在于代码复用性怎么样,而是插入排序的速度太慢,一般不采纳。
二分插入排序的C++实现代码如下:
#include<iostream> using namespace std; void BinaryInsertSort(int *a,int length) { int BinaryFind(int *a,int k,int key); int i,j,key,num; for(i=1;i<length;i++) { key=a[i]; num=BinaryFind(a,i-1,a[i]); if(num==-2) return; for(j=i;j>num+1;j--) { a[j]=a[j-1]; } a[num+1]=key; } } int BinaryFind(int *a,int k,int key) { if(key>=a[k]) { return k; } if(key<a[0]) return -1; int j=k/2; while(j>0) { if(key>a[j]) { j=(k+j)/2; } else if(key<a[j]) { j=j/2; } if(key>=a[j]&&key<=a[j+1]) { return j; } else if(key>=a[j-1]&&key<=a[j]) { return j-1; } } return -2; } int main() { int b[20]={4,6,2,3,5,1,7,9,8,10,14,16,12,13,15,17,19,18,110}; BinaryInsertSort(b,20); for(int i=0;i<20;i++) { cout<<b[i]<<" "; } cout<<endl; return 0; }
其中BinaryFind(int*a,int k,int key)是传入数组a,其中数组中前k+1个数已经排好序,要插入的第k+2个数的值为key,返回值是key要插入的地方的前一个位置的下标,根据返回的下标值我们就可以在BinaryInsertSort中将当前的值直接插入到得到的下标的下一个位置。
链表插入排序此处不再详细解释。