优先队列及最小堆最大堆

为什么优先队列里默认是堆(heap)实现,默认是优先级高的出队,定义结构体重载函数为什么要按照从小到大排序?原来是自己对优先队列还不太了解:

1 堆

1.1 简介

n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):

(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n),当然,这是小根堆,大根堆则换成>=号。//k(i)相当于二叉树的非叶结点,K(2i)则是左孩子,k(2i+1)是右孩子

若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:

树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

1.2 堆的高度

堆可以被看成是一棵树,结点在堆中的高度可以被定义为从本结点到叶子结点的最长简单下降路径上边的数目;定义堆的高度为树根的高度。我们将看到,堆结构上的一些基本操作的运行时间至多是与树的高度成正比,为O(lgn)。

1.3 堆排序

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

(1)用大根堆排序的基本思想

① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区

② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key

③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

……

直到无序区只有一个元素为止。

(2)大根堆排序算法的基本操作:

① 初始化操作:将R[1..n]构造为初始堆;

② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。

注意:

①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。

②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往

1.4 算法分析

堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。

堆排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。

由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

堆排序是就地排序,辅助空间为O(1),

它是不稳定的排序方法。

1.5 算法实现

要将初始文件R[l..n]调整为一个大根堆,就必须将它所对应的完全二叉树中以每一结点为根的子树都调整为堆。

显然只有一个结点的树是堆,而在完全二叉树中,所有序号大于n/2的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,我们只需依次将以序号为n/2,…,1的结点作为根的子树都调整为堆即可。

 #include <iostream>
using namespace std;  

const int MAX = 100;
typedef struct SQLIST
{
    int r[MAX];
    int length;
}SqList;
typedef SqList HeapType;  

void HeapAdjust(HeapType &H, int s, int m)
{
    //已知H.r[s..m]记录的关键字除H.r[s]之外均满足堆的定义,本函数调整H.r[s]
    //的关键字,使H.r[s..m]成为一个小顶堆(对其中记录的关键字而言)
    int rc = H.r[s];
    for (int j = 2 * s; j <= m; j *= 2)//沿着值较大的孩子节点向下筛选
    {
        if (j < m && H.r[j] < H.r[j+1])
        {
            ++ j;//j为值较大的记录的下标
        }
        if (rc >= H.r[j])
        {
            break;//rc应插入在位置s上
        }
        H.r[s] = H.r[j];
        s = j;
    }
    H.r[s] = rc;//插入
}  

void HeapSort(HeapType &H)
{
    //对顺序表H进行堆排序
    for (int i = H.length / 2; i > 0; -- i)//把H.r[1..H.length]建成堆,这里是为了让每个节点都是堆,因为大于n/2的点都是叶子节点,已是堆
    {
        HeapAdjust(H,i,H.length);
    }
    for (int i = H.length; i > 1; -- i)
    {
        int tmp = H.r[1];//将堆顶记录和当前未经排序子序列H.r[1..i]中最后一个记录相互交换
        H.r[1] = H.r[i];
        H.r[i] = tmp;  

        HeapAdjust(H, 1, i -1);//将H.r[1..i-1]重新调整为堆
    }
}
int main()
{
    HeapType H;
    H.r[1] = 4;
    H.r[2] = 3;
    H.r[3] = 5;
    H.r[4] = 1;
    H.r[5] = 6;
    H.r[6] = 2;
    H.length = 6;
    HeapSort(H);
    for (int i = 1; i <= H.length; ++ i)
    {
        cout << H.r[i] << " " ;
    }
    cout << endl;
    return 0;
} 

2 优先队列

2.1 简介

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

重点:优先级队列,是要看优先级的,谁的优先级更高,谁就先得到权限。不分排队的顺序!

基本操作:

                   empty() 如果队列为空返回真

                   pop() 删除对顶元素

                   push() 加入一个元素

                   size() 返回优先队列中拥有的元素个数

                   top() 返回优先队列对顶元素

在默认的优先队列中,优先级高的先出队。在默认的int型中先出队的为较大的数。

2.2 用堆实现优先队列

下面是最小堆实现的优先队列:

#include <iostream>
#include <vector>
using namespace std;  

class Heap
{
public:
    Heap(int iSize);
    ~Heap();  

