足球盘口软件
当前位置: 足球盘口软件 > 前端 >
优先队列,总结笔记

优先队列(priority queue)是允许至少两种操作的数据结构:Insert及DeleteMin(删除最小者)。相当于队列中的Enqueue、Dequeue操作。

优先队列

第六章 优先队列(堆)

优先队列可以用链表、二叉查找树、二叉堆等实现。

  队列是一个操作受限的线性表,数据只能在一端进入,另一端出来,具有先进先出的性质。有时在队列中需要处理优先级的情况,即后面进入的数据需要提前出来,这里就需要优先队列。优先队列是至少能够提供插入和删除最小值这两种操作的数据结构。对应于队列的操作,插入相当于入队,删除最小相当于出队。
  链表,二叉查找树,都可以提供插入和删除最小这两种操作。对于链表的实现,插入需要O(1),删除最小需要遍历链表,故需要O(N)。对于二叉查找树,这两种操作都需要O(logN);而且随着不停的删除最小的操作,二叉查找树会变得非常不平衡;同时使用二叉查找树有些浪费,因此很多操作根本不需要。一种较好的实现优先队列的方式是二叉堆(下面简称堆)。
  

1. 基本概念

一种特殊的队列,至少支持两种操作:Insert和DeleteMin;前者插入元素,相当于队列的enqueue,后者查找、删除、返回最小的元素,相当于队列的dequeue。

二叉堆

1. 结构性质

堆(heap)是一棵完全被填满的二叉树,有可能的例外是在底层,底层上的元素从左向右填入。这样的树称之为完全二叉树。

图片 1

一棵高为h的完全二叉树有2h到2h+1-1个节点。完全二叉树的高为logN。

完全二叉树可以用数组来表示,如果从0开始,对于数组中任意i位置的元素,其左儿子在2i+1位置上,右儿子在2i+2位置上,父节点在floor((i-1)/2)位置上。

图片 2

2.堆序性质

最小堆:对于堆中的每一个节点,其子节点的关键字都大于其的关键字,根节点处为最小值。

最大堆:对于堆中的每一个节点,其子节点的关键字都小于其的关键字,根节点处为最大值。

3.基本的堆操作

Insert:

将要插入的元素X放入到堆末尾,如果不破坏堆的结构,操作完成,否则将X上滤。

图片 3

如果欲插入的元素一直上滤到根处,那么插入的时间高达O(logN)。平均看来,这种上滤要终止的早。基本上执行一次插入平均需要2.607次比较。

DeleteMin:

对于最小堆,删除根节点,将堆末尾的节点放到根部,然后执行下滤操作。操作的平均操作时间为O(logN)。

图片 4

最小堆的其它操作:

DecreaseKey(降低关键字的值):执行上滤操作。

IncreaseKey(增加关键字的值):执行下滤操作。

Delete:将堆末尾的值放于删除的节点处,执行下滤操作。

BuildHeap(构建堆):

1.执行N次Insert操作。总运行时间为O(N)。

图片 5

2.从堆末尾节点的父节点开始,向根的方向,依次执行下滤操作。总运行时间为O(N)。

图片 6

代码:

class MinHeap(object):
    def __init__(self):
        self.heap=[]
        self._count=0
    def __len__(self):
        return self._count
    def insert(self,key):
        self.heap.append(key)
        self._count+=1
        self._up(self._count-1)
    def deleteMin(self):
        a=self.heap.pop()
        self.heap[0]=a
        self._count-=1
        self._down(0)
    def _up(self,i):
        if i>0:
            parent=(i-1)/2
            if self.heap[parent]>self.heap[i]:
                self.heap[parent],self.heap[i]=self.heap[i],self.heap[parent]
                self._up(parent)
    def _down(self,i):
        left=2*i+1
        right=2*i+2
        small=i
        if left<self._count and self.heap[i]>self.heap[left]:
            small=left
        if right<self._count and self.heap[right]<self.heap[small]:
            small=right
        if small!=i:
            self.heap[i],self.heap[small]=self.heap[small],self.heap[i]
            self._down(small)

  

queue)是允许至少两种操作的数据结构:Insert及DeleteMin(删除最小者)。相当于队列中的Enqueue、Dequeue操作。 优先队列可以用链表...

  堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。首先堆是完全二叉树(只有最下面的两层结点度能够小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树),其次任意节点的左右孩子(若有)值都不小于其父亲,这是小根堆,即最小的永远在上面。相反的是大根堆,即大的在上面。
  因为完全二叉树有很好的规律,因此可以只用数据来存储数据而不需要链表。如下图:
  图片 7
  如图保存在数组中的数据A~J,对于任意位置i,其左儿子在数组中位置为2i,其右儿子在数组中位置为2i+1,其父亲位置在floor(i/2)。
  
  堆的插入:插入堆,那么堆中元素将加一,会在最后一个叶子后添加一个叶子,如上图中的J后面。添加了一个叶子后,需要按照大小比较将这个值放到特定的位置,直到满足堆条件,这是个上滤过程。
  堆的删除最小:删除堆中最小的一个,对于小根堆来说就是删除根,删除根后,需要将下面的元素按照大小规则往上移,直到删除最后一个叶子,这是个下滤过程。
  建堆的时间复杂度为O(N),删除堆最小值的时间复杂度为O(logN),N为节点数。

2. 二叉堆

定义一个堆结构并实现数据插入和删除最小值操作:

概念

