【数据结构】5.大根堆和左高树

这篇具有很好参考价值的文章主要介绍了【数据结构】5.大根堆和左高树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.大根堆

1.1 定义

  大根树:树中的每一个节点的值都大于或等于其子节点的值

  大根堆:既是大根树又是完全二叉树(增加了完全二叉树的限制条件)所以下图中只有(a)和(c)是大根堆

【数据结构】5.大根堆和左高树

 1.2 大根堆的插入(数组实现)

  假设在下面大根堆中插入一个元素9,插入步骤如下,时间复杂度为O(height)=O(logn)

  • 尝试插入在6号位置,如果新的元素小于3号位置,则插入;否则把3号位置的元素向下移动到6号位置
  • 尝试插入在3号位置,如果新的元素小于1号元素,则插入;否则把1号位置的元素向下移动到3号位置,循环终止

【数据结构】5.大根堆和左高树

// 为theElement寻找插入位置, currentNode从新叶子节点开始向上移动
int currentNode = ++heapSize;
while (currentNode != 1 && heap[currentNode / 2] < theElement)
{
    heap[currentNode] = heap[currentNode / 2]; // 元素向下移动
    currentNode /= 2;                          // currentNode移动到他的双亲
}

heap[currentNode] = theElement;

1.3 大根堆的出队

  出队元素是最大的元素,也就是根节点,我们首先将1号元素删除,然后拿到最后一个元素,从上向下做插入的操作,时间复杂度为O(height)=O(logn)

【数据结构】5.大根堆和左高树

// 从根节点开始, 为最后一个元素查找插入位置
int currentNode = 1;    // 当前双亲节点
int child = 2;          // 当前孩子节点
while (child <= heapSize)
{
    // heap[child] 应该是 currentNode 更大的孩子
    if (child < heapSize && heap[child] < heap[child + 1])
        child++;

    // 对比 lastElement 是否可以插入到 currentNode
    if (lastElement >= heap[child])
        break;   // 可以插入则直接结束

    // 不可以插入则继续循环
    heap[currentNode] = heap[child]; // 子节点向上移动
    currentNode = child;             // 当前指向节点移动到子节点
    child *= 2;
}
heap[currentNode] = lastElement;

1.4 大根堆的初始化

  自下而上:从最后一个元素开始对比,到第一个元素结束,具体步骤如下

  • ①找到孩子节点 2 和 7 中更大的元素,接下来与其双亲节点对比,7 更大所以交换 5 和 7 的位置
  • ①找到孩子节点 7 和 6 中更大的元素,接下来与其双亲节点对比,7 更大所以交换 1 和 7 的位置;②找到孩子节点 2 和 5 中更大的元素,接下来与其双亲节点对比,5 更大所以交换 1 和 5 的位置

  这里的循环包括两个步骤,第一个步骤是从下到上交换一次,第二个步骤是从上到下再检查一遍

【数据结构】5.大根堆和左高树

// 堆化
for (int root = heapSize / 2; root >= 1; root--)
{
    T rootElement = heap[root];

    // 给元素 rootElement 寻找位置
    int child = 2 * root; // 孩子 child 的双亲是 rootElement 的位置
    // 确定 rootElement 的位置
    while (child <= heapSize)
    {
        // heap[child] 找到孩子里更大的那一个
        if (child < heapSize && heap[child] < heap[child + 1])
            child++;

        // 是否可以把 rootElement 插入到该位置
        if (rootElement >= heap[child])
            break;  // 可以

        // 不可以
        heap[child / 2] = heap[child]; // 孩子向上移动
        child *= 2;                    // 下移一层查看
    }
    heap[child / 2] = rootElement;
}

1.5 完整代码

1.5.1 优先级队列抽象父类

#pragma once
using namespace std;
template<class T>
class maxPriorityQueue
{
public:
    virtual ~maxPriorityQueue() {}
    // 队列是否为空
    virtual bool empty() const = 0;
    // 队列大小
    virtual int size() const = 0;
    // 返回优先级最高的元素
    virtual const T& top() = 0;
    // 弹出优先级最高的元素
    virtual void pop() = 0;
    // 插入元素
    virtual void push(const T& theElement) = 0;
};

1.5.2 大根堆的数组实现

#pragma once
#include "maxPriorityQueue.h"
#include <iostream>
#include <algorithm>

using namespace std;

