常見(jiàn)的排序算法

總結(jié)一下常見(jiàn)的排序算法。 排序分內(nèi)排序和外排序。內(nèi)排序:指在排序期間數(shù)據(jù)對(duì)象全部存放在內(nèi)存的排序。外排序:指在排序期間全部對(duì)象個(gè)數(shù)太多,不能同時(shí)存放在內(nèi)存,必須根據(jù)排序過(guò)程的要求,不斷在內(nèi)、外存之間移動(dòng)的排序。內(nèi)排序的方法有許多種,按所用策略不同,可歸納為五類:插入排序、選擇排序、交換排序、歸并排序、分配排序和計(jì)數(shù)排序。插入排序主要包括直接插入排序,折半插入排序和希爾排序兩種;選擇排序主要包括直接選擇排序和堆排序;交換排序主要包括冒泡排序和快速排序;歸并排序主要包括二路歸并(常用的歸并排序)和自然歸并。分配排序主要包括箱排序和基數(shù)排序。計(jì)數(shù)排序就一種。
穩(wěn)定排序:假設(shè)在待排序的文件中,存在兩個(gè)或兩個(gè)以上的記錄具有相同的關(guān)鍵字,在用某種排序法排序后,若這些相同關(guān)鍵字的元素的相對(duì)次序仍然不變,則這種排序方法是穩(wěn)定的。其中冒泡,插入,基數(shù),歸并屬于穩(wěn)定排序;選擇,快速,希爾,堆屬于不穩(wěn)定排序。 下面是對(duì)這些常見(jiàn)排序算法的介紹。未來(lái)測(cè)試代碼的正確性,我們采取了隨機(jī)生成10個(gè)序列,然后先使用C++STL中給出的排序算法sort來(lái)得到一個(gè)正確的排序,然后再使用我們的方法進(jìn)行排序得到結(jié)果,通過(guò)對(duì)比這兩者的結(jié)果來(lái)驗(yàn)證我們代碼的正確性。
測(cè)試代碼如下:

復(fù)制代碼

void sort_test(void (_sort)(int,int)){ const int N=10; int orig[N]; int standard[N]; int arr[N]; srand(time(0)); for(int j=0;j<15;j++){ for(int i=0;i<N;i++) orig[i]=rand()%100;//隨機(jī)生成序列 cout<<"bef:"; print(orig,N); copy(orig,orig+N,standard); sort(standard,standard+N);//利用sort函數(shù)進(jìn)行排序 cout<<"std:"; print(standard,N); copy(orig,orig+N,arr); _sort(arr,N);//采用我們的方法進(jìn)行排序 cout<<"aft:"; print(arr,N); if(equal(standard,standard+N,arr))//測(cè)試我們的方法是否正確 printf("%sOK%s\n",green,normal); else printf("%sNO%s\n",red,normal); }}
復(fù)制代碼

其中參數(shù)是要測(cè)試的方法,void (_sort)(int,int)是排序方法的指針,我們所有的排序方法都寫(xiě)成這種形式。

  1. 直接插入排序 直接插入排序(straight insertion sort)的作法是:每次從無(wú)序表中取出第一個(gè)元素,把它插入到有序表的合適位置,使有序表仍然有序。
      第一趟比較前兩個(gè)數(shù),然后把第二個(gè)數(shù)按大小插入到有序表中; 第二趟把第三個(gè)數(shù)據(jù)與前兩個(gè)數(shù)從后向前掃描,把第三個(gè)數(shù)按大小插入到有序表中;依次進(jìn)行下去,進(jìn)行了(n-1)趟掃描以后就完成了整個(gè)排序過(guò)程。
      直接插入排序?qū)儆诜€(wěn)定的排序,時(shí)間復(fù)雜性為o(n^2),空間復(fù)雜度為O(1)。
      直接插入排序是由兩層嵌套循環(huán)組成的。外層循環(huán)標(biāo)識(shí)并決定待比較的數(shù)值。 內(nèi)層循環(huán)為待比較數(shù)值確定其最終位置。直接插入排序是將待比較的數(shù)值與它的前一個(gè)數(shù)值進(jìn)行比較,所以外層循環(huán)是從第二個(gè)數(shù)值開(kāi)始的。當(dāng)前一數(shù)值比待比較數(shù) 值大的情況下繼續(xù)循環(huán)比較,直到找到比待比較數(shù)值小的并將待比較數(shù)值置入其后一位置,結(jié)束該次循環(huán)。(從小到大)
    值得注意的是,我們必需用一個(gè)存儲(chǔ)空間來(lái)保存當(dāng)前待比較的數(shù)值,因?yàn)楫?dāng)一趟比較完成時(shí),我們要將待比較數(shù)值置入比它小的數(shù)值的后一位。插入排序類似玩牌時(shí)整理手中紙牌的過(guò)程。

