插入排序(Insertion Sort)

维基百科:http://zh.wikipedia.org/wiki/插入排序

算法思想:
若数组A[n]的前n-1个数已经有序,我们只需把第n个元素插入到适当的位置即可。易分析得算法的时间复杂度为Ο(n^2)

具体描述:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。

一、简单插入排序(simple insertion sort)

#include <cstdlib>
#include <iostream>

using namespace std;

//iterative implementation
template<class T>
void simpleInsertionSort(T *a, int n)
{
    T temp;
    int j;
    for(int i=1; i<n; i++)
    {
        temp = a[i];
        j = i-1;
        while(j >= 0 && a[j] > temp)
        {
            a[j+1] =  a[j];
            j--;
        }
        a[j+1] = temp;  //put a[i] to right place
    }
}

//recursive implementation
template<class T>
void insertionSort(T *a ,int n)
{
    if(n>1)
    {
      insertionSort(a,n-1); //sort the first n-1 elements recursively
      insert(a,n);          //insert a[n-1] in a[0..n-2]
    }
}


/* n the size of the array
 * assume the first n-1 elements is already sorted
*/
template<class T>
void insert(T *a, int n)
{
    T temp = a[n-1];
    int i = n-2;
    
    while(i >= 0 && a[i] > temp)
    {
        a[i+1] = a[i];
        i--;
    }
    a[i+1] =  temp;
}

int main(int argc, char *argv[])
{
    int n;
    int *a = NULL;
    
    while(cin>>n && n > 0)
    {
        a = new int[n];
        for(int i=0; i<n; i++)
        {
            cin>>a[i];
        }
        //simpleInsertionSort(a,n);
        insertionSort(a,n);
        for(int i=0; i<n; i++)
        {
            cout<<a[i]<<" ";
        }
        cout<<endl<<endl;
        delete [] a;
    }
    
    system("PAUSE");
    return EXIT_SUCCESS;
}

二、二分查找排序(binary search sort)

template<class T>
void binarySearchSort(T *a, int n)
{
    T temp;
    int low ,mid,high;
    for(int i=1; i<n; i++)
    {
        temp = a[i];            //暂存记录 
        low = 0;
        high = i-1;
        
        while(low <= high)       //寻找插入点
        {
            mid = (low + high)/2;
            if(temp < a[mid])
            high = mid - 1;     //插入点在低半区间
            else
            low = mid + 1;      //插入点在高半区间
        }
        //high+1为插入点
        for(int k=i-1; k>=high+1; k--)//记录后移
        {
            a[k+1] = a[k];
        }
        a[high+1] = temp;//插入到适当位置
    }
}

注意:

1、虽然折半查找减少了比较次数,但是没有减少移动次数,因而折半插入排序的算法时间复杂度仍为Ο(n^2)

2、折半插入,插入点的确定:

(1)考虑特殊情况,当区间[0,i]恰好是有序的,此时插入点应该为i,即high+1,因为high==i-1

(2)对于折半插入,我们就是要在[0,i-1]之间寻找一个有效的插入点k,使得a[k-1]<=a[i]<a[k],即保证插入是稳定的。

(3)由于搜索区间是以2倍的速度不断递减,必然会有某一时刻出现:low == mid == high的情况,此时分两种情况:

a、若temp <a[mid],插入点在左边区间:high = mid-1;接着由于循环条件low<=high不满足而结束循环,此时temp 刚好比a[low]小,也就是说应该插入在low处。且满足low == mid == high+1

b、若temp>=a[mid],插入点在右边区间:low = mid +1;同样循环结束,此时temp刚好应该插入high点之后,且满足mid == high ==low-1

(4)综合两种情况,插入点都应该为high+1

参考书籍:

[1]《数据结构(C语言版)》严蔚敏

[2]《算法导论》第2版,潘金贵译

点赞