template<class T>
class maxHeap : public maxPriorityQueue<T>
{
private:
    int heapSize;       // 队列中的元素数量
    int arrayLength;    // 队列的容量
    T* heap;            // 元素数组 

public:
    maxHeap(int initialCapacity = 10);
    ~maxHeap() { delete[] heap; }
    bool empty() const { return heapSize == 0; }
    int size() const { return heapSize; }
    const T& top()
    {// 返回优先级最大的元素
        if (heapSize == 0)
        {
            cout << "队列为空" << endl;
            return NULL;
        }
        return heap[1];
    }
    void pop();
    void push(const T&);
    void initialize(T*, int);
    void deactivateArray()
    {
        heap = NULL; arrayLength = heapSize = 0;
    }
    void output() const;
};


template<class T>
void changeLength(T*& a, int oldLength, int newLength)
{// 改变数组长度
    if (newLength < 1)return;
    T* temp = new T[newLength];              // 新的数组
    int number = min(oldLength, newLength);  // 需要复制的元素个数
    copy(a, a + number, temp);
    delete[] a;                              // 清理内存
    a = temp;
}

template<class T>
maxHeap<T>::maxHeap(int initialCapacity)
{// 构造函数
    if (initialCapacity < 1)
    {
        cout << "构造大根堆的数量必须大于1" << endl;
        return;
    }
    arrayLength = initialCapacity + 1;
    heap = new T[arrayLength];
    heapSize = 0;
}

template<class T>
void maxHeap<T>::push(const T& theElement)
{// 将元素插入到大根堆

   // 检查数组容量
    if (heapSize == arrayLength - 1)
    {
        changeLength(heap, arrayLength, 2 * arrayLength);
        arrayLength *= 2;
    }

    // 为theElement寻找插入位置, currentNode从新叶子节点开始向上移动
    int currentNode = ++heapSize;
    while (currentNode != 1 && heap[currentNode / 2] < theElement)
    {
        heap[currentNode] = heap[currentNode / 2]; // 元素向下移动
        currentNode /= 2;                          // currentNode移动到他的双亲
    }

    heap[currentNode] = theElement;
}

template<class T>
void maxHeap<T>::pop()
{// 从大根堆删除最大的元素

    if (heapSize == 0)   // 大根堆是否为空
        return;

    // 删除最大的元素
    heap[1].~T();

    // 拿到最后一个元素, 并将大根堆元素数量减1
    T lastElement = heap[heapSize--];

    // 从根节点开始, 为最后一个元素查找插入位置
    int currentNode = 1;    // 当前双亲节点
    int child = 2;          // 当前孩子节点
    while (child <= heapSize)
    {
        // heap[child] 应该是 currentNode 更大的孩子
        if (child < heapSize && heap[child] < heap[child + 1])
            child++;

        // 对比 lastElement 是否可以插入到 currentNode
        if (lastElement >= heap[child])
            break;   // 可以插入则直接结束

        // 不可以插入则继续循环
        heap[currentNode] = heap[child]; // 子节点向上移动
        currentNode = child;             // 当前指向节点移动到子节点
        child *= 2;
    }
    heap[currentNode] = lastElement;
}

template<class T>
void maxHeap<T>::initialize(T* theHeap, int theSize)
{// 在数组 theHeap[1:theSize] 中初始化大根堆
    delete[] heap;
    heap = theHeap;
    heapSize = theSize;

    // 堆化
    for (int root = heapSize / 2; root >= 1; root--)
    {
        T rootElement = heap[root];

        // 给元素 rootElement 寻找位置
        int child = 2 * root; // 孩子 child 的双亲是 rootElement 的位置
        // 确定 rootElement 的位置
        while (child <= heapSize)
        {
            // heap[child] 找到孩子里更大的那一个
            if (child < heapSize && heap[child] < heap[child + 1])
                child++;

            // 是否可以把 rootElement 插入到该位置
            if (rootElement >= heap[child])
                break;  // 可以

            // 不可以
            heap[child / 2] = heap[child]; // 孩子向上移动
            child *= 2;                    // 下移一层查看
        }
        heap[child / 2] = rootElement;
    }
}

template<class T>
void maxHeap<T>::output() const
{
    for (int i = 1; i < heapSize; i++)
    {
        cout << this->heap[i] << " ";
    }
    cout << endl;
}

2.左高树

2.1 高度优先左高树和重量优先左高树

  左高树合并操作的时间复杂度更低,适合需要合并操作较多的数据

  定义一类特殊的节点为外部节点来代替空子树,其余节点为内部节点。增加了外部节点的二叉树称为扩充二叉树

  定义距离s:s 是该节点到外部节点的最短路径

  定义重量w:w 是该节点重量的和,节点本身重量为1,节点的重量为自身重量与孩子重量的和。