代碼如下:


復(fù)制代碼

void insert_sort(int a[],int n){ _FUNC; for(int i=1;i<n;i++) { int t=a[i]; int j; for(j=i-1;j>=0&&a[j]>t;j--) { a[j+1]=a[j]; } a[j+1]=t; print(a,n); }}


復(fù)制代碼

測(cè)試結(jié)果如下:


  1. 折半插入排序
    折半插入排序(binary insertion sort)是對(duì)插入排序算法的一種改進(jìn),由于排序算法過(guò)程中,就是不斷的依次將元素插入前面已排好序的序列中。由于前半部分為已排好序的數(shù)列,這樣我們不用按順序依次尋找插入點(diǎn),可以采用折半查找的方法來(lái)加快尋找插入點(diǎn)的速度。
    折半插入排序算法的具體操作為:在將一個(gè)新元素插入已排好序的數(shù)組的過(guò)程中,尋找插入點(diǎn)時(shí),將待插入?yún)^(qū)域的首元素設(shè)置為a[low],末元素設(shè)置為 a[high],則輪比較時(shí)將待插入元素與a[m],其中m=(low+high)/2相比較,如果比參考元素小,則選擇a[low]到a[m-1]為新 的插入?yún)^(qū)域(即high=m-1),否則選擇a[m+1]到a[high]為新的插入?yún)^(qū)域(即low=m+1),如此直至low<=high不成 立,即將此位置之后所有元素后移一位,并將新元素插入a[high+1]。
    折半插入排序算法是一種穩(wěn)定的排序算法,比直接插入算法明顯減少了關(guān)鍵字之間比較的次數(shù),因此速度比直接插入排序算法快,但記錄移動(dòng)的次數(shù)沒(méi)有變,所以折半插入排序算法的時(shí)間復(fù)雜度仍然為O(n^2),與直接插入排序算法相同。
    代碼如下:


    復(fù)制代碼

    void binary_insert_sort(int a[],int n){ for(int i=1;i<n;i++){ int low=0; int high=i-1; int t=a[i]; int mid; while(low<=high){ mid=(low+high)/2; if(t<a[mid]) high=mid-1; else low=mid+1; } for(int j=i;j>mid;j--) a[j]=a[j-1]; a[low]=t; }}


    復(fù)制代碼

測(cè)試結(jié)果如下:


  1. 希爾排序
    希爾排序(Shell Sort)又叫做縮小增量排序(diminishing increment sort),是一種很優(yōu)秀的排序法,算法本身不難理解,也很容易實(shí)現(xiàn),而且它的速度很快。 基本思想:
      先取一個(gè)小于n的整數(shù)d1作為第一個(gè)增量,把文件的全部記錄分成d1個(gè)組。所有距離為dl的倍數(shù)的記錄放在同一個(gè)組中。先在各組內(nèi)進(jìn)行直接插入 排序;然后,取第二個(gè)增量d2<d1重復(fù)上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1), 即所有記錄放在同一組中進(jìn)行直接插入排序?yàn)橹埂?br>   該方法實(shí)質(zhì)上是一種分組插入方法。插入排序(Insertion Sort)的一個(gè)重要的特點(diǎn)是,如果原始數(shù)據(jù)的大部分元素已經(jīng)排序,那么插入排序的速度很快(因?yàn)樾枰苿?dòng)的元素很少)。從這個(gè)事實(shí)我們可以想到,如果原 始數(shù)據(jù)只有很少元素,那么排序的速度也很快。--希爾排序就是基于這兩點(diǎn)對(duì)插入排序作出了改進(jìn)。
    下圖是希爾排序的一種實(shí)現(xiàn)方式:


