本文均是自己終結,查考網址:http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.5.1.1.htm
還有一個:二分插入排序 平均時間O(n2) 穩定
1、插入排序
在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反復循環,直到全部排好順序。
直接插入排序是穩定的。算法時間復雜度O(n2)--[n的平方]
main()
{
int a[10],j,i,m;
for(j=1;j<10;j++)
{
m=a[j];
for(i=j-1;i>=0;i--)
{
if(a[i]<m)
break;
else
a[i+1]=a[i];
}
a[i+1]=m;
}
}
加注釋的版本:
void lnsertSort(SeqList R)
{ //對順序表R中的記錄R[1..n]按遞增序進行插入排序
int i,j;
for(i=2;i<=n;i++) //依次插入R[2],…,R[n]
if(R[i].key<R[i-1].key){//若R[i].key大於等於有序區中所有的keys,則R[i]
//應在原有位置上
R[0]=R[i];j=i-1; //R[0]是哨兵,且是R[i]的副本
do{ //從右向左在有序區R[1..i-1]中查找R[i]的插入位置
R[j+1]=R[j]; //將關鍵字大於R[i].key的記錄后移
j-- ;
}while(R[0].key<R[j].key); //當R[i].key≥R[j].key時終止
R[j+1]=R[0]; //R[i]插入到正確的位置上
}//endif
}//InsertSort
2、希爾排序
D.L.shell於1959年在以他名字命名的排序算法中實現了這一思想。算法先將要排序的一組數按某個增量d分成若干組,每組中記錄的下標相差d.對每組中全部元素進行排序,然后再用一個較小的增量對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成一組,排序完成。
下面的函數是一個希爾排序算法的一個實現,初次取序列的一半為增量,
以后每次減半,直到增量為1。
希爾排序是不穩定的。
void shell_sort(int *x, int n)
{
int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/
{
for (j=h; j<n; j++) /*這個實際上就是上面的直接插入排序*/
{
t = *(x+j);
for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
{
if(*(x+k)<t)
break;
else
*(x+k+h) = *(x+k);
}
*(x+k+h) = t;
}
}
}
3、冒泡排序
在要排序的一組數中,對當前還未排好序的范圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較后發現它們的排序與排序要求相反時,就將它們互換。
冒泡排序是穩定的。算法時間復雜度O(n2)--[n的平方]
main()
{
int a[10],i,j,k;
for(i=0;i<9;i++)
for(j=0;j<10-i;j++)
if(a[j]>a[j+1])
{
k=a[j];
a[j]=a[j+1];
a[j+1]=k;
}
}
4、快速排序
快速排序是對冒泡排序的一種本質改進。它的基本思想是通過一趟掃描后,使得排序序列的長度能大幅度地減少。在冒泡排序中,一次掃描只能確保最大數值的數移到正確位置,而待排序序列的長度可能只減少1。快速排序通過一趟掃描,就能確保以某個數為基准點的左邊各數都比它小,右邊各數都比它大。然后又用同樣的方法處理它左右兩邊的數,直到基准點的左右只有一個元素為止。
顯然快速排序可以用遞歸實現,當然也可以用棧化解遞歸實現。
快速排序是不穩定的。最理想情況算法時間復雜度O(nlog2n),最壞O(n2)
main()
{
int a[10],i;
quick_sort(a,0,9);
}
quick_sort(int L[],int first,int end)
{
int split;
if(end>first)
{
split=quick(first,end,L);//進行一次希爾排序,返回值為本次排序基准值的下標值
quick_sort(L,first,split-1);//上面的排序完成后再對基准點左右的數組進行同樣的排序操作
quick_sort(L,split+1,end);
}
}
quick(int first,int end,int L[])
{
int left=first,right=end;
int key=L[first];
while(left<right)
{
while((left<right)&&(L[right]>=key))
right--;
if(left<right)
L[left++]=L[right];
while((left<right)&&(L[left]<=key))
left++;
if(left<right)
L[right--]=L[left];
}
L[left]=key;
return left;
}
5、選擇排序
在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然后在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最后一個數比較為止。
選擇排序是不穩定的。算法復雜度O(n2)--[n的平方]
main()
{
int t,k,i,j,a[10];
for(i=0;i<9;i++)
{
k=i;
for(j=i+1;j<10;j++)
if(a[k]>a[j])
k=j;
t=a[i];
a[i]=a[k];
a[k]=t;
}
}
6、堆排序
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。堆的定義如下:具有n個元素的序列(h1,h2,...,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
時稱之為堆。在這里只討論滿足前者條件的堆。
由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項。完全二叉樹可以
很直觀地表示堆的結構。堆頂為根,其它為左子樹、右子樹。初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲順序,使之成為一個堆,這時堆的根節點的數最大。然后將根節點與堆的最后一個節點交換。然后對前面(n-1)個數重新調整使之成為堆。依此類推,直到只有兩個節點的堆,並對它們作交換,最后得到有n個節點的有序序列。
從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最后一個元素
交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反復調用滲透函數
實現排序的函數。有最大堆和最少堆之分。
堆排序是不穩定的。算法時間復雜度O(nlog2n)。
功能:滲透建堆
void sift(int *x, int n, int s)
{
int t, k, j;
t = *(x+s); /*暫存開始元素*/
k = s; /*開始元素下標*/
j = 2*k + 1; /*右子樹元素下標*/
while (j<n)
{
/*判斷是否滿足堆的條件:滿足就繼續下一輪比較,否則調整。*/
if (j<n-1 && *(x+j) < *(x+j+1))
{
j++;
}
if (t<*(x+j)) /*調整*/
{
*(x+k) = *(x+j);
k = j; /*調整后,開始元素也隨之調整*/
j = 2*k + 1;
}
else /*沒有需要調整了,已經是個堆了,退出循環。*/
{
break;
}
}
*(x+k) = t; /*開始元素放到它正確位置*/
}
功能:堆排序
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;
for (i=n/2-1; i>=0; i--)
{
sift(x,n,i); /*初始建堆*/
}
for (k=n-1; k>=1; k--)
{
t = *(x+0); /*堆頂放到最后*/
*(x+0) = *(x+k);
*(x+k) = t;
sift(x,k,0); /*剩下的數再建堆*/
}
}
7. 歸並排序(Merge Sort)
利用"歸並"技術來進行排序。歸並是指將若干個已排序的子文件合並成一個有序的文件。
1、算法基本思路
設兩個有序的子文件(相當於輸入堆)放在同一向量中相鄰的位置上:R[low..m],R[m+1..high],先將它們合並到一個局部的暫存向量R1(相當於輸出堆)中,待合並完成后將R1復制回R[low..high]中。
(1)合並過程
合並過程中,設置i,j和p三個指針,其初值分別指向這三個記錄區的起始位置。合並時依次比較R[i]和R[j]的關鍵字,取關鍵字較小的記錄復制到R1[p]中,然后將被復制記錄的指針i或j加1,以及指向復制位置的指針p加1。
重復這一過程直至兩個輸入的子文件有一個已全部復制完畢(不妨稱其為空),此時將另一非空的子文件中剩余記錄依次復制到R1中即可。
(2)動態申請R1
實現時,R1是動態申請的,因為申請的空間可能很大,故須加入申請空間是否成功的處理。
2、歸並算法
void Merge(SeqList R,int low,int m,int high)
{//將兩個有序的子文件R[low..m]和R[m+1..high]歸並成一個有序的
//子文件R[low..high]
int i=low,j=m+1,p=0; //置初始值
RecType *R1; //R1是局部向量,若p定義為此類型指針速度更快
R1=(ReeType *)malloc((high-low+1)*sizeof(RecType));
if(! R1) //申請空間失敗
Error("Insufficient memory available!");
while(i<=m&&j<=high) //兩子文件非空時取其小者輸出到R1[p]上
R1[p++]=(R[i].key<=R[j].key)?R[i++]:R[j++];
while(i<=m) //若第1個子文件非空,則復制剩余記錄到R1中
R1[p++]=R[i++];
while(j<=high) //若第2個子文件非空,則復制剩余記錄到R1中
R1[p++]=R[j++];
for(p=0,i=low;i<=high;p++,i++)
R[i]=R1[p];//歸並完成后將結果復制回R[low..high]
} //Merge
8.二分法查找和二分法插入
首先申明,二分法查找只適用與已排序的數列,如果是混亂數列。。我也無能為力~
有一個數組 v 已經按升序排列了,數組 v 有 n=20 個元素。數組中有個元素 x,如何知道 x 位於該數組的第幾位呢?
解決這個問題的一個普遍方法是二分法查找。下面是程序:
int binsearch(int x, int v[], int n) {
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low + high) / 2;
if(x < v[mid])
high = mid - 1;
else if (x > v[mid])
low = mid + 1;
else
return mid; // 看看循環執行了多少次
printf("mid = %d, low = %d, high = %d \n", mid, low, high);
}
return -1; //沒有查找出來返回-1
}
思路很簡單:首先將輸入值 x 與數組 v 的中間元素比較,如果 x 小於中間的元素,則將 high 值設為 中間元素-1,同理,若 x 大於中間元素,則將中間元素 + 1作為 low,再在low 與 high之間進行查找
二分法插入排序
算法思想簡單描述:
在插入第i個元素時,對前面的0~i-1元素進行折半,先跟他們
中間的那個元素比,如果小,則對前半再進行折半,否則對后半
進行折半,直到left>right,然后再把第i個元素前1位與目標位置之間
的所有元素后移,再把第i個元素放在目標位置上。
二分法沒有排序,只有查找。所以當找到要插入的位置時。移動必須從最后一個記錄開始,向后移動一位,再移動倒數第2位,直到要插入的位置的記錄移后一位。
二分插入排序是穩定的,平均時間O(n2)
void binsort(ref int[] data1)
1、二分法查找插入位置
如果R[i]<R[m]成立,那右指針就要向左移動中間指針一位,否則,左指針要向左移動中間指針一位。反復查找,直到左指針大於右指針時停止。
2、后移,有點迷惑,什么時候需要后移呢?有哪些記錄需要移動呢?
雖然我們很清楚的知道,我們需要后移那些排序碼大於R[i]的記錄,但難免會問自己這樣幾個問題。其實它相當於需要移動從i-1到左指針的記錄。
3、插入
由1中得到的左指針其實就是元素要插入的位置。
4、算法
{
int left,right,num;
int middle,j;
for( int i = 1;i < data1.Length;i++)
{
// 准備
left = 0;
right = i-1;
num = data1[i];
// 二分法查找插入位置
while( right >= left)
{
// 指向已排序好的中間位置
middle = ( left + right ) / 2;
if( num < data1[middle] )
// 插入的元素在右區間
right = middle-1;
else
// 插入的元素在左區間
left = middle+1;
}
// 后移排序碼大於R[i]的記錄
for( j = i-1;j >= left;j-- )
{
data1[j+1] = data1[j];
}
// 插入
data1[left] = num;
}
// 插入的元素在左區間
left = middle+1;
}
// 后移排序碼大於R[i]的記錄
for( j = i-1;j >= left;j-- )
{
data1[j+1] = data1[j];
}
// 插入
data1[left] = num;
}
