【数据结构 五】---堆

来源:互联网 发布:校园网络直播系统 编辑:程序博客网 时间:2024/05/21 11:09

在算法的总结过程中,主要是快速排序算法,发现了堆排序,然后之前对堆的概念也不是非常清楚,所以这里做一个总结,帮助自己快速上手来辅助算法的学习。2017.7.15,在总结的过程中用到了以下几个博客的知识,在此感谢:CSDN的http://blog.csdn.net/wypblog/article/details/8076324和博客园的http://www.cnblogs.com/JVxie/p/4859889.html

堆的定义

最大(最小)堆是一棵每一个节点的键值都不小于(大于)其孩子(如果存在)的键值的树。大顶堆是一棵完全二叉树,同时也是一棵最大树。小顶堆是一棵完全完全二叉树,同时也是一棵最小树。

完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
这里写图片描述

注意:
1,堆中任一子树亦是堆。
2,以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。

下图分别给出几个最大堆(大根堆)和最小堆(小根堆)的例子:
最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
这里写图片描述

一些基本术语

A用于表示堆的数组,下标从1开始,一直到n
parent(t):节点t的父节点,即floor(t/2)
leftchild(t):节点t的左孩子节点,即:2*t
rightchild(t):节点t的右孩子节点,即:2*t+1
heapsize(A):堆A当前的元素数目

需要了解的二叉树信息

这里写图片描述

深度h为该层的元素(根节点)t的log2(t),例如第0层就是log2(1)=0.
数组放入完全二叉树的时候遵循以上下标格式,例如
下标从1到9分别加入:{8,5,2,10,3,7,1,4,6}。
这里写图片描述
但此时的完全二叉树并不是一个堆 

堆的基本操作

堆支持以下的基本操作:

•build: 建立一个空堆;
•insert: 向堆中插入一个新元素;
•update:将新元素提升使其符合堆的性质;
•get:获取当前堆顶元素的值;
•delete:删除堆顶元素;
•heapify:使删除堆顶元素的堆再次成为堆。

某些堆实现还支持其他的一些操作,如斐波那契堆支持检查一个堆中是否存在某个元素。

堆的应用

堆排序

堆最常见的应用是堆排序,时间复杂度为O(N lg N)。如果是从小到大排序,用大顶堆;从大到小排序,用小顶堆。
在进行堆排序之前,首先要创建一个堆:该操作主要是将数组A转化成一个大顶堆。思想是,先找到堆的最后一个非叶子节点(即为第n/2个节点),然后从该节点开始,从后往前逐个调整每个子树,使之称为堆,最终整个数组便是一个堆。创建过程如下:

public void buildheap(int[] array, int heapsize) { // 建堆函数    for (int i = heapsize / 2; i >= 1; i--) {            heapify(array, i, heapsize);        }    }public void heapify(int[] array, int i, int heapsize) {    // 堆调整函数(这里使用的是最大堆),也就是根是大于左右子树的        int leftchild = 2 * i + 1; // 左孩子索引        int rightchild = 2 * i + 2; // 右孩子索引        int largest = 0; // 选出当前结点与左右孩子之中的最大值        if (leftchild < heapsize && array[leftchild] > array[i]) { // 当前节点的左孩子比当前节点大            largest = leftchild;        } else            largest = i;        if (rightchild < heapsize && array[rightchild] > array[largest]) {            largest = rightchild;        } // 以上的两个if判断出三者之中最大的,没有管谁是第二大的        if(largest!=i){            exchange(array, i, largest); // 把当前结点和它的最大(直接)子节点进行交换            heapify(array,largest,heapsize);//注意这里的递归操作是以防        }    }

这里写图片描述
创建好一个大顶!大顶堆后要开始进行交换,每次将堆顶和最后一个换了,换了之后数组剩下的继续重现建堆。接着重复以上的步骤

public void heapsort(int[] array, int heapsize) {        buildheap(array, heapsize); // 建好了堆(大顶堆)        for (int i = heapsize; i >= 2; i--) {            exchange(array, 1, i); // 将堆顶元素(当前最大值)与堆的最后一个元素互换(该操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法)            heapsize--; // 从堆中去掉最后一个元素            heapify(array, 1, heapsize); // 从新的堆顶元素开始进行堆调整        }    }

这里写图片描述
完整的代码如下

package 比较排序;public class HeapSort {    public static void main(String[] args) {        int[] array = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大堆排序        int len = array.length;        int heapsize = len; // 堆的大小也就是数组长度        new HeapSort().heapsort(array, heapsize);        System.out.println("堆排序结果:");        for (int i = 0; i < len; i++) {            System.out.print(array[i]);        }    }    public void heapsort(int[] array, int heapsize) {        buildheap(array, heapsize); // 建好了堆(大顶堆)        // ================================================        for (int i = 0; i < heapsize; i++) {            System.out.print(array[i]);        }// 测试点,打印堆:            // ====================================================        for (int i = heapsize; i > 1; i--) { // 堆顶和堆底互换,堆顶的索引是1,但是对应数组array[0]            exchange(array, 0, i - 1); // 将堆顶元素(当前最大值)与堆的最后一个元素互换(该操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法)            heapsize--; // 从堆中去掉最后一个元素            heapify(array, 0, heapsize); // 从新的堆顶元素开始进行堆调整,        }    }    public void buildheap(int[] array, int heapsize) { // 建堆函数        for (int i = heapsize / 2; i >= 1; i--) { // 注意,这里的i用的是堆的标号方式,从1开始。但在堆里的索引1对应数组array[0],所以传入的为i-1            heapify(array, i - 1, heapsize);        }    }    public void exchange(int[] array, int i, int j) // 交换A[i]和A[j]    {        int temp = array[i];        array[i] = array[j];        array[j] = temp;    }    public void heapify(int[] array, int i, int heapsize) { // 堆调整函数(这里使用的是最大堆),也就是根是大于左右子树的        int leftchild = 2 * i + 1; // 左孩子索引        int rightchild = 2 * i + 2; // 右孩子索引        int largest = 0; // 选出当前结点与左右孩子之中的最大值        if (leftchild < heapsize && array[leftchild] > array[i]) { // 当前节点的左孩子比当前节点大            largest = leftchild;        } else            largest = i;        if (rightchild < heapsize && array[rightchild] > array[largest]) {            largest = rightchild;        } // 以上的两个if判断出三者之中最大的,没有管谁是第二大的        if (largest != i) {            exchange(array, i, largest); // 把当前结点和它的最大(直接)子节点进行交换            heapify(array, largest, heapsize);        }    }}
原创粉丝点击