如何构建一个大根堆

数组可以看成是一个完全二叉树,大根堆是一个完全二叉树

在这里插入图片描述

构造大根堆

例子1:[O(N)---->从下到上]

因为堆是对父节点-左/右孩子节点之间的约束,所以从最后一个非叶子节点开始调整。

在这里插入图片描述
注意每次交换后,都要对下一层的子堆进行递归调整,因为交换后有可能破坏已调整子堆的结构。
堆排序

例子2:[O(N)---->从下到上]

1、假设给定无序序列结构如下
在这里插入图片描述
2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。【9下沉之后,9变成了叶子节点,因此不会对子叶产生影响】

在这里插入图片描述
4.找到第二个非叶节点4 【3/2 - 1 = 0】,由于[4,9,8]中9元素最大,4和9交换。【4下沉之后,变动了的子树必须重新调整】
在这里插入图片描述
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
在这里插入图片描述
此时,我们就将一个无需序列构造成了一个大顶堆。

图解排序算法(三)之堆排序

第3个例子【从上到下 】

从一个空的二叉树开始:
在这里插入图片描述
从头开始遍历数组:

for(int i = 0; i < arr.length; i++){
	选出arr[i],插入二叉树
}

第一次循环:当前需要插入的索引是0,令cur = 0,它的父节点parent = (cur - 1) / 2 = 0 。

  • arr[父节点索引] == arr[当前节点索引] ,因此进入下一轮循环
    在这里插入图片描述
    第二次循环:当前需要插入的数据索引是1【需要插入的二叉树的位置索引也是1】,令cur = 1,它的父节点索引是 parent = (cur - 1) / 2 = 0,
  • arr[cur] 与arr[parent]比较,因为 当前节点值 > 父节点值,因为需要最大值登顶,因此进入内循环:
    • 交换 当前节点和父节点的值
    • 将当前索引指向父节点,重新计算父节点
    • 因为cur = 0, par = 0,两者相等,因此跳出内循环

进入下一轮循环
在这里插入图片描述
在这里插入图片描述
第三次循环:需要插入的索引为2,令cur = 2,其父节点 par = (2 - 1)/2 = 0,

  • 当前索引值73与父节点值25比较,当前索引比较大,进入内循环:
    • 交换当前节点与父节点的值
    • 当前索引指向父节点索引,重新计算父节点的索引
    • 因为cur = 0, par = 0,因此跳出循环

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第4次循环,需要插入的索引为3,令cur =3,其父节点 par = (3 - 1)/2 = 1,

  • 当前索引值98与父节点值10比较,当前索引比较大,进入内循环:
    • 交换当前节点与父节点
    • 令cur = 夫节点 = 1,重新计算父节点 par = 0
      在这里插入图片描述
      在这里插入图片描述

判断是否还是在内循环中:

  • 当前cur = 1, par = 0,因为当前节点98比父节点73大,进入内层循环
    • 交换98和73的位置
    • 令cur = 父节点的索引 = 0, 重新计算par = (cur -1) / 2 = 0
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      第4次循环,需要插入的索引为4,令cur =4,其父节点 par = (4- 1)/2 = 1,
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

第5次循环,需要插入的索引为5,令cur =5,其父节点 par = (5- 1)/2 = 2,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
**** 一直循环,直到当前数组循环完毕

总结: 上面的过程可以看成是向数组中不断插入一个元素【向大根堆中插入一个元素】,总结上面经验得知:

  • 元素的插入索引是固定的【一定都是size】,但是插入的位置是不一定的。
    • 如果插入的数据小于等于他的父索引【(size - 1)/2】,什么也不干
    • 如果插入的数据大于它的父索引【(size - 1)/2】,那么就让这个数据不断上浮,直到找到了应该的位置就表示插入成功了

节点上浮:当我们在向最大堆中插入一个节点时,我们必须满足完全二叉树的标准,那么被插入节点的位置是固定的,而且要满足父节点关键值不小于子节点关键值,那么我们就需要去移动父结点和子结点的相互位置关系。

在这里插入图片描述

堆排序动画

堆排序

例子1:

在这里插入图片描述
进行调整后,堆顶元素(array[0])为最大值,将最大值与堆尾部元素(array[count-1])交换,并将count值减去1,则此时得到新的无序数组array[count],此时的堆被破坏;

