1. 堆的定义

堆是一种图的树形结构,被用于实现“优先队列”(priority queues)。优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出。在堆的树形结构中,各个顶点被称为“结点”(node),数据就存储在这些结点中。

  1. 堆的操作

image-20200802163925660

将数字5插入堆

image-20200802164018904

父节点大于子节点交换位置

image-20200802164542477

image-20200802164621236

从堆中取出数据时,取出的是最上面的数据。这样,堆中就能始终保持最上面的数据最小

image-20200802164710773

由于最上面的数据被取出,因此堆的结构也需要重新调整

image-20200802164756817

将最后的数据(此处为6)移动到最顶端

image-20200802164826438

如果子结点的数字小于父结点的,就将父结点与其左右两个子结点中较小的一个进行交换

image-20200802164858831

这里由于父结点的6大于子结点(右)的5大于子结点(左)的3,所以将左边的子结点与父结点进行交换。重复这个操作直到数据都符合规则,不再需要交换为止

image-20200802164942333

现在,子结点(右)的8大于父结点的6大于子结点(左)的4,需要将左边的子结点与父结点进行交换

image-20200802165042861

  1. 堆的解说

堆中最顶端的数据始终最小,所以无论数据量有多少,取出最小值的时间复杂度都为O(1)。

另外,因为取出数据后需要将最后的数据移到最顶端,然后一边比较它与子结点数据的大小,一边往下移动,所以取出数据需要的运行时间和树的高度成正比。假设数据量为n,根据堆的形状特点可知树的高度为log2n,那么重构树的时间复杂度便为O(logn)。

添加数据也一样。在堆的最后添加数据后,数据会一边比较它与父结点数据的大小,一边往上移动,直到满足堆的条件为止,所以添加数据需要的运行时间与树的高度成正比,也是O(logn)。

  1. 堆的实现
public class MyHeap {

    /**
     * 创建堆
     *
     * @param arr
     */
    public void createHeap(int[] arr) {
        int last = parent(arr.length - 1);//求出数组中最后一个元素的父节点,也就是所有包含子节点的元素中的最后一个
        for (int i = last; i >= 0; i--) {//从最后一个右子节点的节点索引开始
            bigHeap(arr, i);//大根堆
            //smallHeap(arr,i);//小根堆
        }
    }

    /**
     * 维持大根堆的特性(父节点比子节点大)
     *
     * @param arr
     * @param i
     */
    public void bigHeap(int[] arr, int i) {
        int left = left(i);//求出指定索引处节点的左子节点索引
        int right = right(i);//求出指定索引处节点的右子节点索引
        int largest = -1;//最大节点索引,初值设置为-1

        if (left < arr.length && arr[i] < arr[left]) {//求出的左子节点索引在数组中并且指定节点比它的左子节点小
            largest = left;//最大节点索引设置为指定节点的左子节点索引
        } else {
            largest = i;//最大节点索引设置为指定节点索引
        }

        if (right < arr.length && arr[largest] < arr[right]) {//求出的右子节点索引在数组中并且指定节点和它的左子节点中最大的一个比它的右子节点小
            largest = right;//最大节点的索引设置为指定节点的右子节点的索引
        }

        if (i != largest) {//如果指定节点不是最大的节点
            //交换指定节点和最大节点的位置
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;
            //由于交换过后不一定满足大根堆特性,利用递归交换知道满足为止
            bigHeap(arr, largest);
        }
    }

    public void smallHeap(int[] arr, int i) {
        int left = left(i);//求出指定索引处节点的左子节点索引
        int right = right(i);//求出指定索引处节点的右子节点索引
        int smallest = -1;//最小节点索引,初值设置为-1

        if (left < arr.length && arr[i] > arr[left]) {//求出的左子节点索引在数组中并且指定节点比它的左子节点大
            smallest = left;//最小节点索引设置为指定节点的左子节点索引
        } else {
            smallest = i;//最小节点索引设置为指定节点索引
        }

        if (right < arr.length && arr[smallest] > arr[right]) {//求出的右子节点索引在数组中并且指定节点和它的左子节点中最小的一个比它的右子节点大
            smallest = right;//最小节点的索引设置为指定节点的右子节点的索引
        }

        if (i != smallest) {//如果指定节点不是最小的节点
            //交换指定节点和最小节点的位置
            int temp = arr[i];
            arr[i] = arr[smallest];
            arr[smallest] = temp;
            //由于交换过后不一定满足小根堆特性,利用递归交换知道满足为止
            smallHeap(arr, smallest);
        }
    }

    /**
     * 求指定索引处节点的左子节点
     *
     * @param i
     * @return
     */
    public int left(int i) {
        return i * 2 + 1;
    }

    /**
     * 求指定索引处节点的右子节点
     *
     * @param i
     * @return
     */
    public int right(int i) {
        return (i + 1) * 2;
    }

    /**
     * 求指定索引处节点的父节点
     *
     * @param i
     * @return
     */
    public int parent(int i) {
        return (i - 1) / 2;
    }
}
posted @ 2020-08-01 15:35  乌利乌利  阅读(67)  评论(0)    收藏  举报