优先队列(堆)及相关操作

二叉堆(堆)

  • 堆是一颗完全二叉树:除了底层每个节点都有两个孩子,底层节点从左到右依次填入(不能有间隔)。
  • 一颗高为 h 的完全二叉树有 2 h 2 h + 1 1 个节点; N 的节点的完全二叉树的高度为 log N
  • 堆可以用数组实现:如果数组下标从1开始,每个位置 i 的左孩子下标为 2 i ,右孩子下标为 2 i + 1 ,父亲下标为 i / 2
  • 堆中每个节点的孩子都大于它自身,称为小堆。堆中每个节点的孩子都小于它自身,称为大堆。

堆的数据结构(数组实现)

  • 基本数据结构:一个存放元素的数组,堆的最大容量,当前堆的大小
typedef struct HeapStruct {     // 定义堆的结构(基于数组)
    int Capacity;               // 堆的最大容量
    int Size;                   // 堆中元素个数
    ElemType* arr;              // 堆中包含数据元素的数组的头指针
}Heap, *BinHeap;

堆的插入操作(以小堆为例)

  • 向堆中插入元素X,首先在下一个空闲位置创建一个“空穴”(满足完全二叉树)。
  • 如果X放入该“空穴”不破坏堆序性(X大于“空穴”的父节点),则插入完成。否则把“空穴”的父节点上的元素移到该空穴中。
  • 继续该操作,直到“空穴”的父节点小于X(以小堆为例),把X放入空穴。插入完成。
  • 这种策略被称为上滤
void insert(BinHeap H, ElemType x)
{
    if (isFull(H))      // 判断队列是否为满
    {
        cout << "The heap is full" << endl;
        exit(1);
    }
    int i = ++H->Size;  // 将队列的大小增1,赋值给i表示要插入的空穴
    while (x < H->arr[i / 2])   // 比较插入位置的父节点的值和要插入的元素的大小,元素值较小,则移动父节点到空穴,空穴上移。。。
    {
        H->arr[i] = H->arr[i / 2];
        i /= 2;
    }
    H->arr[i] = x;      // 停止移动的空穴赋值为x
}

堆的删除操作

  • 删除堆顶元素(最小值),也叫做优先队列出队。首先返回该元素值,将堆顶(根节点)置为“空穴”。
  • 然后选择“空穴”的较小的孩子移动到空穴,“空穴”下移。这里选取堆中最后位置的元素X作为插入的元素。
  • 接下来类似于插入操作,当X都大于“空穴”的孩子,小孩子移动“空穴”,“空穴”下移,直到X小于“空穴”的孩子。
  • 或者,“空穴”移动到堆最末尾。“空穴”停止移动,将X填入空穴。
  • 这种策略被称为下滤。
ElemType DeleteMin(BinHeap H)   // 删除优先队列(堆)中的最小值
{
    if (isEmpty(H))
    {
        cout << "The heap is empty" << endl;
        return H->arr[0];
    }
    ElemType minElem = H->arr[1];       // 要返回的最小值:堆中数组的首元素/树形结构的根节点
    ElemType lastElem = H->arr[H->Size--];  // 记录最后一个元素的值,且更新H->Size
    // 由于删除了根节点,树形结构需要进行调整
    int i = 1;
    int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 求较小的孩子
    while (H->arr[priorChild] < lastElem && priorChild <= H->Size)  // 将根节点置为空穴
    {
        H->arr[i] = H->arr[priorChild];     // 选取较小的孩子填充空穴
        i = priorChild;                     // 空穴下移,"下滤"
        priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新"优先"孩子
    }
    H->arr[i] = lastElem;       // 将原来堆里面最后一个元素的值填入空穴
    return minElem;
}

构建堆

  • 从数组中直接构建堆,既不需要依次插入元素来构建堆。
  • 采用“下滤”的策略,依次从有孩子的节点(堆大小除以2)开始至根节点结束,对这些节点依次执行“下滤”操作。
BinHeap BuildHeap(ElemType* A, int n)       // 直接由数组构建堆
{
    BinHeap H = initializer(n * 2);
    for (int i = 0;i < n;i++)
    {
        H->arr[i + 1] = A[i];
    }
    H->Size = n;            // 将元素填入堆中

    for (int i = n / 2;i > 0;i--)   // 从有孩子的节点开始执行下滤操作
    {
        int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下标
        while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size)        // 当节点i的小孩子小于其自身且其孩子的下标不超过堆大小时,执行循环
        {
            ElemType temp = H->arr[i];
            H->arr[i] = H->arr[SmallChild];
            H->arr[SmallChild] = temp;              // 交换节点i与其小孩子的位置
            i = SmallChild;                         // 节点i下移
            SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新“小孩子”
        }
    }
    return H;
}

附二叉堆实现及相关操作C/C++

#include<iostream>

using namespace std;
typedef int ElemType;

