[数据结构] 2.7 Heap 堆

* 注: 本文/本系列谢绝转载,如有转载,本人有权利追究相应责任。

1.堆是什么?

(如图所示是一个小堆)

1)堆是一颗完全二叉树,它的最后一层不是满的,其他每一层都是满的,最后一层从左到右也没有空隙。

 简单的说?  完全二叉树也就是没有缝隙的二叉树

2)堆常常通过数组实现,因为 父子节点直接的关系直接可以通过数组的索引换算

  parent(i) = i/2

  left child(i) = 2*i + 1

  right child(i) = 2*i + 2

3)对于最大堆来说,每个节点的值都不大于其父节点的值,也就是根节点是最大的。

 对于最小堆来说,每个节点的值都不小于其父节点的值,也就是根节点是最小的

4)堆进行插入和删除的时间复杂度均为 O(LogN)

2.堆的应用

堆可以解决的首先就是topK问题,假设要从N大小的实数数组中找到topK,那么需要K*logN的时间。

当N=10000时

当N=10000*10000时

也就是说N可以放的足够大,K可以适当大一些,经过本机实验,堆在单机单线程下只需要14s就可以处理一亿数据的Top1W操作。

3.堆的实现

堆的操作主要是两个 add、remove,有些地方也会存在buildHeap的操作,我们分别捋一下它们的思路。

一下基于一个最大堆。

1) add操作,添加元素

思路:

将添加的元素放在数组尾部,进行“上浮”操作。也就是比较父元素,如果大于父元素则上浮。

实际长度 ++

        /**
         * 插入过程需要上浮比较
         * @param node
         */
        public void insert(Node node){
            if(actualSize == MAX_N){
                System.out.println("当前堆已满!");
                return;
            }

            // 插入
            // 插入节点
            nodes[actualSize] = node;

            // 上浮过程
            // 调整堆,从这个节点开始与其夫节点进行比较,如果大于父节点则交换上浮
            for(int i = actualSize; i > 0 ;i = getParentIndex(i)){
                Node current = nodes[i];
                Node parent = nodes[getParentIndex(i)];
                if(parent.data < current.data){
                    nodes[i] = parent;
                    nodes[getParentIndex(i)] = current;
                }
            }
            actualSize ++;
        }

2) remove操作,删除元素

思路:

获得堆顶的值,然后用堆底节点替换堆顶节点。并针对此节点进行"下沉"操作,所谓下沉,就是如果比较当前元素与左右子节点的值,如果比它们小,则与最大者交换。

实际长度 --

 /**
         * 删除过程需要下沉比较
         * @param node
         */
        public void remove(Node node){
            if(actualSize == 0){
                System.out.println("当前堆已空!");
                return;
            }

            if(actualSize == 1){
               actualSize --;
               return;
            }

            // 删除过程
            // 使用最后一个节点替换掉顶点
            Node tailNode = nodes[actualSize-1];
            nodes[0] = tailNode;
            actualSize --;

            // 下沉比较
            for(int i = 0; i < actualSize;){
                Node current = nodes[i];

                Node leftChild = null;
                if(getLeftChildIndex(i) < actualSize){
                   leftChild = nodes[getLeftChildIndex(i)];
                }
                Node rightChild = null;
                if(getRightChildIndex(i) < actualSize){
                    rightChild = nodes[getRightChildIndex(i)];
                }

                if(leftChild == null && rightChild == null){
                    return;
                }

                if(leftChild != null && leftChild.data > current.data){
                    if(rightChild != null && rightChild.data > leftChild.data){ // 右枝最大
                        nodes[i] = rightChild;
                        nodes[getRightChildIndex(i)] = current;
                        // i走右枝
                        i = getRightChildIndex(i);
                        continue;
                    }else{ // 左枝最大
                        nodes[i] = leftChild;
                        nodes[getLeftChildIndex(i)] = current;
                        // i走左枝
                        i = getLeftChildIndex(i);
                        continue;
                    }
                }else{
                    if(rightChild != null && rightChild.data > current.data){ // 右枝最大
                        nodes[i] = rightChild;
                        nodes[getRightChildIndex(i)] = current;
                        // i走右枝
                        i = getRightChildIndex(i);
                        continue;
                    }else if(rightChild != null && rightChild.data <= current.data){ // 当前点最大
                        //保持现状,直接结束循环
                        break;
                    }
                }
            }
        }

3)buildHeap操作,给一个数组,进行建立堆操作

思路:

  遍历数组的每一个节点,针对每个节点进行"上浮"操作.

Code:

package ds6.heap;

