java实现堆排序


堆排序全网最优详解:https://blog.csdn.net/u010452388/article/details/81283998
以下的实现方式与上边讲解的思路不太一致,但是更优!

从小到大排序:

写法一:
public class HeapSort {

    public static void sort(int [] a){
        //1.创建大顶堆
        createBigHeap(a,a.length-1);
        //2.range为构建堆的范围最大值,其实也是进行堆排序的元素的个数
        int range = a.length-1;
        //3.进行堆排序
        while(range > 0){
            swap(a,0, range);
            range--;
            sink(a,0, range);
        }
    }

    //将下标i处的元素下层
    private static void sink(int[] a, int i, int range) {
        //1. 当前节点a[i]至少有一个左节点才下层
        while(2*i+1 <= range){
            //2. maxIndex为节点a[i]的左右子节点中最大的索引
            int maxIndex = 0;
            //3. 如果有右节点
            if(2*i+2 <= range){
                //3.1 比较左右节点取最大的值
                maxIndex = a[2*i+1] > a[2*i+2] ? 2*i+1 : 2*i+2;
            }else{
                //3.2 只有最左节点,则直接取左节点的索引值
                maxIndex = 2*i+1;
            }
            // 4. 如果当前节点比子节点中最大的值还大,则无需交换
            if(a[i] > a[maxIndex]) {break;}
            swap(a, i, maxIndex);
            i = maxIndex;
        }
    }

    //构建[0,range]范围的大顶堆
    private static void createBigHeap(int[] a, int range) {
        //currentIndex: i  parentIndex: (i-1)/2  leftIndex: 2*i+1  rightIndex: 2*i+2
        // 1.遍历数组的每个元素,将其放置堆中正确的位置
        for(int i = 0; i <= range; i++){
            // 2.当前下标索引
            int currentIndex = i;
            // 3.父节点下标索引
            int parentIndex = (currentIndex-1)/2;
            // 4.将当前元素向上比较,将其放置正确位置
            while(a[currentIndex] > a[parentIndex]){
                // 4.1 交换
                swap(a,currentIndex,parentIndex);
                // 4.2 重新修改currentIndex和parentIndex
                currentIndex = parentIndex;
                parentIndex = (currentIndex-1)/2;
            }
        }
    }

    private static void swap(int[] a, int i, int j) {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}
写法二:
public class HeapSort{

    /**
     * 判断i索引处的元素是否小于j索引处的元素
     * @param heap 堆
     * @param i 索引
     * @param j 索引
     * @return heap[i] < heap[j]
     */
    private boolean less(Comparable[] heap, int i, int j){
        return heap[i].compareTo(heap[j]) < 0;
    }

    /**
     * 交换heap[i]和heap[j]
     * @param heap 堆
     * @param i 索引
     * @param j 索引
     */
    private void exchange(Comparable[] heap, int i, int j){
        Comparable tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }

    /**
     * 构造大根堆
     * @param source 源数组
     * @param heap 辅助数组
     */
    private void createHeap(Comparable[] source, Comparable[] heap){
        //1.复制source数组的元素给heap,那么heap就是无序的堆
        System.arraycopy(source, 0, heap, 1, source.length);
        //2.从heap.length/2的位置开始,对[0,heap.length/2]的元素进行下沉,使子树中大的节点放到根节点位置,构成大根堆
        //至于为什么从heap.length/2开始,因为heap.length/2后的节点都是没有子节点,所以没必要交换
        for(int i = heap.length/2; i > 0; i--){
            //i表示要下沉的元素的下标,初始时,下沉的范围为整个堆的范围
            sink(heap, i, heap.length-1);
        }
    }

