算法:用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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章