认识堆的用处

目录​​​​​​​

1. 堆的概念

2. 向下调整

2.1 向下调整为大根堆

2.2 向下调整为小根堆

3. 堆的应用——优先级队列

3.1 概念

 3.2 内部原理

3.3 入队列

 3.4 出队列(优先级最高)

3.4 Java中的优先级队列

4. TopK 问题

5. 堆排序


1. 堆的概念

1)堆逻辑上是一颗完全二叉树

2)堆物理上是保存在数组中

3)满足任意节点的值都大于其子树中节点的值,叫做大堆,或者大根堆。

4)满足任意节点的值都小于其子树中节点的值,叫做小堆,或者小根堆,或者最小堆。

2. 向下调整

前提:左右子树必须已经是一个堆,才能调整

说明:

1. array 代表存储堆的数组
2. size 代表数组中被视为堆数据的个数
3. index 代表要调整位置的下标
4. left 代表 index 左孩子下标
5. right 代表 index 右孩子下标
6. min 代表 index 的最小值孩子的下标

2.1 向下调整为大根堆

 //向下调整
    public void shiftDown(int parent, int len) {
        int child = 2 * parent + 1;
        while (child < len) {
            if (child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

    //大根堆
    public void createHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
        for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
            shiftDown(parent, usedSize);
        }
    }

2.2 向下调整为小根堆

过程:
1. index 如果已经是叶子结点,则整个调整过程结束
1. 判断 index 位置有没有孩子
2. 因为堆是完全二叉树,没有左孩子就一定没有右孩子,所以判断是否有左孩子
3. 因为堆的存储结构是数组,所以判断是否有左孩子即判断左孩子下标是否越界,即 left >= size 越界
2. 确定 left right ,谁是 index 的最小孩子 min
1. 如果右孩子不存在,则 min = left
2. 否则,比较 array[left] array[right] 值得大小,选择小的为 min
3. 比较 array[index] 的值 和 array[min] 的值,如果 array[index] <= array[min] ,则满足堆的性质,调整结束
4. 否则,交换 array[index] array[min] 的值
5. 然后因为 min 位置的堆的性质可能被破坏,所以把 min 视作 index ,向下重复以上过程

public static void shiftDown(int[] array, int size, int index) {
    int left = 2 * index + 1;
    while (left < size) {
        int min = left;
        int right = 2 * index + 2;
        if (right < size) {
            if (array[right] < array[left]) {
                min = right;
           }
       }
        
        if (array[index] <= array[min]) {
            break;
       }
        
        int t = array[index];
        array[index] = array[min];
        array[min] = t;
        index = min;
        left = 2 * index + 1;
   }
}

3. 堆的应用——优先级队列

3.1 概念

在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次 高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这 种数据结构就是优先级队列(Priority Queue)

 3.2 内部原理

优先级队列的实现方式有很多,但最常见的是使用堆来构建。

3.3 入队列

过程(以大堆为例):
1. 首先按尾插方式放入数组
2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
3. 否则,交换其和双亲位置的值,重新进行 2 3 步骤
4. 直到根结点

   //调整为大根堆
   private void shiftUp(int child) {
        int parent = (child-1) / 2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                child = parent;
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }

   //入队列
    public void offer(int val) {
        if (isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        shiftUp(usedSize-1);
    }
//队列是否满
    public boolean isFull() {
        return usedSize == elem.length;
    }

 3.4 出队列(优先级最高)

以大根堆为例

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆
1. 交换二叉树的第一个元素和最后一个元素
2.向下调整回大根堆

//向下调整
    public void shiftDown(int parent, int len) {
        int child = 2 * parent + 1;
        while (child < len) {
            if (child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

    public int poll() {
        if (isEmpty()) {
            throw new RuntimeException("优先队列为空!");
        }
        int tmp = elem[0];
        elem[0] = elem[usedSize - 1];
        elem[usedSize-1] = tmp;
        usedSize--;
        shiftDown(0,usedSize);
        return tmp;
    }


    public boolean isEmpty() {
        return usedSize == 0;
    }

3.4 Java中的优先级队列

PriorityQueue implements Queue
错误处理
抛出异常
返回特殊值
入队列
add(e)
offer(e)
出队列
remove()
poll()
队首元素
element(0)
peek()

4. TopK 问题

1、如果求前K个最大的元素,要建一个小根堆。
2、如果求前K个最小的元素,要建一个大根堆。 
3、第K大的元素。建一个小堆,堆顶元素就是第K大的元素。
4. 第K小的元素。堆顶元素就是第K小的元素。堆内的元素,就是前K个最小的元素。

前K个元素大小的代码示例

public class TopK {
    /**
     * 排序找前k个元素大小
     * @param array
     * @param k
     * @return
     */
    public static int[] topK(int[] array, int k) {
        //1.创建一个大小为 K 的大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        //2.遍历数组当中的元素,前K 个元素放到队列当中
        for (int i = 0; i < array.length; i++) {
            if (maxHeap.size() < k) {
                maxHeap.offer(array[i]);
            }else {
                //3.从第K+1 个元素开始,每个元素和堆顶元素进行比较
                int top = maxHeap.peek();
                if (top > array[i]){
                    //先弹出
                        maxHeap.poll();
                        //后存入
                        maxHeap.offer(array[i]);
                }
            }
        }
        int[] tmp = new int[k];
        for (int i =0; i < k; i++) {
            tmp[i] = maxHeap.poll();
        }
        return tmp;
    }

    public static void main(String[] args) {
        int[] array = {12,4,14,45,3,21};
        int[] tmp = topK(array,3);
        System.out.println(Arrays.toString(tmp));
    }
}

5. 堆排序


将二叉树的元素从小到大在数组中排列

1、调整为大根堆
2、0下标和最后1个未排序的元素进行交换即可
3、end-- .

//利用大根堆从小到大排序
    public void heapSort() {
        int end = this.usedSize-1;
        while (end > 0) {
            int tmp = elem[0];
            elem[0] = elem[end];
            elem[end] = tmp;
            shiftDown(0,end);
            end--;
        }
    }

//向下调整
    public void shiftDown(int parent, int len) {
        int child = 2 * parent + 1;
        while (child < len) {
            if (child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/m0_60494863/article/details/125375016