在这里插入图片描述
对应到数组元素为:
在这里插入图片描述
调整堆:与建堆过程类似,堆顶元素被一个比较小的值代替,所以从堆顶元素开始调整,在堆顶、堆顶的左孩子、堆顶的右孩子中找出最大的与堆顶进行交换,被交换的元素再次与他下一层的左右孩子进行比较(递归)调整。

在这里插入图片描述
然后一直重复,直到count = 0

在这里插入图片描述

例子2:

在这里插入图片描述

如果要排序:我们一般从最上面的那个元素开始。

从0开始遍历数组:

令cur = 0, 并且cur 与 size - 1的元素交换位置,
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

大根堆进行从大到小的排序

public class TreeNode {
    // 从最后一个非叶子节点开始调整,调整为一个大根堆
    private static void BuildMaxHeap(int[]arr){
        if (arr == null || arr.length < 2){
            return;
        }

        for (int i = arr.length/2 - 1; i > -1; i--){
            adjustMaxHeap(arr, arr.length, i);
        }


        for (int i = arr.length; i > 0; i--){
            swap(arr, i - 1, 0);
            adjustMaxHeap(arr, i - 1, 0);
        }
    }

    private static void adjustMaxHeap(int[]arr, int size, int i){
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int max = i;

        if (left < size && arr[left] > arr[max]){
            max = left;
        }

        if (right < size && arr[right] > arr[max]){
            max = right;
        }

        if (max != i){
            swap(arr, i, max);
            adjustMaxHeap(arr, size, max);
        }
    }

    private static void swap(int []arr, int i, int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{23,19,81,79,89,83,17,48,55,26};
        BuildMaxHeap(arr);

        System.out.println(Arrays.toString(arr));
    }
}

大根堆中可能用到的行为


    // 从最好一个非叶子节点开始调整
    // 下移过程
    /**
     * 将父节点为aar[i]的子树调整为最大堆
     * @param arr 堆数组
     * @param size 堆数组长度
     * @param index 节点索引
     */
    private static void AdjustHeap(int[] arr, int size, int index){
        if (arr == null || index < 0){
            return;
        }
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        int max = index;
        if (left < size && arr[left] > arr[index]){
            max = left;
        }
        if (right < size && arr[right] > arr[index]){
            max = right;
        }

        if (max != index){
            Swap(arr, max, index);
            AdjustHeap(arr, size, max);
        }
    }


    /**
     * 根据输入的数组构建一个最大堆
     * @param arr 堆数组
     * @param size 堆数组长度
     * @return 堆数组长度
     */
    private static int BuildMaxHeap(int arr[], int size) {
        //对每一个非叶节点开始向下进行最大堆调整
        for (int i = size / 2 - 1; i >= 0; i--)
        {
            AdjustHeap(arr, size, i);
        }
        return size;
    }

    /**
     * 向指定的最大堆中插入节点:首先在堆的最后添加一个节点,然后沿着堆树上升,直到堆树再次调整为最大堆
     * @param arr
     * @param size
     * @param data
     * @return
     */
    int MaxHeapInsert(int arr[], int size,int data)
    {
        int index=size;
        while (index>0 && data>arr[(index-1)/2])
        {
            arr[index]=arr[(index-1)/2];
            index=(index-1)/2;
        }
        arr[index]=data;
        return (size+1);
    }

    /**
     * 最大堆堆顶节点的删除:将堆树中待删除的节点A与堆树中的最后一个节点B互换,然后调整节点B到合适的位置,最后从堆树中删除排在堆数组最后的节点A。
     * @param arr 最大堆数组
     * @param size 堆数组长度
     * @return 删除后的堆数组长度
     */
    private static int MaxHeapDelete(int arr[], int size)
    {
        if(size<=0)return -1;
        Swap(arr, 0, size - 1);
        AdjustHeap(arr,size-1,0);//调整堆为最大堆
        return size-1;
    }

    private static  void Swap(int arr[], int i, int j)
    {
        int temp=arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }


    public static void main(String[] args) {
        int[] arr = new int[]{23,19,81,79,89,83,17,48,55,26,16,1,46,95,10};
        BuildMaxHeap(arr, arr.length); //[95, 89, 81, 79, 26, 83, 23, 48, 55, 19, 16, 1, 46, 17, 10]

        System.out.println(Arrays.toString(arr));
    }

参考

代码写的很清晰

写得非常好,但是还没有看完

堆排序动画

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章