文章目录
二叉树
二叉树(binary tree)是一个有限的结点集合,这个集合或者为空,或者由一个根结点和两棵互不相交的称为左子树(left subtree)和右子树(right subtree)的二叉树组成。
二叉树和度为2的树(2次树)是不同的,对于非空树,其差别表现在以下两点:
- 度为2的树中至少有一个结点的度为2,而二叉树没有这种要求;
- 度为2的树不区分左、右子树,而二叉树是严格区分左、右子树的
满二叉树
在一棵二叉树中,如果所有分支结点都有左孩子结点和右孩子结点,并且叶子结点都集中在二叉树的最下一层.
完全二叉树
若二叉树中最多只有最下面两层的结点的度数可以小于2,并且最下面一层的叶子结点都依次排列在该层最左边的位置上,则这样的二叉树称为完全二叉树(complete binary tree)
二叉树的性质
n 为节点,d为总的度数
- 非空二叉树上的叶子结点数等于双分支结点数加1: n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1
- n = n 0 + n 1 + n 2 n = n_0+n_1+n_2 n=n0+n1+n2
- 高度为 h的二叉树最多有 2 h − 1 2^h-1 2h−1个结点(h ≥ 1)
- 非空二叉树的第i层上最多有 2 i − 1 2^{i-1} 2i−1个结点(i ≥ 1)
- 具有n个(n>0)结点的完全二叉树的高度为 l o g 2 ( n + 1 ) log_2(n+1) log2(n+1)或 l o g 2 n + 1 log_2{n}+1 log2n+1。
二叉树和树的相互转换
树变二叉树
二叉树变树
二叉树变森林
线索二叉树
对于具有n个结点的二叉树,当采用二叉链存储结构时,每个结点有两个指针域,总共有2n个指针域,又由于只有n-1个结点被有效指针域所指向(n个结点中只有根结点没有被有效指针域指向),则共有 2 n − ( n − 1 ) = n + 1 2n-(n-1)=n+1 2n−(n−1)=n+1个空链域
为了利用这些空链域,我们可以这样:
- 如果左指针为空,则指向前驱节点
- 如果右指针为空,则指向后继节点
这样的指向该线性序列中的“前驱结点”和“后继结点”的指针称为线索(thread)。创建线索的过程称为线索化。线索化的二叉树称为线索二叉树(threaded binary-tree)。
由于遍历方式不同,产生的遍历线性序列也不同,会得到相应的线索二叉树。一般有先序线索二叉树、中序线索二叉树和后序线索二叉树。创建线索二叉树的目的是提高该遍历过程的效率。
那么,在线索二叉树中如何区分左指针指向的是左孩子结点还是前驱结点,右指针指向的是右孩子结点还是后继结点呢?
答:增加标志位
哈夫曼树
当二叉树的WPL最小时,该二叉树被称为最优二叉树或哈夫曼树
哈夫曼的构造(贪心)
此时 W P L = 7 ∗ 1 + 5 ∗ 2 + ( 1 + 3 ) ∗ 3 = 4 + 9 + 16 = 29 WPL = 7*1+5*2+(1+3)*3 = 4 + 9 + 16 = 29 WPL=7∗1+5∗2+(1+3)∗3=4+9+16=29
哈夫曼编码
在数据通信中,经常需要将传送的文字转换为二进制字符0和1组成的二进制字符串,称这个过程为编码。显然,我们希望电文编码的代码长度最短。哈夫曼树可用于构造使电文编码的代码长度最短的编码方案。
左分支为0,右分支为1
题目:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
using LL = long long;
int main()
{
int n;
cin >> n;
priority_queue<LL, vector<LL>, greater<LL>> heap;
while(n--)
{
LL x;
cin >> x;
heap.push(x);
}
//建树
LL ret = 0;
while(heap.size() > 1)
{
LL t1 = heap.top(); heap.pop();
LL t2 = heap.top(); heap.pop();
heap.push(t1+t2);
ret += t1+t2;
}
std::cout << ret << std::endl;
}
// 64 位输出请用 printf("%lld")
堆
堆树的定义如下:
(1)堆树是一颗完全二叉树;
(2)堆树中某个节点的值总是不大于或不小于其孩子节点的值;
(3)堆树中每个节点的子树都是堆树。
当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆。如下图所示,左边为最大堆,右边为最小堆。
建堆
堆的主要应用是堆排序,堆排序的核心操作是建堆。
建堆有2种方式:向上调整建堆 和 向下调整建堆
向上调整建堆
向上调整建堆:将a[0]之后的数据看成一次次插入进去,先插入在尾部,而后一步步向上调整。
代码:
for(int i = 1; i < n; i++)
{
AdjustUp(i);
}
template<class T, class cmp>
void Heap<T, cmp>::AdjustUp(int child)
{
if(child == 0) return;
int father = (child-1)/2;
while(cmp()(_heap[child], _heap[father]))
{
std::swap(_heap[child], _heap[father]);
child = father;
father = (child-1)/2;
}
}
复杂度分析:
向下调整建堆
向下调整建堆:从 n/2 处开始调整
for(int i = n/2; i >= 0; i++)
{
AdjustDown(i);
}
template<class T, class cmp