    int Enqueue(int iVal);
    int Dequeue(int &iVal);
    int GetMin(int &iVal);
    void printQueue();
protected:
    int *m_pData;
    int m_iSize;
    int m_iAmount;
};
Heap::Heap(int iSize = 100)//注意这里是从0开始,所以如果根是i,那么左孩子是2*i+1,右孩子是2*i+2
{
    m_pData = new int[iSize];
    m_iSize = iSize;
    m_iAmount = 0;
}
Heap::~Heap()
{
    delete []m_pData;
}  

int Heap::Enqueue(int iVal)//进入堆
{
    if (m_iAmount == m_iSize)
    {
        return 0;
    }
    m_pData[m_iAmount ++] = iVal;
    int iIndex = m_iAmount - 1;
    while (m_pData[iIndex] < m_pData[(iIndex - 1) /2])//上浮,直到满足最小堆
    {
        swap(m_pData[iIndex],m_pData[(iIndex - 1) /2]);
        iIndex = (iIndex - 1) /2;
    }
    return 1;
}  

int Heap::Dequeue(int &iVal)//出堆
{
    if (m_iAmount == 0)
    {
        return 0;
    }
    iVal = m_pData[0];//出堆的数据
    m_pData[0] = m_pData[m_iAmount - 1];//最后一个数据放到第一个根上面
    -- m_iAmount;//总数减1
    int rc = m_pData[0];
    int s = 0;
    for (int j = 2*s +1; j < m_iAmount; j *= 2)//最后一个数放到第一个位置以后,开始下沉,来维持堆的性质
    {
        if (j < m_iAmount - 1 && m_pData[j] > m_pData[j+1])
        {
            ++ j;
        }
        if (rc < m_pData[j])//rc应该插入在s位置上
        {
            break;
        }
        m_pData[s] = m_pData[j];
        s = j;
    }
    m_pData[s] = rc;
    return 1;
}  

int Heap::GetMin(int &iVal)
{
    if (m_iAmount == 0)
    {
        return 0;
    }
    iVal = m_pData[0];
    return 1;
}  

void Heap::printQueue()
{
    for (int i = 0; i < m_iAmount; ++ i)
    {
        cout << m_pData[i] << " ";
    }
    cout << endl;
}
int main()
{
    Heap heap;
    heap.Enqueue(4);
    heap.Enqueue(1);
    heap.Enqueue(3);
    heap.Enqueue(2);
    heap.Enqueue(6);
    heap.Enqueue(5);
    heap.printQueue();  

    int iVal;
    heap.Dequeue(iVal);
    heap.Dequeue(iVal);  

    heap.printQueue();
    return 0;
} 

2.3 STL实现优先队列

使用方法:

头文件:

#include <queue>

声明方式:

1、普通方法:

priority_queue<int>q;

//通过操作,按照元素从大到小的顺序出队

2、自定义优先级:

struct cmp
{
    bool operator()(int x, int y)
    {
        return x > y;
    }
};

//其中,第二个参数为容器类型。第三个参数为比较函数。

结构体声明方式:

struct node
{
      int x, y;
       friend bool operator < (node a, node b)
      {
             return a.x > b.x; //结构体中,x小的优先级高
      }
};

priority_queue<node>q;//定义方法

//在该结构中,y为值, x为优先级。

//通过自定义operator<操作符来比较元素中的优先级。

//在重载”<”时,最好不要重载”>”,可能会发生编译错误

3 应用

首先优先队列是由堆来实现的,所以以后用到优先队列的地方,可以直接用C++中的STL来直接实现,但是最好还是自己实现几次,否则我们只会成为傻瓜,什么东西就知道怎么用,不知道是如何实现的。

优先队列适用的范围很广,比如:

1、构造哈夫曼编码

构造哈夫曼编码是找到节点集合中频率最小的两个点,然后合并节点在插入到集合中,再循环。。。

2、一些任务调度算法

比如操作系统的线程的调度算法,有的是按照优先级来调度的,每次都执行优先级较高的线程

3、合并n个有序文件为一个有序文件

首先把n个有序文件的第一个元素取出来,放到优先队列里面,然后取最小值,然后再插入元素导优先队列,取最小值。。。

4、由于优先队列内部是有堆实现的,所以适用于堆的都适用于优先队列

比如排序,找中位数,找最大的k个数

时间: 2024-10-11 00:08:11

优先队列及最小堆最大堆的相关文章

hdu 4006 The kth great number (优先队列+STB+最小堆)

