`
feargod
  • 浏览: 44157 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

优先队列探究

阅读更多

 

优先队列探究

         队列的特点是先进先出。通常都把队列比喻成排队买东西,大家都很守秩序,先排队的人就先买东西。

         但是优先队列有所不同,它不遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出。通常把优先队列比喻成现实生活中的打印。一个打印店里有很多打印机,每台机器的性能不一样,有的打印机打印很快,有的打印机打印速度很慢。当这些打印机陆陆续续打印完自己的任务时进入排队等候状态。如果我这个时候要打印一份文件,我选的不是第一个排队的打印机,而是性能最好,打印最快的打印机。

         优先队列的特点就是将存储的元素根据其优先权的高低,先取出优先权高的元素。所以,优先队列的实现方法有很多。

如果优先权的范围是已知的,那么就可以尝试用一个二维数组来实现优先队列。每一行表示一个优先级别。例如用大小为a[10][10]的二维数组来实现一个优先队列,a[0]表示一个优先级别,里面存放优先级为0的元素,a[10]则存放优先级最高的元素。这样根据元素的优先级进行存储,取出元素的时候,根据优先级,先取出优先级最高的元素。

上面的方法在优先权范围已知且比较集中可以估计的情况下可以适用,但是如果优先权的范围不清楚,或者间隔很大,就不再使用。实现优先队列也可以用一个链表队列加以实现。链表的节点数据包含两个部分,队列的数据项和该数据项的优先权。将元素存入链表,需要用时,遍历链表,查找优先权最大的数据项。

还可以用下面的方法。用一个数组来存放优先权,另外一个相应的数组存放要存放的元素。由于,不同的元素可能会有相同的优先权。所以,存放元素的数组中不是直接存放元素,而是存放链表,就像用挂链法来解决哈希冲突一样。当有相同优先权的元素不止一个时,则挂在相应数组索引位置的链表上,如图1。根据优先权数组中存放的优先权,来取得优先权最大的元素。由于数组查找比链表要快,所以,这个方法比上面的方法的改进。

 

 

 

Java来实现上述的优先队列:

四个私有属性:

 

private int[] priority
private ElementNode[] elements
private int highest
private int manyItems
private int PriotityCount

 priority数组是用来存放优先权的,初始的值都是0,之后赋予的优先权值必须都大    于0

 

Elements是用来存放元素链表的数组。初始值都是null,然后往里面挂上链表。

highest元素是记录优先权最大的元素的数组索引位置,这样,可以方便使用,提高效  率。因为每次要查找优先权最大的元素时,不需要再重新查找。

manyItems用来记录队列中已经存放的元素个数。

PriotityCount用来记录不同优先权的数目。因为有链表的存在,manyItems不能衡量  数组是否满了,而PriotityCount对应的是优先权数组中的使用优先权个数,可以衡量数  组使用情况。

两个私有方法:

 

private int getHighest(){}
private int contains(int priority){}

 private int getHighest(){}方法是用来取得队列中优先权最大的元素的数组索引位  置。

 

private int contains(int priority){}方法是用来查找队列中是否存在指定优先  权。如果存在,返回优先权所在的数组索引位置,如果不存在,则返回-1

三个公有方法:

 

public Object getFront(){}
public void insert(Object item, int priority){}
public Object removeFront(){}

 public Object getFront(){}方法是用来取得优先权最大的元素的。

 

 public void insert(Object item, int priority){}方法是用来往队列中添加元素  的。衡量优先权的标准可以很多,这里是直接要求用户在存储元素的时候存储优先权。这个  队列也还没有考虑队列满了以后扩充的情况,所以如果满了会报错。在插入元素时,先要查  看这个元素所对应的优先权是否已经存在,如果已经存在了,那就直接挂到相应数组索引位  置的链表下面。如果不存在,则存放在一个还没有存放值的数组位置中。如果加入元素成  功,要检查是否比原来最大的优先权还要大,如果是,则把highest标记为这个新插入元素  的索引位置。

 public Object removeFront(){}方法是用来删除优先权最大的元素的。删除成功时,  会返回被删除的元素值,否则返回null。删除优先权最大的元素后,还要查找出新的优先  权最大的值,并且让highest指向这个值的索引位置。

 示例代码:

 

package cn.priorityQueue;


/**
 * 这个优先队列是基于数组的。
 * 实现方法是,用两个数组,一个存放元素的权限,另外一个存放元素的值,两个数组的位置相互对应。
 * 往这个队列中存储元素,需要放入两个值,一个是元素的优先级值,另外一个是元素的值
 * @author William Job
 *
 */
public class PriorityQueue01 {
	//这个数组priority用来表示每个元素的优先权
	private int[] priority;
	//这个数组element用来存储每个元素的值
	private ElementNode[] elements;
	//这个值highest用来指向最高优先权的元素
	private int highest = 0;
	//manyItems用来计数已经存储的元素个数
	private int manyItems;
	//PriorityCount用来计数已经存储的优先权个数
	private int PriotityCount;
	
	public PriorityQueue01(int initialCapacity){
		priority = new int[initialCapacity];
		elements = new ElementNode[initialCapacity];
		manyItems = 0;
		PriotityCount = 0;
	}
	
	/**
	 * 这个方法是用来取得优先权最大的值的。
	 * @throws IllegalAccessException 
	 * @return:返回拥有最大优先权的值
	 */
	public Object getFront(){
		if(manyItems == 0){
			throw new IllegalArgumentException("没有元素!");
		}
		return elements[highest].element;
	}
	
	
	/**
	 * 这个方法用来向队列中添加一个元素
	 * 在这个优先队列的实现中,必须同时给定元素的值和元素的优先值
	 * @param item:元素的值
	 * @param priority:元素的优先值,必须大于0
	 * @throws Exception 
	 */
	public void insert(Object item, int priority){
		if(PriotityCount >= this.priority.length){
			throw new IllegalArgumentException("队列已满");
		}
		
		if(priority <= 0){
			throw new IllegalArgumentException("优先权必须大于0!");
		}
		int add = contains(priority);
		if(add >= 0){
			ElementNode node = new ElementNode();
			node.element = item;
			node.link = elements[add];
			elements[add] = node;
			manyItems ++;
		}
		else{
			ElementNode node = new ElementNode();
			node.element = item;
			if(this.priority[PriotityCount] == 0){
				elements[PriotityCount] = node;
				
				add = PriotityCount;
			}
			else{
				for(int j = 0; j < this.priority.length; j ++){
					if(this.priority[j] == 0){
						add = j;
					}
				}
				elements[add] = node;
			}
			this.priority[add] = priority;
			manyItems ++;				
			PriotityCount ++;
		}
		
		if(this.priority[add] > this.priority[highest]){
			highest = add;
		}
	}
	
	/**
	 * 这个方法是用来取得队列中优先权最大的元素的
	 * @return:返回优先权最大的元素的索引位置
	 */
	private int getHighest(){
		int index = -1;
		int temp = 0;
		for(int i = 0; i < priority.length; i ++){
			if(priority[i] > temp){
				temp = priority[i];
				index = i;
			}
		}
		return index;
	}
	
	
	/**
	 * 这个方法用来查找队列中是否已经存在这个优先权
	 * @param priority:要查找的优先权
	 * @return:如果这个优先权已经存在则返回这个优先权相应的索引位置,如果不存在则返回-1
	 */
	private int contains(int priority){
		int index = -1;
		for(int i = 0; i < PriotityCount; i ++){
			if(this.priority[i] == priority){
				index = i;
			}
		}
		return index;
	}
	
	/**
	 * 这个方法用来删除优先权最大的元素,同时返回这个元素。
	 * 当删除这个元素后,如果这个索引位置再也没有相应的元素,则这个索引位置的优先权赋值为0
	 * @return:队列中优先权最大的元素
	 */
	public Object removeFront(){
		ElementNode temp = elements[highest];
		if(elements[highest].link != null){
			elements[highest] = elements[highest].link;
		}
		else{
			elements[highest] = null;
			priority[highest] = 0;
			PriotityCount --;
		}
		highest = getHighest();
		manyItems --;
		return temp.element;
	}
	
	

}



package cn.priorityQueue;
/**
 * 这个是元素的节点类。
 * 这个类中有两个属性:
 * element存放元素的值,link指向下一个节点
 * @author William Job
 *
 */
public class ElementNode {
	
	//元素的值
	public Object element;
	//指向下一个元素的地址
	public ElementNode link;
	public Object getElement() {
		return element;
	}
	public void setElement(Object element) {
		this.element = element;
	}
	public Object getLink() {
		return link;
	}
	public void setLink(ElementNode link) {
		this.link = link;
	}
	
	
}

  JDK实现的优先队列PriorityQueue研究


JDK中的java.util.PriorityQueue实现方法不同于上面这些方法,它是基于堆的。

   堆本质是一棵二叉树,其中所有的元素都可以按全序语义(参见附录说明)进行比较。用  堆来进行存储需要符合以下规则:

1.数据集中的元素可以用全序语义进行比较;

2.每个节点的元素必须大于或小于该节点的孩子节点的元素;

3.堆是一棵完全二叉树。

用堆来实现优先队列,跟节点始终是优先权最大的元素节点。

插入的思路是这样的,当插入一个元素时。先将这个元素插入到队列尾,然后将这个新插入的元素和它的父节点进行优先权的比较,如果比父节点的优先权要大,则和父节点呼唤位置,然后再和新的父节比较,直到比新的父节点优先权小为止,参见图2和图3

 

 

 

这个寻找新插入元素位置的过程对应于java.util.PriorityQueue源代码中的

 

    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
}

    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

 咦?不是二叉树结构吗?不是节点吗,为什么这里看到的是操作数组?我原来有这样的   疑问,因为之前自己实现过的二叉树只是止于链式结构。然而,树存在如下特点:


1.根节点的数据总是在数组的位置[0]

2.假设一个非跟节点的数据在数组中的位置[i],那么它的父节点总是在位置[(i-1)/2]

3.假设一个节点的数据在数组中的位置为[i],那么它的孩子(如果有)总是在下面的这两个位置:

         左孩子在[2*i+1]

         右孩子在[2*i+2]

基于这些特点,用数组来表示数会更加方便。源代码中int parent = (k - 1) >>>     1等价于int parent = (k - 1) / 2;对于这里涉及到的位运算,如果有不太明白的,   可以参见附录文档。

从优先队列中删除优先权最大的元素的思路是将队列尾的元素值赋给跟节点,队列为赋   值为null。然后检查新的根节点的元素优先权是否比左右子节点的元素的优先权大,如果   比左右子节点的元素的优先权小,就交换位置,重复这个过程,直到秩序正常。参见图4和   图5

 

 

 

 

  这个删除根节点后,重新恢复合理顺序过程对应于源代码中

 

    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

    关于优先队列的探究目前进行到这里,和大家探讨,里面还有很多内容可以深入探究。

 

 附:

 

 全序语义:

    

  一个类的全序语义要求定义6种比较操作符(==!=>=<=><),以形成符合以下要求的全序:

1. 等同性:当且仅当xy的值相同时,(x == y)为真。

 2.完全性:对于任何两个值xy,下面三种比较中总有一个为真:

(x < y)(x == y)(x . y)

3. 一致性:对于任何两个值xy:

(x>y)(y<x)一致

(x>=y)((x>y)||(x==y))一致

(x<=y)((x<y)||(x==y))一致

(x!=y)!(x==y)一致

4.传递性:对于任何三个值(xyz),如果(x<y)(y<z),那么(x<z)

 

 

  关于位运算,这里有两篇材料介绍地非常不错,可以参考:

  http://flowercat.iteye.com/blog/380859

  http://www.blogjava.net/rosen/archive/2005/08/12/9955.html

  http://topic.csdn.net/u/20080626/20/59a05c26-acb3-4d74-a153-        711ce3a664ff.html

 其余参考资料:

 数据结构Java语言描述  【美】Michael Main  孔芳 周丽琴译

 数据结构与算法C#语言描述 【美】Milchael McMillan  吕秀锋 崔睿译

 Java核心技术卷II

 JDK API文档

 

 

 

 

3
1
分享到:
评论
1 楼 omeweb 2012-11-12  
二维数组来实现优先队列,的确很奇特....是你的idea吗?

相关推荐

    quene队列.zip

    队列的变种有优先队列,其中元素根据优先级而不是FIFO原则进行处理;还有阻塞队列,在并发环境中用于线程间的通信和同步。 在压缩文件“quene.zip”中,可能包含关于队列的各种示例代码、讲解文档或者练习题,用于...

    基于C++进行单源最短路径算法(SSSP)的探究【100012197】

    在实现SSSP时,通常会用到优先队列(如二叉堆)来存储待更新的节点,以便于高效地获取当前最短路径。 "delta-stepping"算法是求解SSSP的一种高效方法,它基于Dijkstra算法的思想,但通过分层更新节点的最短路径来...

    数据结构——栈和队列

    通过PPT的讲解和实例演示,我们可以更直观地掌握这些概念,并将它们应用到实际问题中,如深度优先搜索(DFS)和广度优先搜索(BFS)等算法中。 总之,栈和队列是数据结构的基础,理解和熟练运用它们是成为一名优秀...

    图的深度优先搜索和广度优先搜索

    通过这个实验,你将有机会深入探究这些算法的内部运作,增强你在数据结构和算法领域的专业知识。这不仅有助于你的学业,也将对你的职业生涯产生积极影响,特别是在软件开发、数据分析和人工智能等相关领域。

    动态优先级队列的离散时间排队分析

    本文探讨了动态优先级队列在离散时间下的排队分析,重点研究了D-MAP/PH/1模型,并利用矩阵分析方法对其稳态分布进行了深入探究。 #### 动态优先级队列的优势 动态优先级队列相比于静态优先级策略,能够更灵活地...

    哈弗曼编码 数据结构

    3. **构建优先队列**:使用最小堆(优先队列)存储这些节点,根据频率进行排序,频率小的节点优先级更高。 4. **合并节点**:从优先队列中取出两个频率最小的节点,合并成一个新的节点,该节点的频率是两个子节点的...

    网络爬虫技术探究.doc

    这种策略的优点在于能确保先访问到网页的主干部分,避免陷入深度优先搜索可能导致的局部最优情况,即优先抓取深层但不重要的网页。 在实现广度优先爬行的过程中,通常需要解决以下几个关键问题: 1. **URL管理**:...

    网络爬虫技术探究-本科毕设论文.doc

    实施广度优先爬行的关键在于正确设计数据结构(如队列)和遍历机制,确保每个URL得以依次处理。 2. **数据存储**:在爬虫系统实现过程中,数据存储是一个关键环节。通常需要一个数据库来保存抓取的URL和相关页面...

    进程调度 时间片轮转

    每个进程在进入就绪队列时,其优先数会增加1。这意味着如果一个进程等待更长时间才获得执行,它的优先数将会更高,理论上它将有更大的机会在下一轮调度中被选中。然而,一旦进程开始执行并用完一个时间片,其优先数...

    标准模板库(STL)学习探究

    3. **容器适配器**:主要有`stack`(栈)、`queue`(队列)和`priority_queue`(优先队列),它们基于基础容器实现特定的数据结构行为,如后进先出(LIFO)的栈和先进先出(FIFO)的队列。 ### 迭代器 迭代器是STL中连接...

    爬虫探究20220801

    同时,爬虫还会根据设定的规则(如深度优先或广度优先)决定下一步的抓取顺序。抓取的网页内容会被存储,并进行预处理,如HTML去噪、内容提取等,以便后续分析。 在开发爬虫时,常见的编程语言有Python、Java和...

    数据结构各种习题 很好很强大

    堆是一种特殊的树形数据结构,满足最大堆或最小堆的性质,常用于实现优先队列。在da09.htm或da10.htm中可能会有建立堆、调整堆、堆排序等相关习题。 通过对这些习题的练习,你可以巩固对数据结构的理解,提升算法...

    iOS GCD 多线程

    接下来,我们将通过示例来探究串行子队列的优先级如何影响任务的执行顺序。 ```objective-c dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial_queue", DISPATCH_QUEUE_SERIAL); dispatch...

    数据结构课程设计课作业资料

    另外,堆(如优先队列)和B树/B+树常用于数据库索引。 4. **图**:图结构用于表示复杂关系,如社交网络、网页链接等。图的遍历算法(深度优先搜索和广度优先搜索)是解决许多问题的关键。 5. **哈希表**:通过哈希...

    数据结构实验与实训教程

    6. **堆排序和优先队列**:堆是一种能快速找到最大或最小元素的数据结构,常用于实现堆排序算法和优先队列。 7. **字符串**:在文本处理和搜索中,字符串数据结构扮演着重要角色。常见的字符串操作有模式匹配、动态...

    数据结构题目

    5. **堆**:堆是一种特殊的树形数据结构,满足堆属性(最大堆或最小堆),常用于实现优先队列。堆排序算法就是基于这种数据结构。 6. **哈希表**:哈希表通过哈希函数将键映射到数组的特定位置,实现快速查找。它的...

    20200103-王正正-js的执行顺序和闭包的探究.docx

    函数提升优先于变量提升,即使变量声明在函数声明之后,函数也会先被提升。在上面的例子中,虽然`var a = 3;`在函数定义之后,但`a`的函数定义仍然被提升到顶部。 总的来说,JavaScript的执行顺序和闭包是其核心...

    宽度优先搜索

    - **图的遍历**:当需要对图进行层次遍历,探究图的连通性时,BFS非常适用。例如,它可以用于检测图中是否存在多条路径,或者确定两个节点之间是否存在路径。 - **最短路径**:在简单图中,即图中每条边的权重都为...

    K310.x 数据结构

    5. **堆**:堆是一种特殊的树形数据结构,满足堆性质(最大堆或最小堆),常用于实现优先队列。在K3系统中,如进行任务调度或资源分配时,堆可以快速找到最高优先级的任务。 6. **队列**和**栈**:这两种线性数据...

    十五个经典算法研究与总结.zip_算法_编程

    例如,堆可以用来实现优先队列,而二叉树在二分查找中起到关键作用。 最后,可能会涵盖一些数值计算和优化算法,如牛顿法、梯度下降法,它们在机器学习和数值分析中极为常见。 总之,“十五个经典算法研究与总结”...

Global site tag (gtag.js) - Google Analytics