数据结构——被优先队列玩起来的“树“(2)

简介: 数据结构——被优先队列玩起来的“树“(2)

手动模拟堆


C++选手确实可以直接调用STL容器中的priority_queue实现优先队列。和queue一样,push()用于向队列中插入一个元素,top()用于访问(读取)开头元素,pop()用于删除开头元素。在priority_queue中,开头元素永远是拥有最高优先级的元素。


对于没有这个库函数的选手,可以练习如何手写堆

微信图片_20221017171438.jpg参考代码(C++版本)

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 100010;
int h[N], ph[N], hp[N], cnt;
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}
void down(int u)
{
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}
void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}
int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }
    return 0;
}

模块化剖析


heap_swap

微信图片_20221017171557.jpg

int ph[N];//ph是从指针数组指向堆heap,存的是第k个插入的数在堆里面下标是多少 
int hp[N];//hp就是从堆heap映射到指针,堆里面的某一个点是第几个插入的点

手动实现堆最主要的就是实现它的交换。为此,我们额外开了两个数组。代表从指针数组pointer指向堆heap的数组ph 。 以及代表从堆heap指向指针数组pointer的hp数组。


因为是用数组模拟的堆,在使用指针的思想进行定位的时候,我们利用ph数组中存的索引下标定位到它在堆中的位置。也可以通过堆中的点,找回来它在数组中的索引


////

微信图片_20221017171653.jpg

down(int u)

对这个传入的位置进行向下调整。假如它的左右孩子存在,并且比它这个父结点小,就把父结点交换下去。

    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;

up(int u)

当传入的位置的父结点存在,而且这个位置中的数据比父结点中的数据大,就将这个位置和父结点交换,实现向上调整

while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }

树和森林与二叉树的转换


树转换为二叉树


步骤:

1.加线:把除根节点之外的所有兄弟结点连起来

微信图片_20221017171918.jpg

2.去线:把除长子(最左边的结点)以外的其他孩子结点之间的线去掉

微信图片_20221017171944.jpg

3.层次调整:以根结点为圆心,每个结点顺时针旋转到看起来舒服的位置就好微信图片_20221017172015.jpg

森林转换为二叉树


森林是由若干树组成的,所以完全可以理解为,森林中的每一棵树都是兄弟,我们可以按照处理兄弟的处理方式来操作。

  1. 把森林中每一棵树转换二叉树
  2. 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的右孩子

微信图片_20221017172053.jpg

二叉树转换为树


  1. 加线:如果某个结点的左孩子有右子树,让该结点和其左孩子的右子树的右分支上各结点连起来
  2. 去线:去掉原二叉树中所有结点与其右子树的连线
  3. 调整

微信图片_20221017172146.jpg

二叉树转换为森林


特判。只看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树

从根结点连线,若右孩子存在,就把它与右孩子的线去掉

看看分出的二叉树,若右孩子存在,就继续删除连线,直到没有👀

微信图片_20221017172152.jpg

哈夫曼树和哈夫曼编码


哈夫曼树的定义


  1. 路径:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径
  2. 路径长度:路径上分支数目
  3. 树的路径长度:树根到每一个结点的路径长度之和

微信图片_20221017172401.jpg

如上图所示,第一个图中,根结点到D结点的路径长度是4。第二个图中,根结点到D结点的路径长度是2


如果考虑带权的结点,结点的带权路径长度为从该结点到树根之间的路径长度与权值的乘积。


哈夫曼树:设二叉树有n个叶子结点,每个叶子结点带有权值 wk,从根结点到每个叶子结点的长度为 lk,则其中带权路径长度WPL最小的二叉树。


哈夫曼树的构造


每次把权值最小的两棵二叉树合并。


  1. 将给定的n个权值{w1,w2,…,wn}作为n个根结点的权值构造一个具有n棵二叉树的森林{T1,T2,…,Tn},其中每棵树只有一个根节点。也就是把每个权值单独放在一个结点中,直接作为一棵树
  2. 森林中选取两棵树中根结点权值最小的的二叉树作为左右子树构造一棵新的二叉树,新的二叉树的根结点权值为原来两棵树根结点的权值和
  3. 在森林中将被选取的两个根节点从森林中删除,将新构建的二叉树加入到森林中
  4. 重复2 和 3操作

微信图片_20221017172507.jpg

参考实现代码

//注:这只是核心流程的代码,部分函数这里没有附上的~
typedef struct TreeNode *HuffmanTree;
struct TreeNode{
  int Weight;
  HuffmanTree Left , Right;
};
HuffmanTree Huffman(MinHeap H)
{
  //假设H->Size个权值已经存在H->Elements[]->Weight里
  int i;
  HuffmanTree T;
  BuildMinHeap(H);//将H->Elements[]按权值调整为最小堆
  for(i = 1; i < H->Size;i++)  //做 H->Size -1 次合并 
  {
    T = malloc(sizeof (struct TreeNode)) ;
    T->Left = DeleteMin(H);//从最小堆中删除一个结点,作为新T的左子结点 
    T->Right = DeleteMin(H) ;//最小堆中删除一个结点,作为新T的右子结点 
    T->Weight = T->Left->Weight + T->Right->Weight;//计算新的权值
    Insert(H,T) ;//将新的T插入到最小堆 
  }
  T = DeleteMin(H) ;
  return T;
}

总结哈夫曼树的特点


  1. 没有度为1的结点;
  2. n个叶子结点的哈夫曼树共有2n-1个结点
  3. 哈夫曼树的任意非叶节点的左右子树交换后仍是哈夫曼树
  4. 对同一组权值{w1 ,w2 , …… , wn},存在不同构的两棵哈夫曼树

微信图片_20221017172650.jpg

哈夫曼编码


哈夫曼编码是一种用于使编码总长最短的编码方案。目的也很直接,压缩数据存储和传输的成本,就可以省钱啦~


哈夫曼编码的生成方式


  1. 构造哈夫曼树,设需要编码的字符集合为{d1,d2,…,dn},它们在电文中的权重集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶子结点,w1,w2,…,wn作为它们的权值,构造一棵哈夫曼树
  2. 在哈弗曼树上求叶子结点的编码。规定哈夫曼树中的左分支代表0,右分支代表1,则从根结点到每个叶子结点所经过的路径分支组成的0和1的序列便是该结点的对应字符的不等长编码。


相关文章
|
2月前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
65 0
|
6天前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
38 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
6天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
31 12
|
6天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
32 10
|
6天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
25 2
|
3月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
135 64
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
81 5
|
2月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
119 16
|
2月前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
72 0
|
3月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
37 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)