算法:用Java實現堆排序(HeapSort)

本文我準備用Java實現堆排序。其實我以前在用二叉大頂堆實現優先隊列的時候,就已經順便實現了堆排序,今天把其中堆排序的代碼提取出來,專門作爲排序的一篇博文,並附上以前用二叉大頂堆實現的優先隊列,以及順便實現堆排序的博文地址:點我查看。具體的排序算法過程已經在註釋裏面了,大家可以複製代碼到IDE裏面,用DEBUG模式研究算法的過程:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * @author LiYang
 * @ClassName HeapSort
 * @Description 堆排序算法
 * @date 2019/11/5 16:04
 */
public class HeapSort {

    //本例的堆排序,用優先隊列來實現
    //下面是二叉大頂堆的底層數據存儲
    private List<Integer> elementData;

    /**
     * 堆排序的帶初始值的構造方法
     * 也就是先拿待排序的數組的所有數據先初始化一個二叉大頂堆,
     * 然後在這個大頂堆的基礎上再進行一系列poll操作,創建有序序列
     * @param initialArray 初始數據
     */
    public HeapSort(int[] initialArray){
        //調用初始化大頂堆的方法
        elementData = initialPriorityHeap(initialArray);
    }

    /**
     * 將已經存在的亂序數組,初始化爲大頂堆elementData,用作構造方法
     * 也就是說,效果就像亂序數組都add到了優先隊列那樣
     * 如果是泛型,則接受ArrayList<T>,然後用compare方法實現
     * @param arr 剛開始存在的數據,亂序的
     * @return 已經放入arr數組的數字的優先隊列
     */
    private List<Integer> initialPriorityHeap(int[] arr){
        //先將數組轉化爲ArrayList,方便後續操作
        List<Integer> elementData = new ArrayList(arr.length);

        //遍歷加入
        for (int i = 0; i < arr.length; i++) {
            elementData.add(arr[i]);
        }

        //如果只有一個元素,甚至沒有元素,就直接返回
        if (elementData.size() <= 1){
            return elementData;
        }

        //我們從最後一個元素的父元素開始,一個個嘗試往下滲透
        int maxParentIndex = elementData.size() / 2;

        //從最後一個元素的父元素開始,開始逐一往下滲透
        for (int index = maxParentIndex; index > 0; index--){
            //取到父元素的值
            int parentValue = elementData.get(index - 1);

            //取到父元素的下標
            int parentIndex = index;

            //逐漸往下滲透,直到不能再下去了爲止
            while (parentIndex <= elementData.size() / 2){
                //找到下標較小的子元素的下標
                int smallerIndexSon = parentIndex * 2;

                //找到下標較大的子元素的下標
                int biggerIndexSon = smallerIndexSon + 1;

                //如果下標較大的子元素不存在
                if (biggerIndexSon > elementData.size()){

                    //就只跟下標較小的子元素相比
                    //如果移動上去的末尾元素比子元素小,就交換
                    if (parentValue < elementData.get(smallerIndexSon - 1)){
                        
                        //二者交換
                        int temp = elementData.get(smallerIndexSon - 1);
                        elementData.set(smallerIndexSon - 1, elementData.get(parentIndex - 1));
                        elementData.set(parentIndex - 1, temp);

                        //更新下標爲下標較小的子元素的下標
                        index = smallerIndexSon;

                    //否則,結束往下滲透
                    } else {
                        break;
                    }

                //如果下標較大的子元素存在
                } else {
                    //如果較大下標子元素比較小下標子元素大
                    if (elementData.get(biggerIndexSon - 1) > elementData.get(smallerIndexSon - 1)){

                        //嘗試與較大下標子元素交換
                        if (elementData.get(biggerIndexSon - 1) > parentValue){
                            int temp = elementData.get(biggerIndexSon - 1);
                            elementData.set(biggerIndexSon - 1, elementData.get(parentIndex - 1));
                            elementData.set(parentIndex - 1, temp);

                            //更新下標爲下標較大的子元素的下標
                            parentIndex = biggerIndexSon;

                        //如果子元素不小於較大下標元素,則停止向下滲透
                        } else {
                            break;
                        }

                    //如果較大下標元素不比較小下表元素大
                    } else {

                        //嘗試與較小下標子元素交換
                        if (elementData.get(smallerIndexSon - 1) > parentValue){
                            int temp = elementData.get(smallerIndexSon - 1);
                            elementData.set(smallerIndexSon - 1, elementData.get(parentIndex - 1));
                            elementData.set(parentIndex - 1, temp);

                            //更新下標爲下標較小的子元素的下標
                            parentIndex = smallerIndexSon;

                        //如果子元素不小於較大下標元素,則停止向下滲透
                        } else {
                            break;
                        }
                    }
                }
            }
        }

        //最後返回建好的大頂堆elementData
        return elementData;
    }
    