該圖對(duì)應(yīng)的實(shí)現(xiàn)方式如下:
復(fù)制代碼

void shell_sort(int a[],int n){ _FUNC; int gap=n/2; bool flag=true; while(gap>1||flag) { flag=false; for(int i=0;i+gap<n;i++) if(a[i]>a[i+gap]) { swap(a[i],a[i+gap]); flag=true; } print(a,n); if(gap>1) gap/=2; }}


復(fù)制代碼

測(cè)試結(jié)果如下:



另一種實(shí)現(xiàn)方式:


復(fù)制代碼

void shell_sort2(int a[],int n){// _FUNC; int gap=n/2; while(gap>0){ for(int i=gap;i<n;i++){ int t=a[i]; int j; for(j=i-gap;j>=0&&a[j]>t;j-=gap) a[j+gap]=a[j]; a[j+gap]=t; } gap/=2; }}
復(fù)制代碼
  1. 直接選擇排序
    排序是給每個(gè)位置選擇當(dāng)前元素最小的,比如給第一個(gè)位置選擇最小的,在剩余元素里面給第二個(gè)元素選擇第二小的,依次類推,直到第n-1個(gè)元素,第n個(gè) 元素不用選擇了,因?yàn)橹皇O滤粋€(gè)最大的元素了。那么,在一趟選擇,如果當(dāng)前元素比一個(gè)元素小,而該小的元素又出現(xiàn)在一個(gè)和當(dāng)前元素相等的元素后面,那么 交換后穩(wěn)定性就被破壞了。比較拗口,舉個(gè)例子,序列5 8 5 2 9,我們知道第一遍選擇第1個(gè)元素5會(huì)和2交換,那么原序列中2個(gè)5的相對(duì)前后順序就被破壞了,所以選擇排序不是一個(gè)穩(wěn)定的排序算法。時(shí)間復(fù)雜度是O(n^2)
    代碼如下:


    復(fù)制代碼

    void select_sort(int a[],int n){ for(int i=0;i<n-1;i++) { int min=a[i]; int index=i; for(int j=i+1;j<n;j++) if(a[j]<min) { min=a[j]; index=j; } swap(a[i],a[index]); }}


    復(fù)制代碼

這個(gè)是最基本的:從中找出最小的然后和第一個(gè)數(shù)交換,再?gòu)牡?到n-1中找出最小的和第二個(gè)數(shù)交換
方法二:


復(fù)制代碼

void select_sort2(int a[],int n){ _FUNC; for(int i=n-1;i>0;i--){ for(int j=0;j<i;j++) if(a[j]>a[i]) swap(a[j],a[i]); }}


復(fù)制代碼

這兒感覺(jué)形式上有點(diǎn)類似下面的冒泡排序。
方法三:
這是對(duì)方法二的改進(jìn),判斷過(guò)程中是否有交換發(fā)生,如果沒(méi)有交換,說(shuō)明已經(jīng)完成排序了。


復(fù)制代碼

void select_sort3(int a[],int n){ _FUNC; bool flag=true; for(int i=n-1;i>0&&flag;i--){ flag=false; for(int j=0;j<i;j++) if(a[j]>a[i]) swap(a[j],a[i]),flag=true; print(a,n); }}


復(fù)制代碼

