算法系列--排序算法(六)堆排序

找到一个堆排序讲的比较好的博客,自认为画图没有人家好所以摘抄下来分享给大家。

堆排序

  堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏、最好、平均时间复杂度均为O(nlogn);空间复杂度O(1),它也是不稳定排序。

  堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。

       因为是“完全二叉树“(设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。)可以通过数组结构实现,arr[i] 的子节点 arr[2i+1]、arr[2i+2]。

我们用公式来描述一下堆的定义:

      大根堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

      小根堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

图解

       堆排序的基本思想是:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n-1个元素的次小值。重复上述过程直至树节点数为1。

(1) 构造初始堆。将给定无序序列构造成一个大根堆(一般升序采用大堆,降序采用小根堆)。

  a.假设给定无序序列结构如下

  b.此时我们从最后一个非叶子结点(第一个非叶子结点 arr.length/2-1),从左至右,从下至上进行调整。

  c.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

  d.交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

  此时,我们就将一个无需序列构造成了一个大根堆。

(2)将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到次大元素。如此反复进行交换、重建。

  a.将堆顶元素9和末尾元素4进行交换

  b.重新调整结构,使其继续满足堆定义

  c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

  后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

再简单总结下堆排序的基本思路:

  a.将无需序列构建成一个堆,根据升序降序需求选择大根堆或小根堆;

  b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

思考
       1.排序结果是一个小根堆为什么刚开始的时候就不能构建一个小根堆?不能确定左右子树大小,类似冒泡的倒排序。
       2.每次堆排序都要把几乎最小的元素移动到堆顶,再进行重排?只是一个分支重排。
       3.单纯用一种算法实现可能不是最优解(如何判定最优?时间、空间),只是对算法特性有一定了解以便解决实际问题。

代码实现

/**
 * HeapSort
 *
 * @Date 2018/8/24
 * @Author joker
 **/
public class HeapSortTest {
    private int[] data;

    {
        initData(10, 200);
    }

    /**
     * 堆排序:升序
     * 数组下标 i 的 左子树 2i+1, 右子树 2i+2, 根 (i+1)/2
     */
    @Test
    public void bigHeapSortTest() {
        int offset = data.length - 1;
        // 找到最外侧非叶子节点
        int no_leaf = (offset + 1) / 2;
        // 创建大根堆,遍历非叶子节点
        while (no_leaf >= 0) {
            change(no_leaf, offset);
            no_leaf--;
        }
        while (offset >= 0) {
            // 交换跟节点和offset节点
            changeNode(0, offset--);
            // 恢复大根堆
            change(0, offset);
        }
        System.out.println("排序结果:");
        System.out.println(new Gson().toJson(data));
    }


    /**
     * 递归交换当前非根节点并检查交换子树
     */
    private void change(int index, int lastIndex) {
        int left = index * 2 + 1, right = left + 1;
        // 没有子节点
        if (left > lastIndex) {
            return;
        }
        // 只有左子树
        if (right > lastIndex) {
            if (data[left] > data[index]) {
                changeNode(index, left);
            }
        } else {
            // 两个子节点都有
            if (data[left] >= data[right] && data[left] > data[index]) {
                changeNode(index, left);
                change(left, lastIndex);
            } else if (data[left] < data[right] && data[right] > data[index]) {
                changeNode(index, right);
                change(right, lastIndex);
            }
        }
    }

    /**
     * 交换节点
     */
    private void changeNode(int i, int j) {
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }


    private void initData(int length, int maxRange) {
        data = new int[length];
        IntStream.range(0, length).forEach(e ->
                data[e] = new Double(Math.random() * maxRange).intValue()
        );
        System.out.println("初始化:");
        System.out.println(new Gson().toJson(data));
    }
}

 

参考:https://www.cnblogs.com/chengxiao/p/6129630.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值