具有结构性质和堆序性质的二叉树(或者说具有堆序性质的完全二叉树)

#define max 20
struct queue
{
  int size;
  int data[max];
};

//insert a data to queue;
void insert(queue &q, int x)
{
  int tmp = q.size;  //store data at the first site data[0]
  q.data[tmp] = x;
  while ((tmp-1)/2>=0)
  {
    if(q.data[tmp] < q.data[(tmp-1)/2])
    {
      q.data[tmp] = q.data[(tmp-1)/2];
      q.data[(tmp-1)/2] = x;
      tmp = (tmp-1)/2;
    }else break;    //
  }
  q.size++;
}

//delete the minimum data in the queue
void deletemin(queue &q)
{
  int tmp = 0;
  while (tmp*2+2 < q.size)
  {
    if(q.data[tmp*2+1]<q.data[tmp*2+2])
    {
      q.data[tmp] = q.data[tmp*2+1];
      tmp = tmp*2+1;
    }
    else
    {
      q.data[tmp] = q.data[tmp*2+2];
      tmp = tmp*2+2;
    }
  }
  q.data[tmp] = q.data[q.size-1];
  q.data[q.size-1] = 0;
  q.size--;
}

int main()
{
  queue q;
  q.size = 0; //it's necessary
  insert(q,7);insert(q,5);insert(q,2);
  insert(q,3);insert(q,1);insert(q,4);
  deletemin(q);
  for(int i=0; i<q.size; i++)
    cout << q.data[i];
  return 0;
}

性质

  • 结构性质:完全二叉树
  • 堆序性质:父节点小于任意子节点

参考:
  优先队列及最小堆最大堆
  Priority Queue(Heaps)–优先队列(堆)

实现方法

数组即可, 鉴于其完全二叉树的性质,乘以2可以到达左子节点,除以2可以到达父节点。

STL中的优先队列:priority_queue

操作及其时间复杂度:(后面四种操作需要节点位置信息为基础)

  • Insert: 采用上滤,最坏为O(logN),平均只要比较2.607次,上移1.607层,O(1)
  • DeleteMin: 采用下滤,最坏为O(logN),平均为O(logN)
  • DecreaseKey:
  • IncreaseKey:
  • Delete:
  • BuildHeap: 空堆N个Insert操作来完成,总运行时间O(N)

  priority_queue为STL中实现的优先队列结构,输入存入队列会自动按照大根堆的性质保存。它的模板声明带有三个参数:
  priority_queue<Type, Container, Functional> Type 为数据类型,Container 为保存数据的容器,Functional 为元素比较方式。Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list。STL里面默认用的是 vector,比较方式默认用 operator< , 所以如果你把后面俩个参数缺省的话,优先队列就是大顶堆,队头元素最大。
  priority_queue<int,vector<int>,greater<int> >q3;//定义小的先出队

3. d堆

  • 类似二叉堆,只是节点的儿子数都是d个(除了最底层的一些节点)。
  • 有证据显示,4堆在实践中可以胜过二叉堆。

常用的函数:

4.左式堆

  push()    //插入一个数;
  pop()     //删除最小(大)的那个数;
  //front()   //返回队首元素;
  //back()    //返回队尾元素;
  top()     //返回队顶元素,没有上面两个,queue才有
  empty()   //队列是否为空
  size()    //队列中元素个数

概念

具有堆序性质和和零路径长相关的结构性质的二叉树。

priority_queue定义在头文件queue中,在此文件中还有一种数据类型queue,即单向队列,基本操作也是上面的几个函数,与双向队列deque略有差别。
基本定义:queue<int> q;

性质

  • 堆序性质:父节点小于任意子节点;
  • 结构性质:左儿子节点的零路径长不小于右儿子节点的零路径长。(=>N个节点的左式堆右路径上节点不超过[log(N+1)])
int main()
{
  queue<int> s;
  priority_queue<int> ss;
  priority_queue<int,vector<int>,greater<int> >q3;
  s.push(1);s.push(2); s.pop();
  ss.push(1);ss.push(4);ss.push(0);ss.pop();
  q3.push(1);q3.push(4);q3.push(0);q3.pop();
  return 0;
}

实现方法

采用二叉树的实现方法,每个节点增加零路径长域。

需要注意的是:优先队列priority_queue是不支持迭代器的运算的,读取元素只能在队列首部读取(top), queue也是一样的不支持,因此不能对queue进行排序,只能读取队首和队尾的数据(front,back()),同样的stack也是一样不能使用迭代器,只能读取栈顶的数据。
对于自己定义的数据类型作为队列参数,用法参考:priority_queue的用法

操作及其实践复杂度

  • Merge:从由路径节点入手,O(logN)
  • Insert:合并一个节点左式堆,O(logN)
  • DeleteMin:O(logN)

5. 斜堆

概念

具有堆序的二叉树,但不具有结构性质;其基本操作Merge和左氏堆一致,但是右路径上的节点左右儿子的交换是无条件的(除了右路径上最大节点不交换其左右儿子)

操作

  • Merge:任意单次操作最坏时间O(N),摊还时间O(logN)
  • ...

6. 二项队列(binomial queue)

概念

具有堆序的二项树的集合(森林)。

操作

  • Merge: O(logN)
  • Insert:O(logN);从空堆连续N次插入总最坏时间O(N)
  • Delete:O(logN)
上一篇:没有了 下一篇:文件描述符和重定向,符和重定向
返回顶部