    /**
     * 对source数组的元素进行排序(从小到大)
     * @param source
     */
    public void sort(Comparable[] source){
        //1.创建heap数组,数组大小为source.length+1,因为下标是从1开始的
        Comparable[] heap = new Comparable[source.length + 1];
        //2.构造大根堆
        createHeap(source, heap);
        //3.初始化一个变量N,用于缩小sink的范围
        int N = heap.length-1;
        //4.进行排序
        // 思路是,每次将堆顶最大元素与最后一个元素交换,此时的堆顶元素是整个堆的最大元素,
        // 已经归位,所以下沉范围N-1,归位的元素不需要再参与,交换完后,我们的堆被破坏了
        // 需要将堆顶的元素下沉,放到正确的位置,然后继续上述操作,直到N==1,只有最后一个元素没有归位,相当于归位,被迫归位,排序结束
        while(N != 1){
            //4.1 交换堆顶元素与最后一个元素
            exchange(heap, 1, N);
            //4.2 范围缩小
            N--;
            //4.3 下沉
            sink(heap, 1, N);
        }
        //5.排序序后的heap,重新复制给source
        System.arraycopy(heap, 1, source, 0, source.length);
    }

    /**
     * 在heap中,对target索引的元素进行下沉,范围在[0,range]
     * @param heap
     * @param target
     * @param range
     */
    private void sink(Comparable[] heap, int target, int range){
        //1.至少有一个左节点才下沉
        while(2 * target <= range){
            //2.保存子树最大节点的下标
            int max = 0;
            //3.,存在右节点
            if(2 * target + 1 <= range){
                if(less(heap,2*target, 2*target+1)){
                    max = 2 * target +1;
                }else{
                    max = 2 * target;
                }
            }else{
                //4.不存在右节点,只有左节点
                max = 2 * target;
            }
            //5.找到左右子节点的最大值 与 根节点进行比较,如果根节点比max大,则结束,无需比较
            if(!less(heap, target, max)) break;
            //6.否则,根节点比max小,则需交换
            exchange(heap, target, max);
            //7.更新target的值
            target = max;
        }
    }
}

从大到小排序:

public class HeapSort {

    private boolean less(Comparable[] heap, int i, int j){
        return heap[i].compareTo(heap[j]) < 0;
    }


    private void exchange(Comparable[] heap, int i, int j){
        Comparable tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }

    //构造小根堆
    private void createHeap(Comparable[] source, Comparable[] heap){
        System.arraycopy(source,0, heap, 1, source.length);
        for(int i = heap.length/2; i > 0; i--){
            sink(heap, i, heap.length-1);
        }
    }

    private void sink(Comparable[] heap, int target, int range){
        while(2*target <= range){
            int min = 0;
            if(2*target + 1 <= range){
                if(less(heap, 2*target, 2* target+1)){
                    min = 2*target;
                }else{
                    min = 2*target+1;
                }
            }else{
                min = 2*target;
            }
            if(less(heap, target, min)) break;
            exchange(heap, target, min);
            target = min;
        }
    }

    //堆排序 (从大到小)
    public void sort(Comparable[] source){
        Comparable[] heap = new Comparable[source.length + 1];
        createHeap(source,heap);
        int N = heap.length-1;
        while(N != 1){
            exchange(heap,1, N);
            N--;
            sink(heap,1, N);
        }
        System.arraycopy(heap,1, source, 0, source.length);
    }
}

递归实现堆排序:

public class Main6 {

