1. 程式人生 > 程式設計 >使用C語言實現12種排序方法

使用C語言實現12種排序方法

1.氣泡排序

思路:比較相鄰的兩個數字,如果前一個數字大,那麼就交換兩個數字,直到有序。

時間複雜度O(n^2),穩定性:這是一種穩定的演算法。

程式碼實現:

void bubble_sort(int arr[],size_t len){
 size_t i,j;
 for(i=0;i<len;i++){ 
 bool hasSwap = false; //優化,判斷陣列是否已經有序,如果有序可以提前退出迴圈
 for(j=1;j<len-i;j++){ //這裡j<len-i是因為最後面的肯定都是最大的,不需要多進行比較
 if(arr[j-1]>arr[j]){ //如果前一個比後一個大
 swap(&arr[j-1],&arr[j]); //交換兩個資料
 hasSwap = true;
 } 
 }
 if(!hasSwap){
 break; 
 }
 }
}

2.插入排序

思路:把一個數字插入一個有序的序列中,使之仍然保持有序,如對於需要我們進行排序的陣列,我們可以使它的前i個數字有序,然後再插入i+1個數字,插入到合適的位置使之仍然保持有序,直到所有的數字有序。

時間複雜度:O(n^2) 穩定性:穩定的演算法

程式碼實現:

void insert_sort(int arr[],int len){
 int i,j;
 for(i=1;i<len;i++){
 int key = arr[i]; //記錄當前需要插入的資料
 for(j= i-1;i>=0&&arr[j]>key;j--){ //找到插入的位置
 arr[j+1] = arr[j]; //把需要插入的元素後面的元素往後移
 }
 arr[j+1] = key; //插入該元素
 }
}

3.折半插入排序

思路:本質上是插入排序,但是通過半分查詢法找到插入的位置,讓效率稍微快一點。

時間複雜度:O(n^2),穩定性:穩定的演算法。

程式碼實現:

void half_insert_sort(int arr[],j;
 for(i=1;i<len;i++){
 int key = arr[i];
 int left = 0;
 int right = i-1;
 while(left<=right){ //半分查詢找到插入的位置
 int mid = (left+right)/2;
 if(key<arr[mid]){
 right = mid-1; 
 }else{
 left = mid+1; 
 } 
 }
 for(j=i-1;j>=left;j--){ //把後面的元素往後移
 arr[j+1]=arr[j]; 
 }
 arr[j+1] = key; //插入元素
 }
}

4.希爾排序

思路:先取一個正整數d1<n,把所有序號相隔d1的陣列元素放一組,組內進行直接插入排序;然後取d2<d1,重複上述分組和排序操作;直至di=1,即所有記錄放進一個組中排序為止。

時間複雜度:O(n^1.3),演算法效率上大大提高 。穩定性:不穩定的演算法。

程式碼實現:

void shell_sort(int arr[],int len){ //本質上也是一種插入排序,避免了大量資料的移動,在每一組排序過後,每個資料已經到了大致的位置。
 int i,j;
 int step=0;
 for(step = len/2;step>=1;step=step/2){ //分組 分為step組,對每組的元素進行插入排序
 for(i=step;i<len;i++){
 int key = arr[i];
 for(j=i-step;j>=0&&arr[j]>key;j=j-step){
 arr[j+step] = arr[j]; 
 } 
 arr[j+step] = key;
 }
 }
}

5.選擇排序

思路:通過迴圈找到最大值所在的位置,然後把最大值和最後一個元素進行交換,通過迴圈直到所有的資料有序。時間複雜度:O(n^2) 穩定性:不穩定的演算法

程式碼實現:

void select_sort(int arr[],j;
 for(i=0;i<len-1;i++){
 int max = 0; //最大值下標
 for(j=1;j<len-i;j++){
 if(arr[max]<arr[j]){ //找到最大值的下標
 max = j; 
 } 
 }
 if(max!=j-1){ 
 swap(&arr[max],&arr[j-1]); //把最後一個元素和最大值進行交換
 }
 }
}

6.雞尾酒排序

思路:選擇排序的一種改進,一次迴圈直接找到最大值和最小值的位置,把最大值和最後一個元素進行交換,最小值和最前一個元素進行交換,所以最外層的迴圈只需要執行len/2次即可