5. 堆排序
我們知道堆的結(jié)構(gòu)是節(jié)點(diǎn)i的孩子為2i和2i+1節(jié)點(diǎn),大頂堆要求父節(jié)點(diǎn)大于等于其2個(gè)子節(jié)點(diǎn),小頂堆要求父節(jié)點(diǎn)小于等于其2個(gè)子節(jié)點(diǎn)。在一個(gè)長(zhǎng)為n 的序列,堆排序的過(guò)程是從第n/2開(kāi)始和其子節(jié)點(diǎn)共3個(gè)值選擇最大(大頂堆)或者最小(小頂堆),這3個(gè)元素之間的選擇當(dāng)然不會(huì)破壞穩(wěn)定性。但當(dāng)為n /2-1, n/2-2, ...1這些個(gè)父節(jié)點(diǎn)選擇元素時(shí),就會(huì)破壞穩(wěn)定性。有可能第n/2個(gè)父節(jié)點(diǎn)交換把后面一個(gè)元素交換過(guò)去了,而第n/2-1個(gè)父節(jié)點(diǎn)把后面一個(gè)相同的元素沒(méi) 有交換,那么這2個(gè)相同的元素之間的穩(wěn)定性就被破壞了。所以,堆排序不是穩(wěn)定的排序算法。
堆排序的代碼如下:

復(fù)制代碼