The kth great number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others) Total Submission(s): 6637    Accepted Submission(s): 2671 Problem Description Xiao Ming and Xiao Bao are playing a simple Numbers game. In a roun

poj 2010 Moo University - Financial Aid(优先队列(最小堆)+ 贪心 + 枚举)

Description Bessie noted that although humans have many universities they can attend, cows have none. To remedy this problem, she and her fellow cows formed a new university called The University of Wisconsin-Farmside,"Moo U" for short. Not wish

Black Box--[优先队列 、最大堆最小堆的应用]

Description Our Black Box represents a primitive database. It can save an integer array and has a special i variable. At the initial moment Black Box is empty and i equals 0. This Black Box processes a sequence of commands (transactions). There are t

最小堆(优先队列)基本概念,即一个完整建立,插入,删除代码

堆(优先队列)priority queue特殊的队列,取出元素的顺序是依照元素的优先权(关键字)大小,而出元素进入队列的先后顺序操作:查找最大值(最小值),删除(最大值) 数组:链表:有序数组:有序链表: 采用二叉搜索树? NO 采用完全二叉树 YES堆的连个特性结构性:用数组表示的完全二叉树:有序性:任一结点的关键字是其字树所有结点的最大值(或最小值) 最大堆(MaxHeap)也称大顶堆:最大值 最小堆(MinHeap)也称"小顶堆":最小值 从根节点到任意结点路径上结点序列的有序性

数据结构-最大堆、最小堆【手动实现】

0,堆的简介 数据结构中的堆是一种特殊的二叉树,不同于 Java 内存模型中的堆. 堆必须符合以下两个条件: 是一棵完全二叉树. 任意一个节点的值都大于(或小于)左右子节点的值. 从第一点可以知道,堆适合用数组来存储. 第二点中,若父节点都大于等于左右子节点,则被称为大顶堆,反之则为小顶堆. 图-最大堆及其存储方式 0.1节点的父.子节点关系 一个节点[根节点除外]的父节点地址为其地址的二分之一,它的左子节点地址为其地址值的2倍,右子节点地址为其地址2倍加1.  例如:现在有个节点的地址为3,其

通用的最小堆(最大堆)D-ary Heap

听说有一种最小(大)堆,不限于是完全二叉树,而是完全D叉树,名为D-ary Heap(http://en.wikipedia.org/wiki/D-ary_heap).D可以是1,2,3,4,100,对于优先队列该有的功能都没有问题. 动手写一个D-ary Heap,应该不难.简单起见,不考虑像STL一样通过template传入Comp类,下面的实现要求T类型重载了operator <和operator >. template<class T> class DaryHeap { s

最小堆_最大堆

在大数查找中会遇到一类问题,例如在100亿条数据中找出 最大的(最小的) 前1000个元素.以int型4Byte为例,有1*1010*4 B = 4*1010/(230) B = 37.25G. 直接读取到内存中显然不合适,那么就需要: 首先,读取前1000个元素,建立一个最小堆(最大堆): 其次,之后每读取一个元素就与最小堆根元素(1000个数中最小值)进行比较: 如果,新元素大于(小于)堆顶元素 则,删除堆顶元素,将新元素插入堆顶.然后调整堆序,删除堆顶....循环往复 #define le

Google 面试题:Java实现用最大堆和最小堆查找中位数 Find median with min heap and max heap in Java

Google面试题 股市上一个股票的价格从开市开始是不停的变化的,需要开发一个系统,给定一个股票,它能实时显示从开市到当前时间的这个股票的价格的中位数(中值). SOLUTION 1: 1.维持两个heap,一个是最小堆,一个是最大堆. 2.一直使maxHeap的size大于minHeap. 3. 当两边size相同时,比较新插入的value,如果它大于minHeap的最大值,把它插入到minHeap.并且把minHeap的最小值移动到maxHeap. ...具体看代码 1 /*********

C++ priority_queue 最大堆、最小堆

问题描述 通常在刷题的时候,会遇到最大堆.最小堆的问题,这个时候如果自己去实现一个也是OK的,但是通常时间不太够,那么如何处理?这时,就可以借助C++ STL的priority_queue. 具体分析 需要注意的是,C++ STL默认的priority_queue是将优先级最大的放在队列最前面,也即是最大堆.那么如何实现最小堆呢? 假设有如下一个struct: struct Node { int value; int idx; Node (int v, int i): value(v), idx