【数据结构】5.大根堆和左高树

  高度优先左高树(height-biased leftist tree,HBLT):这个二叉树任何一个内部节点左孩子的 s 值都大于等于右孩子的 s 值。其中上图(a)就不是一棵左高树,因为它左侧子树的左孩子值为0,而右孩子值为1,下图是2个HBLT。

【数据结构】5.大根堆和左高树

  重量优先左高树(weight-biased leftist tree,WBLT):这个二叉树任何一个内部节点左孩子的 w 值都大于或等于右孩子的 w 值。

2.2 HBLT的性质

  1. 堆的性质:任意节点值的大小 ≤ 其孩子节点的大小(小根堆,也可以是 ≥ )
  2. 左偏性质:左孩子的距离 s ≥ 右孩子的距离 s 
  3. 任意节点的距离 s 都等于其右孩子的距离 + 1
  4. 一棵有 n 个节点的二叉树,根的距离dis ≤ long(n+1) - 1(距离最远就是满二叉树的情况,否则一定有更短路径出去)

2.3 HBLT的插入和删除

  插入相当于将已有的树和一棵仅有一个元素的树进行合并操作

  删除相当于将左右两棵HBLT进行合并操作

2.4 HBLT的合并(链式实现)

   用一张图解释合并的过程,我们需要把两棵树合并到 x 上

  • 对比 x 指向节点数值和 y 指向节点数值的大小
  • 如果节点数值 x < y,则将 x 指向的地址和 y 指向的地址交换
  • 将 x 指针移动到 x 的右孩子位置

  这就是图左完整的递归过程,递归的过程实际上就是在比较两个左高树右侧孩子链的大小,并来回交换来保证堆的性质,所以整个递归应该是O(logm)+O(logn)的时间复杂度

  • 首先 x 移动到下一个位置指向元素 7,对比元素 7 和元素 8,元素 8 更大,所以指针指向的地址互换,元素 8 移动到右孩子的位置
  • 然后 x 移动到下一个位置指向元素 6,对比元素 6 和元素 7,元素 7 更大,所以指针指向的地址呼唤,元素 7 移动到右孩子的位置
  • 然后 x 移动到下一个位置指向 NULL,因为 x 指向 NULL,所以直接将 x 指向 y 所指向的地址,递归结束

  最后一步是回溯,已经完成元素的插入后,需要维护左高树的性质,回溯的过程就是根据距离不断交换孩子节点的位置,保证左孩子的距离 ≥ 右孩子的距离

【数据结构】5.大根堆和左高树

template<class T>
void maxHblt<T>::merged(binaryTreeNode<pair<int, T> >*& x, binaryTreeNode<pair<int, T> >*& y)
{// 合并两棵左高树, x是自己的根节点地址, y是传入的根节点地址
    if (y == NULL)   // 如果传入的树为空, 不需要操作
        return;
    if (x == NULL)   // 如果x节点为空, 则直接让该节点的值等于传入节点的值
    {
        x = y; return;
    }

    // 维护堆的性质, 保证 x 是更大的那一个
    if (x->element.second < y->element.second)
        swap(x, y);

    // 接下来进行递归, x 向其右孩子移动一位
    merged(x->rightChild, y);

    // 最后是回溯的过程, 需要维护左高树的性质, 保证左孩子的路径长度大于右孩子
    if (x->leftChild == NULL)
    {// 左树为空直接交换即可
        x->leftChild = x->rightChild;
        x->rightChild = NULL;
        x->element.first = 1;
    }
    else
    {// 左树不为空则对比他们的路径长度
        if (x->leftChild->element.first < x->rightChild->element.first)
            swap(x->leftChild, x->rightChild);
        // 最后更新路径长度
        x->element.first = x->rightChild->element.first + 1;
    }
}

2.5 HBLT的初始化

template<class T>
void maxHblt<T>::initialize(T* theElements, int theSize)
{// 根据给定的数组初始化左高树
    arrayQueue<binaryTreeNode<pair<int, T> >*> q(theSize);

    // 初始化树的队列
    for (int i = 1; i <= theSize; i++)
        // 建立一个只有一个节点的树插入队列
        q.push(new binaryTreeNode<pair<int, T> >
            (pair<int, T>(1, theElements[i])));

    // 从队列中重复取出树进行合并操作
    for (int i = 1; i <= theSize - 1; i++)
    {// 从队列中取出2个树
        binaryTreeNode<pair<int, T> >* b = q.front();
        q.pop();
        binaryTreeNode<pair<int, T> >* c = q.front();
        q.pop();
        merged(b, c);
        // 把合并后的树插入队列
        q.push(b);
    }

    if (theSize > 0)
        this->root = q.front();
    this->treeSize = theSize;
}

 文章来源地址https://www.toymoban.com/news/detail-711609.html

