C语言(CED)排序算法总结。比较完整和详细
排序算法可以分為內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存
內(nèi)部排序算法有:直接插入排序,折半插入排序,希爾排序,選擇排序、冒泡排序、歸并排序、快速排序、堆排序、基數(shù)排序等。詳細(xì)如何劃分在文章中的敘述會(huì)有體現(xiàn),字母為大類排序方法。
本文將依次介紹上述排序算法。
A、插入排序
一、直接插入排序。
直接插入排序是一種最簡(jiǎn)單直觀的排序算法,它的工作原理是通過構(gòu)建有序序列,對(duì)于未排序數(shù)據(jù),在已排序序列中從后向前掃描,找到相應(yīng)位置并插入。
?
算法步驟:
1)將第一待排序序列第一個(gè)元素看做一個(gè)有序序列,把第二個(gè)元素到最后一個(gè)元素當(dāng)成是未排序序列。
2)從頭到尾依次掃描未排序序列,將掃描到的每個(gè)元素插入有序序列的適當(dāng)位置。(如果待插入的元素與有序序列中的某個(gè)元素相等,則將待插入元素插入到相等元素的后面。)
代碼實(shí)現(xiàn):
void insert_sort(int array[],unsignedint n) {int i,j;int temp;for(i = 1;i < n;i++){temp = array[i];for(j = i;j > 0&& array[j - 1] > temp;j--){array[j]= array[j - 1];}array[j] = temp;} }排序過程(gif):
適用情況:
直接插入排序適用于順序存儲(chǔ)和鏈?zhǔn)酱鎯?chǔ)的線性表。當(dāng)為鏈?zhǔn)酱鎯?chǔ)時(shí),可以從前往后查照指定元素的位置。注意:大部分排序算法都僅適用于順序存儲(chǔ)的線性表。是一個(gè)穩(wěn)定的排序方法!
二、折半插入排序?
在直接插入排序的基礎(chǔ)上,得出折半插入排序。將直接插入排序的比較和移動(dòng)分離。
算法步驟:
與直接插入排序類似,在此就不再敘述。
代碼實(shí)現(xiàn):
void binary_insertion_sort(int arr[], int len) {int i, j, temp, m, low, high;for (i = 1; i < len; i++){temp = arr[i];low = 0; high = i-1;while (low <= high){m = (low +high) / 2;if(arr[m] > temp)high = m-1;elselow = m+1;}}for (j = i-1; j>=high+1; j--)arr[j+1] = arr[j];arr[j+1] = temp; }排序過程:
適用情況:
適用于順序存儲(chǔ)的線性表,是穩(wěn)定的排序。
三、希爾排序
也稱遞減增量排序方法,是插入排序的一種更高效的改進(jìn)版本。但希爾排序是非穩(wěn)定的排序。其基本思想:先將整個(gè)待排序的記錄序列分割成若干子序列并分別進(jìn)行直接插入排序,待整個(gè)序列中的記錄“基本有序”時(shí),再對(duì)全體記錄進(jìn)行依次直接插入排序。
算法步驟:
先取一個(gè)小于n的步長(zhǎng)d1,把表中全部記錄分成d1個(gè)組,所有距離為d1的倍數(shù)的記錄放在同一個(gè)分組中,在各分組中進(jìn)行直接插入排序;然后取第二個(gè)步長(zhǎng)d2<d1,重復(fù)上述過程,直到所取的dt=1,即所有記錄都放在同一分組里,再進(jìn)行直接插入排序,由于此時(shí)已經(jīng)具有較好的局部有序性,故很快得到最終結(jié)果。到目前為止,尚未求得一個(gè)最好的增量序列,希爾提出的方法是d1=n/2;d1+1=di/2(向下取);并且最后一個(gè)增量等于1。
代碼實(shí)現(xiàn):
#include<stdio.h> #include<math.h>#define MAXNUM 10void main() {void shellSort(int array[],int n,int t);//t為排序趟數(shù)int array[MAXNUM],i;for(i = 0;i < MAXNUM;i++)scanf("%d",&array[i]);shellSort(array,MAXNUM,int(log(MAXNUM + 1) / log(2)));//排序趟數(shù)應(yīng)為log2(n+1)的整數(shù)部分for(i = 0;i < MAXNUM;i++)printf("%d ",array[i]);printf("\n"); }//根據(jù)當(dāng)前增量進(jìn)行插入排序 void shellInsert(int array[],int n,int dk) {int i,j,temp;for(i = dk;i < n;i++)//分別向每組的有序區(qū)域插入{temp = array[i];for(j = i-dk;(j >= i % dk) && array[j] > temp;j -= dk)//比較與記錄后移同時(shí)進(jìn)行array[j + dk] = array[j];if(j != i - dk)array[j + dk] = temp;//插入} }//計(jì)算Hibbard增量 int dkHibbard(int t,int k) {return int(pow(2,t - k + 1) - 1); }//希爾排序 void shellSort(int array[],int n,int t) {void shellInsert(int array[],int n,int dk);int i;for(i = 1;i <= t;i++)shellInsert(array,n,dkHibbard(t,i)); }//此寫法便于理解,實(shí)際應(yīng)用時(shí)應(yīng)將上述三個(gè)函數(shù)寫成一個(gè)函數(shù)。排序過程(gif):
適用情況:
僅適用于當(dāng)線性表為順序存儲(chǔ)的情況,非穩(wěn)定排序。
?
?
希爾排序舉例:
待排序數(shù)組:
{6, 5, 3, 1, 8, 7, 2, 4, 9, 0}
第一次步長(zhǎng)h=4,
那么數(shù)組按照步長(zhǎng)可以拆分成4個(gè)小數(shù)組([0]6的意思是下標(biāo)[0]的值為6)
{[0]6, [4]8, [8]9}
{[1]5, [5]7, [9]0}
{[2]3, [6]2}
{[3]1, [7]4}
對(duì)這4個(gè)小數(shù)組分別進(jìn)行插入排序后,4個(gè)小數(shù)組變成:
{[0]6, [4]8, [8]9}
{[1]0, [5]5, [9]7}
{[2]2, [6]3}
{[3]1, [7]4}
合并起來就是:{6, 0, 2, 1, 8, 5, 3, 4, 9, 7}
第二次步長(zhǎng)h=1,
那么數(shù)組按照步長(zhǎng)只有1個(gè)數(shù)組了
{6, 0, 2, 1, 8, 5, 3, 4, 9, 7}
對(duì)這個(gè)數(shù)組進(jìn)行一次插入排序后,最終順序就成為:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
B、交換排序
基于交換排序的排序方法有很多,主要并且常見的是冒泡排序和快速排序。
一、冒泡排序
也是一種簡(jiǎn)單直觀的排序算法。它重復(fù)地走訪過要排序的數(shù)列,一次比較兩個(gè)元素,如果他們的順序錯(cuò)誤就把他們交換過來。走訪數(shù)列的工作是重復(fù)地進(jìn)行直到?jīng)]有再需要交換,也就是說該數(shù)列已經(jīng)排序完成。這個(gè)算法的名字由來是因?yàn)樵叫〉脑貢?huì)經(jīng)由交換慢慢“浮”到數(shù)列的頂端。
算法步驟:
1)比較相鄰的元素。如果第一個(gè)比第二個(gè)大,就交換他們兩個(gè)。
2)對(duì)每一對(duì)相鄰元素作同樣的工作,從開始第一對(duì)到結(jié)尾的最后一對(duì)。這步做完后,最后的元素會(huì)是最大的數(shù)。
3)針對(duì)所有的元素重復(fù)以上的步驟,除了最后一個(gè)。
4)持續(xù)每次對(duì)越來越少的元素重復(fù)上面的步驟,直到?jīng)]有任何一對(duì)數(shù)字需要比較。
代碼實(shí)現(xiàn):
void bubble_sort(int a[], int n) {int i, j, temp;for (j = 0;j < n - 1;j++)for (i = 0;i < n - 1 - j;i++){if(a[i] > a[i + 1]){temp = a[i];a[i] = a[i + 1];a[i + 1] = temp;}} }排序過程(gif):
適用情況:
是一個(gè)穩(wěn)定的排序方法,最壞的情況下,時(shí)間復(fù)雜度就是O(N^2),平均時(shí)間復(fù)雜度O(N^2)。
二、快速排序
是對(duì)冒泡排序的一種改進(jìn)。其基本思想是基于分治法的:在待排序表L[1...n]中任取一個(gè)元素pivot作為基準(zhǔn),通過一趟排序?qū)⒋判虮韯澐譃楠?dú)立的兩部分L[1...K-1]和L[K+1....n],使得L[1......K-1]中所有元素小于pivot,L[k+1....n]中所有元素大于或等于pivot,則pivot放在了其最終位置L[K]上,這個(gè)過程稱作一趟快速排序。而后分別遞歸地對(duì)兩個(gè)子表重復(fù)上述過程,直至每部分內(nèi)只有一個(gè)元素或空為止,即所有元素放在了其最終位置。
算法步驟:
1?從數(shù)列中挑出一個(gè)元素,稱為?“基準(zhǔn)”(pivot),
2?重新排序數(shù)列,所有元素比基準(zhǔn)值小的擺放在基準(zhǔn)前面,所有元素比基準(zhǔn)值大的擺在基準(zhǔn)的后面(相同的數(shù)可以到任一邊)。在這個(gè)分區(qū)退出之后,該基準(zhǔn)就處于數(shù)列的中間位置。這個(gè)稱為分區(qū)(partition)操作。
3?遞歸地(recursive)把小于基準(zhǔn)值元素的子數(shù)列和大于基準(zhǔn)值元素的子數(shù)列排序。
遞歸的最底部情形,是數(shù)列的大小是零或一,也就是永遠(yuǎn)都已經(jīng)被排序好了。雖然一直遞歸下去,但是這個(gè)算法總會(huì)退出,因?yàn)樵诿看蔚牡?#xff08;iteration)中,它至少會(huì)把一個(gè)元素?cái)[到它最后的位置去。
代碼實(shí)現(xiàn):
void Qsort(int a[], int low, int high) {if(low >= high){return;}int first = low;int last = high;int key = a[first];/*用字表的第一個(gè)記錄作為樞軸*/while(first < last){while(first < last && a[last] >= key){--last;}a[first] = a[last];/*將比第一個(gè)小的移到低端*/while(first < last && a[first] <= key){++first;}a[last] = a[first]; /*將比第一個(gè)大的移到高端*/}a[first] = key;/*樞軸記錄到位*/Qsort(a, low, first-1);Qsort(a, first+1, high); }考研的數(shù)據(jù)結(jié)構(gòu)的快排代碼是以嚴(yán)版教材為準(zhǔn),看自己總結(jié)的黑色筆記本。
排序過程(gif):
適用情況:
快速排序是不穩(wěn)定的排序,最好的情況下,時(shí)間復(fù)雜度為:Ο(n?log?n) ,最差時(shí)為n^2??焖倥判蚴撬袃?nèi)部排序算法中平均性能最優(yōu)的排序算法。
C、選擇排序
選擇排序的基本思想是:每一趟(例如第i趟)在后面n-i+1(i=1,2,3...,n-1)個(gè)待排序元素中選取關(guān)鍵字最小的元素,作為有序子序列的第i個(gè)元素,直到第n-1趟做完,待排序元素只剩下一個(gè),就不用再選了。
一、簡(jiǎn)單選擇排序
是一種簡(jiǎn)單直觀的排序方法
算法步驟:
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再?gòu)氖S辔磁判蛟刂欣^續(xù)尋找最小(大)元素,然后放到已排序序列的末尾。
3.重復(fù)第2步,直到所有元素均排序完畢。
代碼實(shí)現(xiàn):
void select_sort(int *a,int n) {register int i,j,min,t;for(i = 0;i < n-1;i++){min = i;//查找最小值for(j = i + 1;j < n;j++)if(a[min] > a[j])min = j;//交換if(min != i){t = a[min];a[min] = a[i];a[i] = t;}} }*register是做聲明的,為了提高效率。C語言允許將局部變量的值放在CPU中的寄存器中,這種變量叫寄存器變量。定義這個(gè)變量適用于頻繁使用某個(gè)變量,以加快運(yùn)行速度,因?yàn)楸4嬖诩拇嫫髦?#xff0c;省去了從內(nèi)存中調(diào)用,要注意定義了這個(gè)變量后,不能取地址!!就是不能使用&符號(hào)
排序過程(gif):
適用情況:
是一種不穩(wěn)定的排序算法,時(shí)間復(fù)雜度是O(n^2)。
二、堆排序
是指利用“堆”這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序方法。堆積是一個(gè)近似完全二叉樹的結(jié)構(gòu),并同時(shí)滿足堆積的性質(zhì):即子結(jié)點(diǎn)的鍵值或索引總是小于(或者大于)它的父節(jié)點(diǎn)。其平均時(shí)間復(fù)雜度為Ο(nlogn) 。
算法步驟:
1.創(chuàng)建一個(gè)堆H[0...n-1]
2.把堆首(最大值)和堆尾交換。(建立小根堆)
? ?若把堆首(最小值)和堆尾交換則是建立大根堆。
詳細(xì):n個(gè)結(jié)點(diǎn)的完全二叉樹,最后一個(gè)結(jié)點(diǎn)是第[n/2](向下取整)個(gè)結(jié)點(diǎn)的孩子。對(duì)第[n/2](向下取整)個(gè)結(jié)點(diǎn)為根的子樹篩選(對(duì)于大根堆:若根結(jié)點(diǎn)的關(guān)鍵字小于左右子女中關(guān)鍵字較大者,則交換),使該子樹成為堆。之后向前依次對(duì)各結(jié)點(diǎn)([n/2]-1~1)(n/2向下取整)為根的子樹進(jìn)行篩選,看該結(jié)點(diǎn)值是否大于其左右子結(jié)點(diǎn)的值,若不是,將左右子結(jié)點(diǎn)中較大值與之交換,交換后可能會(huì)破壞下一級(jí)的堆,于是繼續(xù)采用上述方法構(gòu)造下一級(jí)的堆,直到以該結(jié)點(diǎn)為根的子樹構(gòu)成堆為止。反復(fù)利用上述調(diào)整堆的方法建堆。
代碼實(shí)現(xiàn):
//array是待調(diào)整的堆數(shù)組,i是待調(diào)整的數(shù)組元素的位置,nlength是數(shù)組的長(zhǎng)度 //本函數(shù)功能是:根據(jù)數(shù)組array構(gòu)建大根堆 void HeapAdjust(int array[],int i,int nLength) {int nChild;int nTemp;for(; 2 * i + 1 < nLength;i = nChild){//子結(jié)點(diǎn)的位置=2*(父結(jié)點(diǎn)位置)+1nChild = 2 * i + 1;//得到子結(jié)點(diǎn)中較大的結(jié)點(diǎn)if(nChild < nLength - 1 && array[nChild + 1] > array[nChild]) ++nChild;//如果較大的子結(jié)點(diǎn)大于父結(jié)點(diǎn)那么把較大的子結(jié)點(diǎn)往上移動(dòng),替換它的父結(jié)點(diǎn)if(array[i] < array[nChild]){nTemp = array[i];array[i] = array[nChild];array[nChild] = nTemp; }else break; //否則退出循環(huán)} } //堆排序算法 void HeapSort(int array[],int length) {int i;//調(diào)整序列的前半部分元素,調(diào)整完之后第一個(gè)元素是序列的最大的元素//length/2-1是最后一個(gè)非葉節(jié)點(diǎn),此處"/"為整除for(i = length / 2 - 1;i >= 0;--i)HeapAdjust(array,i,length);//從最后一個(gè)元素開始對(duì)序列進(jìn)行調(diào)整,不斷的縮小調(diào)整的范圍直到第一個(gè)元素for(i = length - 1;i > 0;--i){//把第一個(gè)元素和當(dāng)前的最后一個(gè)元素交換,//保證當(dāng)前的最后一個(gè)位置的元素都是在現(xiàn)在的這個(gè)序列之中最大的array[i] = array[0] ^ array[i];array[0] = array[0] ^ array[i];array[i] = array[0] ^ array[i];//不斷縮小調(diào)整heap的范圍,每一次調(diào)整完畢保證第一個(gè)元素是當(dāng)前序列的最大值HeapAdjust(array,0,i);} }排序過程(gif):
適用情況:
是一種不穩(wěn)定的排序方法,平均時(shí)間復(fù)雜度O(nlogn)。
?
*1.插入排序耗時(shí)主要在有序表中,選擇排序在無序表中。
?2.看其算法步驟即可得出二者區(qū)別。
?
D、其他排序
一、歸并排序
是建立在歸并操作上的一種有效的排序算法。該算法是采用分治法(Divide?and?Conquer)的一個(gè)非常典型的應(yīng)用。
算法步驟:
1.?申請(qǐng)空間,使其大小為兩個(gè)已經(jīng)排序序列之和,該空間用來存放合并后的序列
2.?設(shè)定兩個(gè)指針,最初位置分別為兩個(gè)已經(jīng)排序序列的起始位置
3.?比較兩個(gè)指針?biāo)赶虻脑?#xff0c;選擇相對(duì)小的元素放入到合并空間,并移動(dòng)指針到下一位置
4.?重復(fù)步驟3直到某一指針達(dá)到序列尾
5.?將另一序列剩下的所有元素直接復(fù)制到合并序列尾
代碼實(shí)現(xiàn):
#include <stdlib.h> #include <stdio.h>void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex) {int i = startIndex, j=midIndex+1, k = startIndex;while(i != midIndex + 1 && j != endIndex + 1){if(sourceArr[i] >= sourceArr[j])tempArr[k++] = sourceArr[j++];elsetempArr[k++] = sourceArr[i++];}while(i != midIndex+1)tempArr[k++] = sourceArr[i++];while(j != endIndex+1)tempArr[k++] = sourceArr[j++];for(i = startIndex; i <= endIndex; i++)sourceArr[i] = tempArr[i]; }//內(nèi)部使用遞歸 void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {int midIndex;if(startIndex < endIndex){midIndex = (startIndex + endIndex) / 2;MergeSort(sourceArr, tempArr, startIndex, midIndex);MergeSort(sourceArr, tempArr, midIndex+1, endIndex);Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);} }排序過程(gif):
?
適用情況:
是一種穩(wěn)定的排序方法,時(shí)間復(fù)雜度未O(nlogn)。
二、基數(shù)排序
是一種很特別的排序方法,它不是基于比較進(jìn)行排序的,而是采用多關(guān)鍵字排序思想(即基于關(guān)鍵字各位的大小進(jìn)行排序的),借助“分配”和“收集”兩種操作對(duì)單邏輯關(guān)鍵字進(jìn)行排序?;鶖?shù)排序又分為最高位優(yōu)先(MSD)排序和最低位優(yōu)先(LSD)排序。
算法步驟:
1.算出要排序的數(shù)中的最大位數(shù),設(shè)置一個(gè)定量的數(shù)組當(dāng)作空桶子(桶的容量即為前面算得的最大位數(shù)),桶的序號(hào)為0~9,十個(gè)位置。
2.對(duì)要排列的數(shù)據(jù),從個(gè)位開始,根據(jù)其大小,放入對(duì)應(yīng)的序號(hào)的桶中,按照從左往右,從下往上的順序,例如:756個(gè)位是6,所以在第一趟中放入序號(hào)為6的桶中。收集時(shí)也是同樣的順序。(這樣排序的結(jié)果為從小到大)
3.一直進(jìn)行第二步,直到把所有的位數(shù)遍歷完,最后一次收集到的數(shù)據(jù),即是從小到大排好的順序。
代碼實(shí)現(xiàn):
int maxbit(int data[], int n) //輔助函數(shù),求數(shù)據(jù)的最大位數(shù) {int d = 1; //保存最大的位數(shù)int p = 10;for(int i = 0; i < n; ++i){while(data[i] >= p){p *= 10;++d;}}return d; } void radixsort(int data[], int n) //基數(shù)排序 {int d = maxbit(data, n);int *tmp = newint[n];int *count = newint[10]; //計(jì)數(shù)器int i, j, k;int radix = 1;for(i = 1; i <= d; i++) //進(jìn)行d次排序{for(j = 0; j < 10; j++)count[j] = 0; //每次分配前清空計(jì)數(shù)器for(j = 0; j < n; j++){k = (data[j] / radix) % 10; //統(tǒng)計(jì)每個(gè)桶中的記錄數(shù)count[k]++;}for(j = 1; j < 10; j++)count[j] = count[j - 1] + count[j]; //將tmp中的位置依次分配給每個(gè)桶for(j = n - 1; j >= 0; j--) //將所有桶中記錄依次收集到tmp中{k = (data[j] / radix) % 10;tmp[count[k] - 1] = data[j];count[k]--;}for(j = 0; j < n; j++) //將臨時(shí)數(shù)組的內(nèi)容復(fù)制到data中data[j] = tmp[j];radix = radix * 10;}delete[]tmp;delete[]count; }排序過程:
詳細(xì)的例題可以查看有關(guān)數(shù)據(jù)結(jié)構(gòu)的參考書。
適用情況:
穩(wěn)定的排序方法,時(shí)間復(fù)雜度為O(n)。
*缺點(diǎn):
1)首先是空間復(fù)雜度比較高,需要的額外開銷大。排序有兩個(gè)數(shù)組的空間開銷,一個(gè)存放待排序數(shù)組,一個(gè)就是所謂的桶,比如待排序值是從0到m-1,那就需要m個(gè)桶,這個(gè)桶數(shù)組就要至少m個(gè)空間。
2)其次待排序的元素都要在一定的范圍內(nèi)等等。
?總結(jié)
各種排序的穩(wěn)定性,時(shí)間復(fù)雜度、空間復(fù)雜度、穩(wěn)定性總結(jié)如下圖:
不穩(wěn)定的排序算法有:快、希、選、堆------>“快些選一堆”
注:排序的穩(wěn)定性是這樣定義的:
假定在待排序的記錄序列中,存在多個(gè)具有相同的關(guān)鍵字的記錄,若經(jīng)過排序,這些記錄的相對(duì)次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩(wěn)定的;否則稱為不穩(wěn)定的。
總結(jié)
以上是生活随笔為你收集整理的C语言(CED)排序算法总结。比较完整和详细的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 注册公司在哪个部门办理 了解这些步骤不
- 下一篇: C语言小游戏 ——俄罗斯方块