背景
在多用户环境中,操作系统调度程序必须决定在若干进程中运行哪个进程。一般一个进程只能被允许运行一个固定的时间片。一种算法是使用队列。开始时作业被放到队列的末尾。调度程序将反复提取队列中的第一个作业并运行它,直到运行完毕或者该作业的时间片用完,若作业未被运行完毕就将其放到队列的末尾。这种策略一般并不太合适,因为一些很短的作业由于一味等待运行而要花费很长的时间去处理。一般说来,短的作业要尽可能快地结束,这一点很重要,因此在已经被运行的作业当中这些短作业应该拥有优先权。此外,有些作业虽不短小但很重要,也应该拥有优先权。
所以提出啦优先队列
二叉堆实现优先队列
结构性质
1.堆是一个完全填满的二叉树,称为完全二叉树。
2.可以使用数组表述 。对于数组中任一-位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则在位置| i/2上。因此,这里不仅不需要链,而且遍历该树所需要的操作也极简单下图
堆序性质
最小元由root处找到
应用这个逻辑,可以得到堆序性质。在堆中,对于每一个结点X,X的父亲中的键小于(或等于)X中的键,根结点除外(它没有父亲)’。在图6-5中左边的树是一个堆,但是,右边的树却不是(虚线表示堆序性质被破坏)。
emplate<typename Comparable>
class Binaryheap
{
public:
explicit Binaryheap(int capacity=100);
explicit Binaryheap(const vector<Comparable>&items);
bool isempty();
void insert();
const Comparable&findMin()const;
void deleteMin();
void deleteMin(const Comparable&minitem);
void makempty();
private:
int currentsize;
vector<Comparable> array;
void buildHeap();
void percolatedown(int hole);
};
ADT操作
1.insert
为将一个元素X插入到堆中,我们在下一个空闲位置创建一个空穴,因为否则该堆将不是完全树。如果X可以放在空穴中而并不破坏堆序,那么插入完成。否则,把空穴的父结点上的元素移入空穴中,这样,空穴就朝着根的方向上行一步。继续该过程直到X能被放入空穴中为止。为了插入14,我们在堆的下一个可用位置建立空穴。由于将14插入空穴破坏了堆序性质,因此将31移入空穴。在图6-7中继续这种策略,直到找出置入14的正确位置。
称为上滤;
void insert()
{
if(currentsize==array.size()-1);
array.resize(array.size()*2);
//percolate up
int hole=++currentsize;//
for(;hole>1&&array<[hole/2];hole/=2)
array[hole]=array[hole/2];
array[hole]=x;
}
2.deleteMin
deleteMin以类似于插入的方式处理。找出最小元是容易的;困难的部分是删除它。当删除一个最小元时,要在根结点建立一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。如果X可以被放到空穴中,那么deleteMin完成。不过这一般不太可能,因此我们将空穴的两个儿子中的较小者移入空穴,这样就把空穴向下推了-一层。重复该步骤直到X可以被放入空穴中。因此,我们的作法是将X置入沿着从根开始包含最小儿子的一条路径上的一个正确位置。
左边的图显示deleteMin之前的堆。删除13后,必须要正确地将31放到堆中。31不能放在空穴中,因为这将破坏堆序性质。于是,把较小的儿子14置入空穴,同时空穴下滑一层。重复该过程,由于31大于19,因此把I9置入空穴,在更下一层上建立一个新的空穴。然后,再把26置入空穴,在底层又建立一个新的空穴。最后,我们得以将31置入空穴中。这种一般的策略叫作下滤(percolate down)。在其实现例程中我们使用类似于insert例程中用过的技巧来避免进行交换操作。
void percolatedown(int hole);
{
int child;
Comparable tmp=array[hole];
for(;hole*2<=currentsize;hole=child)
{
child=hole*2;
if(child!=currentsize&&array[child+1]<array[child])
child++;
if(array[child]<tmp)
array[hole]=array[child];
else
break;
}
}
在堆的实现中经常发生的错误是,当堆中存在偶数个元素时,将出现一个结点只有一个儿子的情况。我们必须以结点不总有两个儿子为前提,因此这就涉及一个附加的测试。在图6-12描述的程序中,在第40行进行了这种测试。一种极其巧妙的解决方法是始终保证算法把每一个结点都看成有两个儿子。为了实施这种解法,当堆的大小为偶数时,在每个下滤开始处,可将其值大于堆中任何元素的标记放到堆的终端后面的位置上。必须在深思熟虑以后再这么做,而且必须判断
void deleteMin();
{
if(isempty())
throw UnderflowExceprtion();
array[1]=array[currentsize--];
percolatedown(1);
}
void deleteMin(const Comparable&minitem);
{
if(isempty())
throw UnderflowExceprtion();
minitem=array[1];
array[1]=array[currentsize--];
percolatedown(1);
}
3。buildheap
有时候二叉堆通过项的原始集合来构造。这个构造函数将N项作为输入并把它们放入一个堆中。很明显,这可以通过N次连续的insert来完成。由于每个insert操作都花费O(1)的平均时间,以及O(logN)的最坏情形时间,这个算法的总的运行时间就是O(N)平均时间,其最坏情形时间为O(NlogN)。由于这是一种特殊的指令而且没有其他的操作介入,并且已知该指令可以在线性平均时间内完成,那么合理的线性时间界是可以保证的。
void buildHeap();
{
for(int i=currentsize/2;i>0;i++)
{
percolatedown(i);
}
}
完整代码
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129649.html