1. 程式人生 > >排序演算法之選擇排序(直接選擇、堆排序)

排序演算法之選擇排序(直接選擇、堆排序)

排序演算法穩定性

假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,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