C/C++ 數據結構之算法

數據結構中的排序算法。html

排序算法的相關知識:ios

(1)排序的概念:所謂排序就是要整理文件中的記錄,使之按關鍵字遞增(或遞減)次序排列起來。git

(2)穩定的排序方法:在待排序的文件中,若存在多個關鍵字相同的記錄,通過排序後這些具備相同關鍵字的記錄之間的相對次序保持不變,該排序方法是穩定的。相反,若是發生改變,這種排序方法不穩定。程序員

(3)排序算法的分類(分爲5類):插入排序、選擇排序、交換排序、歸併排序和分配排序。算法

(4)排序算法兩個基本操做:<1>比較關鍵字的大小。數組

                                      <2>改變指向記錄的指針或移動記錄自己。數據結構

具體的排序方法:ide

插入排序函數

<1>插入排序(Insertion Sort)的思想:每次將一個待排序的記錄按其關鍵字大小插入到前面已經排好序的子記錄中的適當位置,直到所有記錄插入完成爲止。性能

<2>經常使用的插入排序方法有直接插入排序和希爾排序。

(1)直接插入排序

<1>算法思路:把一個記錄集(如一個數組)分紅兩部分,前半部分是有序區,後半部分是無序區;有序區一開始有一個元素r[0],無序區一開始是從r[1]到以後的全部元素;而後每次從無序區按順序取一個元素r[i],拿到有序區中由後往前進行比較,每次比較時,有序區中比r[i]大的元素就日後移動一位,直到找到小於r[i]的元素,這時r[i]插到小元素的後面,則完成一趟直接插入排序。如此反覆,從無序區不斷取元素插入到有序區,直到無序區爲空,則插入算法結束。

<2>算法演示:

複製代碼
複製代碼
複製代碼
//直接插入排序:#include<iostream> using namespace std;
void InsertSort(int r[],int n);
int main() { int r[]={24,1,56,2,14,58,15,89}; InsertSort(r,8); for(int i=0;i<8;i++) { cout<<r[i]<<'\t'; } cout<<endl; return 0; }
void InsertSort(int r[],int n) { for(int i=1;i<n;i++) { for(int j=i-1,s=r[i];s<r[j] && j>=0;j--) { r[j+1]=r[j]; } r[j+1]=s; } }
複製代碼
複製代碼
複製代碼

(2)折半插入排序

