数据结构--堆的实现(下)

1,堆作为优先级队列的应用

对于普通队列而言,具有的性质为FIFO,只要实现在队头删除元素,在队尾插入元素即可。因此,这种队列的优先级可视为按 时间到达 的顺序来衡量优先级的。到达得越早,优先级越高,就优先出队列被调度。

更一般地,很多应用不能单纯地按时间的先后来分优先级,比如按CPU占用时间或者其它方式……在这种情形下,使用堆更容易表达优先级队列。

2,堆的两个性质:①结构性质--堆从结构上看是一颗完全二叉树。然而,从物理存储上看,堆的实现基本上是使用一个一维数组存储堆中所有的结点。②ordering property---这是由堆的定义决定的,如大顶堆:根结点的值要大于左右孩子的值。

由于堆具有这两个性质,故在对堆进行操作时,如插入操作、删除操作……都需要维护好这两个性质,因此:这也是为什么堆的插入、删除操作经常需要进行向上调整和向下调整,这就是为了维护堆的 ordering property。

3,建堆时间复杂度的分析

数据结构--堆的实现(上)中,分析了建堆的两种方法,时间复杂度一种为O(nlogn),一种为O(n)。现在仔细分析如下:

 1 import java.io.File;
 2 import java.io.FileNotFoundException;
 3 import java.util.Scanner;
 4
 5 public class Sort {
 6     public static void main(String[] args) throws FileNotFoundException{
 7         Scanner sc = new Scanner(new File("inputfile"));//read heap‘s element from inputfile
 8         int n = sc.nextInt();//first line in the file gives number of integers to be read
 9         ArrayMaxHeap<Integer> sortHeap = new ArrayMaxHeap<Integer>(n);
10         //build heap
11         for(int i = 0; i < n; i++)
12             sortHeap.add(sc.nextInt());//O(nlogn)
13
14         //sort phase
15         while(!sortHeap.isEmpty())
16             System.out.println(sortHeap.removeMax());
17     }
18 }

在上面for循环中的建堆操作中,添加堆中的第一个元素时,完全二叉树高度为1,调用add方法进行堆调整的最坏情况需要 Log1 时间。添加第二个元素时,完全二叉树高度为2,进行堆调整的最坏情况需要 log2 时间……添加第 i 个元素时,最坏需要 logi 时间进行堆调整。故将 n 个元素添加到堆中需要时间:

log1 + log2 + log3 + …… + log(n-1) + logn = log(1*2*3……*n) = log n!

n! = (n/e)nsqrt(2n*pi)

故 O(logn!) = O(nlogn)

同理,也可分析下 while 循环中的堆排序。removeMax()的时间复杂度为logn,n为当前堆中元素的个数。故堆排序的时间复杂度为O(nlogn)

从这里可以看出,此种建堆的方法是调用堆定义的接口来实现的。即调用 堆的接口add()来实现。

另一种建堆的方式则是直接操作堆的底层存储---一维数组 来建堆。此方法建堆的时间复杂度为O(n)

 1 Integer[] arr = new Integer[4];arr[0] = 20;arr[1] = 40;arr[2] = 30;arr[3] = 10;
 2 ArrayMaxHeap<Integer> heap2 = new ArrayMaxHeap<Integer>(arr);
 3
 4 public ArrayMaxHeap(T[] entries){
 5         heap = (T[]) new Comparable[entries.length + 1];//how to use generic array...
 6         lastIndex = entries.length;
 7         for(int index = 0; index < entries.length; index++)
 8         {
 9             heap[index + 1] = entries[index];//第0号位置不存放元素
10             System.out.println(heap[index + 1]);
11         }
12         for(int index = lastIndex / 2; index >= 1; index--)
13             reheap(index);//从最后一个非叶结点到根结点调用reheap进行堆调整操作
14     }

在第12行的for循环中,从最后一个非叶结点(lastIndex/2)开始,直接调用reheap()操作Integer数组。

private void reheap(int rootIndex){
        boolean done = false;//标记堆调整是否完成
        T orphan = heap[rootIndex];
        int largeChildIndex = 2 * rootIndex;//默认左孩子的值较大
        //堆调整基于以largeChildIndex为根的子树进行
        while(!done && (largeChildIndex <= lastIndex)){
            //largeChildIndex 标记rootIndex的左右孩子中较大的孩子
            int leftChildIndex = largeChildIndex;//默认左孩子的值较大
            int rightChildIndex = leftChildIndex + 1;
            //右孩子也存在,比较左右孩子
            if(rightChildIndex <= lastIndex && (heap[largeChildIndex].compareTo(heap[rightChildIndex] )< 0))
                largeChildIndex = rightChildIndex;
            if(orphan.compareTo(heap[largeChildIndex]) < 0){
                heap[rootIndex] = heap[largeChildIndex];
                rootIndex = largeChildIndex;
                largeChildIndex = 2 * rootIndex;//总是默认左孩子的值较大
            }
            else//以rootIndex为根的子树已经构成堆了
                done = true;
        }
        heap[rootIndex] = orphan;
    }

reheap的伪代码如下:

input:array A[0...n-1]
output: max heap in A[0...n-1]

x = n/2 - 1
while(x>=0)
    v=value at x
    siftdown(v)
    x=x-1
endwhile

时间复杂度为O(n)的分析:

假设堆的高度为 h,当reheap某个结点时,需要对 以该结点为根 的子树进行向下的调整。向下调整时根结点有两次比较(与左右孩子的比较)。

