排序演算法之選擇排序(直接選擇、堆排序)
排序演算法穩定性
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序演算法是穩定的;否則稱為不穩定的。 ————百度百科
排序原理
每一趟從待排序序列中找出最小的元素,順序放在已排好序的序列最後,直到全部序列排序完畢。
直接排序原理
假設有個陣列array={裡面有n個數據},第一趟排序從array[1] ~ array[n]中找出比array[0]小的元素,交換順序。第一趟排序從array[2] ~ array[n]中找出比array[1]小的元素,交換順序,以此類推,迴圈直到array[n-1]結束,因為array[n]此時一定是最大的。
堆排序原理
根據堆的性質,根節點一定是最小(或最大)的元素,通過不斷刪除最小元(或最大元)來維持一個有序陣列即可達到排序要求。由於需要使用一個數組來儲存每次刪除的最小元(或最大元),因此,儲存需求增加了一倍,迴避使用第二個陣列的方法可以利用堆的結構性。每次deleteMin之後,堆縮小1,因此,位於堆中最後的單元可以用來存放剛剛刪去的元素。
若是使用最小堆(即根節點是最小元),則最後的這個陣列是以遞減的順序存放元素。如果我們想要這些元素排成更典型的遞增順序,那麼可以使用最大堆來實現,下面的堆排序演算法就是利用最大堆來實現排序。
直接選擇排序
從以上的這個栗子中我們可以更好的理解直接選擇排序,並且需要注意的是,在第二趟排序過程中,第一個5的順序被交換到了第二個5的後面,因此也可以看出直接選擇排序是不穩定的排序演算法。
排序過程 巨集觀過程
程式碼如下:
public void selectSort(T[] a){
int k; //記錄目前找到的最小值的下標
T tmp;
for(int i=0;i<a.length;i++){
k = i;
for(int j=k+1;j<a.length;j++){
if(a[j].compareTo(a[k])<0){
k = j;
}
}
//內層迴圈結束,如果在本次迴圈找到更小的數,則交換
if(i != k){
tmp = a[i];
a[i] = a[k];
a[k] = tmp;
}
}
}
時間複雜度:O(N²)
空間複雜度:O(1)
堆排序
上圖展示的在構建完堆序之後的最大堆以及在進行一次deleteMax操作之後堆。我們知道堆可以用一個數組來實現,並且也看出當刪除最大元后我們將該最大元置於陣列剛剛元素31的位置上,再經過5次deleteMax操作之後,該堆實際上只有一個元素,而堆陣列中留下的元素將是排序後的順序。
上程式碼:
/**
* 構建堆
*/
private <T extends Comparable<? super T>> T[] buildHeap(T[] array, int i,int n){
T tmp;
int child;
for(tmp = array[i];leftChild(i) < n;i = child){
child = leftChild(i);
if(child != n-1 && array[child].compareTo(array[child+1])<0){
child++;
}
if(tmp.compareTo(array[child])<0){
array[i] = array[child];
} else {
break;
}
}
array[i] = tmp;
return array;
}
/**
* 刪除最大元
*/
public <T extends Comparable<? super T>> T[] deleteMax(T[] array, int i){
T item = array[0];
array[0] = array[i];
array[i] = item;
return buildHeap(array, 0, i);
}
private int leftChild(int i){
return i * 2 + 1;
}
/**
* 堆排序
* 傳入陣列,從最後一個葉子節點的父節點處開始構建最大堆
* 最大堆序性質:任一節點的父節點大於等於該節點
* 刪除最大元后,應該重新保持堆序性質
*/
public <T extends Comparable<? super T>> void heapSort(T[] array){
for(int i=array.length/2 - 1;i>=0;i--){
array = buildHeap(array, i, array.length);
}
for(int i=array.length-1;i>=0;i--){
array = deleteMax(array, i);
}
}
上面的堆排序演算法中,我們需要在一開始將陣列構建成一個最大堆的形式,方式則是從最後一個葉子節點的父節點開始構建堆,直到根節點構建完畢。之後進行刪除最大元操作即可。
時間複雜度:O(NlogN)
空間複雜度:O(1)
總結
直接選擇排序和堆排序都是不穩定的排序演算法。但是在《資料結構與演算法分析 for Java》第三版的P193頁中寫到,堆排序是一個非常穩定的演算法,這點我百度了許多堆排序,都說堆排序是不穩定的演算法,這裡大家可以自行驗證堆排序的穩定性,哈哈哈。
更多瞭解,還請關注我的個人部落格:www.zhyocean.cn