void adjust(int b[],int m,int n){// int b=a-1; int j=m; int k=2m; while(k<=n){ if(k<n&&b[k]<b[k+1]) k++; if(b[j]<b[k]) swap(b[j],b[k]); j=k; k*=2; }}void heap_sort(int a[],int n){ _FUNC; int *b=a-1; for(int i=n/2;i>=1;i--) adjust(b,i,n); for(int i=n-1;i>=1;i--){ swap(b[1],b[i+1]); adjust(b,1,i); }}
復(fù)制代碼

需要注意的是,如果使用數(shù)組表示堆的話,要從下標(biāo)1開(kāi)始,而不是從0開(kāi)始。所以,這兒采用了一個(gè)技巧,讓int*b=a-1;這樣的話b[1]就相當(dāng)于對(duì)原數(shù)組從0開(kāi)始似的,即a[0]。

  1. 冒泡排序
    冒泡排序就是把小的元素往前調(diào)或者把大的元素往后調(diào)。比較是相鄰的兩個(gè)元素比較,交換也發(fā)生在這兩個(gè)元素之間。所以,如果兩個(gè)元素相等,是不用交換的;如果兩個(gè)相等的元素沒(méi)有相鄰,那么即使通過(guò)前面的兩兩交換把兩個(gè)相鄰起來(lái),這時(shí)候也不會(huì)交換,所以相同元素的前后順序并沒(méi)有改 變,所以冒泡排序是一種穩(wěn)定排序算法。
    代碼如下:


    復(fù)制代碼

    void bubble_sort(int a[],int n){ _FUNC; for(int i=n-1;i>0;i--) for(int j=0;j<i;j++) if(a[j]>a[j+1]) swap(a[j],a[j+1]);}


    復(fù)制代碼

下面的方法是加入了是否已經(jīng)排好序的判斷。

復(fù)制代碼

void bubble_sort2(int a[],int n){ bool flag=true; for(int i=n-1;i>0&&flag;i--){ flag=false; for(int j=0;j<i;j++) if(a[j]>a[j+1]) swap(a[j],a[j+1]),flag=true; }}
復(fù)制代碼

  1. 快速排序
    快速排序(Quicksort)是對(duì)冒泡排序的一種改進(jìn)。由C. A. R. Hoare在1962年提出。它的基本思想是:通過(guò)一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然 后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序,整個(gè)排序過(guò)程可以遞歸進(jìn)行,以此達(dá)到整個(gè)數(shù)據(jù)變成有序序列。



    快速排序有兩個(gè)方向,左邊的i下標(biāo)一直往右走,當(dāng)a[i] <= a[center_index],其中center_index是中樞元素的數(shù)組下標(biāo),一般取為數(shù)組第0個(gè)元素。而右邊的j下標(biāo)一直往左走,當(dāng)a[j] > a[center_index]。如果i和j都走不動(dòng)了,i <= j, 交換a[i]和a[j],重復(fù)上面的過(guò)程,直到i>j。交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交 換的時(shí)候,很有可能把前面的元素的穩(wěn)定性打亂,比如序列為 5 3 3 4 3 8 9 10 11,現(xiàn)在中樞元素5和3(第5個(gè)元素,下標(biāo)從1開(kāi)始計(jì))交換就會(huì)把元素3的穩(wěn)定性打亂,所以快速排序是一個(gè)不穩(wěn)定的排序算法,不穩(wěn)定發(fā)生在中樞元素和 a[j] 交換的時(shí)刻。
    下面的代碼中中樞元素采用的中間的元素:


    復(fù)制代碼

    void qsort(int a[],int l,int r){ int pvt=a[(l+r)/2]; int i=l,j=r; while(i<=j){ while(a[i]<pvt) i++; while(a[j]>pvt) j--; if(i<=j){ if(i!=j) swap(a[i],a[j]); i++; j--; } } if(j>l) qsort(a,l,j); if(i<r) qsort(a,i,r);}void quick_sort(int a[],int n){ qsort(a,0,n-1);}
    復(fù)制代碼
  1. 二路歸并排序

歸并排序是把序列遞歸地分成短序列,遞歸出口是短序列只有1個(gè)元素(認(rèn)為直接有序)或者2個(gè)序列(1次比較和交換),然后把各個(gè)有序的段序列合并成一個(gè)有 序的長(zhǎng)序列,不斷合并直到原序列全部排好序??梢园l(fā)現(xiàn),在1個(gè)或2個(gè)元素時(shí),1個(gè)元素不會(huì)交換,2個(gè)元素如果大小相等也沒(méi)有人故意交換,這不會(huì)破壞穩(wěn)定 性。那么,在短的有序序列合并的過(guò)程中,穩(wěn)定是是否受到破壞?沒(méi)有,合并過(guò)程中我們可以保證如果兩個(gè)當(dāng)前元素相等時(shí),我們把處在前面的序列的元素保存在結(jié) 果序列的前面,這樣就保證了穩(wěn)定性。所以,歸并排序也是穩(wěn)定的排序算法。




方法一:遞歸形式的歸并排序
復(fù)制代碼

void merge(int a[],int b[],int l,int m,int r){// int *b=new int[r-l+1]; int i,j,k; i=l; j=m+1; k=l; while(i<=m&&j<=r){ if(a[i]<a[j]) b[k++]=a[i++]; else b[k++]=a[j++]; } while(i<=m) b[k++]=a[i++]; while(j<=r) b[k++]=a[j++]; for(int s=l;s<=r;s++) a[s]=b[s];// delete[] b;}void msort(int a[],int b[],int l,int r){ if(l<r){ int m=(l+r)/2; msort(a,b,l,m); msort(a,b,m+1,r); merge(a,b,l,m,r); }}void merge_sort(int a[],int n){ _FUNC; int *b=new int[n]; msort(a,b,0,n-1); delete[] b;}
復(fù)制代碼

方法二:去除遞歸的方法

復(fù)制代碼

void merge_pass(int x[],int y[],int s,int n){ int i=0; while(i+2s-1<n){ merge(x,y,i,i+s-1,i+2s-1); i+=2*s; } if(i+s<n) merge(x,y,i,i+s-1,n-1); else for(int j=i;j<=n-1;j++) y[j]=x[j];}void merge_sort2(int a[],int n){ _FUNC; int *b=new int [n]; int s=1; while(s<n){ merge_pass(a,b,s,n); s+=s; merge_pass(b,a,s,n); s+=s; } delete[] b;}
復(fù)制代碼

  1. 自然歸并排序



    下面的兩種形式是一樣的,開(kāi)始我先采用的vector來(lái)記錄子序列的位置,后來(lái)發(fā)現(xiàn)其實(shí)采用一個(gè)數(shù)組就可以了。兩種代碼都放在這兒吧。
    形式1:


    復(fù)制代碼

    void merge_sort3(int a[],int n){ vector<int> st; for(int i=0;i<n-1;i++){ if(a[i]>a[i+1]) st.push_back(i); } st.push_back(n-1);// copy(st.begin(),st.end(),ostream_iterator<int>(cout," "));// cout<<endl; int *b=new int [n]; int l,m,r; l=0; if(!st.empty()) { m=st.front(); st.erase(st.begin()); } while(!st.empty()){ r=st.front(); st.erase(st.begin()); merge(a,b,l,m,r);// print(a,n);// copy(st.begin(),st.end(),ostream_iterator<int>(cout," "));// cout<<endl; m=r; }// print(a,n); delete [] b;}
    復(fù)制代碼

形式2:

復(fù)制代碼

void merge_sort4(int a[],int n){ _FUNC; int *pos=new int[n]; int k=0; for(int i=0;i<n-1;i++){ if(a[i]>a[i+1]) pos[k++]=i; } pos[k++]=n-1; int *b=new int [n]; int l,m,r; l=0; int p=0; if(p<k) m=pos[p++]; while(p<k){ r=pos[p++]; merge(a,b,l,m,r); m=r; } delete [] b;}
復(fù)制代碼

  1. 箱排序



一個(gè)簡(jiǎn)單的測(cè)試?yán)尤缦拢?div id="u0z1t8os" class="image-package">

View Code
上面的程序采用一個(gè)簡(jiǎn)單的Node類來(lái)描述學(xué)生的姓名和成績(jī),采用STL中的list來(lái)實(shí)現(xiàn)箱子排序。同樣進(jìn)行隨機(jī)生成了多個(gè)實(shí)例來(lái)測(cè)試程序的正確性。而所采用的標(biāo)準(zhǔn)是STL中的multimap容器。因?yàn)檫@個(gè)容器可以自動(dòng)根據(jù)關(guān)鍵字進(jìn)行排序。本來(lái)想使用map容器,但是map容器不允許重復(fù),而我們的測(cè)試實(shí)例中有很多的重復(fù)元素。測(cè)試的部分結(jié)果如下:

  1. 基數(shù)排序
    基數(shù)排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時(shí)候有些屬性是有優(yōu)先級(jí)順序的,先按低優(yōu)先級(jí)排序,再按高優(yōu) 先級(jí)排序,最后的次序就是高優(yōu)先級(jí)高的在前,高優(yōu)先級(jí)相同的低優(yōu)先級(jí)高的在前?;鶖?shù)排序基于分別排序,分別收集,所以其是穩(wěn)定的排序算法。