import java.util.Arrays;

public class Heap {
    static class Node{
        long data;

        public Node(long data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    ‘}‘;
        }
    }

    /**
     * TopN最大值堆
     */
    static class TopNMaxHeap{
        int MAX_N = 10; // Top N ,指定堆的最大大小
        Node[] nodes;
        int actualSize = 0;

        public TopNMaxHeap(int MAX_N) {
            this.MAX_N = MAX_N;
            nodes = new Node[MAX_N];
        }

        public void foreachPrint(){
            System.out.println(Arrays.toString(nodes));
        }

        /**
         * 删除过程需要下沉比较
         * @param node
         */
        public void remove(Node node){
            if(actualSize == 0){
                System.out.println("当前堆已空!");
                return;
            }

            if(actualSize == 1){
               actualSize --;
               return;
            }

            // 删除过程
            // 使用最后一个节点替换掉顶点
            Node tailNode = nodes[actualSize-1];
            nodes[0] = tailNode;
            actualSize --;

            // 下沉比较
            for(int i = 0; i < actualSize;){
                Node current = nodes[i];

                Node leftChild = null;
                if(getLeftChildIndex(i) < actualSize){
                   leftChild = nodes[getLeftChildIndex(i)];
                }
                Node rightChild = null;
                if(getRightChildIndex(i) < actualSize){
                    rightChild = nodes[getRightChildIndex(i)];
                }

                if(leftChild == null && rightChild == null){
                    return;
                }

                if(leftChild != null && leftChild.data > current.data){
                    if(rightChild != null && rightChild.data > leftChild.data){ // 右枝最大
                        nodes[i] = rightChild;
                        nodes[getRightChildIndex(i)] = current;
                        // i走右枝
                        i = getRightChildIndex(i);
                        continue;
                    }else{ // 左枝最大
                        nodes[i] = leftChild;
                        nodes[getLeftChildIndex(i)] = current;
                        // i走左枝
                        i = getLeftChildIndex(i);
                        continue;
                    }
                }else{
                    if(rightChild != null && rightChild.data > current.data){ // 右枝最大
                        nodes[i] = rightChild;
                        nodes[getRightChildIndex(i)] = current;
                        // i走右枝
                        i = getRightChildIndex(i);
                        continue;
                    }else{ // 当前点最大
                        //保持现状,直接结束循环
                        break;
                    }
                }
            }
        }

        /**
         * 插入过程需要上浮比较
         * @param node
         */
        public void insert(Node node){
            if(actualSize == MAX_N){
                System.out.println("当前堆已满!");
                return;
            }

            // 插入
            // 插入节点
            nodes[actualSize] = node;

            // 上浮过程
            // 调整堆,从这个节点开始与其夫节点进行比较,如果大于父节点则交换上浮
            for(int i = actualSize; i > 0 ;i = getParentIndex(i)){
                Node current = nodes[i];
                Node parent = nodes[getParentIndex(i)];
                if(parent.data < current.data){
                    nodes[i] = parent;
                    nodes[getParentIndex(i)] = current;
                }
            }
            actualSize ++;
        }

        public int getParentIndex(int x){
            return (x-1)/2;
        }

        public int getLeftChildIndex(int x){
            return 2*x + 1;
        }

        public int getRightChildIndex(int x){
            return 2*x + 2;
        }
    }

    public static void main(String[] args) {
        TopNMaxHeap topNMaxHeap = new TopNMaxHeap(10);
        topNMaxHeap.insert(new Node(27));
        topNMaxHeap.insert(new Node(33));
        topNMaxHeap.insert(new Node(30));
        topNMaxHeap.insert(new Node(31));
        Node nodeToRemove = new Node(40);
        topNMaxHeap.insert(nodeToRemove);
        topNMaxHeap.insert(new Node(20));
        topNMaxHeap.foreachPrint();
        topNMaxHeap.remove(nodeToRemove);
        topNMaxHeap.foreachPrint();
    }

}

result:

[Node{data=40}, Node{data=33}, Node{data=30}, Node{data=27}, Node{data=31}, Node{data=20}, null, null, null, null]
[Node{data=33}, Node{data=31}, Node{data=30}, Node{data=27}, Node{data=20}, Node{data=20}, null, null, null, null]

4.堆排序

因为堆的这个特性,通过不断poll出堆顶元素就可以对元素列表进行排序。