    public static void main(String[] args) {
        int[] arr = {9,3,6,7,5,8,1,0,4,2};

        // 大顶堆[9, 7, 8, 4, 5, 6, 1, 0, 3, 2]
        createHeap(arr, arr.length);
        System.out.println(Arrays.toString(arr));

        // 排序[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 递归版堆排序
     * @param arr
     */
    public static void heapSort(int[] arr){
        int size = arr.length;
        createHeap(arr, size);
        while(size > 1){
            // 每次将第0个元素与最后一个元素进行交换
            swap(arr, 0, size - 1);
            // 排序范围缩小
            size--;
            // 对第0个元素进行重新下沉,构成[0,size]大顶堆
            sink(arr, 0, size);
        }
    }


    /**
     * 构建大顶堆
     * @param arr
     * @param range 数组长度
     */
    public static void createHeap(int[] arr, int range){
        // 从最后一个非叶子节点开始构建,最后一个非叶子节点的索引为:arr.length/2-1
        for(int i = range/2 - 1; i >= 0; i--){
            sink(arr, i, range);
        }
    }

    /**
     * 对第i个元素进行下沉
     * @param arr
     * @param i
     * @param range
     */
    private static void sink(int[] arr, int i, int range) {
        int leftIndex = 2 * i + 1;  //元素arr[i]的左节点的索引: 2i+1
        int rightIndex = 2 * i + 2; //元素arr[i]的右节点的索引: 2i+2
        int maxIndex = i;   // 左右孩子节点中最大的索引

        if(leftIndex < range && arr[leftIndex] > arr[maxIndex]){
            maxIndex = leftIndex;
        }
        if(rightIndex < range && arr[rightIndex] > arr[maxIndex]){
            maxIndex = rightIndex;
        }

        // 左右孩子存在大于父节点的元素,进行元素交换,并继续操作子树
        if(maxIndex != i){
            swap(arr, maxIndex, i);
            sink(arr, maxIndex, range);
        }
    }


    /**
     * 交换元素arr[i]与arr[j]
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr, int i, int j){
        arr[i] = arr[i] + arr[j];
        arr[j] = arr[i] - arr[j];
        arr[i] = arr[i] - arr[j];
    }
}

非递归实现堆排序:

public class Main7 {

    public static void main(String[] args) {
        int[] arr = {9,3,6,7,5,8,1,0,4,2};

        // 大顶堆[9, 7, 8, 4, 5, 6, 1, 0, 3, 2]
        createHeap(arr, arr.length);
        System.out.println(Arrays.toString(arr));

        // 排序[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        heapSort(arr);
        System.out.println(Arrays.toString(arr));

    }

    /**
     * 堆排序非递归
     * @param arr
     */
    public static void heapSort(int[] arr){
        int size = arr.length;
        createHeap(arr, size);
        while(size > 1){
            swap(arr, 0, size - 1);
            size--;
            // 将第0个元素进行下沉
            sink(arr, 0, size);
        }
    }

    /**
     * 对第i个元素进行下沉
     * @param arr
     * @param i
     * @param range
     */
    public static void sink(int[] arr, int i, int range) {

        //存在一个左节点时进行下沉
        while(2*i+1 < range){
            int leftIndex = 2 * i + 1;
            int rightIndex = 2 * i + 2;
            int maxIndex;
            if(rightIndex < range){
                // 存在左右孩子节点
                maxIndex = arr[leftIndex] >= arr[rightIndex] ? leftIndex : rightIndex;
            }else{
                maxIndex = leftIndex;
            }
            // 如果当前节点i,比左右孩子节点中最大的还大,那么不用比较了,直接结束
            if(arr[i] >= arr[maxIndex]){
                break;
            }
            // 当前节点i与左右孩子节点中最大节点进行交换
            swap(arr, i, maxIndex);
            // 当前节点变为maxIndex,继续判断下去
            i = maxIndex;
        }
    }


    /**
     * 构建大顶堆
     * @param arr
     * @param range 数组长度
     */
    public static void createHeap(int[] arr, int range){
        for(int i = range/2-1; i >= 0; i--){
            int currentIndex = i;
            int maxIndex = getMaxforleftAndRight(arr, currentIndex, range);
            while(arr[maxIndex] > arr[currentIndex]){
                swap(arr, maxIndex, currentIndex);
                currentIndex = maxIndex;
                maxIndex = getMaxforleftAndRight(arr, currentIndex, range);
            }
        }
    }

    /**
     * 获取左右节点中最大值的索引
     * @return
     */
    public static int getMaxforleftAndRight(int[] arr, int i, int range){
        int leftIndex = 2 * i + 1;  //左孩子索引
        int rightIndex = 2 * i + 2; //右孩子索引
        int maxIndex;
        if(rightIndex < range){
            //存在左右孩子节点
            maxIndex = arr[leftIndex] >= arr[rightIndex] ? leftIndex : rightIndex;
        }else if(leftIndex < range){
            maxIndex = leftIndex;
        }else{
            maxIndex = i;
        }
        return maxIndex;
    }


    /**
     * 交换元素arr[i]与arr[j]
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr, int i, int j){
        arr[i] = arr[i] + arr[j];
        arr[j] = arr[i] - arr[j];
        arr[i] = arr[i] - arr[j];
    }

}

堆排序最终模板写法

    public static void main(String[] args) {
        int[] arr = {5,4,3,2,1};
//        int[] arr = {3,2,5,4,1,6};
//        int[] arr = {1,2,3,4,5};

        //堆排序
        //heapSort(arr, 0, arr.length - 1);    //写法一
        heapSort2(arr, 0, arr.length - 1);	   //写法二,优化
        System.out.println(Arrays.toString(arr));
    }
    private static void heapSort2(int[] arr, int left, int right) {

        // 从最后一个包含孩子节点的元素开始,优化
        for(int i = (right+1)/2-1; i >= 0; i--){
            sink(arr, i, right+1);
        }

        int heapSize = right + 1;
        while(--heapSize > 0){
            // 交换堆顶元素和最后一个元素
            swap(arr, 0, heapSize);
            // 堆顶元素下层,重新构成大顶堆
            sink(arr, 0, heapSize);
        }
    }

    private static void heapSort(int[] arr, int left, int right) {
        /*
           前置条件:
               当前元素的下标:i
               当前元素的左孩子节点下标:2*i+1
               当前元素的右孩子节点下标:2*i+2
               当前元素的父节点下标:(i-1)/2

           构建大顶堆:
           1.依次将数组元素一个一个追加到数组中,并判断当前元素是否比父元素大,如果大的话就上推
            上推的结束条件:
                1.父节点的值大于等于当前节点的值:arr[(i-1)/2] >= arr[i]
                2.当前元素到达堆顶:i <= 0
         */
        // 构建大顶堆
        for(int i = 0; i < arr.length; i++){
            // 一个一个元素插入,构建大顶堆
            heapInsert(arr, i);
        }
        /*
        堆排序:
        1.将堆顶元素与最后一个元素交换
        2.然后把堆顶元素下层,重新构成堆,堆的长度-1
            下层判断孩子节点中最大的一个是否大于父节点,如果大于则进行交换
            下层结束条件:arr[topIndex] >= max(arr[left], arr[right])

        */
        int heapSize = right + 1;
        while(--heapSize > 0){
            // 交换堆顶元素和最后一个元素
            swap(arr, 0, heapSize);
            // 堆顶元素下层,重新构成大顶堆
            sink(arr, 0, heapSize);
        }
    }