下面的一種方法是采用STL的鏈表容器list來(lái)實(shí)現(xiàn)的,這種實(shí)現(xiàn)比較直觀:

復(fù)制代碼

void radix_sort2(int a[],int n){ int bits=maxbits(a,n); list<int> x(a,a+n); int range=10; vector<list<int> > bin(range); list<int> y; list<int>::iterator ite; int adix=1; for(int i=0;i<bits;i++){ for(ite=x.begin();ite!=x.end();ite++){ int d=(ite/adix)%10; bin[d].push_back(ite); } vector<list<int> >::iterator ite2; y.clear(); for(ite2=bin.begin();ite2!=bin.end();++ite2){ for(ite=ite2->begin();ite!=ite2->end();++ite) y.push_back(ite); ite2->clear(); } x=y; adix=10; } int i=0; for(ite=x.begin();ite!=x.end();ite++) a[i++]=*ite;}
復(fù)制代碼

另一種方法是采用多個(gè)數(shù)組來(lái)實(shí)現(xiàn),不是很容易理解,這是參考的網(wǎng)上的代碼。具體代碼如下:

復(fù)制代碼

int maxbits(int a[],int n){ int d=0; for(int i=0;i<n;i++){ int b=1; int r=a[i]; while(r/10>0){ b++; r/=10; } if(d<b) d=b; } return d;}void radix_sort(int a[],int n){ _FUNC; int d=maxbits(a,n); int *temp=new int[n]; int count=new int[10]; int adix=1; for(int b=1;b<=d;b++){ for(int i=0;i<10;i++) count[i]=0; for(int i=0;i<n;i++){ int k=(a[i]/adix)%10; count[k]++; } for(int i=1;i<10;i++) count[i]+=count[i-1]; for(int i=n-1;i>=0;i--){ int k=(a[i]/adix)%10; count[k]--; temp[count[k]]=a[i]; } for(int i=0;i<n;i++) a[i]=temp[i]; adix=10; } delete[] temp; delete[] count;}
復(fù)制代碼

