数据结构-常用的排序算法
總第123篇
好久不見哈,我終于又更新了,驚不驚喜,意不意外,哈哈哈哈。等之后會專門寫一篇文章給大家匯報匯報我最近在忙什么呢,今天這篇還是接著之前的數據結構系列繼續,主要講講數據結構里面常用的幾種排序算法。
1.排序的基本概念
假設現在有n個記錄的序列{r1,r2,……,rn},其相應關鍵字分別為{k1,k2,……,kn},需要確定1,2,……,n的一種排列p1,p2,……,pn,使其相應的關鍵字蠻子kp1<=kp2<=kp3<=……<=kpn的關系,即使得序列成為一個按關鍵字有序排列的序列,這樣的操作稱為排序。
1.1排序的穩定性
關鍵字又分為主關鍵字和次關鍵字,主關鍵字只有一個,但是次關鍵字個數不限。因為排序不僅針對主關鍵字,也會參考次關鍵字進行排序。因待排序的記錄序列中可能存在兩個或兩個以上的關鍵字相等的記錄,排序結果會出現不唯一的情況。如果兩個序列值在按關鍵字排序前是A在B前面,如果經過排序后,A仍在B前面,則稱排序是穩定的;若排序后A到了B的后面,則排序是不穩定的。
1.2內排序與外排序
內排序是在排序的整個過程中,待排序的所有記錄全部被放置在內存中。外排序是由于排序的記錄個數太多,不能同時放置在內存中,整個排序過程需要在內外存之間多次交換數據才能進行。
1.3排序算法類別
排序總共有四種類別,七種算法,具體類別如下:
1.3.1插入類排序
插入類排序重點在插入這兩個字,具體是在一個已經有序的序列中,插入一個新的關鍵字,通過將待插入關鍵字與已經有序的序列中每個值進行比較,找到合適的插入位置,使插入后整個序列依然是有序的。直接插入排序、折半排序、希爾排序均屬于這類排序。
1.3.2交換類排序
交換類排序重點在交換這兩個字,通過不停比較兩個數值之間的大小然后交換兩個數值的位置,重復這個過程,直到最后整個序列有序。冒泡排序和快速排序屬于這類別排序。
1.3.3選擇類排序
選擇類排序的重點在選擇這兩個字,從待排序序列中選出一個最小(或最大)的關鍵字,把它和序列中的第一個(或最后一個)關鍵字進行交換,這樣最小(或最大)的關鍵字就排到了有序的位置,繼續循環這個過程,直到整個序列有序。簡單選擇排序和堆排序屬于這種排序類別。
1.3.4歸并類排序
歸并類排序就是將兩個或兩個以上的有序序列合并成一個新的有序序列
1.4排序算法用的結構與函數
用于排序的順序表結構,此結構將會用于接下來要講的所有順序結構。
#define?MAXSIZE?10????//要排序數組個數最大值 typedef?struct {int?r[MAXSIZE?+?1];????//用來存儲要排序數組int?length;????//用來記錄順序表長度 }排序最常用的操作就是數組兩元素的交換,我們將這個交換過程寫成函數,方便之后的調用。
void?swap(SqList?*L,int?i,int?j) {int?temp?=?L->r[i];L->r[i]?=?L->r[j]L->r[j]?=?temp; }2.插入類排序
2.1直接插入排序
直接插入排序類似于我們在打撲克的時候的理牌過程,我們一般都是一邊接牌一邊理牌,理牌的時候都是直接通過將新接的牌(待排序的數據)插入到前面已經排好序的序列中。插入排序的具體過程如下:
step1:從第一個元素開始,默認第一個元素是已經排好序的;
step2:取出下一個元素作為待插入元素,然后將這個待插入元素與它前面的已經排好序的每一個元素(即已經插入到序列中的所有值)進行比較,如果待插入元素的值比它前面已經排序好的數值小,則將已經在序列中的數后移一位,直到不需要后移了,新元素插入到空位;
重復step2,直到待插入序列中的所有數值全部插入完畢。
具體實現代碼如下:
void?Insertsort(int?R[],int?n)//R[]用來存放待插入排序的所有值 {int?i,j;int?temp;?//用來存放待插入值for(i?=?1;i?<?n;++i){temp?=?R[i];//待插入值為序列R[]中的第i個值j?=?i?-?1;//待插入數值前一位while(j>=0&&temp<R[j])//當待插入值temp小于它前面的數時,前面的數就需要后移{R[j+1]?=?R[j];--j;//通過前移遍歷已排序好的序列中的每一個值}R[j+1]?=?temp;//將temp插入到R[j+1]的位置}}2.2折半插入排序
折半排序是在直接插入排序的基礎上進行改進的,直接插入是遍歷待排序中的每一個值,而折半插入排序是采用每次尋找當前位置的一半進行插入排序,其實就是我們高中學的二分查找求根的方法。
2.3希爾排序
直接插入排序在有些時候效率是很高的,比如待排序的數據本身是基本有序的,我們只需要執行少量的插入操作即可;或者是待排序的記錄很少時,直接插入排序效率也是挺高。但是現實中的數據很難滿足這兩個條件,所以就需要人為去把數據整理成符合這兩個條件的數據。
如何讓待排序的記錄個數較少呢?比較直接的方法就是將原來的大量記錄數進行分組,分割成若干個子序列,這樣待排序記錄數就變少了,然后可以在各個組內直接利用插入排序,當整個序列基本有序時,再對全體記錄進行一次直接插入排序。我們把這種方式稱作希爾排序。
現在比較重要的問題就是如何對原數據進行分組,如果直接等距離切割的話,比如原序列是{9,1,5,8,3,7,4,6,2},現在把他們等距離切割成三份,{9,1,5},{8,3,7},{4,6,2},對這三組分別采用直接插入排序以后變成{1,5,9},{3,7,8},{2,4,6},然后將三組進行合并變成{1,5,9,3,7,8,2,4,6},這個序列目前還是雜亂的,還需要對這個序列整體再來一遍直接插入排序,但是這個經過切割以后序列和原序列并沒有太大的不同,這主要是因為我們切割的時候是等距切割的。為了避免這種切割以后用處不大的情況,所以我們采用以某種相隔距離開始分組,比如選擇1、3、5位置的數作為一組,2、4、6位置的數作為一組這樣跳躍切割的方式進行。實現代碼如下:
void?ShellSort(SqList?*L) {int?i,j;int?incremrnt?=?L->length;do{incremrnt?=?increment/3+1;????//間隔序列for(i=increment+1;i<L-length;i++){if(L->r[i]<L->[i-increment]){L->r[0]=L->r[i];for(j=i-increment;j>0&&L->r[0]<L->r[j];j-=increment)L->r[j+increment]=L->r[j];????//記錄后移,查找插入位置L->r[j+increment]=L->r[0];????????//插入}}} }3.交換類排序
3.1冒泡排序
冒泡排序是一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序記錄為止。
實現步驟如下:
step1:從第一位開始,兩兩比較相鄰數值,如果兩個數值是降序,則交換彼此的位置,直到最后一位,這樣最后一位肯定是最大的數值;
step2:因最后一位已經是最大值了,所以除最后一位外,其他數值再次執行step1;
step3:重復上述的step1、step2直到所有數值排序完成。
3.1.1最基礎冒泡排序實現
遍歷序列中的每一個值,然后將該值與其后面序列中的每個值作比較,如果大于則交換彼此位置。
void?BubbleSort0(SqList?*L) {int?i,j;for(i=1;i<L-length;i++){for(j=i+1;j<L-length;j++){if(L->r[i]?>?L->r[j]){swap(L,i,j)}}} }這種算法嚴格意義上并不是冒泡排序,因為他不滿足兩兩比較相鄰記錄的冒泡排序思想。
3.1.2正規冒泡排序
正規冒泡排序執行的過程是按照我們前面的講過的冒泡排序的基本步驟來的,具體實現代碼如下:
void?BubbleSort(SqList?*L) {int?i,j;for(i=1;i<L-length;i++){for(j=L-length-1;j>=i;j--){if(L->r[j]?>?L->r[j+1])????//注意這里是比較j與j+1{swap(L,j,j+1);????????//交換L->r[j]與L->r[j+1]的位置}}} }上面代碼表示從序列的末尾依次往前遍歷比較,先從倒數第二位與倒數第一位開始比較,i值用來控制最后不參加比較的數值的位數(即已經是最大值的位數),剛開始i值是1,表示所有數值數值均參與排序,當比較完一次(即j的for循環執行完一次)以后,i的值加1,而參與比較的數值個數減1,循環此過程,直到所有的數值均已排序完成(即i的值大于等于待排序序列的長度)。
3.1.3改進版冒泡排序
我們上面講的普通的冒泡排序中,只有最后一位不參與排序,除最后一位以外的其他序列還是都得參與排序,不管是否有序,如果除第一位以外的其他序列已經是全部或部分有序的,那么是不是就可以不去遍歷比較了呢?答案肯定是的,只需要添加一個標志用來判斷哪一部分序列是排好序的。
void?BubbleSort2(SqList?*L) {int?i,j;Status?flag?=?True;????//flag用來標記哪部分是已排序好的for(i=1,i<L-length&&flag;i++){flag?=?False;for(j=L->length-1;j>=i;j--){if(L->r[j]?>?L->r[j+1]){swap(L[j],L[j+1])flag?=?True}}} }3.2快速排序
終于到了傳說中的快排了,快排是快速排序的簡稱,是交換類排序的其中一種。快速排序在開始排序之前會先選中一個中間值M(注意這里的中間值并非實際意義上的中值),一般會用待排序列中的第一個數,每執行一次排序會將待排序序列分成兩部分,其中一部分中的所有數都要比中間值M大,而另一部分中的所有值要比中間值M小,在這兩部分內再分別進行快排,也是同樣先找一個中間值M,然后進行數區間切分,循環這個過程,直到所有的序列切分完畢,最后會得到一個有序的序列,具體實現代碼如下:
void?QuickSort(int?R[],int?low,int?high) {int?temp;int?i?=?low;j?=?high;if(low?<?high){temp?=?R[low];//序列的第一個值作為中間值while(i<j){while(j>i&&R[j]>=temp)//從右往左尋找小于temp的值--j;if(i<j){R[i]=R[j];//放在temp左邊++i;//i后移一位}while(i<j&&R[i]<temp)//從左往右尋找大于temp的值++i;if(i<j){R[j]=R[i];//放在temp右邊--j;//左移一位}}}R[i]?=?temp;//將temp擋在最終位置QuickSort(R,low,i-1);//對temp左邊的值再執行快排QuickSort(R,i+1,high);//對temp右邊的值再執行快排 }4.選擇類排序
4.1簡單選擇排序
冒泡排序是在一邊比較一邊交換,只要出現后面的值小于前面的值,就把兩者進行調換,然后繼續比較;而簡單選擇排序是比較某一位置的數與該位置之后的所有數,如果該位置之后序列中的數有比該位置的數小,則調換兩者的位置,否則進入下一個循環,這個方法有點類似于基礎的冒泡排序。簡單選擇排序與冒泡排序相比就是省略了很多交換的過程。具體實現代碼如下:
void?SelectSort(SqList?*L) {int?i,j,min;for(i=1;i<L->length;i++){min?=?i;for(j?=?i+1;j<=L->length;j++){if(L->r[min]?>?L->r[j])?//如果存在i后面的值比i值小//則把該值傳給參數minmin?=?j;}if(i?!=?min)???//如果最小值不是i,則交換i和min的位置swap(L,i,min);}}4.2堆排序
前面的簡單選擇排序中,在待排序的n個記錄中選擇一個最小的記錄需要比較n-1次,繼續在剩下的n-1條記錄里面比較n-2次才能得出第二個最小值。每次都需要比較剩下的所有值,然后從中挑選出最小值。但實際上有一些值之間是已經對比過了,就沒必要再對比一次,如果要是可以針對已經比較過的值做一個調整,那樣就可以避免很多不必要的比較啦。堆排序就是專門為了解決這個問題的,堆排序是改進版的簡單選擇排序。
4.2.1堆概念
堆是具有下列性質的完全二叉樹:每個結點的值都大于或等于其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小于或等于其左右孩子結點的值,稱為小頂堆。
4.2.2堆排序實現
堆排序就是利用堆進行排序的方法。它的基本思想是,將待排序的序列構成一個大頂堆。這樣,整個序列的最大值就是堆頂的根節點。將根節點移走,根節點是最大值,然后再將剩余的n-1個序列重新構造成一個堆,新堆的根節點是新堆的最大值,也是這n個元素中的次大值。如此重復,便可得到一個有序序列。
所以堆排序其實就是兩個步驟,第一步是將待排序數據轉換成一個大堆頂,第二步就是逐步將每個最大值的根結點移走,并且再次調整為大頂堆。具體實現代碼如下:
void?HeapSort(SqList?*L) {int?i;for(i=L->length/2;i>0,i--)????//將L中的r構建成一個大堆頂HeapAdjust(L,i,L->length)????//HeaPAdjust是將待排序數據調整為大頂堆的過程for(i=L->length;i>1;i--)????{swap(L,1,i);????//將堆頂記錄和當前未經排序子序列的最后一個記錄進行交換???HeapAdjust(L,1,i-1);????} }HeapAdjust函數實現代碼
/已知L->r[s...m]中記錄的關鍵字除L->r[s]之外均滿足堆的定義/ /本函數調整L->r[s]的關鍵字,使L->r[s...m]成為一個大頂堆/ void?HeapAdjust(SqList?*L,int?s,int?m) {int?temp,j;temp?=?L->r[s];for(j=2*s;j<=m;j*=2)????//沿關鍵字較大的孩子結點向下篩選{if(j<m&&L->r[j]<L->r[j+1])++j;???????????????????????//j為關鍵字中較大的記錄的下標if(temp>=L->r[j])break;????????????????????//rc應插入的位置sL->r[s]=L->r[j];s=j;}L->r[s]=temp;????????//插入 }5.歸并排序
歸并有序是一種分而治之的算法,歸并排序有多路歸并,我們以最簡單的二路歸并進行講解:先將整個序列分成兩半,對每一半內分別再進行歸并排序,這樣將得到兩個有序序列,然后將這兩個有序序列歸并成一個序列即可。具體實現代碼如下:
void?mergeSort(int?A[],int?low,int?high) {if(low?<?high){int?mid?=?(low?+?high)/2;mergeSort(A,low,mid);?//歸并排序前半段mergeSort(A,mid+1,high);//歸并排序后半段merge(A,low,mid,high);//合并兩個歸并排序后的有序序列} }你還可以看:
數據結構—線性表
數據結構-棧和隊列
數據結構—字符串
數據結構—樹與二叉樹
數據結構-圖
總結
以上是生活随笔為你收集整理的数据结构-常用的排序算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 索尼Nextorage推出首款PCIe
- 下一篇: 数据结构-常用的查找算法