數據結構與算法之歸併、快速排序
這是排序問題中的兩個重要且基礎的排序算法。之所以拿來一起介紹使用爲他倆有着異曲同工之妙,只要理解了歸併排序,就知道了快排是咋回事的了。
歸併排序就是從局部無序變有序,最後全局無序變有序。基本思想就是先倆倆一組,通過合併排序,保證組內有序後,我們在將相鄰的兩組合並排序(將兩組鏈接並排序)成爲新的一組。然後再合併排序,再依次循環,直到一組的長度是整個待排序數組的長度。
傳統的排序算法就是寫兩個嵌套的循環,然後一個個的對比,那麼此時的算法的時間複雜度就是O(nn),當要求排序的數組是非常大的時候,這是不可接受的。而歸併排序的時間複雜度是O(nlogn),顯然是優於傳統的排序算法。
代碼實現:
1,我們先設計出合併排序代碼,即將兩組數組合並的同時進行排序。
const int maxn=100;
void merge(int a[],int l1,int r1,int l2,int r2){
int temp[maxn];
int i=l1;
int j=l2;
int index=i;
while(i<=r1&&j<=r2){
if(a[i]<a[j]){
j++;
temp[i]=a[i];
index+=1;
}
else{
i++;
temp[index]=a[j];
index+=1;
}
}
if(i<=r1){
for(int k=r1;k<=r1;k++){
temp[index]=a[k];
index+=1;
}
}
if(j<=r2){
for(int k=r2;k<=r2;k++){
temp[index]=a[k];
index+=1;
}
}
for(int i=l1;i<=r2;i++){
a[i]=temp[i];
}
}
歸併排序的非遞歸實現:
void mergesort(int a[]){
for(int step=1;step<=n/2;step=step*2){
for(int i=0;i<n;i=i+step;){
merge(a,i,i+step-1,i+step,min(n,i+2*step-1))
}
}
}
遞歸實現:
void mergesort(int a[],int left ,int right){
if(left<right){
int mid=(right+left)/2;
mergesort(a,left,mid);
mergesort(a,mid+1,right);
merge(a,left,mid,mid+1,right);//相當於二叉樹的後序遍歷
}
}
注意遞歸代碼如果看不懂的話,可以結合二叉樹的後序遍歷,來畫一下二叉搜索樹模擬一下。
下面我們在介紹一下快速排序。第一步是選擇主元,那啥是主元呢?說白了就是待排序的那個元素。第二步是將主元放在正確的位置。下面結合一個例子:
待排序的數組:5 7 2 3 6 ,要求按增序順序
排序過程:將5作爲主元,然後核心思想就是將大於5的放在5的右邊,小於5的放在5的左邊。操作後就變爲:3 2 5 7 6。
我們發現現在離成功進了一步,因爲元素5的位置已經正確。繼續觀察,元素5兩側的序列仍是無序狀態,所以我們以5爲界限,拆成兩個子序列,然後對兩個子序列分別再進行一次,仍然選擇子序列的第一個元素爲主元。。。。。依次進行,直至子序列的長度爲1。代表排序完成。所以不難算出該算法的時間複雜度爲O(n*logn)。
快速排序代碼如下:
爲啥說有點相似呢?因爲他倆都有二分的一影子,前者是數量二分,後者是位置二分。他倆又是不同的,歸併排序“目光短淺”,他一開始就讓小段小段的有序,直至後來全局有序。而快速排序“目光長遠”,先讓混沌無序的數組變得有一點有序,即有一個元素處於正確的位置,然後整體有序。
另外當數組中的元素越隨機時,快速排序算法的效率越高。特別的,當待排序的數組已經是增序或者減序時,該算法的時間複雜度是O(n*n),該算法完全退化爲一般的排序算法(即相當於兩層for循環)。因爲每次的子序列就是它母序列本身。
以下是快速排序的遞歸實現的代碼:
void qucksort(int a[],int left,int right){
int temp;
a[left]=temp;
int i=left;
int j=right;
int mid;
while(i<j){
while(a[j]>=temp&&i<j){
j--;
}
a[i]=a[j];
while(a[i]<temp&&i<j){
i++;
}
a[j]=a[i];
}
mid=j;
a[j]=temp;
qucksort(a,left,mid-1);
qucksort(a,mid+1,left);
}