到了这里,关于【数据结构】5.大根堆和左高树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Tecplot数据结构——结构数据(结构网格)与非结构数据(非结构网格)

    结构数据可以是一维、二维或三维的,下面以二维的数据格式为例。 在记事本中写入以下字符,并将文件以.plt或.dat为后缀命名。 其中数据总数为I*J=20,结构数据顺序为point格式,顺序为:(I,J)=(1,1), (I,J)=(2,1), … (I,J)=(Imax,1), (I,J)=(1,2), (I,J)=(2,2), (I,J)=(Imax,2), … (I,J)=(Imax,Jmax).

    2024年02月15日
    浏览(55)
  • 数据结构与算法——数据结构有哪些,常用数据结构详解

    数据结构是学习数据存储方式的一门学科,那么,数据存储方式有哪几种呢?下面将对数据结构的学习内容做一个简要的总结。 数据结构大致包含以下几种存储结构: 线性表,还可细分为顺序表、链表、栈和队列; 树结构,包括普通树,二叉树,线索二叉树等; 图存储结构

    2024年02月15日
    浏览(63)
  • 算法 数据结构分类 数据结构类型介绍 数据结构线性非线性结构 算法合集 (一)

     数据结构分为:                            a.线性结构                            b.非线性结构  a.线性结构:                       数据与结构存在一对一的线性关系; a . 线性结构 存储 分为:                                   顺序存储

    2024年02月10日
    浏览(53)
  • 结构化数据、非结构化数据、半结构化数据

    结构化的数据一般是指可以使用关系型数据库表示和存储,可以用二维表来逻辑表达实现的数据。例如:需要多少个属性,每个属性什么类型,每个属性的取值范围等等,类似下图所示, 提前定义好了一个二维矩阵的元数据 ,包含有列名称、列的类型、列的约束等:   可见

    2024年02月09日
    浏览(67)
  • 【数据结构】何为数据结构。

    🚩 WRITE IN FRONT 🚩    🔎 介绍:\\\"謓泽\\\"正在路上朝着\\\"攻城狮\\\"方向\\\"前进四\\\" 🔎 🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2022博客之星TOP100|TOP63、阿里云专家博主、掘金优秀创作者、全网粉丝量6w+、全网访问量100w+ 🏅 🆔 文章内容由 謓泽 原创 如需相关

    2024年02月08日
    浏览(61)
  • 【数据结构】什么是数据结构?

    🦄 个人主页 :修修修也 🎏 所属专栏 :数据结构 ⚙️ 操作环境 : Visual Studio 2022 目录 🎏数据结构的定义 🎏结语 数据结构(Data Structure)是计算机 存储 , 组织数据的方式 ,指 相互之间存在一种或多种特定关系的数据元素的集合 . 这么讲可能有些抽象,放一张图大家可能好理解一

    2024年02月07日
    浏览(52)
  • 数据结构和算法——数据结构

    目录 线性结构  队列结构的队列 链表结构的队列 链表的面试题 单向链表应用场景 约瑟夫环问题 栈结构 中缀表达式 前缀表达式 后缀表达式 非线性结构 图 递归解决迷宫问题 递归解决八皇后问题 顺序存储方式,顺序表 常见的顺序存储结构有:数组、队列、链表、栈 链式存

    2024年02月07日
    浏览(56)
  • 【数据结构(一)】初识数据结构

    ❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵 关注我带你学更多数据结构知识 数据结构是计算机存储,组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合,从这篇文章开始,我们将一起进入数

    2024年04月09日
    浏览(57)
  • 数据结构学习之数据结构绪论

      《大话数据结构》是程杰老师著作的一本书,作者将跟着程杰老师写的这本书,记录自己数据结构学习之旅。   数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科。   我的理解,数据结构就是数据和数据之

    2024年02月04日
    浏览(91)
  • 数据结构(王道)——数据结构之 树

           树的概念补充: 结点之间的关系描述    结点、树的属性描述: 有序树、无序树: 1、第i层至多有m^(i-1)个结点 2、高度为h的m叉树至多有(m^h-1)/(m-1)个结点   3、高度为h的m叉树至少有h个结点 高度为h,度为m的树至少有h+m-1个结点   4、具有n个结点的m叉树的最小高度

    2024年02月17日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包