因此,假设某结点在第 i 层,0<= i <h,该结点一共需要 2(h-i)次比较。(最后一层叶结点是不需要比较的,因为建堆是从非叶结点开始)

由于是完全二叉树,故第 i 层的结点个数为 2i

总比较次数为:除去最后一层叶子结点外,其它层的所有的结点的比较次数之和.设总比较次数为S

因此,终于明白这种建堆方法的时间复杂度为O(n)了。

参考:数据结构--堆的实现(上)

时间: 2024-10-11 07:05:53

数据结构--堆的实现(下)的相关文章

数据结构--堆的实现之深入分析

一,介绍 以前在学习堆时,写了两篇文章:数据结构--堆的实现(上)   和   数据结构--堆的实现(下),  感觉对堆的认识还是不够.本文主要分析数据结构 堆(讨论小顶堆)的基本操作的一些细节,比如 insert(插入)操作 和 deleteMin(删除堆顶元素)操作的实现细节.分析建堆的时间复杂度.堆的优缺点及二叉堆的不足. 二,堆的实现分析 堆的物理存储结构是一维数组,逻辑存储结构是完全二叉树.堆的基本操作有:insert--向堆中插入一个元素:deleteMin--删除堆顶元素 故堆的类

C 数据结构堆

引言 - 数据结构堆 堆结构都很耳熟, 从堆排序到优先级队列, 我们总会看见它的身影. 相关的资料太多了, 堆 - https://zh.wikipedia.org/wiki/%E5%A0%86%E7%A9%8D 无数漂亮的图片接二连三, 但目前没搜到一个工程中可以舒服用的代码库. 本文由此痛点而来. 写一篇奇妙数据结构堆的终结代码. 耳熟终究比不过手热 ->--- 对于 heap 接口思考, 我是这样设计 #ifndef _H_HEAP #define _H_HEAP // // cmp_f

数据结构-从上往下打印二叉树

题目:从上往下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印. 分析:其实就是按层的遍历方式 #include <iostream> #include <deque> using namespace std; struct BinaryTree{ int data; BinaryTree* lchild; BinaryTree* rchild; }; void PrintLeverTree(BinaryTree* root){ if(root == NULL){ ret

基本数据结构——堆(Heap)的基本概念及其操作

基本数据结构――堆的基本概念及其操作 小广告:福建安溪一中在线评测系统 Online Judge 在我刚听到堆这个名词的时候,我认为它是一堆东西的集合... 但其实吧它是利用完全二叉树的结构来维护一组数据,然后进行相关操作,一般的操作进行一次的时间复杂度在 O(1)~O(logn)之间. 可谓是相当的引领时尚潮流啊(我不信学信息学的你看到log和1的时间复杂度不会激动一下下)!. 什么是完全二叉树呢?别急着去百度啊,要百度我帮你百度: 若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1

【C/C++学院】0828-STL入门与简介/STL容器概念/容器迭代器仿函数算法STL概念例子/栈队列双端队列优先队列/数据结构堆的概念/红黑树容器

STL入门与简介 #include<iostream> #include <vector>//容器 #include<array>//数组 #include <algorithm>//算法 using namespace std; //实现一个类模板,专门实现打印的功能 template<class T> //类模板实现了方法 class myvectorprint { public: void operator ()(const T &

【转】数据结构与算法(下)

这篇文章是常见数据结构与算法整理总结的下篇,上一篇主要是对常见的数据结构进行集中总结,这篇主要是总结一些常见的算法相关内容,文章中如有错误,欢迎指出. 一.概述 二.查找算法 三.排序算法 四.其它算法 五.常见算法题 六.总结 一.概述 以前看到这样一句话,语言只是工具,算法才是程序设计的灵魂.的确,算法在计算机科学中的地位真的很重要,在很多大公司的笔试面试中,算法掌握程度的考察都占据了很大一部分.不管是为了面试还是自身编程能力的提升,花时间去研究常见的算法还是很有必要的.下面是自己对于算法这

数据结构-堆

堆(英语:Heap),是一种拥有像树那样的特殊数据结构,或者理解为具有优先级的树.它的特点是父节点的值大于(或小于)两个子节点的值(分别称为大顶堆和小顶堆).它常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等.堆通常是一个可以被看做一棵树的数组(或ArrayList)对象.常见的堆有二叉堆.二项堆.斐波那契堆等. 二叉堆(Binary heap) 二叉堆是一种特殊的堆,实为二叉树的一种:是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子

浅谈数据结构-堆

在数据结构的世界中有一个叫堆的玩意,这玩意有什么用呢?无用,都去pq了 堆,其实就是一棵完全二叉树. “若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树”  by 谋财害命公司 百度 ↑清真的 完全二叉树 ↓ 啊那么为什么会闲的无聊出现这种奇怪的数据结构呢? 因为我们的某些算法可能需要堆来进行优化,如dj,prim. 堆可以在O(1)的时间取出最优值,但是需要O(logn)的时间修改和O(nlogn)

数据结构-----堆的基本操作和应用

(一)用仿函数实现大堆小堆 堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构. 堆结构的二叉树存储是 最大堆:每个父节点的都大于孩子节点. 最小堆:每个父节点的都小于孩子节点. 仿函数(functor),就是使一个类的使用看上去象一个函数.其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了.在实现大,小堆的过程中,有些功能的的代码,会在不同的成员函数中用到,想复用这些代码,有两种途径. 1)公共的函数,这是一个解决方法,不过函数用到的一些变量,就可