测试:

    public static void test2(){
        int[] toSort = new int[]{
                7,10,6,8,9,3,5,4
        };

        TopNMaxHeap heap = new TopNMaxHeap(toSort.length);
        for(int i = 0 ; i < toSort.length; i++ ){
            heap.insert(new Node(toSort[i]));
        }

        int[] result = new int[toSort.length];
        for(int i = 0; i < toSort.length; i++ ){
            result[i]=(int)heap.nodes[0].data;
            heap.remove(heap.nodes[0]);
        }

        System.out.println(Arrays.toString(result));

    }

result:

[10, 9, 8, 7, 6, 5, 4, 3]

原文地址:https://www.cnblogs.com/yosql473/p/10769650.html

时间: 2024-08-01 00:34:35

[数据结构] 2.7 Heap 堆的相关文章

Stack栈 Heap堆

Stack(栈) 栈(stack) 又名堆栈,它是一种运算受限的线性表.其限制是仅允许在表的一端进行插入和删除运算.这一端被称为栈顶,相对地,把另一端称为栈底.向一个栈插入新元素又称作进栈.入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素:从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素. 在计算机科学中是限定仅在表尾进行插入或删除操作的线性表.栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数

D&amp;F学数据结构系列——二叉堆

二叉堆(binary heap) 二叉堆数据结构是一种数组对象,它可以被视为一棵完全二叉树.同二叉查找树一样,堆也有两个性质,即结构性和堆序性.对于数组中任意位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元2i+1中,它的父亲在[i/2](向下取整)中. 因此,一个数据结构将由一个数组.一个代表最大值的整数.以及当前的堆的大小组成.一个典型的优先队列(priority queue)如下: 1 #ifndef _BinHeap_H 2 struct HeapStruct; 3 type

【数据结构】二叉堆

看到一篇很好的博文,来自http://blog.csdn.net/morewindows/article/details/6709644 下面是博文内容 堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆).

【数据结构】左式堆

复杂度: 实现: 1 #ifndef LEFTIST_HEAP_H 2 #define LEFTIST_HEAP_H 3 4 #include "dsexceptions.h" 5 #include <iostream> 6 using namespace std; 7 8 // Leftist heap class 9 // 10 // CONSTRUCTION: with no parameters 11 // 12 // ******************PUBLI

【数据结构】优先队列和堆

优先队列(priority queue) 对于一般的队列是在队列的尾部添加数据,在头部删除,以便先进先出. 而优先队列中的每个元素都有一个优先级,每次将一个元素放入队列中,而每次出队时都是将优先级最大的那个元素出队,称为高进先出(largest-in,first-out). 优先队列必须实现以下几个操作 1.入队(push):将一个元素放入队列中. 2.出队(pop):将优先级最高的元素从队列中删除. 要实现上面的操作可以维护一个有序的链表.每次插入数据时维护整个链表有序,即使用插入法,每次出队

java 中的内存分为四个部分:stack(栈),heap(堆),data segment

http://www.qdmm.com/BookReader/113167,54166719.aspx http://www.qdmm.com/BookReader/113167,54166867.aspx http://www.qdmm.com/BookReader/113167,54166868.aspx http://www.qdmm.com/BookReader/113167,54166869.aspx http://www.qdmm.com/BookReader/113167,5416

linux heap堆分配

heap堆分配在用户层面:malloc函数用于heap内存分配 void* malloc(size_t size); 进程的虚拟内存地址布局: 对用户来说,主要关注的空间是User Space.将User Space放大后,可以看到里面主要分为如下几段: Code:这是整个用户空间的最低地址部分,存放的是指令(也就是程序所编译成的可执行机器码) Data:这里存放的是初始化过的全局变量 BSS:这里存放的是未初始化的全局变量 Heap:堆,这是我们本文重点关注的地方,堆自低地址向高地址增长,后面

Java Stack栈和Heap堆的区别

首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: 堆存储: heapstorage 堆存储分配: heapstorage allocation 堆存储管理: heap storage management 栈编址: stack addressing 栈变换:stack transformation 栈存储器:stack memory 栈单元: stack cell 接着,总结在Jav

数据结构 之 二叉堆(Heap)

注:本节主要讨论最大堆(最小堆同理). 一.堆的概念 堆,又称二叉堆.同二叉查找树一样,堆也有两个性质,即结构性和堆序性. 1.结构性质: 堆是一棵被全然填满的二叉树.有可能的例外是在底层.底层上的元素从左到右填入.这种树称为全然二叉树(complete binary tree).下图就是这样一个样例. 对于全然二叉树,有这样一些性质: (1).一棵高h的全然二叉树,其包括2^h ~ (2^(h+1) - 1)个节点.也就是说.全然二叉树的高是[logN],显然它是O(logN). (2).全然