    private static void sink(int[] arr, int topIndex, int heapSize) {
        // 当左孩子存在时,才需要进行下层,否则当前元素就是有序的
        while(2 * topIndex + 1 < heapSize){
            int leftIndex = 2 * topIndex + 1;
            int rightIndex = 2 * topIndex + 2;

            // 孩子节点中较大的下标
            int lastIndex = 0;
            // 左右节点都存在
            if(rightIndex < heapSize){
                lastIndex = arr[leftIndex] > arr[rightIndex] ? leftIndex : rightIndex;
            }else{ // 只存在左节点
                lastIndex = leftIndex;
            }
            // 父节点 > 孩子节点中的最大值,说明已经构成大顶堆,结束退出
            if(arr[topIndex] >= arr[lastIndex]){
                break;
            }

            // 否则进行交换,继续进行
            swap(arr, topIndex, lastIndex);
            // 更新下标
            topIndex = lastIndex;
        }
    }

    /**
     * 插入一个元素到堆中
     * @param arr
     * @param currIndex 当前插入的元素
     */
    private static void heapInsert(int[] arr, int currIndex) {
        int parentIndex = (currIndex - 1) / 2;
        while(currIndex > 0 && arr[parentIndex] < arr[currIndex]){
            // 交换当前元素和父元素
            swap(arr, parentIndex, currIndex);
            // 更新当前元素下标和父元素下标
            currIndex = parentIndex;
            parentIndex = (currIndex - 1) / 2;
        }
    }
    public static void swap(int[] arr, int i, int j){
        if(i == j){
            return;
        }
        arr[i] = arr[i] + arr[j];
        arr[j] = arr[i] - arr[j];
        arr[i] = arr[i] - arr[j];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值