時間複雜度:O(n^2) 穩定性:不穩定的演算法

程式碼實現:

void cocktail_sort(int arr[],j;
 for(i=0;i<len/2;i++){
 int max = i; //最大值下標
 int min = i; //最小值下標
 for(j=i+1;j<len-i;j++){
 if(arr[max]<arr[j]){ //找到最大值下標
 max = j; 
 } 
 if(arr[min]>arr[j]){ //找到最小值下標
 min = j; 
 }
 }
 if(max!=j-1){
 swap(&arr[max],&arr[j-1]); //交換最大值和未進行排序的最後一個元素
 }
 if(min == j-1){ //如果最小值在未進行排序的最後一個位置,那麼經過最大值的交換,已經交換到了最大值所在的位置
 min = max; //把最小值的座標進行改變
 }
 if(min!=i){
 swap(&arr[i],&arr[min]); //交換最小值和未進行排序的最前的元素
 }
 }
}

7.堆排序

思路:把資料進行大堆化,然後依次交換堆頂(最大值)和最後一個元素,在使堆頂重新大堆化,最後迴圈過後陣列便有序。過程:

最大堆調整(Max Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點建立最大堆(Build Max Heap):將堆中的所有資料重新排序

堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算時間複雜度:O(nlgn) 穩定性:不穩定的演算法

實現程式碼:

void re_heap(int arr[],size_t index,size_t len){
 size_t child = 2*index+1; //左節點座標
 int key = arr[index]; //當前節點值
 while(child<len){
 if(child+1<len&&arr[child]<arr[child+1]){ //如果右節點存在且右節點的值比左節點大,那就child記錄較大位元組點的座標
 child++; 
 } 
 if(arr[child]>key){ //如果子節點的值比根節點的值大
 arr[index] = arr[child]; //改變根節點的值
 }else{
 break; 
 }
 index = child;
 child = 2*index+1;
 }
 arr[index] = key; //插入記錄好的值
}
void heap_sort(int arr[],size_t len){
 int i;
 for(i=len/2;i>=0;i--){
 re_heap(arr,i,len); //對第i個根節點進行大堆化
 }
 for(i=len-1;i>0;i--){
 swap(&arr[0],&arr[i]); //交換第一個和最後一個元素
 re_heap(arr,i); //對第一個元素進行大堆化
 }
}

8.快速排序

思路:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。過程:

(1)首先設定一個分界值,通過該分界值將陣列分成左右兩部分

(2)將大於或等於分界值的資料集中到陣列右邊,小於分界值的資料集中到陣列的左邊。此時,左邊部分中各元素都小於或等於分界值,而右邊部分中各元素都大於或等於分界值。

(3)然後,左邊和右邊的資料可以獨立排序。對於左側的陣列資料,又可以取一個分界值,將該部分資料分成左右兩部分,同樣在左邊放置較小值,右邊放置較大值。右側的陣列資料也可以做類似處理。

(4)重複上述過程,可以看出,這是一個遞迴定義。通過遞迴將左側部分排好序後,再遞迴排好右側部分的順序。當左、右兩個部分各資料排序完成後,整個陣列的排序也就完成了。時間複雜度:O(nlog2n) 穩定性:不穩定的演算法

程式碼實現:

void quick_sort(int arr[],size_t left,size_t right){
 if(left>=right){ //如果只有一個元素,那就是有序的,返回
 return; 
 }
 int i = left;
 int j = right;
 int key = arr[left]; //基準值
 while(i<j){ //找到基準值的位置,使得基準值右邊的元素都比基準值大,左邊的元素都比基準值小
 while(i<j&&arr[j]>=key){ //從右邊找一個比基準值小的數,
 --j;
 }
 arr[i] = arr[j];//把這個值放到基準值的位置處
 while(i<j&&arr[i]<=key){ //從左邊找一個比基準值大的數
 ++i; 
 }
 arr[j] = arr[i]; //把這個元素放到j的位置
 }
 arr[i] = key;
 if(i-left>1) //元素個數至少兩個才進行遞迴呼叫,這樣可以少一次遞迴
 quick_sort(arr,left,i-1); //對基準值左邊的元素進行排序
 if(right-i>1)
 quick_sort(arr,i+1,right); //對基準值右邊的元素進行排序
}

9.歸併排序

思路:對於兩個有序的子序列,可以把它們合併在一起,變成一個新的完全有序的序列,因此歸併排序和快排差不多,都是遞迴的進行。時間複雜度:O(nlog2n) 穩定性:穩定的演算法程式碼實現:

void merge(int arr[],int left,int right){
 int i,j,k;
 int mid = (left+right)/2;
 int len = mid-left+1;
 int *temp = malloc(sizeof(arr[0])*len);
 for(i=0;i<len;i++){
 temp[i] = arr[i+left]; //把這個陣列的所有元素都複製到臨時陣列中
 }
 i=0,j=mid+1,k=left;
 while(i<len&&j<=right){
 if(temp[i]<arr[j]){ //把臨時陣列的元素和 [mid+1,right]這部分的元素一個一個的進行比較,如果誰小,那麼arr裡就存放誰的元素
 arr[k++] = temp[i++]; 
 }else{
 arr[k++] = arr[j++]; 
 }
 }
 while(i<len){ //如果temp這個陣列的元素還沒有全部遍歷完,那就把temp後面的元素都複製到arr裡面去,
 //因為arr[mid+1,right] 這部分的元素本來就是arr後面部分的有序的元素,所以如果arr[mid+1,right]這部分沒有遍歷完也沒關係的,
 arr[k++] = temp[i++]; 
 }
 free(temp);
}
void merge_sort(int arr[],int right){
 if(left>=right){ //如果只有一個元素說明這個序列有序,那就返回
 return; 
 } 
 int mid = (left+right)/2; //對兩個有序的陣列進行排序,
 merge_sort(arr,mid); //對[left,mid]這個區間的元素進行排序
 merge_sort(arr,mid+1,right); //對[mid+1,right]這個區間內的元素進行排序
 merge(arr,right); //這個序列的[left,mid]為有序的序列 [mid+1,right]也為有序的序列
}

10.計數排序

思路:這是一種基於比較的演算法,我們用一個大陣列來存放這些資料,這些資料在這個大陣列中的表現形式是以這個大陣列的下標存在的,比如57,60,42這三個數字進行排序,那麼用一個大陣列,這個大陣列的arr[57] = 1,arr[60] = 1,arr[42] = 1,然後遍歷這個大陣列就行了。

時間複雜度:O(n+k),其中這個k為資料的範圍,所以計數排序最適合資料比較集中的陣列排序。穩定性:穩定的演算法

程式碼實現:

void count_sort(int arr[],size_t len){
 int max = arr[0]; //最大值
 int min = arr[0]; //最小值
 size_t i;
 for(i=0;i<len;i++){
 if(max<arr[i]){ //找到最大值
 max =arr[i]; 
 }
 if(min > arr[i]){ //找到最小值
 min = arr[i]; 
 }
 }
 int cnt = max-min+1; //範圍
 int *prr = malloc(cnt*sizeof(int)); //申請臨時空間
 for(i=0;i<cnt;i++){ //這個臨時陣列全部置0
 prr[i] = 0; 
 }
 for(i=0;i<len;i++){ //對需要進行排序的序列進行遍歷
 prr[arr[i]-min]++; //讓下標為(arr[i]-min)的臨時大陣列的值+1
 }
 size_t j=0;
 for(i=0;i<cnt;i++){ //遍歷這個臨時陣列
 while(prr[i]){ //如果這個陣列下標為i的值不等於0
 arr[j++] = i+min; //那就讓需要進行排序的陣列的值為i+min;
 --prr[i];
 } 
 }
 free(prr); //釋放掉申請的動態記憶體
}

11.桶排序

思路:工作的原理是將陣列分到有限數量的桶子裡。每個桶子再個別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。這是一種以消耗大量空間來換取高效率的排序方式,時間複雜度:O(N+C),其中C=N*(logN-logM),M為桶的數量。所以對於桶排序,桶的數量越多,其排序效率越高。穩定性:穩定的演算法程式碼實現:首先定義桶這個型別:

typedef struct Bucket{
 int vect[100]; //其實這裡使用連結串列更好,但是我比較懶,就懶得用連結串列了
 int cnt; //當前桶記憶體放資料的個數
}

Bucket;

void bucket_sort(int arr[],size_t len){
 int min = arr[0];
 int max = arr[0];
 size_t i;
 for(i=0;i<len;i++){
 if(min>arr[i]){ //找到最小值
 min = arr[i]; 
 }
 if(max<arr[i]){ //找到最大值
 max = arr[i]; 
 }
 }
 int size = max-min+1;
 Bucket bucket[5] = {}; //其實桶可以動態規劃,但為了方便我這裡直接分為5個桶
 for(i=0;i<len;i++){ //遍歷待排序的陣列,把每個元素放到相應的桶當中,
 //比如[0,200]之間的元素放到下標為0的桶中,[201,400]之間的元素放到下標為1的桶中..
 //以此類推,直到放完所有的資料
 int index = (arr[i]-min)/(size/5); //用來判斷當前元素arr[i]需要放到哪個桶當中
 bucket[index].vect[bucket[index].cnt++] = arr[i];
 }
 size_t j=0,k=0;
 for(i=0;i<5;i++){ //對這五個桶進行遍歷
 count_sort(bucket[i].vect,bucket[i].cnt); //首先對這個桶內的元素進行排序,
 //這裡可以呼叫其他排序方法,也可以遞迴呼叫當前排序方法,但是為了節省記憶體,我選擇呼叫其他排序方法,
 for(j=0;j<bucket[i].cnt;j++){
 arr[k++] = bucket[i].vect[j]; //對排序好的桶進行遍歷,並且把裡面的元素複製到arr中去 
 }
 }
}

12.基數排序

基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法是屬於穩定性的排序,其時間複雜度為O (nlog®m),其中r為所採取的基數,而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。

解法:

1.首先根據個位數的數值,在走訪數值時將它們分配至編號0到9的桶子中; 2.接下來將這些桶子中的數值重新串接起來,接著再進行一次分配,這次是根據十位數來分配; 3.接下來將這些桶子中的數值重新串接起來,持續進行以上的動作直至最高位數為止。時間複雜度:設待排序列為n個記錄,d個關鍵碼,關鍵碼的取值範圍為radix,則進行鏈式基數排序的時間複雜度為O(d(n+radix)),其中,一趟分配時間複雜度為O(n),一趟收集時間複雜度為O(radix),共進行d趟分配和收集。穩定性:穩定的演算法;

程式碼實現:

還是定義桶的型別:

typedef struct Bucket{
 int vect[100]; //同樣的可以用連結串列
 int cnt;
}Bucket;

void base_sort(int arr[],size_t len){
 size_t i;
 Bucket bucket[10] = {}; //十個桶
 int max = arr[0];
 for(i=0;i<len;i++){ //尋找最大值,就可以判斷最大值的位數
 if(arr[i]>max){
 max = arr[i]; 
 } 
 }
 size_t j,k;
 int num = 1; //用來獲得相應位數上的數字的關鍵引數,
 //比如要獲得個位上的引數時num = 1;
 //獲得十位上的數字時num = 10;
 //以此類推
 do{
 for(i=0;i<len;i++){ //遍歷待排序的陣列,把每個元素放入相應的桶中
 //比如251,當獲得個位上的數字時,251放到下標為1的桶當中
 //當獲得十位上的數字時,251放到下標為5的桶當中
 //當獲得百位上的數字時,251放到下標為2的桶當中
 //當獲得千位上的數字時,251放到下標為0的桶當中
 //以此類推
 int index = arr[i]/num%10; //獲得相應位數上的數字
 bucket[index].vect[bucket[index].cnt++] = arr[i]; //把這個數字放到相應的桶中
 }
 k=0;
 for(i=0;i<10;i++){
 for(j=0;j<bucket[i].cnt;j++){
 arr[k++] = bucket[i].vect[j]; //把這些桶按順序依次遍歷,
 //把桶中的元素重新放回arr當中
 } 
 bucket[i].cnt = 0; //記得讓桶中的cnt變為0,方便下一次存放
 }
 num*=10; //num*10
 }while(max/=10);//迴圈條件
}

總結

以上所述是小編給大家介紹的用C語言完整實現12種排序方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!