1. 程式人生 > >理解快速排序和歸併排序

理解快速排序和歸併排序

從本人很久以前的部落格上轉過來的。原創哈!

1,快速排序

快速排序通過分割值列,然後遞迴的對兩個部分進行排序,從而實現對值列的排序。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序

首先任意選取一個數據(通常選用第一個資料)作為關鍵資料,然後將所有比它小的數都放到它前面,所有比它大的數都放到它後面,這個過程稱為一趟快速排序


怎麼實現呢?---

它的關鍵在於完成一趟快排後,基準元素在哪個位置,每次都選取一個分割列的第一個元素作為基準元素,來尋找用它來分割排序列後它自己所處的位置,編寫一個

int  findPartition(data,min,max)方法,用於使用data[min]作為基準元素把data[min]到data[max]分割為兩個部分,並返回分割以後基準元素自己所在的位置索引。


在主函式裡這樣來呼叫:

複製程式碼 publicstaticvoid quickSort(Comparable[] data,int min,int max) {

int mid;
if(min < max)
{
mid
= findPartition(data,min,max);
quickSort(data,min,mid
-1);
quickSort(data,mid
+1,max);
}

}
複製程式碼



理解遞迴過程:::if(min < max),實際上就是要將值列分割為單一的元素(遞迴的最深一層),在得到基準元素的位置min後,基準元素在陣列中的位置就最終確定,剩下只對左右兩側的分割列
排序:quickSort(data,min,mid-1); quickSort(data,mid+1,max);

下面來看最重要的實現部分,如何實現findPartition函式:

將第一個元素作為基準元素不要動,設兩個指標left和right,left從左往右移動尋找比基準元素大的數,right從右往左移動尋找比基準元素小的數,當它們都找到對應的left和right的位置時,交換
這兩個元素。重複這個過程,直到right < left.這時,除基準元素外,從min+1到right的元素都比基準元素小,left到max的元素都比基準元素大。形成了這樣一個序列:

基準元素(data[min])

  ....比基準元素小的......   data[right]    |    data[left]  ......比基準元素大的  

交換基準元素和data[right]即可得到  以基準元素為分割線的 左右兩個分割列。

這裡我把  排序遞迴的呼叫  與   分割值列和尋找分割位置(核心部分) 分開寫成兩個方法,為了顯得邏輯更加清楚。


完整程式碼   :

複製程式碼 //快排:快排是一個遞迴的過程!!!!publicstaticvoid quickSort(Comparable[] data,int min,int max) {

int mid;
if(min < max)
{
mid
= findPartition(data,min,max);
quickSort(data,min,mid
-1);
quickSort(data,mid
+1,max);
}

}

//快排的支援方法publicstaticint findPartition(Comparable[] data,int min,int max){
int left,right;
Comparable temp,partitionelement;

left
= min;right = max;
partitionelement
= data[min];//partitionelement 是data[min]的一個副本while(left < right)
{
while(data[left].compareTo(partitionelement) <=0&& left < right)
left
++;
while(data[right].compareTo(partitionelement) >0)
right
--;
if(left < right)
{
temp
= data[left];
data[left]
= data[right];
data[right]
=temp;
}
}

temp
= data[min];
data[min]
= data[right];
data[right]
= temp;

//錯錯誤的寫法,要的是把第一個元素和data[right]交換,而不是它的副本
//temp = data[right];
//data[right] = partitionelement;
//partitionelement = temp;return right;
}
複製程式碼



2,歸併排序


歸併排序也是一個遞迴的實現,其關鍵在於合併兩個有序集:

void merge(Comparable[] data,int min,int mid,int max)

陣列data從min到mid有序,從mid+1到max有序,合併這兩部分,使得從min到max有序,且這個有序序列仍然放在data的從min到max下標裡

這樣來遞迴呼叫:


複製程式碼 //歸併排序:歸併是一個遞迴的過程,其關鍵部分在於合併2個有序集publicstaticvoid mergSort(Comparable[] data,int min,int max){
int mid = (min + max)/2;
//遞迴的過程if(max > min){
mergSort(data,min,mid);
mergSort(data,mid
+1,max);
merge(data,min,mid,max);
}
}

複製程式碼

理解遞迴的過程:每次merg一次,在data原來的那一段位置上把data排好序了,不影響其他位置

下面來實現關鍵部分,在data裡合併min到mid,mid+1到max兩個有序集,合併後位置仍放到min到max上,藉助於申請一個與data大小相等的空間!!!

這個地方開始沒有想清楚:應該是Comparable[] temp = new Comparable[max+1];

而不是Comparable[] temp = new Comparable[max - min + 1];想一想為什麼?

每遞迴一次就會呼叫一次merge,實際上temp每次都會重新分配一下,利用temp把data從min到max排好序,並放在data的這一段位置上


完整程式碼:

複製程式碼 //歸併排序:歸併是一個遞迴的過程,其關鍵部分在於合併2個有序集publicstaticvoid mergSort(Comparable[] data,int min,int max){
int mid = (min + max)/2;
//遞迴的過程if(max > min){
mergSort(data,min,mid);
mergSort(data,mid
+1,max);
merge(data,min,mid,max);
}
}

//支援方法:合併2個有序集,藉助於申請一個與兩個有序集大小之和相等的空間。publicstaticvoid merge(Comparable[] data,int min,int mid,int max){
Comparable[] temp
=new Comparable[max+1];
//Comparable[] temp = new Comparable[max - min + 1];錯誤!!!int begin1 = min;
int begin2 = mid +1;
int index = min;//注意index是從min開始的!!!while(begin1 <= mid && begin2 <= max){
if(data[begin1].compareTo(data[begin2]) <0)
temp[index
++] = data[begin1++];
else
temp[index
++] = data[begin2++];
}

if(begin1 > mid)
for(int i = begin2;i <= max;i++)
temp[index
++] = data[i];
if(begin2 > max)
for(int i = begin1;i <= mid;i++)
temp[index
++] = data[i];

for(int i = min;i <= max;i++)//對應的位置!!!! data[i] = temp[i];
}

}
複製程式碼

這個程式除錯了半天才好,主要還是沒把遞迴的過程想清楚,data在每次遞迴的過程中不能被覆蓋,它排好了序的部分就不能動了!!!

另外就是temp申請空間這個地方,是max+1,而不是max-min+1的大小,否則會導致陣列越界

剛才程式碼效果亂了,悲劇 ,又重新複製了一遍,耽誤了半天時間。


好了,有空再寫個效能的比較總結。