typedef struct HeapStruct {     // 定义堆的结构(基于数组)
    int Capacity;               // 堆的最大容量
    int Size;                   // 堆中元素个数
    ElemType* arr;              // 堆中包含数据元素的数组的头指针
}Heap, *BinHeap;

BinHeap initializer(int Capacity)       // 初始化二叉堆,参数为最大容量
{
    BinHeap H = new Heap;               // 创建一个堆结构
    H->Capacity = Capacity;             
    H->arr = new ElemType[Capacity];    // 为堆中数组分配空间
    H->arr[0] = -9999;      // 将数组的0元素赋值为无穷小,表示添加一条哑信息,保证该位置元素小于任何堆中元素
    H->arr[1] = 50;
    H->Size = 0;
    return H;
}

bool isEmpty(BinHeap H)
{
    return H->Size == 0;
}

bool isFull(BinHeap H)
{
    return H->Size == H->Capacity;
}


void insert(BinHeap H, ElemType x)
{
    if (isFull(H))      // 判断队列是否为满
    {
        cout << "The heap is full" << endl;
        exit(1);
    }
    int i = ++H->Size;  // 将队列的大小增1,赋值给i表示要插入的空穴
    while (x < H->arr[i / 2])   // 比较插入位置的父节点的值和要插入的元素的大小,元素值较小,则移动父节点到空穴,空穴上移。。。
    {
        H->arr[i] = H->arr[i / 2];
        i /= 2;
    }
    H->arr[i] = x;      // 停止移动的空穴赋值为x
}

ElemType DeleteMin(BinHeap H)   // 删除优先队列(堆)中的最小值
{
    if (isEmpty(H))
    {
        cout << "The heap is empty" << endl;
        return H->arr[0];
    }
    ElemType minElem = H->arr[1];       // 要返回的最小值:堆中数组的首元素/树形结构的根节点
    ElemType lastElem = H->arr[H->Size--];  // 记录最后一个元素的值,且更新H->Size
    // 由于删除了根节点,树形结构需要进行调整
    int i = 1;
    int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 求较小的孩子
    while (H->arr[priorChild] < lastElem && priorChild <= H->Size)  // 将根节点置为空穴
    {
        H->arr[i] = H->arr[priorChild];     // 选取较小的孩子填充空穴
        i = priorChild;                     // 空穴下移,"下滤"
        priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新"优先"孩子
    }
    H->arr[i] = lastElem;       // 将原来堆里面最后一个元素的值填入空穴
    return minElem;
}

BinHeap BuildHeap(ElemType* A, int n)       // 直接由数组构建堆
{
    BinHeap H = initializer(n * 2);
    for (int i = 0;i < n;i++)
    {
        H->arr[i + 1] = A[i];
    }
    H->Size = n;            // 将元素填入堆中

    for (int i = n / 2;i > 0;i--)   // 从有孩子的节点开始执行下滤操作
    {
        int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下标
        while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size)        // 当节点i的小孩子小于其自身且其孩子的下标不超过堆大小时,执行循环
        {
            ElemType temp = H->arr[i];
            H->arr[i] = H->arr[SmallChild];
            H->arr[SmallChild] = temp;              // 交换节点i与其小孩子的位置
            i = SmallChild;                         // 节点i下移
            SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1;     // 更新“小孩子”
        }
    }
    return H;
}

int main()
{
    const int rawdata[] = { 19, 13, 9, 8, 23, 39, 4, 2, 75, 100, 43, 58 };
    int tablesize = 12;

    BinHeap myHeap = initializer(tablesize*2);

    for (int i = 0;i < sizeof(rawdata) / sizeof(int);i++)
    {
        insert(myHeap, rawdata[i]);     // 向堆中插入给定数据
    }

    cout << "Prior Deque from the Heap : \n";   // 依次优先出队列
    while (!isEmpty(myHeap))
    {
        cout << DeleteMin(myHeap) << " ";
    }
    cout << endl;
    int newdata[] = { 9, 8, 23, 39, 2, 75, 100, 43, 58 };

    BinHeap newHeap = BuildHeap(newdata, 9);
    cout << "Build a new Heap directly from an array...\n"; 
    cout << "Prior Deque from the new Heap : \n";   // 依次优先出队列
    while (!isEmpty(newHeap))
    {
        cout << DeleteMin(newHeap) << " ";
    }
    cout << endl;
    delete myHeap;
    delete newHeap;
    system("pause");
    return 0;
}
  • 操作运行结果
Prior Deque from the Heap :
2 4 8 9 13 19 23 39 43 58 75 100
Build a new Heap directly from an array...
Prior Deque from the new Heap :
2 8 9 23 39 43 58 75 100
请按任意键继续. . .

参考资料

Mark Allen Weiss: 数据结构与算法分析

猜你喜欢

转载自blog.csdn.net/weixin_40170902/article/details/80751573
今日推荐