您的位置:首页 > 编程语言 > C语言/C++

优先队列--C语言实现与Java例子

2016-07-28 00:07 716 查看
在我自学计算机科学的过程当中,常常在不同的途径看见不同的大牛,说同一个观点:经常要解决类似的问题。比如Extreme Programming Installed的作者Ron Jeffries就说“这么多年下来,我发现所有程序都有及其相似的元素构成。例如“在集合中查找某物”。不管是雇员记录数据库还是key-value或者哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目。”

我们经常可以遇见的一种情况是,要在一系列类似的元素(打印任务,进程)中,按照某种原则,或者为了某个最优化的目的,挑选出下一个要处理的元素。比如,操作系统调度程序必须决定在若干进程中运行哪个进程。

假如某种进程调度算法是用队列在实现的,反复提取队列的第一个作业去运行它。这种策略可行,但是却不太适合。能不能按照某种最大最小的原则,来选取下一个进程运行??

如何解决这些类似的问题呢?这就需要一类特殊的队列,称之为优先队列(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更加方便快捷,可直接调用,而不必要去编写具体的插入/删除代码。快捷不止一点点。—–这算不算也是一种不重复发明轮子呢?

.

.

.

应用

队列和优先队列在不少大型项目项目中得到了应用,操作系统本身,是超大型项目。

任何操作系统内核都少不了一种编程模型:生产者和消费者。该模式中,生产者创造数据(如错误信息,或者网络包),而消费者则反过来,读取信息和处理包或者以其他方式消费这些数据。实现该模型最简单的方式就是使用队列了。

生产者将数据推进队列,消费者从队列中读取数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: