排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整爲“有序”的記錄序列。
——–假設含有n個記錄的序列爲{r1,r2,…,rn},其相應的關鍵字分別爲{k1,k2,…,kn},需確定1,2,…,n的一種排列p1,p2,…pn,使其相應的關鍵字滿足kp1<=kp2<=…<=kpn非遞減(或非遞增)關係,即使得序列成爲一個按關鍵字有序的序列{rp1,rp2,…rpn},這樣的操作就稱爲排序。
•在排序問題中,通常將數據元素稱爲記錄。
–顯然我們輸入的是一個記錄集合,排序後輸出的也是一個記錄集合。
–所以我們可以將排序看成是線性表的一種操作。
•排序的依據是關鍵字之間的大小關係,那麼對同一記錄集合,針對不同的關鍵字進行排序,可以得到不同序列。
內部排序和外部排序——
若待排序記錄可全部放入內存,整個排序過程不需要訪問外存便能完成,則稱此類排序問題爲內部排序;
若待排序記錄的數量很大,以至內存一次不能容納全部記錄,在排序過程中尚需對外存進行訪問,則稱此類排序問題爲外部排序。
排序方法的穩定性——
對於一種排序方法,若排序後具有相同關鍵字的記錄仍維持原來的相對次序,則稱之爲穩定的,否則稱之爲不穩定的。
影響排序算法性能的幾個要素——時間性能、輔助空間、算法的複雜性
冒泡排序(通過頻繁的交換完成排序):
1.兩兩注意是相鄰的兩個元素的意思
2.如果有n個元素需要比較n-1次,每一輪減少1次比較
3.既然叫冒泡排序,那就是從下往上兩兩比較,所以看上去就跟泡泡往上冒一樣。
#include<stdio.h>
void BubbleSort(int k[], int n)
{
int i, j, temp, count1=0, count2=0;
for(i=0 ;i<n-1 ;i++)
{
for(j=n-1 ;j > i ;j--)
{
count1++;
if(k[j-1] > k[j])
{
count2++;
temp = k[j-1];
k[j-1]= k[j];
k[j] = temp;
}
}
}
printf("總共進行了%d次比較,進行了%d次移動!", count1, count2);
}
int main()
{
int i ,a[10] ={11,2,6,4,22,31,0,9,7,13};
BubbleSort(a, 10);
printf("排序後的結果是:");
for(i =0;i<10 ;i++)
{
printf("%d ",a[i]);
}
return 0;
}
選擇排序:
選擇排序法 是對 定位比較交換法(也就是冒泡排序法) 的一種改進。選擇排序的基本思想是:每一趟在n-i+1(i=1,2,…n-1)個記錄中選取關鍵字最小的記錄作爲有序序列中第i個記錄。
簡單選擇排序:
#include<stdio.h>
void SlecetSort(int k[], int n)
{
int i, j, temp, count1=0, count2=0;;
for(i=0 ;i< n-1; i++)
{
int min = i;
for(j =i+1;j<n ;j++)
{
count1++;
if(k[j]<k[min])
{
min = j;
}
}
if(min !=i)
{
count2++;
temp = k[min];
k[min] = k[i];
k[i] = temp;
}
}
printf("總共進行了%d次比較,進行了%d次移動!", count1, count2);
}
int main()
{
int i ,a[10] ={11,2,6,4,22,31,0,9,7,13};
SlecetSort(a, 10);
printf("排序後的結果是:");
for(i =0;i<10 ;i++)
{
printf("%d ",a[i]);
}
return 0;
}
直接插入排序(最簡單,基於順序查找):
void InsertSort(int k[],int n)
{
int i, j, temp;
for(i=1; i<n ;i++)
{
if(k[i]<k[i-1])
{
temp = k[i];
for(j =i-1; k[j] > temp;j--)
{
k[j+1] = k[j];
}
k[j+1] = temp;
}
}
}
希爾排序(縮小增量排序):
將記錄序列分成若干子序列(邏輯上分組),分別對每個子序列進行插入排序。此時插入排序所作用的數據量比較小(每一個小組),插入的效率比較高
例如:將 n 個記錄分成 d 個子序列:
{ R[1],R[1+d],R[1+2d],…,R[1+kd] }
{ R[2],R[2+d],R[2+2d],…,R[2+kd] }
…
{ R[d],R[2d],R[3d],…,R[kd],R[(k+1)d] }
其中,d 稱爲增量,它的值在排序過程中從大到小逐漸縮小,直至最後一趟排序減爲 1。
就像直接插入排序跨度爲1
實現分組有序整體不一定有序
void ShellSort(int k[], int n)
{
int i,j,temp;
int dk;
//進行分組,最開始的增量爲數組長度的一半
for(dk = n/2; dk>0 ; dk /=2)
{
//對數組做一趟希爾插入排序,dk爲本趟增量
for( i=dk; i < n; i++ )//分別向每組的有序區域插入
{
if( k[i] < k[i-dk] )
{
temp = k[i];
for( j=i-dk; k[j] > temp; j-=dk )//比較與記錄後移同時進行
{
k[j+dk] = k[j];
}
k[j+dk] = temp;
}
}
}
}
希爾排序不是穩定的,雖然插入排序是穩定的,但是希爾排序在插入的時候是跳躍性插入的,有可能破壞穩定性
堆排序:
堆是 n 個元素的序列 ( K1, K2, …,Kn ),該序列滿足如下條件:
在完全二叉樹中根結點一定是堆中所有結點最大或者最小者
下標i與2i和2i+1是雙親和子女關係。
那麼把大頂堆和小頂堆用層序遍歷存入數組,則一定滿足上面的表達式。
堆排序算法
•堆排序(Heap Sort)就是利用堆進行排序的算法,它的基本思想是:
–將待排序的序列構造成一個大頂堆(或小頂堆)。
–此時,整個序列的最大值就是堆頂的根結點。將它移走(就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值)。
–然後將剩餘的n-1個序列重新構造成一個堆,這樣就會得到n個元素中的此大值。
–如此反覆執行,便能得到一個有序序列了。
void swap(int k[], int i, int j)
{
int temp;
temp = k[i];
k[i] = k[j];
k[j] = temp;
}
void HeapAdjust(int k[], int s, int n)
{
int i, temp;
temp = k[s];
for( i=2*s; i <= n; i*=2 )
{
if( i < n && k[i] < k[i+1] )
{
i++;
}
if( temp >= k[i] )
{
break;
}
k[s] = k[i];
s = i;
}
k[s] = temp;
}
void HeapSort(int k[], int n)
{
int i;
for( i=n/2; i > 0; i-- )
{
HeapAdjust(k, i, n);
}
for( i=n; i > 1; i-- )
{
swap(k, 1, i);
HeapAdjust(k, 1, i-1);
}
}
歸併排序:
歸併排序的基本思想——
假設初始序列含有 n 個記錄,則可看成是 n 個有序的子序列,每個子序列的長度爲1,然後兩兩歸併,得到 én/2ù 個長度爲 2 或 1 的有序子序列;再兩兩歸併,……,如此重複,直至得到一個長度爲 n 的有序序列爲止,這種排序方法稱爲2-路歸併排序。
2-路歸併排序的核心操作:將兩個位置相鄰的記錄有序子序列,歸併爲一個記錄的有序序列。
容易看出,對 n 個記錄進行歸併排序的時間複雜度爲Ο(n·log2n)。即:
每一趟歸併的時間複雜度爲 O(n),
總共需進行 élog2nù 趟。
歸併排序需要和待排記錄等數量的輔助空間,即空間複雜度爲O(n)。歸併排序是需要輔助空間最多的一種排序方法。
#include<stdio.h>
#define MAXSIZE 20
//遞歸實現歸併,並把最後的結果存放到list1
void merging(int *list1,int list1_size,int *list2, int list2_size)
{
int i, j, k;
int temp[MAXSIZE];
i = j = k = 0;
while(i < list1_size && j<list2_size)
{
if(list1[i] < list2[j])
{
temp[k++] = list1[i++];
}
else
{
temp[k++] = list2[j++];
}
}
while(i < list1_size)
{
temp[k++] = list1[i++];
}
while(j < list2_size)
{
temp[k++] = list2[j++];
}
int m;
for(m=0; m < list1_size+list2_size; m++)
{
list1[m] = temp[m];
}
}
void mergeSort(int k[],int n)
{
if(n>1)
{
int *list1 = k;
int list1_size = n/2;
int *list2 = k + n/2;
int list2_size = n - list1_size;
mergeSort(list1, list1_size);
mergeSort(list2, list2_size );
merging(list1, list1_size,list2, list2_size);
}
}
int main()
{
int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
mergeSort(a, 10);
printf("排序後的結果是:");
for( i=0; i < 10; i++ )
{
printf("%d ", a[i]);
}
printf("\n\n");
return 0;
}
//迭代實現
void MergeSort(int k[], int n)
{
int i, next, left_min, left_max, right_min, right_max;
int *temp = (int *)malloc(n * sizeof(int));
for( i=1; i < n; i*=2 )
{
for( left_min=0; left_min < n-i; left_min = right_max )
{
right_min = left_max = left_min + i;
right_max = left_max + i;
if( right_max > n )
{
right_max = n;
}
next = 0;
while( left_min < left_max && right_min < right_max )
{
if( k[left_min] < k[right_min] )
{
temp[next++] = k[left_min++];
}
else
{
temp[next++] = k[right_min++];
}
}
while( left_min < left_max )
{
k[--right_min] = k[--left_max];
}
while( next > 0 )
{
k[--right_min] = temp[--next];
}
}
}
}
快速排序
快速排序(Quick Sort)使用分治法策略。它的基本思想是:選擇一個基準數,通過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的所有數據都比另外一部分的所有數據都要小。然後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
流程如下:
-
從數列中挑出一個基準值。
-
將所有比基準值小的擺放在基準前面,所有比基準值大的擺在基準的後面(相同的數可以到任一邊);在這個分區退出之後,該基準就處於數列的中間位置。
-
遞歸地把"基準值前面的子數列"和"基準值後面的子數列"進行排序。
void quickSort(int a[], int l, int r)
{
if( l<r )
{
int i,j,x;
i=l;
j=r;
x = a[i];
while(i<j)
{
while(x<a[j] && i<j)
j--;// 從右向左找第一個小於x的數
if(i<j)
a[i++] = a[j];
while(x>a[i] && i<j)
i++;// 從左向右找第一個大於x的數
if(i<j)
a[j--] = a[i];
}
a[i] = x;
quickSort(a,l,i-1);
quickSort(a,i+1,r);
}
}
快速排序性能分析:
快速排序的穩定性:快速排序是不穩定的算法,它不滿足穩定算法的定義;所謂算法穩定性指的是對於一個數列中的兩個相等的數a[i]=a[j],在排序前,a[i]在a[j]前面,經過排序後a[i]仍然在a[j]前,那麼這個排序算法是穩定的。
|
排序方法 |
最好情況 |
最壞情況 |
平均時間 |
輔助存儲 |
穩定性 |
1 |
直接插入 |
O(n) |
O(n2) |
O(n2) |
O(1) |
穩定 |
2 |
折半插入 |
O(n) |
O(n2) |
O(n2) |
O(1) |
穩定 |
3 |
希爾排序 |
—— |
—— |
—— |
O(1) |
不穩定 |
4 |
起泡排序 |
O(n) |
O(n2) |
O(n2) |
O(1) |
穩定 |
5 |
快速排序 |
O(nlog2n) |
O(n2) |
O(nlog2n) |
O(log2n) |
不穩定 |
6 |
簡單選擇 |
O(n2) |
O(n2) |
O(n2) |
O(1) |
不穩定 |
7 |
堆排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(1) |
不穩定 |
8 |
歸併排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(n) |
穩定 |
9 |
基數排序 |
O(d(n+rd)) |
O(d(n+rd)) |
O(d(n+rd)) |
O(n+rd) |
穩定 |