最近由於長時間沒寫過基本的排序算法,結果導致只知道大概思想便不知怎麼去編寫這些算法的代碼了,所以藉着一下午的時間把基本的幾個排序算法的代碼寫了一邊,算是對它的複習吧!
一 .「冒泡排序」:
冒泡排序(Bubble Sort)是一種簡單的排序算法,它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因爲越小的元素會經由交換慢慢“浮”到數列的頂端,
基本的做法如下:
1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
3.針對所有的元素重複以上的步驟,除了最後一個。
4. 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。代碼如下:
void Bubble_Sort(int *array,int count){
int i=count,j=0,temp=0;
while(i>0){
for(j=0;j<i-1;j++){
if(array[j]>array[j+1]){
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
i--;
}
}
當然可以對它進行算法的改良,從而實現shaker排序(雙向冒泡),做法只是在冒泡是採用雙向移動(在移動最大值到右端後,藉着執行的是選取最小值移動到最左端而非繼續移動次大值)來提升效率:
void shakerSort(int *number) { /*雙向冒泡排序*/
int left = 0, right = MAX - 1, flag_recond = 0;
while(left < right) {
// 向左進行冒泡排序
int i;
for(i = left; i < right; i++) {
if(number[i] > number[i+1]) {
SWAP(number[i], number[i+1]);
flag_recond = i;
}
}
right = flag_recond;
// 向右進行冒泡排序
int k;
for(k = right; k > left; k--) {
if(number[k] < number[k-1]) {
SWAP(number[k], number[k-1]);
flag_recond = k;
}
}
left = flag_recond;
}
}
數據結構 |
數組 |
---|---|
最差時間複雜度 | |
最優時間複雜度 | |
平均時間複雜度 | |
最差空間複雜度 | 總共,需要輔助空間 |
最佳算法 | No |
二. 「插入排序」
插入排序(Insertion Sort)的算法描述是一種簡單直觀的排序算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。基本做法是:
1 從第一個元素開始,該元素可以認爲已經被排序
2 取出下一個元素,在已經排序的元素序列中從後向前掃描
3 如果該元素(已排序)大於新元素,將該元素移到下一位置
4 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
5 將新元素插入到該位置後
6 重複步驟2~5
代碼如下:
void insert_Sort(int *array,int first,int count){
int i,j,temp;
for(i=first+1;i<count;i++){
temp=array[i];
j=i-1;
while((j>=first)&&(array[j]>temp)){
array[j+1]=array[j];
j--;
}
array[j+1]=temp;
}
}
數據結構 | 數組 |
---|---|
最差時間複雜度 | |
最優時間複雜度 | |
平均時間複雜度 | |
最差空間複雜度 | 總共 ,需要輔助空間 |
最佳算法 | No |
三.「選擇排序」
選擇排序(Selection sort)是一種簡單直觀的排序算法。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。 選擇排序的主要優點與數據移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,因此對n個元素的表進行排序總共進行至多n-1次交換。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。
複雜度分析:選擇排序的交換操作介於0和(n-1)次之間。選擇排序的比較操作爲n(n-1)/2次之間。選擇排序的賦值操作介於0和3(n-1)次之間。 比較次數O(n^2),比較次數與關鍵字的初始狀態無關,總的比較次數N=(n-1)+(n-2)+...+1=n*(n-1)/2。 交換次數O(n),最好情況是,已經有序,交換0次;最壞情況是,逆序,交換n-1次。 交換次數比冒泡排序少多了,由於交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快。
代碼如下:
遞歸實現:
#define swap(x,y) {int t; t = x; x = y; y = t;}
int maxp(int *array,int n){
int flag;
if(n==1){
return 0 ;
}
flag=maxp(array,n-1);
if(array[flag]>array[n-1])
return flag;
else
return n-1;
}
void sort(int *array,int n){
int p;
if(n==1){
return ;
}
p=maxp(array,n);
swap(&array[p],&array[n-1]);
sort(array,n-1);
}
非遞歸實現:
void select_Sort(int *array,int count){
int i,j,max,temp;
for(i=0;i<count-1;i++){
max=i;
for(j=i+1;j<count;j++){
if(array[max]>array[j]){
max=j;
}
}
if(max!=i){
temp=array[max];
array[max]=array[i];
array[i]=temp;
}
}
}
數據結構 | 數組 |
---|---|
最差時間複雜度 | О(n²) |
最優時間複雜度 | О(n²) |
平均時間複雜度 | О(n²) |
最差空間複雜度 | О(n) total, O(1) auxiliary |
最佳算法 | 偶爾 |
四.「歸併排序 」
歸併排序(Merge sort)是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。指的是將兩個已經排序的序列合併成一個序列的操作。歸併排序算法依賴歸併操作,
基本做法:
1 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
2 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
3 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
4 重複步驟3直到某一指針達到序列尾5 將另一序列剩下的所有元素直接複製到合併序列尾
代碼如下:
void Merge_Sort(int *array, int count){
/*非遞歸做法--迭代算法,將序列每相鄰兩個數字進行歸併操作,形成(n/2)個序列,
排序後每個序列包含兩個元素,將上述序列再次歸併,形成(n/4)個序列,
每個序列包含四個元素重複步驟2,直到所有元素排序完畢*/
int i,left_min,left_max,right_min,right_max,next;
int *tmp=(int *)malloc(sizeof(int)*count);
if(NULL==tmp){
printf("Out of memory !");
abort();
}
for(i=1;i<count;i*=2){
for(left_min=0;left_min<count-i;left_min=right_max){
left_max=right_min=left_min+i;
right_max=left_max+i;
if(right_max>count)
right_max=count;
next=0;
while (left_min < left_max && right_min < right_max)
tmp[next++] = array[left_min] > array[right_min] ? array[right_min++] : array[left_min++];
while (left_min < left_max)
array[--right_min] = array[--left_max];
while (next > 0)
array[--right_min] = tmp[--next];
}
}
}
void merge_sort(int *array,unsigned int first,unsigned int last){
int mid=0; /* 遞歸做法*/
if(first<last){
mid=(first&last)+((first^last)>>1); //大神寫的一段代碼,分離二進制中相同位,具體拿兩個數作一下位運算便大概知道什麼意思了。可以防止高位溢出與容錯。
merge_sort(array,first,mid);
merge_sort(array,mid,last);
}
}
數據結構 | 數組 |
---|---|
最差時間複雜度 | |
最優時間複雜度 | |
平均時間複雜度 | |
最差空間複雜度 | |
最佳算法 | 有時是 |
五.「快速排序」
快速排序是由東尼·霍爾所發展的一種排序算法。在平均狀況下,排序 n 個項目要Ο(n log n)次比較。在最壞狀況下則需要Ο(n2)次比較,但這種狀況並不常見。事實上,快速排序通常明顯比其他Ο(n log n) 算法更快,因爲它的內部循環(inner loop)可以在大部分的架構上很有效率地被實現出來。
基本做法是:
1 從數列中挑出一個元素,稱爲 "基準"(pivot),
2 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作。
3 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,但是這個算法總會退出,因爲在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
代碼如下:
void quick_Sort(int *array,int mnd,int count){
int i,j,key;
if (mnd>=count)
return ;
i=mnd;j=count;key=array[i];
while(i<j){
while(i<j&&array[j]>key)
j--;
if(i<j)
array[i++]=array[j];
while(i<j&&array[i]<key)
i++;
if(i<j)
array[j--]=array[i];
}
array[i]=key;
if(mnd<i-1)
quick_Sort(array,mnd,i-1);
if(i+1<count)
quick_Sort(array,i+1,count);
}
數據結構 | 不定 |
---|---|
最差時間複雜度 | |
最優時間複雜度 | |
平均時間複雜度 | |
最差空間複雜度 | 根據實現的方式不同而不同 |
最佳算法 | 有時是 |
六.「希爾排序 」
希爾排序,也稱遞減增量排序算法,是插入排序的一種高速而穩定的改進版本。 希爾排序是基於插入排序的以下兩點性質而提出改進方法的: 1 插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率 2 但插入排序一般來說是低效的, 因爲插入排序每次只能將數據移動一位 .
基本做法(網絡抄得):原始的算法實現在最壞的情況下需要進行O(n2)的比較和交換。V. Pratt的書[1] 對算法進行了少量修改,可以使得性能提升至O(n log2 n)。這比最好的比較算法的O(n log n)要差一些。希爾排序通過將比較的全部元素分爲幾個區域來提升插入排序的性能。這樣可以讓一個元素可以一次性地朝最終位置前進一大步。然後算法再取越來越小的步長進行排序,算法的最後一步就是普通的插入排序,但是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。假設有一個很小的數據在一個已按升序排好序的數組的末端。如果用複雜度爲O(n2)的排序(冒泡排序或插入排序),可能會進行n次的比較和交換才能將該數據移至正確位置。而希爾排序會用較大的步長移動數據,所以小數據只需進行少數比較和交換即可到正確位置。一個更好理解的希爾排序實現:將數組列在一個表中並對列排序(用插入排序)。重複這過程,不過每次用更長的列來進行。最後整個表就只有一列了。將數組轉換至表是爲了更好地理解這算法,算法本身僅僅對原數組進行排序(通過增加索引的步長,例如是用i
+= step_size而不是i++)。
代碼如下:
#define SWAP( x,y) {int t; t = x; x = y; y = t;}
void shellsort( int *number) {
int i, j, k, gap, t;
gap = MAX / 2;
while( gap > 0) {
for( k = 0; k < gap; k++) {
for( i = k+gap; i < MAX; i+=gap) {
for( j = i - gap; j >= k; j-=gap) {
if( number[j] > number[j+gap]) {
SWAP( number[j], number[j+gap]);
}
else
break;
}
}
}
printf( "\ngap = %d:", gap);
for( i = 0; i < MAX; i++)
printf( "%d ", number[i]);
printf( "\n");
gap /= 2;
}
}
數據結構 | 數組 |
---|---|
最差時間複雜度 | 根據步長串行的不同而不同。 已知最好的: |
最優時間複雜度 | O(n) |
平均時間複雜度 | 根據步長串行的不同而不同。 |
最差空間複雜度 | O(n) |
最佳算法 | 非最佳算法 |
七.「.堆排序 」
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。通常堆是通過一維數組來實現的。在起始數組爲 0 的情形中:
1 父節點i的左子節點在位置 (2*i+1);
2 父節點i的右子節點在位置 (2*i+2);
3 子節點i的父節點在位置 floor((i-1)/2);在堆的數據結構中,堆中的最大值總是位於根節點。
堆中定義以下幾種操作:
1 最大堆調整(Max_Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
2 創建最大堆(Build_Max_Heap):將堆所有數據重新排序
3 堆排序(HeapSort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算.
代碼如下:
void sift_heap(int *array,int search_find,int len){
int i=search_find;
int tmd=2*i+1;
while(tmd<len){
if(tmd+1<len&&array[tmd]<array[tmd+1]){
tmd++;
}
if(array[i]>array[tmd]){
break;
}else{
int tmp=array[i];
array[i]=array[tmd];
array[tmd]=tmp;
}
i=tmd;
tmd=2*i+1;
}
return ;
}
void heap_sort(int *array,int count){
for(int i=count/2;i>=0;i--){
sift_heap(array,i,count);
}
for(int j=0;j<count;j++){
int t=array[0];
array[0]=array[count-j-1];
array[count-j-1]=t;
sift_heap(array,0,count-j-1);
}
}
數據結構 | 數組 |
---|---|
最差時間複雜度 | |
最優時間複雜度 | [1] |
平均時間複雜度 | |
最差空間複雜度 | total, auxiliary |
最佳算法 | 不是 |
只整理了這七種算法,還有其他好多的排序算法等以後有時間再追加吧!