12.計(jì)數(shù)排序


代碼如下:
復(fù)制代碼

void rank(int arr[],int n,int r[]){ for(int i=0;i<n;i++) r[i]=0; for(int i=1;i<n;i++){ for(int j=0;j<i;j++) { if(arr[j]<=arr[i]) r[i]++; else r[j]++; } }}
復(fù)制代碼


代碼如下:
復(fù)制代碼

void rank_sort2(int a[],int n){ int *r=new int[n]; rank(a,n,r); int *u=new int[n]; for(int i=0;i<n;i++) u[r[i]]=a[i]; for(int i=0;i<n;i++) a[i]=u[i]; delete[] r; delete[] u;}


復(fù)制代碼

代碼如下:


復(fù)制代碼

void rank_sort(int arr[],int n){ int *r=new int[n]; rank(arr,n,r); for(int i=0;i<n;i++) { while(r[i]!=i) { int t=r[i]; swap(arr[i],arr[t]); swap(r[i],r[t]); } } delete[] r;}


復(fù)制代碼

排序算法復(fù)雜度:
按平均時(shí)間將排序分為四類:(1)平方階(O(n2
))排序  一般稱為簡(jiǎn)單排序,例如直接插入、直接選擇和冒泡排序;(2)線性對(duì)數(shù)階(O(nlgn))排序  如快速、堆和歸并排序;(3)O(n1+£
)階排序  £是介于0和1之間的常數(shù),即0<£<1,如希爾排序;(4)線性階(O(n))排序  如桶、箱和基數(shù)排序。


