• 第六章节 优先队列/堆


    一般发送到打印机的作业 放到队列中,但这并不一定是最好的做法 ,比如可能有一个作业很重要,可以先打印,这个时候,就可以用到优先队列。

    另外,短的作业一般应该首先完成 ,因此,在运行的程序中,短的作业有更高的优先权。

    一、模型

    优先队列一定要有的两个操作:insert  ,deleteMin(找到并删除最小的).

    其中,insert相当于队列中的enqueue, deleteMin类似于队列中的dequeue.

    二、一些简单的实现 

    有几种简单的办法可以实现

    • 使用简单的链表

    在表头以O(1)进行插入,遍历以O(N)删除最小的.或者使链表保存有序的状态,则insert 要O(N), 而deleteMin要O(1).

    • 使用二权查找树

    deleteMin/ insert复杂度都是 O(logN).但是使用插找树有些过份了,因为它还支持很多别的工作,此外,在删除的最坏的情况下,会有失去平衡等问题。

    • 二叉堆

    不需要用到链,支持最坏的情形时O(logN),且插入操作平均用时为常数时间。

    三、二叉堆

    对于优先队列的实现时,使用的很普遍。和二叉查找树一样,堆也有两个性质,结构性与堆序性。对堆的操作可能破坏其中的一个,因此,堆 的操作一定要

    到堆的所有性质都满足才终止。

    • 性质结构 

    堆是一个完全填满的二叉树。一个完全二叉树可以用数组表示。对一个位置i的元素,左儿子在2i,右儿子在2i+1.父亲在i/2取整数。因此我们不用使用链,而且遍历很简单。唯一的问题是

    要先估计大小 。

    一个堆结构由一个comparable对象数组与一个代表当前堆大小的整数组成。如下,是一个堆

     

    package c6;
    
    public class BinaryHeap<AnyType extends Comparable<? super AnyType>> {
    	
    	private static final int DEFAULT_CAP=10 ;
    	private int currentSize ;
    	private AnyType [] array ;
    	
    	public BinaryHeap(){
    		
    	}
    	public BinaryHeap(int cap){
    		
    	}
    	public BinaryHeap(AnyType [] items){
    		currentSize = items.length ;
    		array = (AnyType [])new Comparable[(currentSize+2)*11/10];
    		int i=1;
    		for (AnyType item : items){
    			array[i++] = item ;
    		}
    		buildHeap() ;
    	}
    	/**
    	 * 平均2.6,也就是O(1),最坏insert最小的元素时发生,O(lgN)
    	 * @param x
    	 */
    	public void insert(AnyType x ){
    		if (currentSize== array.length-1)
    			enlargeArray(array.length*2+1) ;
    		
    		//percolate up 
    		int hole = ++ currentSize ;
    		for(; hole>1 && x.compareTo(array[hole/2])<0;hole/=2){
    			array[hole] = array[hole/2] ;
    		}
    		array[hole] =x ;
    	}
    	public AnyType findMin() throws Exception{
    		if (isEmpty())
    			throw new Exception() ;
    		return array[1] ;//array[0]没有用?
    	}
    	/**
    	 * O(lgN)--最坏和平均都是,因为要percolateDown
    	 * @return
    	 * @throws Exception
    	 */
    	public AnyType deleteMin() throws Exception{
    		if (isEmpty())
    			throw new Exception();
    		AnyType minIten = findMin();
    		//将array[currentSize]移动到空穴,再将currentSize-1
    		array[1] = array[currentSize--] ;
    		percolateDown(1) ;
    		return minIten ;
    	}
    	public boolean isEmpty(){
    		return currentSize==0 ;//?是否有问题
    	}
    	public void makeEmpty(){
    		
    	}
    	/**
    	 * 删除的时候用到
    	 * @param hole 空穴,这里是用的堆最后的一个元素 
    	 */
    	private void percolateDown(int hole){
    		int child ;
    		AnyType temp = array[hole] ;
    		for (;2*hole<= currentSize;hole = child){
    			child = 2*hole;
    			//有两儿子时(child!=currentSize),先出两个小的一个
    			if (child!= currentSize && array[child+1].compareTo(array[child])<0){
    				child++ ;
    			}
    			//空穴往下滤
    			if (array[child].compareTo(temp)<0){
    				array[hole] = array[child] ;
    			}else {
    				break ;
    			}
    		}
    		array[hole] = temp ;
    	}
    	/**
    	 * O(N),这个操作从下而上,不能反
    	 */
    	private void buildHeap(){
    		for (int i= currentSize/2 ;i>0;i--){
    			percolateDown(i);
    		}
    	}
    	private void enlargeArray(int newSize){
    		
    	}
    	
    }
    

      

     

    • 堆性质

    一个堆中,对于每一个节点X, X的父亲的关键字小于或者等于X中的关键字。因此,最小的元素就在根处。

    堆的基本操作

    insert 

    在下一个可用的位置放一个空穴:

    如果 X可以放在这个空穴中,则完成。

    如果不可,将空穴的父节点放到 空穴中,这样空穴就上移,直到X能放入空穴为止。

    这种操作是上滤。新的元素在堆中上滤直到找到正确的位置。

    如果插入的是最小元素,则要上滤到根处,将用时O(logN),平均来看,性能好很多,插入一次只要2.6次比较。性能好很多。

    deleteMin 

    找到最小元素是简单的,但是删除比较复杂。

    当删除一个最小元素时,根处出现 一个空穴,由于现在堆少了一个元素,因此堆中的最后一个元素X要移动到一个地方。

    如果X可以直接放到空穴中,删除完成 。

    如果不可以,将空穴的两儿子中小的移动到空穴,这样空穴下滤一层,重复上过程直到X可放到空穴中。

    因此 ,做法就是将X放到沿着根开始,有最小儿子的一条路径 上的一个正确 的路径上。

    对于一个节点如果只有一个儿子,我们要进行附加的测试,

    这种操作的最坏情况运行时间为O(logN),平均来说,也是O(logN).

    其它操作

    事实上一个堆所蕴含的有序信息很少,如果不对整个堆进行线性搜索,是没有办法找到任何特定的关键字的。

    buildHeap操作

    可以将N个元素insert到一个空堆中,每一个insert将花费O(1)的平均时间和O(logN)的最坏时间,因此整个过和将花费O(N)平均时间,而不是O(NlogN)最坏时间。这是一种特殊的指令,没有

    别的操作干扰。

    一般的算法 是将N项以任意的顺序放到树中,保持结构特性,然后再percolatedown (i),以构造一个堆序的树。

    四、优先队列的使用

    选择问题

    从N个元素中找出第k个最大的元素。下面给出两个在 k=N/2时,最坏以O(NlogN)运行的算法 。

    算法1

    只考虑找到第k个最小的元素,将N个元素读入数组,进行buildHeap算法 ,最后,进行k次deleteMin,得到结果。使用的时间为

    O(N+k*logN)。如果 k很大,则为O(klogN),如果 k=N/2则为O(NlogN).如k=N,则相当于给N个元素进行了排序。

    算法2

    我们维持一个大小为k的堆,根元素就是这个小集合中最小的,再读入一个新的元素,与根进行比较。(略)。

    除了不能进行find操作,堆最大的缺点是将两个堆合并是一个很困难的操作。下面讨论几个可以支持以O(NlogN)的时间进行merge的数据结构。

  • 相关阅读:
    【作业】Python面向对象
    Python使用使用第三方源(国内源:豆瓣)下载包文件 超快!!!
    【案例】Python
    【个人笔记】Python
    定义函数相关内容
    列表,for循环相关.
    while应用和函数学习
    斗地主发牌器
    字符串索引切片.
    随机生成20以内加减法,5次答题并统计正确和错误题数
  • 原文地址:https://www.cnblogs.com/chuiyuan/p/4524069.html
Copyright © 2020-2023  润新知