堆排序

堆排序是对直接选择排序的一种优化:直接选择排序,在n个键值中选出最小值,至少进行n-1次比较。然而继续在剩余的n-1个键值中选出次小值,要比较n-2次。怎样才能利用前面n-1次的比较所得的信息,来减少以后比较的次数,所以有了堆排序。

堆排序:

设有 n 个元素,欲将其按关键字排序。可以首先将这 n 个元素按关键字建成堆,将堆顶 元素输出,得到 n 个元素中关键字最大(或最小)的元素。然后,再将剩下的 n-1 个元素重 新建成堆,再输出堆顶元素,得到 n 个元素中关键字次大(或次小)的元素。如此反复执行, 直到最后只剩一个元素,则可以得到一个有序序列,这个排序过程称之为堆排序。

堆的特点:

稳定性: 不稳定

使用条件: 在待排序记录较少时不适用,记录数多时很有效

时间复杂度: 运行时间主要消耗在初始建堆和不断“筛选”的过程,所以平均时间为O(nlog2n),最坏情况下时间复杂度也为O(nlog2n)

空间效率:只需要一个辅助空间

时间复杂度:O(n logn)

 

2个问题值得思考:

对于待排序的初始序列{28 , 26 , 17 , 36 , 20 , 42 ,  11 , 53}

1)如何由一个初始序列建成一个堆?

2)如何在输出堆顶元素之后调整剩余元素成为一个堆?

设有一个具有 m 个元素的堆,输出堆顶元素后,剩下 m-1 个元素。具体的调整方法是:

首先,将堆底元素(最后一个元素)送入堆顶,此时堆被破坏,其原因仅是根结点不满足堆 的性质,而根结点的左右子树仍是堆。然后,将根结点与左、右子女中较大(或较小)的进行交换。若与左孩子交换,则左子树堆被破坏,且仅左子树的根结点不满足堆的性质;若与右孩子交换,则右子树堆被破坏,且仅右子树的根结点不满足堆的性质。继续对不满足堆性 质的子树进行上述交换操作,直到叶子结点,则堆被重建。我们称这个自根结点到叶子结点 的调整过程为筛选。

 

其实这就是一个反复“筛选”的过程:

首先将要排序的所有键值看成一颗完全二叉树的各个结点(这时的完全二叉树并不一定具备堆的特性),

根据完全二叉树性质最后一个非终端节点是第[n/2]个元素,即对于i>[n/2]的结点Ki都没有孩子结点,因此以这样的Ki为根的子树已经是堆,

所以“筛选”只需要从[n/2]开始,逐步把以K[n/2],K[n/2]-1,K[n/2]-2,...,K1为根的子树“筛选”成堆,就完成建堆的过程了。

代码展示:

  public void Heap(int[] a) {

        int n = a.length;

        // 第一步:初始化最小堆
        // 思路:
        //1. 最后一个非终端节点是第[n/2]个元素,
        //2.  即对于i>[n/2]的结点Ki都没有孩子结点,因此以这样的Ki为根的子树已经是堆。
        //3.  所以“筛选”只需要从[n/2]开始,逐步把以K[n/2],K[n/2]-1,K[n/2]-2,...,K1为根的子树“筛选”成堆,就完成建堆的过程了。

//        因为数组下标是从0开始的,所以第一个非终端节点的下标识n/2 -1
        for (int i = n/2 -1; i >=0; i--) {
            //调用建堆的方法
            Sift(a, i, n);
        }

        // 第二步:堆顶和最后一个交换,调整为最大堆
        for (int i = n-1; i >=1; i--) {
            //将堆顶记录和堆中最后一个记录互换
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            // 交换完成之后,调整剩余元素成为一个最大堆
            //现在从堆顶开始与左右孩子比较
            Sift(a, 0, i);
        }

    }
 /**
     * 建立最小堆的过程
     * @param a
     * @param currentRootNode
     * @param size
     */
    public void Sift(int[] a, int currentRootNode, int size) {
        // 父节点
        int i = currentRootNode;
        // 父节点的左孩子
        int j = 2 * i + 1;

        // size-1 :因为数组下标是从0开始的
        // 有左孩子的情况下
        while (j <= size-1) {
            // 如果有右孩子并且左孩子大于右孩子
            if ((j < size-1) && a[j] > a[j + 1]) {
                // j为左右孩子中的最小
                j++;
            }
            // 父节点值小于左右孩子中的最小值
            if (a[i] < a[j]) {
                // 跳出这个循环,判断下一个节点
                break;
            } else {
                // 父节点与最大子节点交换位置
                int temp = a[i];
                a[i] = a[j];
                a[j] = temp;
                // 交换完位置之后,再进行与下面孩子的比较(这里需要特别注意一下)
                i = j;
                j = 2 * i+1;
            }

        }

    }

测试堆排序:

 /**
     * 堆排序测试
     **/
   @Test
    public void testHeapSort() {
        int[] a = new int[]{3, 1, 4, 6, 5, 9, 7, 2, 8, 0};
        Heap(a);

        System.out.println("--------排序好的堆--------");
        for (Integer yuansu : a) {
            System.out.println(yuansu);
        }
    }

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 38
    评论
评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值