高级数据结构---堆树和堆排序

堆树介绍:

之前在二叉树的时候说到过一种特殊的二叉树---完全二叉树(除了最后一层,其他层的每个结点都是满的,且最后一层结点全部靠左排列,这样就可以很方便的用数组来表示,下标从0开始如果父结点索引是i那么它两个子结点的索引就是2i+12i+2,具体的图解见二叉树)。而堆树又是一种特殊的完全二叉树。它的每一个结点值都大于等于或者小于等于其左右结点的值。这里和二叉搜索树不一样,搜索树是左节点小于根,右结点大于根。为什么是大于等于或者小于等于呢,如果大于等于,那么根就是最大的数,这样的就是大顶堆;如果是小于等于,那么根就是最小的数,这样就是小顶堆。

 

堆树的操作:

插入:

堆树插入之后要进行一个堆化的操作,也就是让这棵树满足堆树的性质。

一种是从下往上另一种是从上往下的堆化。

从下往上:

因为完全二叉树用数据构造之后,那么新插入的数据都在最后,但是插入之后,可能就不满足堆树的要求了,所以需要进行变动。以上图的大顶堆为例,我们在最后插入9之后是不满足堆树的性质的。所以我们需要与其父结点进行交换,直到不能再交换位置。不用担心数据量大了交换的次数问题,完全二叉树近似一个满二叉树,最多交换logn-1次,想想232的次方是多少,这么大的数据最多才31次。

 

删除:

假设我们要删除掉根结点10,并且删除之后,还要满足堆树的性质。

如何才能删除之后还能保证性质呢。其实就是将要删除的元素和最后一个元素交换之后,删除最后一个元素之后,再从上往下进行堆化的操作。

 

修改数据之后,同样要进行堆化操作,根据修改之后的数据和他父结点和子结点比较来决定是向上还是向下进行堆化操作。

 

堆排序:

排序算法一种,就是先将数组构造成堆树,再进行排序。那么怎么将一个数组构造成堆树。其实一个数组我们都可以看成是一棵完全二叉树,但是需要将这棵树进行改造,才能得到堆树。如何改造?

比如这个数组:[8 4 20 7 3 1 25 14 17]

树结构:

 

 我们从最后一个非叶子结点逆向依次进行向下堆化操作,如上图也就是先从7(最后一个非叶子结点)开始向下堆化,7会和17进行对换,然后逆向一次执行,接下来就是20向下执行堆化,会和25对换;然后是4,这个时候会将刚才换上来17换上去,然后4继续和下面比较,和14进行交换.......依次这样操作之后就可以得到一棵堆树了。图解如下:

 

 

那么得到堆树之后,看起来也是无序的啊,怎么就能实现排序呢?

可以看到堆树的根要么是最大的数要么是最小的数,那我们依次将根取出来,之后再进行堆化操作,又会得到剩余数中最大或者最小的。所以,我们将根与最后一个数交换之后,再进行堆化操作(这里的堆化操作就要除开最后一个点了),然后再新的根和倒数第二个交换再堆化,依次这样操作,后面数慢慢的都是有序的了。

图解如下:

 

代码实现数组转堆树和堆排序(不稳定):

 

    /**
     * 堆排序
     * @param arr
     */
    private static void heapSort(int[] arr) {
        int len = arr.length;
        /**
         * 数组构造堆树,从倒数第一个非叶子结点开始逆向一次进行堆化操作。最后一个叶子结点的父结点就是最后一个非叶子结点。
         * 索引从0开始。两个子结点索引是2i+1和2i+2,所以最后一个非叶子结点的索引就是 len/2 - 1
         */
        for (int i = len / 2 - 1; i >=0 ; i--) { //时间复杂度nlogn
            createMaxHeap(arr, i, len);
        }
        for (int i = len - 1; i > 0 ; i--) { //时间复杂度nlogn
            int maxData = arr[0]; //第一个数最大
            arr[0] = arr[i];
            arr[i] = maxData;
            /**
             * 交换第一个最后一个位置,然后重新构造大顶堆。每循环一次就构造好了一个数的位置,
             * 最后到i为止都是排好序的,堆化的时候不需要再操作了
             */
            createMaxHeap(arr, 0, i);
        }
    }
    /**
     * 大顶堆构造及堆化过程
     * @param arr
     * @param start  
     * @param end  end之后是已经排好序的,所以需要end下标来判断截止
     */
    private static void createMaxHeap(int[] arr, int start, int end) {
        int parentIndex = start;
        int leftChildIndex = 2 * parentIndex + 1;
        while (leftChildIndex < end) {
            int tempIndex = leftChildIndex;
            //比较左右结点谁大,记录谁的下标
            if (leftChildIndex + 1 < end && arr[leftChildIndex] < arr[leftChildIndex + 1]) {
                tempIndex = leftChildIndex + 1;
            }
            //父结点比孩子大,不交换
            if (arr[parentIndex] > arr[tempIndex]) {
                return;
            }
            else {
                //交换数据,刷新父结点继续执行堆化操作
                int tempData = arr[parentIndex];
                arr[parentIndex] = arr[tempIndex];
                arr[tempIndex] = tempData;
                parentIndex = tempIndex;
                leftChildIndex = 2 * parentIndex + 1;
            }
        }
    }

 堆树和堆排序在JDK中的应用,可以参见优先队列:java.util.PriorityQueue

posted @ 2020-05-02 23:58  白露非霜  阅读(1349)  评论(0编辑  收藏  举报
访问量