<1>算法思路:咱們看到在直接插入排序算法中,須要在有序區查找比r[i]的小的元素,而後插入到這個元素後面,但這裏要注意這個元素是從無序區算第一個比r[i]小的元素。折半插入排序就是在有序區折半查找這個元素。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//折半插入排序#include<iostream> using namespace std;
void BinInsertSort(int r[],int n);
int main() { int r[]={53,34,76,23,55,28,63,88,34,66}; BinInsertSort(r,10); for(int i=0;i<10;i++) { cout<<r[i]<<"\t"; } cout<<endl; return 0; }
void BinInsertSort(int r[],int n) { for(int i=1;i<n;i++) { int s=r[i]; int low=0; int high=i-1; while(low <= high) { int mid=(low+high)/2; if(s < r[mid]) { high=mid-1; } else { low=mid+1; } } for(int j=i-1;j>=high+1;j--) { r[j+1]=r[j]; } r[high+1]=s; //r[high+1]是要找的元素 } }
複製代碼
複製代碼
複製代碼

(3)希爾排序(Shell Sort)

<1>算法思路:把整個記錄近一個步長step(通常取記錄長度的1/2),分紅step個組,再分別對每一個級進行直接插入排序;而後再把整個記錄近一個新的步長(通常取step/2)分紅step/2個組,再分別對每一個組進行直接插入排序;如此不斷的縮小步長,反覆分組排序,直到步長等於1爲此(步長爲1則不可能再分組,1是元素之間距離的最小值)。能夠看出,希爾排序實質是一種分組插入方法。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//希爾排序:#include<iostream> using namespace std;
void ShellSort(int r[],int n); int main() { int r[]={24,1,56,2,14,58,15,89}; ShellSort(r,8); for(int i=0;i<8;i++) { cout<<r[i]<<'\t'; } cout<<endl; return 0; }
void ShellSort(int r[],int n) { int step=n/2; while(step >= 1) { for(int i=step;i<n;i+=step) { for(int j=i-step,s=r[i];s<r[j] && j>=0;j-=step) { r[j+step]=r[j]; } r[j+step]=s; } step/=2; } }
複製代碼
複製代碼
複製代碼

 

選擇排序

 

<1>選擇排序的思想:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已經排好的記錄最後,直到所有記錄排序完畢。

 

<2>經常使用的選擇排序方法有直接選擇排序和堆排序。

 

(1)直接選擇排序

 

 

<1>算法思路:把待排序的n個元素當作一個有序區和一個無序區,開始的時候有序區爲空,無序區包含了所有n個元素。排序的時候,每次從無序區中選擇比較出其中最小一個元素放到有序區中。如此反覆操做,無序區中每小一個元素,有序區中就多一個元素,直到無序區的全部元素都轉到有序區中。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//簡單選擇排序:#include<iostream> using namespace std;
void SelectSort(int r[],int n);
int main() { int r[]={53,34,76,23,55,28,63,88,34,66}; SelectSort(r,10); for(int i=0;i<10;i++) { cout<<r[i]<<"\t"; } cout<<endl; return 0; }
void SelectSort(int r[],int n) { for(int i=0;i<n-1;i++) { int small_loc=i; for(int j=i+1;j<n;j++) { if(r[small_loc] > r[j]) { small_loc=j; } } if(small_loc != i) { int temp=r[i]; r[i]=r[small_loc]; r[small_loc]=temp; } } }
複製代碼
複製代碼
複製代碼

(2)堆排序

<1>算法思路:大根堆二叉樹中的非終端結點的元素值均大於它的左右孩子的值,所以知道堆的最大值是它的根結點。當根結點移出,則從新調整堆後,堆的次大值稱爲根結點,依次操做,能夠獲得堆的從大到小的有序序列。這個算法過程就是堆排序。

      堆排序有一個建堆、篩選堆、調整堆的過程。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//堆排序:#include<iostream> using namespace std;
void HeapAdjust(int r[],int i,int j); void HeapSort(int r[],int n);
int main() { int r[]={53,34,76,23,55,28,63,88,34,66}; HeapSort(r,10); for(int i=0;i<10;i++) { cout<<r[i]<<"\t"; } cout<<endl; return 0; }
void HeapAdjust(int r[],int i,int j) //調整堆{ int child=2*i; int temp=r[i]; //temp臨時存放根結點 while(child <= j) //沿大兒子向下調整 { if(child<j && r[child+1]>r[child]) child++; if(temp >= r[child]) break; r[child/2]=r[child]; child=2*child; } r[child/2]=temp; }
void HeapSort(int r[],int n) //建堆{ for(int i=(n-1)/2;i>=0;--i) { HeapAdjust(r,i,n-1); //初始建堆 } for(i=n-1;i>0;--i) { //將當前堆頂元素與當前堆尾元素互換 int temp=r[0]; r[0]=r[i]; r[i]=temp; HeapAdjust(r,0,i-1); //將剩下的元素從新調整成堆 } }
複製代碼
複製代碼
複製代碼

 

 

交換排序

 

<1>交換排序的思想:兩兩比較待排序記錄的關鍵字,發現兩個記錄的次序相反時即進行交換,直到沒有反序的記錄爲止。

<2>經常使用的交換排序方法有冒泡排序和快速排序。

 

(1)冒泡排序

<1>算法思路:經過相鄰元素的值的大小比較,並交換值較大的(較小的)元素,使得元素從一端移到到另外一端,就像水底冒出的氣泡同樣。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//起泡法排序:#include<iostream> using namespace std; #define N 5   //N爲數的總個數
void BubbleSort(int r[],int n);
int main() { int i; int a[N]; cout<<"請輸入"<<N<<"個數字:"; for(i=0;i<N;i++) { cin>>a[i]; } BubbleSort(a,N); for(i=0;i<N;i++) { cout<<a[i]<<"\t"; } cout<<endl; return 0; }
void BubbleSort(int r[],int n) { for(int i=0;i<n-1;i++) //進行n-1次循環,實現n-1趟比較 { for(int j=0;j<n-1-i;j++) //在每一趟中進行n-1-i次比較 { if(r[j]>r[j+1]) { int temp=r[j]; r[j]=r[j+1]; r[j+1]=temp; } } } }
複製代碼
複製代碼
複製代碼

(2)快速排序

<1>算法思路:經過一趟排序將準備排序的元素集合分紅兩個部分,其中一部分的元素的值都小於另外一部分,而後對這兩部分的元素集合內部再分別重複進行上面的排序過程,直到全部的元素都排列有序。

<2>算法演示:

複製代碼
複製代碼
複製代碼
//快速排序:#include<iostream> using namespace std;
int Partition(int r[],int low,int high); void QuickSort(int r[],int low,int high);
int main() { int r[]={53,34,76,23,55,28,63,88,34,66}; QuickSort(r,0,10-1); for(int i=0;i<10;i++) { cout<<r[i]<<"\t"; } cout<<endl; return 0; }
int Partition(int r[],int low,int high) { int pivotkey=r[low]; int i=low; int j=high; while(i<j) { while(i<j && r[j]>pivotkey) j--; if(i<j){r[i]=r[j];i++;} while(i<j && r[i]<pivotkey) i++; if(i<j){r[j]=r[i];j--;} } r[j]=pivotkey; return j; }
void QuickSort(int r[],int low,int high) { if(low<high) { int pivot=Partition(r,low,high); QuickSort(r,low,pivot-1); QuickSort(r,pivot+1,high); } }
複製代碼
複製代碼
複製代碼

 

歸併排序

<1>歸併排序的思想:假設數組r有n個元素,那麼能夠當作數組r是由n個有序的子序列組成,每一個子序列的長度爲1,而後再兩合併,獲得了一個長度是2(或1)的有序子序列,再兩兩合併,如此重複,直到獲得一個長度爲n的有序數據序列爲止,這種排序方法稱爲二路歸併排序。

<2>經常使用的交換排序方法有二路歸併排序和三路歸併排序。

(1)二路歸併排序

<1>算法思路:如上。

<2>算法演示:

 

複製代碼
複製代碼
複製代碼
//二路歸併排序#include <iostream> using namespace std; int *a=new int[20]; int n=0;
//歸併排序,排序結果放到了b[]中void Merge(int a[],int b[],int left ,int mid,int right)//此處的right指向數組中左後一個元素的位置{ int i=left; int j=mid+1; int k=left; while(i<=mid && j<=right) { if(a[i]<=a[j])b[k++]=a[i++]; else b[k++]=a[j++]; } while(i<=mid) b[k++]=a[i++]; while(j<=right) b[k++]=a[j++]; }
//從b[]中又搬到了a[]中void Copy(int a[],int b[],int left,int right)//right同上{ for(int i=left;i<=right;i++) a[i]=b[i]; }
//劃分並排序void MergeSort(int a[],int left,int right)//同上{ int *b = new int[right-left+1]; if(left<right) { //將當前傳經來的數組劃分紅更小的大小几乎相同的數組 int i=(left+right)/2; MergeSort(a,left,i); MergeSort(a,i+1,right); //將小數組合成大數組,同時排序,結果放到b[]中 Merge(a,b,left,i,right); //從b[]中挪到a[]中 Copy(a,b,left,right); } }
void Input() { cout<<"Please Input array's size:"; cin>>n; cout<<"Array's elemants:"<<endl; for(int i=0;i<n;i++) cin>>a[i]; //調用算法 MergeSort(a,0,n-1); }
void Output() { for(int i=0;i<n;i++) cout<<a[i]<<" "; cout<<endl; }
int main() { Input(); Output(); return 0; }

一、線性數據結構:典型的有:數組、棧、隊列和線性表
(1)數組和鏈表
a、數組:存放着一組相同類型的數據,須要預先指定數組的長度,有一維數組、二維數組、多維數組等
b、鏈表:鏈表是C語言中一種應用普遍的結構,它採用動態分配內存的形式實現,用一組任意的存儲單元存放數據元素鏈表的,通常爲每一個元素增設指針域,用來指向後繼元素
c、數組和鏈表的區別:
從邏輯結構來看:數組必須事先定義固定的長度,不能適應數據動態地增減的狀況;鏈表動態地進行存儲分配,能夠適應數據動態地增減的狀況,且能夠方便地插入、刪除數據項(數組中插入、刪除數據項時,須要移動其它數據項)
從內存存儲來看:(靜態)數組從棧中分配空間(用NEW建立的在堆中), 對於程序員方便快速,可是自由度小;鏈表從堆中分配空間, 自由度大可是申請管理比較麻煩
從訪問方式來看:數組在內存中是連續存儲的,所以,能夠利用下標索引進行隨機訪問;鏈表是鏈式存儲結構,在訪問元素的時候只能經過線性的方式由前到後順序訪問,因此訪問效率比數組要低
 
(2)棧、隊列和線性表:可採用順序存儲和鏈式存儲的方法進行存儲
順序存儲:藉助數據元素在存儲空間中的相對位置來表示元素之間的邏輯關係
鏈式存儲:藉助表示數據元素存儲地址的指針表示元素之間的邏輯關係
a、棧:只容許在序列末端進行操做,棧的操做只能在棧頂進行,通常棧又被稱爲後進先出或先進後出的線性結構
   順序棧:採用順序存儲結構的棧稱爲順序棧,即須要用一片地址連續的空間來存儲棧的元素,順序棧的類型定義以下:
  
   鏈棧:採用鏈式存儲結構的棧稱爲鏈棧:
    
 
b、隊列:只容許在序列兩端進行操做,通常隊列也被稱爲先進先出的線性結構
    循環隊列:採用順序存儲結構的隊列,須要按隊列可能的最大長度分配存儲空空,其類型定義以下:
  
  鏈隊列:採用鏈式存儲結構的隊列稱爲鏈隊列,通常須要設置頭尾指針只是鏈表的頭尾結點:
  
 
c、線性表:容許在序列任意位置進行操做,線性表的操做位置不受限制,線性表的操做十分靈活,經常使用操做包括在任意位置插入和刪除,以及查詢和修改任意位置的元素
  順序表:採用順序存儲結構表示的線性表稱爲順序表,用一組地址連續的存儲單元一次存放線性表的數據元素,即以存儲位置相鄰表示位序相繼的兩個元素之間的前驅和後繼關係,爲了不移動元素,通常在順序表的接口定義中只考慮在表尾插入和刪除元素,如此實現的順序表也可稱爲棧表:
 
 線性表:通常包括單鏈表、雙向鏈表、循環鏈表和雙向循環鏈表
 單鏈表:
  
 雙向鏈表:
    
 線性表兩種存儲結構的比較:
 順序表:
 優勢:在順序表中,邏輯中相鄰的兩個元素在物理位置上也相鄰,查找比較方便,存取任一元素的時間複雜度都爲O(1)
 缺點:不適合在任意位置插入、刪除元素,由於須要移動元素,平均時間複雜度爲O(n)
 鏈表:
 優勢:在連接的任意位置插入或刪除元素只需修改相應指針,不須要移動元素;按需動態分配,不須要按最大需求預先分配一塊連續空空
 缺點:查找不方便,查找某一元素須要從頭指針出發沿指針域查找,所以平均時間複雜度爲O(n)
 
二、樹形結構:結點間具備層次關係,每一層的一個結點能且只能和上一層的一個結點相關,但同時能夠和下一層的多個結點相關,稱爲「一對多」關係,常見類型有:樹、堆
(1)二叉樹:二叉樹是一種遞歸數據結構,是含有n(n>=0)個結點的有限集合,二叉樹具備如下特色:
二叉樹能夠是空樹;二叉樹的每一個結點都剛好有兩棵子樹,其中一個或兩個可能爲空;二叉樹中每一個結點的左、右子樹的位置不能顛倒,若改變二者的位置,就成爲另外一棵二叉樹
 
(2)徹底二叉樹:從根起,自上而下,自左而右,給滿二叉樹的每一個結點從1到n連續編號,若是每一個結點都與深度爲k的滿二叉樹中編號從1至n的結點一一對應,則稱爲徹底二叉樹
a、採用順序存儲結構:用一維數組存儲徹底二叉樹,結點的編號對於與結點的下標(如根爲1,則根的左孩子爲2*i=2*1=2,右孩子爲2*i+1=2*1+1=2)
   
b、採用鏈式存儲結構:
二叉鏈表:
 
三叉鏈表:它的結點比二叉鏈表多一個指針域parent,用於執行結點的雙親,便於查找雙親結點
 
兩種存儲結構比較:對於徹底二叉樹,採用順序存儲結構既能節省空間,又可利用數組元素的下標值肯定結點在二叉樹中的位置及結點之間的關係,但採用順序存儲結構存儲通常二叉樹容易形成空間浪費,鏈式結構能夠克服這個缺點
 
(3)二叉查找樹:二叉查找樹又稱二叉排序樹,或者是一課空二叉樹,或者是具備以下特徵的二叉樹:
a、若它的左子樹不空,則左子樹上全部結點的值均小於根結點的值
b、若它的右子樹不空,則右子樹上全部結點的值均大於根結點的值
c、它的左、右子樹也分別是二叉查找樹
 
(4)平衡二叉樹:平衡二叉查找樹簡稱平衡二叉樹,平衡二叉樹或者是棵空樹,或者是具備下列性質的二叉查找樹:它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的高度之差的絕對值不超過1
 
平衡二叉樹的失衡及調整主要可概括爲下列四種狀況:LL型、RR型、LR型、RL型
 
(5)樹:樹是含有n(n>=0)個結點的有限集合,在任意一棵非空樹種:
a、有且僅有一個特定的稱爲根的結點
b、當n>1時,其他結點可分爲m(m>0)個互不相交的有限集T1,T2,...,Tm,其中每個集合自己又是一棵樹,而且T1,T2,...,Tm稱爲根的子樹
 
(6)堆:堆是具備如下特性的徹底二叉樹,其全部非葉子結點均不大於(或不小於)其左右孩子結點。若堆中全部非葉子結點均不大於其左右孩子結點,則稱爲小頂堆(小根堆),若堆中全部非葉子結點均不小於其左右孩子結點,則稱爲大頂堆(大根堆)
 
 
(7)並查集:並查集是指由一組不相交子集所構成的集合,記做:S={S1,S2,S3,...,Sn}
(8)B樹
 
三、圖形結構:在圖形結構中,容許多個結點之間相關,稱爲「多對多」關係,可分爲有向圖和無向圖
 
排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納所有的排序記錄,在排序過程當中須要訪問外存。

咱們這裏說說八大排序就是內部排序。

    

    當n較大,則應採用時間複雜度爲O(nlog2n)的排序方法:快速排序、堆排序或歸併排序序。

   快速排序:是目前基於比較的內部排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;


 

1.插入排序—直接插入排序(Straight Insertion Sort)

基本思想:

將一個記錄插入到已排序好的有序表中,從而獲得一個新,記錄數增1的有序表。即:先將序列的第1個記錄當作是一個有序的子序列,而後從第2個記錄逐個進行插入,直至整個序列有序爲止。

要點:設立哨兵,做爲臨時存儲和判斷數組邊界之用。

直接插入排序示例:


 

若是遇見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。因此,相等元素的先後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,因此插入排序是穩定的。

算法的實現:

 

[cpp]  view plain  copy
 
  1. //直接插入排序:將第一個數據看作一個順序表,將後面的數據一次插入表中  
  2. void InsertSort(int a[], int n)    
  3. {    
  4.     for(int i= 1; i<n; i++){    
  5.         if(a[i] < a[i-1]){               //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入    
  6.             int j= i-1;   //表中最後一個數據  
  7.             int x = a[i];        //複製爲哨兵,即存儲待排序元素    
  8.             a[i] = a[i-1];           //前後移一個元素 (由於a[i]就是X,因此不怕丟失)   
  9.             while(j>=0 && x < a[j]){  //查找在有序表的插入位置  (遍歷表)  
  10.                 a[j+1] = a[j];    
  11.                 j--;         //元素後移    
  12.             }    
  13.             a[j+1] = x;      //插入到正確位置    
  14.         }    
  15.     }    
  16.         
  17. }   
  18. int main()  
  19. {  
  20.     int n;  
  21.     cin>>n;  
  22.     int *a=new int[n];  
  23.     for(int j=0;j<n;j++)  
  24.         cin>>a[j];  
  25.     InsertSort(a,n);  
  26.     for(int i=0;i<n;i++)  
  27.         cout<<a[i];  
  28.     delete []a;  
  29. }  

 

效率:

時間複雜度:O(n^2).

其餘的插入排序有二分插入排序,2-路插入排序。

 

 2. 插入排序—折半插入排序(二分插入)

將有序數列折半,看看插入到哪一個序列中去

 

 

[cpp]  view plain  copy
 
  1. //折半插入  
  2. void BInsertSort(int a[], int n)    
  3. {    
  4.     for(int i= 1; i<n; i++){    
  5.         int low=0,high=i;  
  6.         if(a[i] < a[i-1]){               //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入    
  7.             int x = a[i];        //複製爲哨兵,即存儲待排序元素    
  8.             a[i] = a[i-1];           //前後移一個元素 (由於a[i]就是X,因此不怕丟失)   
  9.             while(low<=high){  //查找在有序表的插入位置  (遍歷表)  
  10.                 int m=(low+high)/2;  
  11.                 if(x<a[m])  high=m-1;  
  12.                 else low=m+1;  
  13.             }    
  14.             for(int j=i-1;j>=high+1;j--)  
  15.                 a[j+1]=a[j];  
  16.             a[j+1] = x;      //插入到正確位置    
  17.         }    
  18.     }    
  19.         
  20. }   
  21. int main()  
  22. {  
  23.     int n;  
  24.     cin>>n;  
  25.     int *a=new int[n];  
  26.     for(int j=0;j<n;j++)  
  27.         cin>>a[j];  
  28.     BInsertSort(a,n);  
  29.     for(int i=0;i<n;i++)  
  30.         cout<<a[i];  
  31.     delete []a;  
  32. }  

 

 3. 插入排序—希爾排序(Shell`s Sort)

希爾排序是1959 年由D.L.Shell 提出來的,相對直接排序有較大的改進。希爾排序又叫縮小增量排序

基本思想:

先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。

操做方法:

  1. 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列個數k,對序列進行k 趟排序;
  3. 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

希爾排序的示例:

 

算法實現:

 

咱們簡單處理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1} n爲要排序數的個數

即:先將要排序的一組記錄按某個增量dn/2,n爲要排序數的個數)分紅若干組子序列,每組中記錄的下標相差d.對每組中所有元素進行直接插入排序,而後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。繼續不斷縮小增量直至爲1,最後使用直接插入排序完成排序。

 

[cpp]  view plain  copy
 
  1. //希爾排序:去增量爲d1的分爲一組,共分紅d1組分別進行插入排序,而後每組對應元素放在一塊兒,而後取d2...知道d=1  
  2. void ShellSort(int a[],int n)  
  3. {  
  4.     int dk;  
  5.     int tmp;  
  6.     for(dk=n/2;dk>0;dk/=2)  
  7.         for(int i=dk;i<n;i++)  
  8.         {  
  9.             tmp=a[i];  
  10.             for(int j=i;j>=dk;j-=dk)  
  11.                 if(tmp<a[j-dk])  
  12.                     a[j]=a[j-dk];  
  13.                 else break;  
  14.             a[j]=tmp;  
  15.         }  
  16. }  
  17. int main()  
  18. {  
  19.     int n;  
  20.     cin>>n;  
  21.     int *a=new int[n];  
  22.     for(int j=0;j<n;j++)  
  23.         cin>>a[j];  
  24.     ShellSort(a,n);  
  25.     for(int i=0;i<n;i++)  
  26.         cout<<a[i];  
  27.     delete []a;  
  28. }  

 

 

希爾排序時效分析很難,關鍵碼的比較次數與記錄移動次數依賴於增量因子序列d的選取,特定狀況下能夠準確估算出關鍵碼的比較次數和記錄的移動次數。目前尚未人給出選取最好的 增量因子序列的方法。增量因子序列能夠有各類取法,有取奇數的,也有取質數的,但須要注意:增量因子中除1 外沒有公因子,且最後一個增量因子必須爲1。希爾排序方法是一個不穩定的排序方法。

 

4. 選擇排序—簡單選擇排序(Simple Selection Sort)

基本思想:

在要排序的一組數中,選出最小(或者最大)的個數與第1個位置的數交換;而後在剩下的數當中再找最小(或者最大)的與第2個位置的數交換,依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最後個數)比較爲止。

簡單選擇排序的示例:

 

操做方法:

第一趟,從n 個記錄中找出關鍵碼最小的記錄與第一個記錄交換;

第二趟,從第二個記錄開始的n-1 個記錄中再選出關鍵碼最小的記錄與第二個記錄交換;

以此類推.....

第i 趟,則從第i 個記錄開始的n-i+1 個記錄中選出關鍵碼最小的記錄與第i 個記錄交換,

直到整個序列按關鍵碼有序。


算法實現:

 

[cpp]  view plain  copy
 
  1. //簡單選擇排序:遍歷一次找到最小與第一個元素呼喚位置,再從第二個元素開始遍歷找到最小與第二個元素呼喚位置...  
  2. void SelectSort(int a[],int n)  
  3. {  
  4.     for(int i=0;i<n-1;i++)  
  5.     {  
  6.         int k=i;//記錄最小的那個下標的  
  7.         for(int j=i+1;j<n;j++)  
  8.             if(a[j]<a[k])  
  9.                 k=j;  
  10.         if(k!=i)  
  11.         {  
  12.             int t=a[i];  
  13.             a[i]=a[k];  
  14.             a[k]=t;  
  15.         }  
  16.   
  17.     }  
  18. }  
  19. int main()  
  20. {  
  21.     int n;  
  22.     cin>>n;  
  23.     int *a=new int[n];  
  24.     for(int j=0;j<n;j++)  
  25.         cin>>a[j];  
  26.     SelectSort(a,n);  
  27.     for(int i=0;i<n;i++)  
  28.         cout<<a[i];  
  29.     delete []a;  
  30. }  

 

 簡單選擇排序的改進——二元選擇排序(有bug)

參考另外一篇:http://blog.csdn.net/ye_scofield/article/details/39312717

簡單選擇排序,每趟循環只能肯定一個元素排序後的定位。咱們能夠考慮改進爲每趟循環肯定兩個元素(當前趟最大和最小記錄)的位置,從而減小排序所需的循環次數。改進後對n個數據進行排序,最多隻需進行[n/2]趟循環便可。具體實現以下:

  1. void SelectSort(int r[],int n) {  
  2.     int i ,j , min ,max, tmp;  
  3.     for (i=1 ;i <= n/2;i++) {    
  4.         // 作不超過n/2趟選擇排序   
  5.         min = i; max = i ; //分別記錄最大和最小關鍵字記錄位置  
  6.         for (j= i+1; j<= n-i; j++) {  
  7.             if (r[j] > r[max]) {   
  8.                 max = j ; continue ;   
  9.             }    
  10.             if (r[j]< r[min]) {   
  11.                 min = j ;   
  12.             }     
  13.       }    
  14.       //該交換操做還可分狀況討論以提升效率  
  15.       tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;  
  16.       tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;   
  17.   
  18.     }   
  19. }  

5. 選擇排序—堆排序(Heap Sort)

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

基本思想:

堆的定義以下:具備n個元素的序列(k1,k2,...,kn),當且僅當知足


時稱之爲堆。由堆的定義能夠看出,堆頂元素(即第一個元素)必爲最小項(小頂堆)。
若以一維數組存儲一個堆,則堆對應一棵徹底二叉樹,且全部非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:

(a)大頂堆序列:(96, 83,27,38,11,09)

  (b)  小頂堆序列:(12,36,24,85,47,30,53,91)

 

初始時把要排序的n個數的序列看做是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成爲一個堆,將堆頂元素輸出,獲得n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。而後對前面(n-1)個元素從新調整使之成爲堆,輸出堆頂元素,獲得n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們做交換,最後獲得有n個節點的有序序列。稱這個過程爲堆排序。

所以,實現堆排序需解決兩個問題:
1. 如何將n 個待排序的數建成堆;
2. 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成爲一個新堆。


首先討論第二個問題:輸出堆頂元素後,對剩餘n-1元素從新建成堆的調整過程。
調整小頂堆的方法:

1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其緣由僅是根結點不知足堆的性質。

2)將根結點與左、右子樹中較小元素的進行交換。

3)若與左子樹交換:若是左子樹堆被破壞,即左子樹的根結點不知足堆的性質,則重複方法 (2).

4)若與右子樹交換,若是右子樹堆被破壞,即右子樹的根結點不知足堆的性質。則重複方法 (2).

5)繼續對不知足堆性質的子樹進行上述交換操做,直到葉子結點,堆被建成。

稱這個自根結點到葉子結點的調整過程爲篩選。如圖:


再討論對n 個元素初始建堆的過程。
建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。

1)n 個結點的徹底二叉樹,則最後一個結點是第個結點的子樹。

2)篩選從第個結點爲根的子樹開始,該子樹成爲堆。

3)以後向前依次對各結點爲根的子樹進行篩選,使之成爲堆,直到根結點。

如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)
                              


                              

 

 算法的實現:

從算法描述來看,堆排序須要兩個過程,一是創建堆,二是堆頂與堆的最後一個元素交換位置。因此堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。

 

[cpp]  view plain  copy
 
  1. //堆排序:樹形選擇排序,將帶排序記錄當作完整的二叉樹,第一步:創建初堆,第二步:調整堆  
  2. //第二步:調整堆  
  3. void HeapAdjust(int a[],int s,int n)  
  4. {  
  5.     //調整爲小根堆,從小到大  
  6.     int rc=a[s];  
  7.     for(int j=2*s;j<=n;j*=2)  
  8.     {  
  9.         if(j<n && a[j]>a[j+1])//判斷左右子數大小  
  10.             j++;  
  11.         if(rc<=a[j])  
  12.             break;  
  13.         a[s]=a[j];  
  14.         s=j;  
  15.     }  
  16.     a[s]=rc;  
  17. }  
  18. //第一步:建初堆  
  19. void CreatHeap(int a[],int n)  
  20. {  
  21.     //小根堆  
  22.     for(int i=n/2;i>0;i--)  
  23.         HeapAdjust(a,i,n);  
  24. }  
  25. //整合  
  26. void HeapSort(int a[],int n)  
  27. {  
  28.     CreatHeap(a,n);//第一步,創建初堆  
  29.     for(int i=n;i>1;i--)  
  30.     {  
  31.         int x=a[1];//堆頂與最後一個元素互換  
  32.         a[1]=a[i];  
  33.         a[i]=x;  
  34.         HeapAdjust(a,1,i-1);  
  35.     }  
  36.   
  37. }  
  38. int main()  
  39. {  
  40.     int n;  
  41.     cin>>n;  
  42.     int *a=new int[n+1];  
  43.     for(int j=1;j<n;j++)//注意:這裏是從1開始的  
  44.         cin>>a[j];  
  45.     HeapSort(a,n);  
  46.     for(int i=1;i<n;i++)  
  47.         cout<<a[i];  
  48.     delete []a;  
  49. }  

 

分析:

設樹深度爲k,。從根到葉的篩選,元素比較次數至多2(k-1)次,交換記錄至多k 次。因此,在建好堆後,排序過程當中的篩選次數不超過下式: 

                                

而建堆時的比較次數不超過4n 次,所以堆排序最壞狀況下,時間複雜度也爲:O(nlogn )。

 

6. 交換排序—冒泡排序(Bubble Sort)

基本思想:

在要排序的一組數中,對當前還未排好序的範圍內的所有數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。

冒泡排序的示例:

 

算法的實現:

 

[cpp]  view plain  copy
 
  1. //傳統冒泡排序  
  2. void maopao(int a[],int n)  
  3. {  
  4.     for(int i=0;i<n-1;i++)  
  5.         for(int j=0;j<n-i-1;j++)  
  6.             if(a[j]>a[j+1])  
  7.             {  
  8.                 int t=a[j];  
  9.                 a[j]=a[j+1];  
  10.                 a[j+1]=t;  
  11.             }  
  12. }  
  13. int main()  
  14. {  
  15.     int n;  
  16.     cin>>n;  
  17.     int *a=new int[n];  
  18.     for(int j=0;j<n;j++)  
  19.         cin>>a[j];  
  20.     maopao(a,n);  
  21.     for(int i=0;i<n;i++)  
  22.         cout<<a[i];  
  23.     delete []a;  
  24. }  

 

冒泡排序算法的改進

對冒泡排序常見的改進方法是加入一標誌性變量exchange,用於標誌某一趟排序過程當中是否有數據交換,若是進行某一趟排序時並無進行數據交換,則說明數據已經按要求排列好,可當即結束排序,避免沒必要要的比較過程。本文再提供如下兩種改進算法:

1.設置一標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置。因爲pos位置以後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置便可。

改進後算法以下:

 

[cpp]  view plain  copy
 
  1. //冒泡排序改進1,添加標誌位,若是某一次排序中出現沒有交換位置,說明排序完成  
  2. void maopao(int a[],int n)  
  3. {  
  4.     int flag=0;  
  5.     for(int i=0;i<n-1;i++)  
  6.     {  
  7.         flag=0;  
  8.         for(int j=0;j<n-i-1;j++)  
  9.             if(a[j]>a[j+1])  
  10.             {  
  11.                 int t=a[j];  
  12.                 a[j]=a[j+1];  
  13.                 a[j+1]=t;  
  14.                 flag=1;  
  15.             }  
  16.         if(flag==0)  
  17.             break;  
  18.     }  
  19. }  
  20. int main()  
  21. {  
  22.     int n;  
  23.     cin>>n;  
  24.     int *a=new int[n];  
  25.     for(int j=0;j<n;j++)  
  26.         cin>>a[j];  
  27.     maopao(a,n);  
  28.     for(int i=0;i<n;i++)  
  29.         cout<<a[i];  
  30.     delete []a;  
  31. }  

 

2.改進後的算法實現爲:

 

[cpp]  view plain  copy
 
  1. //冒泡排序改進2,添加標誌位,記錄最後一次交換位置的地方,證實最後一次交換位置以後的地方時排好序的,下一次只須要排最後一次以前的地方就行了  
  2. void maopao(int a[],int n)  
  3. {  
  4.     int flag=n-1;//剛開始,最後交換位置的地方設置爲數組的最後一位  
  5.     while(flag>0)//flag在逐漸減少,到最後確定會變爲0  
  6.     {  
  7.         int pos=0;//每一輪的最開始,標誌位置在數組0  
  8.         for(int i=0;i<flag;i++)  
  9.             if(a[i]>a[i+1])  
  10.             {  
  11.                 int t=a[i];  
  12.                 a[i]=a[i+1];  
  13.                 a[i+1]=t;  
  14.                 pos=i;  
  15.             }  
  16.         flag=pos;     
  17.     }  
  18. }  
  19. int main()  
  20. {  
  21.     int n;  
  22.     cin>>n;  
  23.     int *a=new int[n];  
  24.     for(int j=0;j<n;j++)  
  25.         cin>>a[j];  
  26.     maopao(a,n);  
  27.     for(int i=0;i<n;i++)  
  28.         cout<<a[i];  
  29.     delete []a;  
  30. }  

 

3.傳統冒泡排序中每一趟排序操做只能找到一個最大值或最小值,咱們考慮利用在每趟排序中進行正向和反向兩遍冒泡的方法一次能夠獲得兩個最終值(最大者和最小者) , 從而使排序趟數幾乎減小了一半。

改進後的算法實現爲:

[cpp]  view plain  copy
 
  1. //冒泡改進3,傳統冒泡每趟排序遍歷一次找到一個最大值或者最小值,若是每趟遍歷兩次就會找打一個最大值和一個最小值,減小了一半的排序趟數  
  2. void maopao ( int r[], int n){    
  3.     int low = 0;     
  4.     int high= n -1; //設置變量的初始值    
  5.     int tmp,j;    
  6.     while (low < high) {    
  7.         for (j= low; j< high; ++j) //正向冒泡,找到最大者    
  8.             if (r[j]> r[j+1]) {    
  9.                 tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;    
  10.             }     
  11.         --high;                 //修改high值, 前移一位    
  12.         for ( j=high; j>low; --j) //反向冒泡,找到最小者    
  13.             if (r[j]<r[j-1]) {    
  14.                 tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;    
  15.             }    
  16.         ++low;                  //修改low值,後移一位    
  17.     }     
  18. }   
  19. int main()  
  20. {  
  21.     int n;  
  22.     cin>>n;  
  23.     int *a=new int[n];  
  24.     for(int j=0;j<n;j++)  
  25.         cin>>a[j];  
  26.     maopao(a,n);  
  27.     for(int i=0;i<n;i++)  
  28.         cout<<a[i];  
  29.     delete []a;  
  30. }  

7. 交換排序—快速排序(Quick Sort)

基本思想:

1)選擇一個基準元素,一般選擇第一個元素或者最後一個元素,

2)經過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另外一部分記錄的 元素值比基準值大。

3)此時基準元素在其排好序後的正確位置

4)而後分別對這兩部分記錄用一樣的方法繼續進行排序,直到整個序列有序。

快速排序的示例:

(a)一趟排序的過程:

(b)排序的全過程

算法的實現:

 遞歸實現:

 

[cpp]  view plain  copy
 
  1. //快速排序   
  2. //第一個參數要排的數組,第二個參數第一個數,第三個參數數組成員個數  
  3. void kuaipai(int array[],int low,int hight)  
  4. {  
  5.     int i,j,t,m;  
  6.     if(low<hight)  
  7.     {  
  8.         i=low;  
  9.         j=hight;  
  10.         t=array[low];//第一個數爲軸  
  11.         while(i<j)  
  12.         {  
  13.             while(i<j && array[j]>t)//從右邊找出小於軸的數  
  14.                 j--;  
  15.             if(i<j)//將小於軸的數array[j]放到左邊array[i]的位置  
  16.             {  
  17.                 m=array[i];  
  18.                 array[i]=array[j];  
  19.                 array[j]=m;  
  20.                 i++;  
  21.             }  
  22.             while(i<j && array[i]<=t)//從左邊找出大於軸的數  
  23.                 i++;  
  24.             if(i<j)//將大於軸的數array[i]放在右邊array[j]的位置  
  25.             {  
  26.                 m=array[j];  
  27.                 array[j]=array[i];  
  28.                 array[i]=m;  
  29.                 j--;  
  30.             }     
  31.         }  
  32.           
  33.         array[i]=t;//軸放在中間,如今就有兩個區域了分別是[0 i-1]和[i+1 hight],分別快排  
  34.         kuaipai(array,0,i-1);  
  35.         kuaipai(array,i+1,hight);  
  36.     }  
  37. }  
  38. void PX_kuaipai(int buf[],int size)  
  39. {  
  40.     kuaipai(buf,0,size-1);  
  41. }  
  42. void main()  
  43. {  
  44.     while(1)  
  45.     {  
  46.         int m,i;  
  47.         cin>>m;  
  48.         int *buf=new int[m];  
  49.         for(i=0;i<m;i++)  
  50.             cin>>buf[i];  
  51.         PX_kuaipai(buf,m);  
  52.         for(i=0;i<m;i++)  
  53.             cout<<buf[i];  
  54.         cout<<'\n';  
  55.         delete []buf;  
  56.     }  
  57.       
  58. }  

 

分析:

快速排序是一般被認爲在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化爲冒泡排序。爲改進之,一般以「三者取中法」來選取基準記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼居中的調整爲支點記錄。快速排序是一個不穩定的排序方法。

 
快速排序的改進

在本改進算法中,只對長度大於k的子序列遞歸調用快速排序,讓原序列基本有序,而後再對整個基本有序序列用插入排序算法排序。實踐證實,改進後的算法時間複雜度有所下降,且當k取值爲 8 左右時,改進算法的性能最佳。算法思想以下:

參考:http://blog.csdn.net/hguisu/article/details/7776068

  1. void print(int a[], int n){  
  2.     for(int j= 0; j<n; j++){  
  3.         cout<<a[j] <<"  ";  
  4.     }  
  5.     cout<<endl;  
  6. }  
  7.   
  8. void swap(int *a, int *b)  
  9. {  
  10.     int tmp = *a;  
  11.     *a = *b;  
  12.     *b = tmp;  
  13. }  
  14.   
  15. int partition(int a[], int low, int high)  
  16. {  
  17.     int privotKey = a[low];                 //基準元素  
  18.     while(low < high){                   //從表的兩端交替地向中間掃描  
  19.         while(low < high  && a[high] >= privotKey) --high; //從high 所指位置向前搜索,至多到low+1 位置。將比基準元素小的交換到低端  
  20.         swap(&a[low], &a[high]);  
  21.         while(low < high  && a[low] <= privotKey ) ++low;  
  22.         swap(&a[low], &a[high]);  
  23.     }  
  24.     print(a,10);  
  25.     return low;  
  26. }  
  27.   
  28.   
  29. void qsort_improve(int r[ ],int low,int high, int k){  
  30.     if( high -low > k ) { //長度大於k時遞歸, k爲指定的數  
  31.         int pivot = partition(r, low, high); // 調用的Partition算法保持不變  
  32.         qsort_improve(r, low, pivot - 1,k);  
  33.         qsort_improve(r, pivot + 1, high,k);  
  34.     }   
  35. }   
  36. void quickSort(int r[], int n, int k){  
  37.     qsort_improve(r,0,n,k);//先調用改進算法Qsort使之基本有序  
  38.   
  39.     //再用插入排序對基本有序序列排序  
  40.     for(int i=1; i<=n;i ++){  
  41.         int tmp = r[i];   
  42.         int j=i-1;  
  43.         while(tmp < r[j]){  
  44.             r[j+1]=r[j]; j=j-1;   
  45.         }  
  46.         r[j+1] = tmp;  
  47.     }   
  48.   
  49. }   
  50.   
  51.   
  52.   
  53. int main(){  
  54.     int a[10] = {3,1,5,7,2,4,9,6,10,8};  
  55.     cout<<"初始值:";  
  56.     print(a,10);  
  57.     quickSort(a,9,4);  
  58.     cout<<"結果:";  
  59.     print(a,10);  
  60.   
  61. }  

 

8. 歸併排序(Merge Sort)

 

基本思想:

歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。

歸併排序示例:

 

 

合併方法:

設r[i…n]由兩個有序子表r[i…m]和r[m+1…n]組成,兩個子表長度分別爲n-i +一、n-m。

  1. j=m+1;k=i;i=i; //置兩個子表的起始下標及輔助數組的起始下標
  2. 若i>m 或j>n,轉⑷ //其中一個子表已合併完,比較選取結束
  3. //選取r[i]和r[j]較小的存入輔助數組rf
    若是r[i]<r[j],rf[k]=r[i]; i++; k++; 轉⑵
    不然,rf[k]=r[j]; j++; k++; 轉⑵
  4. //將還沒有處理完的子表中元素存入rf
    若是i<=m,將r[i…m]存入rf[k…n] //前一子表非空
    若是j<=n ,  將r[j…n] 存入rf[k…n] //後一子表非空
  5. 合併結束。

 

[cpp]  view plain  copy
 
  1. //歸併排序  
  2. void copyArray(int source[], int dest[],int len,int first)    
  3. {    
  4.     int i;    
  5.     int j=first;    
  6.     for(i=0;i<len;i++)    
  7.     {    
  8.         dest[j] = source[i];    
  9.         j++;    
  10.     }    
  11.             
  12. }   
  13. //相鄰兩個有序子序列的歸併函數,將a[low...mid]和a[mid+1...high]歸併到T[LOW..high]中   
  14. void merge(int a[],int left,int right)    
  15. {    
  16.     int begin1 = left;    
  17.     int mid = (left+right)/2 ;    
  18.     int begin2 = mid+1;    
  19.     int k=0;    
  20.     int newArrayLen = right-left+1;    
  21.     int *b = (int*)malloc(newArrayLen*sizeof(int));    
  22.     while(begin1<=mid && begin2<=right)    
  23.     {    
  24.         if(a[begin1]<=a[begin2])    
  25.             b[k++] = a[begin1++];    
  26.         else    
  27.             b[k++] = a[begin2++];    
  28.     }    
  29.     while(begin1<=mid)    
  30.         b[k++] = a[begin1++];    
  31.     while(begin2<=right)    
  32.         b[k++] = a[begin2++];    
  33.     copyArray(b,a,newArrayLen,left);    
  34.     free(b);    
  35. }    

 

兩路歸併的遞歸算法

 

[cpp]  view plain  copy
 
  1. //歸併函數,將a[low...high]歸併到T[low...high]中  
  2. void mergeSort(int a[],int left,int right)    
  3. {    
  4.     int i;    
  5.     // 保證至少有兩個元素    
  6.     if(left < right)    
  7.     {    
  8.         i = (left+right)/2;    
  9.         mergeSort(a,left,i);    
  10.         mergeSort(a,i+1,right);    
  11.         merge(a,left,right);    
  12.     }    
  13. }   
  14. void MergeSort(int a[],int n)  
  15. {  
  16.     mergeSort(a,0,n-1);  
  17. }  

完整程序

 

 

[cpp]  view plain  copy
 
  1. //歸併排序  
  2. void copyArray(int source[], int dest[],int len,int first)    
  3. {    
  4.     int i;    
  5.     int j=first;    
  6.     for(i=0;i<len;i++)    
  7.     {    
  8.         dest[j] = source[i];    
  9.         j++;    
  10.     }    
  11.             
  12. }   
  13. //相鄰兩個有序子序列的歸併函數,將a[low...mid]和a[mid+1...high]歸併到T[LOW..high]中   
  14. void merge(int a[],int left,int right)    
  15. {    
  16.     int begin1 = left;    
  17.     int mid = (left+right)/2 ;    
  18.     int begin2 = mid+1;    
  19.     int k=0;    
  20.     int newArrayLen = right-left+1;    
  21.     int *b = (int*)malloc(newArrayLen*sizeof(int));    
  22.     while(begin1<=mid && begin2<=right)    
  23.     {    
  24.         if(a[begin1]<=a[begin2])    
  25.             b[k++] = a[begin1++];    
  26.         else    
  27.             b[k++] = a[begin2++];    
  28.     }    
  29.     while(begin1<=mid)    
  30.         b[k++] = a[begin1++];    
  31.     while(begin2<=right)    
  32.         b[k++] = a[begin2++];    
  33.     copyArray(b,a,newArrayLen,left);    
  34.     free(b);    
  35. }    
  36. //歸併函數,將a[low...high]歸併到T[low...high]中  
  37. void mergeSort(int a[],int left,int right)    
  38. {    
  39.     int i;    
  40.     // 保證至少有兩個元素    
  41.     if(left < right)    
  42.     {    
  43.         i = (left+right)/2;    
  44.         mergeSort(a,left,i);    
  45.         mergeSort(a,i+1,right);    
  46.         merge(a,left,right);    
  47.     }    
  48. }   
  49. void MergeSort(int a[],int n)  
  50. {  
  51.     mergeSort(a,0,n-1);  
  52. }  
  53. int main()  
  54. {  
  55.     int n;  
  56.     cin>>n;  
  57.     int *a=new int[n];  
  58.       
  59.     for(int j=0;j<n;j++)  
  60.         cin>>a[j];  
  61.     MergeSort(a,n);  
  62.     for(int i=0;i<n;i++)  
  63.         cout<<a[i];  
  64.     delete []a;  
  65.       
  66. }  


 

9. 桶排序/基數排序(Radix Sort)

說基數排序以前,咱們先說桶排序:

基本思想:是將陣列分到有限數量的桶子裏。每一個桶子再個別排序(有可能再使用別的排序算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種概括結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並非 比較排序,他不受到 O(n log n) 下限的影響。
         簡單來講,就是把數據分組,放在一個個的桶中,而後對每一個桶裏面的在進行排序。  

 

 例如要對大小爲[1..1000]範圍內的n個整數A[1..n]排序  

 首先,能夠把桶設爲大小爲10的範圍,具體而言,設集合B[1]存儲[1..10]的整數,集合B[2]存儲   (10..20]的整數,……集合B[i]存儲(   (i-1)*10,   i*10]的整數,i   =   1,2,..100。總共有  100個桶。  

  而後,對A[1..n]從頭至尾掃描一遍,把每一個A[i]放入對應的桶B[j]中。  再對這100個桶中每一個桶裏的數字排序,這時可用冒泡,選擇,乃至快排,通常來講任  何排序法均可以。

  最後,依次輸出每一個桶裏面的數字,且每一個桶中的數字從小到大輸出,這  樣就獲得全部數字排好序的一個序列了。  

  假設有n個數字,有m個桶,若是數字是平均分佈的,則每一個桶裏面平均有n/m個數字。若是  

  對每一個桶中的數字採用快速排序,那麼整個算法的複雜度是  

  O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   -   nlogm)  

  從上式看出,當m接近n的時候,桶排序複雜度接近O(n)  

  固然,以上覆雜度的計算是基於輸入的n個數字是平均分佈這個假設的。這個假設是很強的  ,實際應用中效果並無這麼好。若是全部的數字都落在同一個桶中,那就退化成通常的排序了。  

        前面說的幾大排序算法 ,大部分時間複雜度都是O(n2),也有部分排序算法時間複雜度是O(nlogn)。而桶式排序卻能實現O(n)的時間複雜度。但桶排序的缺點是:

        1)首先是空間複雜度比較高,須要的額外開銷大。排序有兩個數組的空間開銷,一個存放待排序數組,一個就是所謂的桶,好比待排序值是從0到m-1,那就須要m個桶,這個桶數組就要至少m個空間。

        2)其次待排序的元素都要在必定的範圍內等等。

       桶式排序是一種分配排序。分配排序的特定是不須要進行關鍵碼的比較,但前提是要知道待排序列的一些具體狀況。

 

分配排序的基本思想:說白了就是進行屢次的桶式排序。

基數排序過程無須比較關鍵字,而是經過「分配」和「收集」過程來實現排序。它們的時間複雜度可達到線性階:O(n)。

實例:

撲克牌中52 張牌,可按花色和麪值分紅兩個字段,其大小關係爲:
花色: 梅花< 方塊< 紅心< 黑心  
面值: 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A

若對撲克牌按花色、面值進行升序排序,獲得以下序列:


即兩張牌,若花色不一樣,不論面值怎樣,花色低的那張牌小於花色高的,只有在同花色狀況下,大小關係才由面值的大小肯定。這就是多關鍵碼排序。

爲獲得排序結果,咱們討論兩種排序方法。
方法1:先對花色排序,將其分爲4 個組,即梅花組、方塊組、紅心組、黑心組。再對每一個組分別按面值進行排序,最後,將4 個組鏈接起來便可。
方法2:先按13 個面值給出13 個編號組(2 號,3 號,...,A 號),將牌按面值依次放入對應的編號組,分紅13 堆。再按花色給出4 個編號組(梅花、方塊、紅心、黑心),將2號組中牌取出分別放入對應花色組,再將3 號組中牌取出分別放入對應花色組,……,這樣,4 個花色組中均按面值有序,而後,將4 個花色組依次鏈接起來便可。

設n 個元素的待排序列包含d 個關鍵碼{k1,k2,…,kd},則稱序列對關鍵碼{k1,k2,…,kd}有序是指:對於序列中任兩個記錄r[i]和r[j](1≤i≤j≤n)都知足下列有序關係:

                                                               

其中k1 稱爲最主位關鍵碼,kd 稱爲最次位關鍵碼     。

 

兩種多關鍵碼排序方法:

多關鍵碼排序按照從最主位關鍵碼到最次位關鍵碼或從最次位到最主位關鍵碼的順序逐次排序,分兩種方法:

最高位優先(Most Significant Digit first)法,簡稱MSD 法:

1)先按k1 排序分組,將序列分紅若干子序列,同一組序列的記錄中,關鍵碼k1 相等。

2)再對各組按k2 排序分紅子組,以後,對後面的關鍵碼繼續這樣的排序分組,直到按最次位關鍵碼kd 對各子組排序後。

3)再將各組鏈接起來,便獲得一個有序序列。撲克牌按花色、面值排序中介紹的方法一便是MSD 法。

最低位優先(Least Significant Digit first)法,簡稱LSD 法:

1) 先從kd 開始排序,再對kd-1進行排序,依次重複,直到按k1排序分組分紅最小的子序列後。

2) 最後將各個子序列鏈接起來,即可獲得一個有序的序列, 撲克牌按花色、面值排序中介紹的方法二便是LSD 法。

 

基於LSD方法的鏈式基數排序的基本思想

  「多關鍵字排序」的思想實現「單關鍵字排序」。對數字型或字符型的單關鍵字,能夠看做由多個數位或多個字符構成的多關鍵字,此時能夠採用「分配-收集」的方法進行排序,這一過程稱做基數排序法,其中每一個數字或字符可能的取值個數稱爲基數。好比,撲克牌的花色基數爲4,面值基數爲13。在整理撲克牌時,既能夠先按花色整理,也能夠先按面值整理。按花色整理時,先按紅、黑、方、花的順序分紅4摞(分配),再按此順序再疊放在一塊兒(收集),而後按面值的順序分紅13摞(分配),再按此順序疊放在一塊兒(收集),如此進行二次分配和收集便可將撲克牌排列有序。   

基數排序:

是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,因此是穩定的。

算法實現:

  1. Void RadixSort(Node L[],length,maxradix)  
  2. {  
  3.    int m,n,k,lsp;  
  4.    k=1;m=1;  
  5.    int temp[10][length-1];  
  6.    Empty(temp); //清空臨時空間  
  7.    while(k<maxradix) //遍歷全部關鍵字  
  8.    {  
  9.      for(int i=0;i<length;i++) //分配過程  
  10.     {  
  11.        if(L[i]<m)  
  12.           Temp[0][n]=L[i];  
  13.        else  
  14.           Lsp=(L[i]/m)%10; //肯定關鍵字  
  15.        Temp[lsp][n]=L[i];  
  16.        n++;  
  17.    }  
  18.    CollectElement(L,Temp); //收集  
  19.    n=0;  
  20.    m=m*10;  
  21.   k++;  
  22.  }  
  23. }  


 

 

 

 

總結

各類排序的穩定性,時間複雜度和空間複雜度總結:

 咱們比較時間複雜度函數的狀況:

 

                             時間複雜度函數O(n)的增加狀況

因此對n較大的排序記錄。通常的選擇都是時間複雜度爲O(nlog2n)的排序方法。

 

時間複雜度來講:

(1)平方階(O(n2))排序
  各種簡單排序:直接插入、直接選擇和冒泡排序;
 (2)線性對數階(O(nlog2n))排序
  快速排序、堆排序和歸併排序;
 (3)O(n1+§))排序,§是介於0和1之間的常數。

       希爾排序
(4)線性階(O(n))排序
  基數排序,此外還有桶、箱排序。

說明:

當原表有序或基本有序時,直接插入排序和冒泡排序將大大減小比較次數和移動記錄的次數,時間複雜度可降至O(n);

而快速排序則相反,當原表基本有序時,將蛻化爲冒泡排序,時間複雜度提升爲O(n2);

原表是否有序,對簡單選擇排序、堆排序、歸併排序和基數排序的時間複雜度影響不大。

 

穩定性:

排序算法的穩定性:若待排序的序列中,存在多個具備相同關鍵字的記錄,通過排序, 這些記錄的相對次序保持不變,則稱該算法是穩定的;若經排序後,記錄的相對 次序發生了改變,則稱該算法是不穩定的。 
     穩定性的好處:排序算法若是是穩定的,那麼從一個鍵上排序,而後再從另外一個鍵上排序,第一個鍵排序的結果能夠爲第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,若是排序算法穩定,能夠避免多餘的比較;

穩定的排序算法:冒泡排序、插入排序、歸併排序和基數排序

不是穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序

 

選擇排序算法準則:

每種排序算法都各有優缺點。所以,在實用時需根據不一樣狀況適當選用,甚至能夠將多種方法結合起來使用。

選擇排序算法的依據

影響排序的因素有不少,平均時間複雜度低的算法並不必定就是最優的。相反,有時平均時間複雜度高的算法可能更適合某些特殊狀況。同時,選擇算法時還得考慮它的可讀性,以利於軟件的維護。通常而言,須要考慮的因素有如下四點:

1.待排序的記錄數目n的大小;

2.記錄自己數據量的大小,也就是記錄中除關鍵字外的其餘信息量的大小;

3.關鍵字的結構及其分佈狀況;

4.對排序穩定性的要求。

設待排序元素的個數爲n.

1)當n較大,則應採用時間複雜度爲O(nlog2n)的排序方法:快速排序、堆排序或歸併排序序。

   快速排序:是目前基於比較的內部排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;
       堆排序 :  若是內存空間容許且要求穩定性的,

       歸併排序:它有必定數量的數據移動,因此咱們可能過與插入排序組合,先得到必定長度的序列,而後再合併,在效率上將有所提升。

2)  當n較大,內存空間容許,且要求穩定性 =》歸併排序

3)當n較小,可採用直接插入或直接選擇排序。

    直接插入排序:當元素分佈有序,直接插入排序將大大減小比較次數和移動記錄的次數。

    直接選擇排序 :元素分佈有序,若是不要求穩定性,選擇直接選擇排序

5)通常不使用或不直接使用傳統的冒泡排序。

6)基數排序
它是一種穩定的排序算法,但有必定的侷限性:
  一、關鍵字可分解。
  二、記錄的關鍵字位數較少,若是密集更好
  三、若是是數字時,最好是無符號的,不然將增長相應的映射覆雜度,可先將其正負分開排序。

相關文章
相關標籤/搜索