优先队列--C语言实现与Java例子
2016-07-28 00:07
716 查看
在我自学计算机科学的过程当中,常常在不同的途径看见不同的大牛,说同一个观点:经常要解决类似的问题。比如Extreme Programming Installed的作者Ron Jeffries就说“这么多年下来,我发现所有程序都有及其相似的元素构成。例如“在集合中查找某物”。不管是雇员记录数据库还是key-value或者哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目。”
我们经常可以遇见的一种情况是,要在一系列类似的元素(打印任务,进程)中,按照某种原则,或者为了某个最优化的目的,挑选出下一个要处理的元素。比如,操作系统调度程序必须决定在若干进程中运行哪个进程。
假如某种进程调度算法是用队列在实现的,反复提取队列的第一个作业去运行它。这种策略可行,但是却不太适合。能不能按照某种最大最小的原则,来选取下一个进程运行??
如何解决这些类似的问题呢?这就需要一类特殊的队列,称之为优先队列(priority queue)。利用二叉堆(binary heap)来实现优先队列。
堆是一棵被完全填满的二叉树,具有两个特性:
结构性:用数组表示的完全二叉树。
序列性:任一结点的关键字是其子树所有结点的最大值(or最小值)。
最大堆(MaxHeap):任一关键字是其子树所有结点的最大值。
最小堆(MinHeap):同理。
由于完全二叉树很有规律,因此可以用数组来表示。
堆的抽象数据类型描述:
.
.
.
MaxHeap Create( int MaxSize):创建一个空的最大堆
.
.
.
Insert( MaxHeap H, ElementType item ):将元素item插入最大堆H
.
.
.
堆的插入过程,要自己一边画,一边理解。插入过程首先要保证结构性,既插入时要先不管不顾一杆插到底,i = ++H->Size直接将i指向最后一个位置。然后再来调整i的位置,以此来保证堆的有序性。
.
.
.
ElementType DeleteMax( MaxHeap H):返回H中最大元素
取出根节点(最大值)元素,同时删除堆的一个结点。
.
.
.
所有方法代码:
.
.
.
PriorityQueue在Java中被作为容器类来实现,先理解Queue更有利于掌握Java中PriorityQueue的用法。那么就先来看看Queue。
Queue既队列,生活中的经常需要排队,一般的原则是先来先被服务(FIFO)。
offer( ) 方法是将一个元素插入到队尾。
peek( )会在队列为空时返回null。
element( )会抛出NoSuchElementException异常。
poll( ) 和 remove( )方法将移除并返回队头,区别在于poll()在队列为空时返回null, 而remove()会抛出NoSuchElementException异常。
看个小例子:
PriorityQueue直接作为Queue的子类继承,于是在该例子的基础之上,编写PriorityQueue的小例子:
.
.
.
由打印结果可知,PriorityQueue默认的排序是有小到大,可以自定定义优先级。
总而言之,在学习数据结构和算法时,通过理解+手抄一两遍C语言代码,可加深对该数据结构实现的理解,但运用方面,Java更加方便快捷,可直接调用,而不必要去编写具体的插入/删除代码。快捷不止一点点。—–这算不算也是一种不重复发明轮子呢?
.
.
.
任何操作系统内核都少不了一种编程模型:生产者和消费者。该模式中,生产者创造数据(如错误信息,或者网络包),而消费者则反过来,读取信息和处理包或者以其他方式消费这些数据。实现该模型最简单的方式就是使用队列了。
生产者将数据推进队列,消费者从队列中读取数据。
我们经常可以遇见的一种情况是,要在一系列类似的元素(打印任务,进程)中,按照某种原则,或者为了某个最优化的目的,挑选出下一个要处理的元素。比如,操作系统调度程序必须决定在若干进程中运行哪个进程。
假如某种进程调度算法是用队列在实现的,反复提取队列的第一个作业去运行它。这种策略可行,但是却不太适合。能不能按照某种最大最小的原则,来选取下一个进程运行??
如何解决这些类似的问题呢?这就需要一类特殊的队列,称之为优先队列(priority queue)。利用二叉堆(binary heap)来实现优先队列。
堆是一棵被完全填满的二叉树,具有两个特性:
结构性:用数组表示的完全二叉树。
序列性:任一结点的关键字是其子树所有结点的最大值(or最小值)。
最大堆(MaxHeap):任一关键字是其子树所有结点的最大值。
最小堆(MinHeap):同理。
由于完全二叉树很有规律,因此可以用数组来表示。
堆的抽象数据类型描述:
类型名称:最大堆(MaxHeap) 数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值 操作集:最大堆H,元素item,主要操作有: MaxHeap Create( int MaxSize):创建一个空的最大堆 Boolean IsFull( MaxHeap H ):判断最大堆H是否已满 Insert( MaxHeap H, ElementType item ):将元素item插入最大堆H Boolean IsEmpty( MaxHeap H ):判断最大堆H是否为空 ElementType DeleteMax( MaxHeap H):返回H中最大元素
.
.
.
MaxHeap Create( int MaxSize):创建一个空的最大堆
typedef struct HeapStruct *MaxHeap; struct HeapStruct{ ElementType *Elements;/*存储堆元素的数组*/ int Size;//堆的当前元素个数 int Capacity;//堆的最大容量 } MaxHeap Create( int MaxSize ) { MaxHeap H = malloc( sizeof( struct HeapStruct ) ); H->Elements = malloc( (MaxSize + 1 ) * sizeof( ElementType ) ); H->Size = 0; H->Capacity = MaxSize; H->Elements[ 0 ] = MaxData;//定义哨兵为大于堆中所有可能元素的值 return H; }
.
.
.
Insert( MaxHeap H, ElementType item ):将元素item插入最大堆H
void Insert( MaxHeap H, ElementType item ) { int i; if ( IsFull( H ) ){ printf("最大堆已满"); retrun ; } i = ++H->Size;//将 i指向插入后堆中的最后一个元素的位置 for( ; H->Elements[ i / 2 ] < item; i /= 2 ){ H->Elements[ i ] = H->Elements[ i / 2 ];//向下过滤结点 } H->Elements[ i ] = item;//将item插入 }
.
.
.
堆的插入过程,要自己一边画,一边理解。插入过程首先要保证结构性,既插入时要先不管不顾一杆插到底,i = ++H->Size直接将i指向最后一个位置。然后再来调整i的位置,以此来保证堆的有序性。
.
.
.
ElementType DeleteMax( MaxHeap H):返回H中最大元素
取出根节点(最大值)元素,同时删除堆的一个结点。
ElementType DeleteMax ( MaxHeap H ) { int parent, child; ElementType MaxItem, temp; if ( IsFull( H ) ){ printf("最大堆已满"); return ; } MaxItem = H->Elements[ 1 ];//取出根节点最大值 //用最大堆中最后一个元素从根节点开始向上过滤下层结点 temp = H->Elements[ H->Size-- ];//最后一个元素在H->Elements[ H->Size--] for ( parent = 1; parent * 2 <= H->Size; parent = child ){ child = parent * 2; if ( ( child != parent * 2 ) && ( H->Elements[ child ] < H->Elements[ child + 1 ] ) ){ child++; } if ( temp >= H->Elements[ child ] ){ break; }else { H->Elements[ parent ] = H->Elements[ child ]; } } H->Elements[ parent ] = temp; retrun MaxItem; }
.
.
.
所有方法代码:
typedef struct HNode *Heap; /* 堆的类型定义 */ struct HNode { ElementType *Data; /* 存储元素的数组 */ int Size; /* 堆中当前元素个数 */ int Capacity; /* 堆的最大容量 */ }; typedef Heap MaxHeap; /* 最大堆 */ typedef Heap MinHeap; /* 最小堆 */ #define MAXDATA 1000 /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */ MaxHeap CreateHeap( int MaxSize ) { /* 创建容量为MaxSize的空的最大堆 */ MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode)); H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType)); H->Size = 0; H->Capacity = MaxSize; H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/ return H; } bool IsFull( MaxHeap H ) { return (H->Size == H->Capacity); } bool Insert( MaxHeap H, ElementType X ) { /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */ int i; if ( IsFull(H) ) { printf("最大堆已满"); return false; } i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */ for ( ; H->Data[i/2] < X; i/=2 ) H->Data[i] = H->Data[i/2]; /* 上滤X */ H->Data[i] = X; /* 将X插入 */ return true; } #define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */ bool IsEmpty( MaxHeap H ) { return (H->Size == 0); } ElementType DeleteMax( MaxHeap H ) { /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */ int Parent, Child; ElementType MaxItem, X; if ( IsEmpty(H) ) { printf("最大堆已为空"); return ERROR; } MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */ /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */ X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */ for( Parent=1; Parent*2<=H->Size; Parent=Child ) { Child = Parent * 2; if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) ) Child++; /* Child指向左右子结点的较大者 */ if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ else /* 下滤X */ H->Data[Parent] = H->Data[Child]; } H->Data[Parent] = X; return MaxItem; } /*----------- 建造最大堆 -----------*/ void PercDown( MaxHeap H, int p ) { /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */ int Parent, Child; ElementType X; X = H->Data[p]; /* 取出根结点存放的值 */ for( Parent=p; Parent*2<=H->Size; Parent=Child ) { Child = Parent * 2; if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) ) Child++; /* Child指向左右子结点的较大者 */ if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ else /* 下滤X */ H->Data[Parent] = H->Data[Child]; } H->Data[Parent] = X; } void BuildHeap( MaxHeap H ) { /* 调整H->Data[]中的元素,使满足最大堆的有序性 */ /* 这里假设所有H->Size个元素已经存在H->Data[]中 */ int i; /* 从最后一个结点的父节点开始,到根结点1 */ for( i = H->Size/2; i>0; i-- ) PercDown( H, i ); }
.
.
.
抛弃黄脸婆C,鲜嫩Java任我嘿嘿嘿
在利用C语言这种较低层次的语言对优先队列的插入、删除过程有了具体的理解之后,我尝试去做题目。。。发现。。。理解是远远不够的,要熟练到空手写算法,要有强大的记忆能力。。。然而我并没有,也不打算去一行一行的背代码。。。于是就翻到Thinking in Java一书,看看Java这种较高级的语言,是如何实现对优先队列的支持的。PriorityQueue在Java中被作为容器类来实现,先理解Queue更有利于掌握Java中PriorityQueue的用法。那么就先来看看Queue。
Queue既队列,生活中的经常需要排队,一般的原则是先来先被服务(FIFO)。
offer( ) 方法是将一个元素插入到队尾。
peek( )会在队列为空时返回null。
element( )会抛出NoSuchElementException异常。
poll( ) 和 remove( )方法将移除并返回队头,区别在于poll()在队列为空时返回null, 而remove()会抛出NoSuchElementException异常。
看个小例子:
import java.util.*; public class QueueDemo { public static void printQ( Queue queue ){ //该方法用来打印队列元素 while ( queue.peek() != null ){ System.out.print(queue.remove() + " "); } System.out.println(); } public static void main(String[] args) { Queue<Integer> queue = new LinkedList<Integer>(); Random rand = new Random(47); for ( int i = 0; i < 10; i++ ){ queue.offer(rand.nextInt( i + 10 ) ); } printQ( queue ); } } //打印结果:8 1 1 1 5 14 3 1 0 1
PriorityQueue直接作为Queue的子类继承,于是在该例子的基础之上,编写PriorityQueue的小例子:
import java.util.*; public class PriorityQueueDemo { public static void main(String[] args) { PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(); Random rand = new Random(47); for ( int i = 0; i < 10; i++ ){ priorityQueue.offer(rand.nextInt( i + 10 ) ); } QueueDemo.printQ(priorityQueue); } } //打印结果:0 1 1 1 1 1 3 5 8 14
.
.
.
C还是Java
注意PriorityQueue继承自Queue,由于在Queue中实现了printQ,所以在打印PriorityQueue时,直接调用该方法即可。由打印结果可知,PriorityQueue默认的排序是有小到大,可以自定定义优先级。
总而言之,在学习数据结构和算法时,通过理解+手抄一两遍C语言代码,可加深对该数据结构实现的理解,但运用方面,Java更加方便快捷,可直接调用,而不必要去编写具体的插入/删除代码。快捷不止一点点。—–这算不算也是一种不重复发明轮子呢?
.
.
.
应用
队列和优先队列在不少大型项目项目中得到了应用,操作系统本身,是超大型项目。任何操作系统内核都少不了一种编程模型:生产者和消费者。该模式中,生产者创造数据(如错误信息,或者网络包),而消费者则反过来,读取信息和处理包或者以其他方式消费这些数据。实现该模型最简单的方式就是使用队列了。
生产者将数据推进队列,消费者从队列中读取数据。
相关文章推荐
- C++中标准类string常用示例
- c++对象内存模型分析工具
- C++中string和int之间的转换
- leetcode 328. Odd Even Linked List 16ms beats 96.25% c++
- hdoj1879 继续畅通工程
- hdoj1233 还是畅通工程
- hdoj1232 畅通工程
- hdoj 1863 畅通工程
- hdoj1875 畅通工程再续
- hdoj5734 Acperience
- hdoj5742 It's All In The Mind
- hdoj4497
- hdoj5428 The Factor
- 2016多校联训第二场 Keep On Movin hdoj5744
- hdoj3791 二叉搜索树
- C++ 值传递、指针传递、引用传递详解
- 观察者模式-c++实现
- 2016 多校联训 Permutation Bo hdoj5753
- c语言中static的作用以及(递归,八大算法原理)
- C++11之lock_guard学习总结和代码实例