不同條件下,排序方法的選擇(1)若n較小(如n≤50),可采用直接插入或直接選擇排序。  當(dāng)記錄規(guī)模較小時(shí),直接插入排序較好;否則因?yàn)橹苯舆x擇移動(dòng)的記錄數(shù)少于直接插人,應(yīng)選直接選擇排序?yàn)橐恕?2)若文件初始狀態(tài)基本有序(指正序),則應(yīng)選用直接插人、冒泡或隨機(jī)的快速排序?yàn)橐耍?3)若n較大,則應(yīng)采用時(shí)間復(fù)雜度為O(nlgn)的排序方法:快速排序、堆排序或歸并排序。  快速排序是目前基于比較的內(nèi)部排序中被認(rèn)為是最好的方法,當(dāng)待排序的關(guān)鍵字是隨機(jī)分布時(shí),快速排序的平均時(shí)間最短;  堆排序所需的輔助空間少于快速排序,并且不會(huì)出現(xiàn)快速排序可能出現(xiàn)的最壞情況。這兩種排序都是不穩(wěn)定的。  若要求排序穩(wěn)定,則可選用歸并排序。但本章介紹的從單個(gè)記錄起進(jìn)行兩兩歸并的 排序算法并不值得提倡,通??梢詫⑺椭苯硬迦肱判蚪Y(jié)合在一起使用。先利用直接插入排序求得較長(zhǎng)的有序子文件,然后再兩兩歸并之。因?yàn)橹苯硬迦肱判蚴欠€(wěn)定 的,所以改進(jìn)后的歸并排序仍是穩(wěn)定的。4)在基于比較的排序方法中,每次比較兩個(gè)關(guān)鍵字的大小之后,僅僅出現(xiàn)兩種可能的轉(zhuǎn)移,因此可以用一棵二叉樹(shù)來(lái)描述比較判定過(guò)程。  當(dāng)文件的n個(gè)關(guān)鍵字隨機(jī)分布時(shí),任何借助于"比較"的排序算法,至少需要O(nlgn)的時(shí)間。  箱排序和基數(shù)排序只需一步就會(huì)引起m種可能的轉(zhuǎn)移,即把一個(gè)記錄裝入m個(gè)箱子之一,因此在一般情況下,箱排序和基數(shù)排序可能在O(n)時(shí)間內(nèi)完成對(duì)n個(gè) 記錄的排序。但是,箱排序和基數(shù)排序只適用于像字符串和整數(shù)這類有明顯結(jié)構(gòu)特征的關(guān)鍵字,而當(dāng)關(guān)鍵字的取值范圍屬于某個(gè)無(wú)窮集合(例如實(shí)數(shù)型關(guān)鍵字)時(shí), 無(wú)法使用箱排序和基數(shù)排序,這時(shí)只有借助于"比較"的方法來(lái)排序。  若n很大,記錄的關(guān)鍵字位數(shù)較少且可以分解時(shí),采用基數(shù)排序較好。雖然桶排序?qū)﹃P(guān)鍵字的結(jié)構(gòu)無(wú)要求,但它也只有在關(guān)鍵字是隨機(jī)分布時(shí)才能使平均時(shí)間達(dá)到 線性階,否則為平方階。同時(shí)要注意,箱、桶、基數(shù)這三種分配排序均假定了關(guān)鍵字若為數(shù)字時(shí),則其值均是非負(fù)的,否則將其映射到箱(桶)號(hào)時(shí),又要增加相應(yīng) 的時(shí)間。(5)有的語(yǔ)言(如Fortran,Cobol或Basic等)沒(méi)有提供指針及遞歸,導(dǎo)致實(shí)現(xiàn)歸并、快速(它們用遞歸實(shí)現(xiàn)較簡(jiǎn)單)和基數(shù)(使用了指針)等排序算法變得復(fù)雜。此時(shí)可考慮用其它排序。(6)本章給出的排序算法,輸人數(shù)據(jù)均是存儲(chǔ)在一個(gè)向量中。當(dāng)記錄的規(guī)模較大時(shí),為避免耗費(fèi)大量的時(shí)間去移動(dòng)記錄,可以用鏈表作為存儲(chǔ)結(jié)構(gòu)。譬如插入排 序、歸并排序、基數(shù)排序都易于在鏈表上實(shí)現(xiàn),使之減少記錄的移動(dòng)次數(shù)。但有的排序方法,如快速排序和堆排序,在鏈表上卻難于實(shí)現(xiàn),在這種情況下,可以提取 關(guān)鍵字建立索引表,然后對(duì)索引表進(jìn)行排序。然而更為簡(jiǎn)單的方法是:引人一個(gè)整型向量t作為輔助表,排序前令t[i]=i(0≤i<n),若排序算法 中要求交換R[i]和R[j],則只需交換t[i]和t[j]即可;排序結(jié)束后,向量t就指示了記錄之間的順序關(guān)系: R[t[0]].key≤R[t[1]].key≤…≤R[t[n-1]].key 若要求最終結(jié)果是: R[0].key≤R[1].key≤…≤R[n-1].key則可以在排序結(jié)束后,再按輔助表所規(guī)定的次序重排各記錄,完成這種重排的時(shí)間是O(n)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容