中心思想:考慮你正在鬥地主,對方發給你一堆牌,你右手摸牌,左手拿牌,第一次摸到10放到左手,第二次摸到3就放到10左邊,第三次摸到K放10右邊,左手上的牌一直保持有序,直到左手拿着所有的牌爲止。這就是插入排序。
插入排序從第二個元素開始,在前面已排好序的序列中尋找合適的位置並插入,使之仍然有序。
插入排序最多的步驟在於移位操作,每個元素插入之前,數組的大於待插入值的元素必須事先挪位,故插入排序在最壞情況下是o(n*n)的複雜度。
直接插入:
//插入排序,不支持參數檢查
void insert_sort(int * a , int n)
{
int i,j,key;
for(i=1;i<n;++i)
{
key = a[i];//key 爲待插入的元素
//挪位
for( j = i-1;j>=0 && a[j] >key;--j)
{
a[j+1] = a[j];//兩重循環,故O(n*n)
}
//在合適的位置插入
a[j+1] = key;
}
}
在一萬個數據的運行結果:
插入排序有個挪位操作,每次循環必須判斷移動的元素是否大於待插入的元素,爲了省去不必要的判斷,我們可以先用二分法尋找合適的插入位置。
折半插入:
void insert_sort(int * a , int n)
{
int i,j,key,low,high,mid;
for(i=1;i<n;++i)
{
key = a[i];
//二分查找運算
low = 0,high = i-1;
while(low <= high)
{
mid = (low + high)/2;
if(key > a[mid])
low = mid + 1;
else// if(key <= a[mid])
high = mid - 1;
}
//挪位
for( j = i-1;j >= low;--j)
{
a[j+1] = a[j];
}
a[j+1] = key;
}
}
在一萬個數據的運行結果:
結果快了大概十多毫秒。。。我想應該是移位操作步數並沒有因此減少,只是對比較操作優化了一下而已。所以效果並不明顯。
二路插入:
那麼有沒有辦法減少移位的步數呢?仔細一想,有一種情況是不需要移位的,那就是待插入的元素剛好插入到有序序列的最後。我們想一下,如果有序序列前面還有位置放會怎樣?本來需要把所有有序序列移位的現在也變成不需要移位了。那麼如何實現呢?我們可以利用循環數組的想法來做。用first表示有序序列的開始,last表示有序序列的結尾,如果待插入元素小於first值或者大於last值,就放到外面去;如果待插入元素剛好處於first值和last值之間,那麼就插入到有序序列中間,這和直接插入一樣。
舉例:如果有序序列是 1 2 3 5,待插入的是0,那麼我就把它放到數組的最後,讓first等於最後一個(first = n -1),讓last = 2。
二路插入的思想是以空間換取時間的想法。
void insert_sort(int * a , int n)
{
int *t;
t = new int [n];
int i,j,key,first=0,last=0;
t[0] = a[0];
for(i=1;i<n;++i)
{
key = a[i];//key 爲待插入的元素
//如果待插入元素比first值還小
if(key <= t[first])
{
first = (first-1+n)%n;
t[first] = key;
}
//如果待插入元素比last值還大
else if(key >= t[last])
{
last = last + 1;
t[last] = key;
}
//如果待插入元素處於first和last之間
else //if(key > t[first] && key < t[last])
{
//挪位
for( j = last;j != first && t[j] > key; j= (j-1+n)%n)
{
t[(j+1)%n] = t[j];//兩重循環,故O(n*n)
}
//在合適的位置插入
t[(j+1)%n] = key;
last = (last + 1)%n;
}
}
//賦值給a數組
j=0;
i =first;
do{
a[j++] = t[i];
i = (i+1)%n;
}while(i != (last + 1)%n);
}
但是運行結果卻出乎意料:
但是對於完全逆序的數據卻出乎意料的快:
經過仔細分析,原來是取餘(循環鏈表需要用到模運算)造成的影響,於是修改一下:
void insert_sort(int * a , int n)
{
int *t;
t = new int [n];
int i,j,key,first=0,last=0;
t[0] = a[0];
for(i=1;i<n;++i)
{
key = a[i];//key 爲待插入的元素
//如果待插入元素比first值還小
if(key <= t[first])
{
first = (first-1+n)%n;
t[first] = key;
}
//如果待插入元素比last值還大
else if(key >= t[last])
{
t[++last] = key;
}
//如果待插入元素處於first和last之間
else //if(key > t[first] && key < t[last])
{
//挪位
int k1,k2;
for( j = last;j != first && t[j] > key; j= (!j)?(j-1+n):j-1)//避免取餘帶來的性能損耗
{
k1= (!(j-n+1))?j+1-n:j+1;
t[k1] = t[j];//兩重循環,故O(n*n)
}
//在合適的位置插入
t[(!(j-n+1))?j+1-n:j+1] = key;
++last;
}
}
//賦值給a數組
j=0;
i =first;
do{
a[j++] = t[i];
i = (!(i-n+1))?i+1-n:i+1;
}while(i != last + 1);
}
運行結果:
我只能說我已經盡力了,對於均勻隨機產生的數據,二路插入只有70多毫秒,比直接插入還要慢一點,但是對於極端情況即完全逆序或者完全升序的情況來說是很快的,權衡一下應該選擇二路插入好一點。
希爾排序:
爲了省略移位帶來的影響,希爾提出了這樣一種思路:如果待排序的數組元素相對有序,那麼是否會降低移位操作的頻率?
如果在插入元素之前,數組都相對有序,大的元素都在後頭,小的元素都在前面,這樣只需移動少許的元素就可以使數組升序。
上代碼:
void insert_sort(int * a , int n)
{
int i,j,key,step;//step 爲步長
step = n >>1;
while(step!=0)
{
//cout<<step<<endl;
for(i=0;i<n;i += step)
{
key = a[i];//key 爲待插入的元素
//挪位
for( j = i-step;j>=0 && a[j] >key;j -= step)
{
a[j+step] = a[j];//兩重循環,故O(n*n)
}
//在合適的位置插入
a[j+step] = key;
}
step = step >>1;
}
}
我感覺希爾排序跟直接插入排序的代碼差不多,不同的是下標值的變換,希爾排序會依次縮小步長,增大待排序的元素,直到步長爲1,即變成了直接插入排序,但由於之前的排序是數組相對有序(大小錯落有致),所以移位運算大大減少,因此性能得到提高:
以上,便是插入排序的大致內容,這是我的第一篇博客,謝謝觀看。