    /**
     * 從優先隊列裏面彈出優先級最高的元素,用作排序
     * 注意,每次poll了之後,剩下的elementData的第一個是之前第二大的
     * @return 返回優先級最高的元素,或者null
     */
    public Integer poll(){
        //如果沒有元素了,返回空
        if (elementData.size() == 0){
            return null;
        }

        //如果只有一個元素,直接返回該元素
        if (elementData.size() == 1){
            return elementData.remove(0);
        }

        //先將第一個,也就是優先級最高的元素取出
        int maxPriority = elementData.get(0);

        //將最後一個元素取出來,並移除
        int lastElement = elementData.remove(elementData.size() - 1);

        //最後一個元素,加在隊首,也就是第一個,其他的往後移
        elementData.set(0, lastElement);

        //如果最後一個元素不是最大的,得往下滲透
        int index = 1;

        //如果該元素還有子元素
        while (index <= elementData.size() / 2){
            //找到下標較小的子元素的下標
            int smallerIndexSon = index * 2;

            //找到下標較大的子元素的下標
            int biggerIndexSon = smallerIndexSon + 1;

            //如果下標較大的子元素不存在
            if (biggerIndexSon > elementData.size()){

                //就只跟下標較小的子元素相比
                //如果移動上去的末尾元素比子元素小,就交換
                if (lastElement < elementData.get(smallerIndexSon - 1)){
                    
                    //二者交換
                    int temp = elementData.get(smallerIndexSon - 1);
                    elementData.set(smallerIndexSon - 1, elementData.get(index - 1));
                    elementData.set(index - 1, temp);

                    //更新下標爲下標較小的子元素的下標
                    index = smallerIndexSon;

                //否則,結束往下滲透
                } else {
                    break;
                }

            //如果下標較大的子元素存在
            } else {
                //如果較大下標子元素比較小下標子元素大
                if (elementData.get(biggerIndexSon - 1) > elementData.get(smallerIndexSon - 1)){

                    //嘗試與較大下標子元素交換
                    if (elementData.get(biggerIndexSon - 1) > lastElement){
                        int temp = elementData.get(biggerIndexSon - 1);
                        elementData.set(biggerIndexSon - 1, elementData.get(index - 1));
                        elementData.set(index - 1, temp);

                        //更新下標爲下標較大的子元素的下標
                        index = biggerIndexSon;

                    //如果子元素不小於較大下標元素,則停止向下滲透
                    } else {
                        break;
                    }

                //如果較大下標元素不比較小下表元素大
                } else {

                    //嘗試與較小下標子元素交換
                    if (elementData.get(smallerIndexSon - 1) > lastElement){
                        int temp = elementData.get(smallerIndexSon - 1);
                        elementData.set(smallerIndexSon - 1, elementData.get(index - 1));
                        elementData.set(index - 1, temp);

                        //更新下標爲下標較小的子元素的下標
                        index = smallerIndexSon;

                    //如果子元素不小於較大下標元素,則停止向下滲透
                    } else {
                        break;
                    }
                }
            }
        }

        //最後返回優先級最大的元素
        return maxPriority;
    }

    /**
     * 堆排序(通過二叉大頂堆的優先隊列,實現堆排序)
     * @param arr 排序前的數組
     * @param increase 是否升序排序
     * @return 返回排序後的數組
     */
    public static int[] heapSort(int[] arr, boolean increase){
        //先用排序前的數組,構建一個最大堆
        HeapSort heapSort = new HeapSort(arr);

        //聲明一個空數組,用於裝排好序的數組
        int[] sortedArray = new int[arr.length];

        /*
         * 通過不斷地poll,生成從大到小有序的序列,並放到上面的數組中
         */
        //如果是升序排序
        if (increase){
            for (int i = sortedArray.length-1; i >= 0; i--) {
                sortedArray[i] = heapSort.poll();
            }

        //如果是降序排序
        } else {
            for (int i = 0; i < sortedArray.length; i++) {
                sortedArray[i] = heapSort.poll();
            }
        }

        //返回排好序的數組
        //如果是小頂堆實現,則poll出來的就是從小到大
        return sortedArray;
    }

    /**
     * 堆排序(HeapSort)的驅動程序
     * @param arr 待排序數組
     * @return 堆排序完成後的有序數組
     */
    public static int[] heapSort(int[] arr) {
        //調用升序的堆排序,並將排好序的數組返回
        return heapSort(arr, true);
    }

    /**
     * 驗證堆排序算法
     * @param args
     */
    public static void main(String[] args) {
        //待排序數組
        int[] arr = new int[30];

        //隨機數類
        Random random = new Random();

        //隨機生成排序數組(100以內的整數)
        for (int i = 0; i < arr.length; i++) {
            arr[i] = random.nextInt(100);
        }

        //打印待排序數組
        System.out.println("堆排序前:" + Arrays.toString(arr));

        //進行堆排序(調用驅動程序,就是隻有一個參數的heapSort()方法)
        //注意,堆排序跟之前的排序不太一樣,要返回排好序的數組
        //而不是最後有序的數組就是原來的數組
        int[] heapSortedArr = heapSort(arr);
        
        //這裏可以將排好序的數組,重新賦給原來的數組,保持之前的操作
        arr = heapSortedArr;

        //打印堆排序後的數組
        System.out.println("堆排序後:" + Arrays.toString(arr));
    }

}

運行 HeapSort 類的main方法,堆排序算法測試通過:

堆排序前:[49, 59, 18, 34, 7, 23, 98, 33, 37, 36, 78, 54, 12, 78, 14, 83, 57, 90, 96, 76, 1, 39, 32, 97, 11, 35, 28, 35, 54, 99]
堆排序後:[1, 7, 11, 12, 14, 18, 23, 28, 32, 33, 34, 35, 35, 36, 37, 39, 49, 54, 54, 57, 59, 76, 78, 78, 83, 90, 96, 97, 98, 99]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章