- 描述:
二叉树是一个若干个节点的集合,这个集合要么为空集,要么由一个根节点和两颗互不相交的左右子树构成,并且左右子树也是二叉树。
- 分类
- 满二叉树
满二叉树是指除了叶子节点之外的所有内部节点均有左右子树。
- 完全二叉树
如果一颗二叉树所有度数小于二的节点都集中在这棵树最下面两层,并且最下面一层的所有度数小于二的节点都集中在最左边的连续位置上,这样的二叉树叫做完全二叉树。
- 扩充二叉树
在二叉树中所有出现空子树的位置增加空树叶,使所有的节点都成为内部节点,所有的空树叶都成为外部节点,整个二叉树变成满二叉树。
- 二叉树的主要性质
- 二叉树中第i(i>=0)层上最多有2i个节点。
- 深度为K(K>=0)的二叉树最多有2K+1-1个节点。
1+21+22+…+2k = 2K+1-1
- 任何一颗二叉树,若度为0的节点数为N0,度为2的节点数为N2,则有N0=N2+1成立。
- 满二叉树(无度数为1的节点)中,叶子节点数等于分支节点数加1。
- 一个非空二叉树的空字数数目等于其分支节点数加1。
- N个节点的完全二叉树的高度为「log2(N+1)「,深度为「log2(N+1)「-1。
- 对于有N个节点的完全二叉树,节点按层次由左到右编号,则对任意节点有:如果i=0,则节点是该完全二叉树的根节点,如果i>0,则其父节点为」(i-1)/2」,左子节(如果有的话)点为2i+1,右子节点(如果有的话)为2i+2。
- 二叉树的周游(遍历)
- 深度优先遍历(DFS)
- 先根次序(前序法tLR)
先访问根节点
再访问左子节点
最后访问右子节点
- 后根次序(后序法LRt)
先访问左子节点
再访问右子节点
最后访问根节点
- 中根次序(中序法LtR)
先访问左子节点
再访问根节点
最后访问右节点
二. 广度优先遍历(BFS)
广度优先周游二叉树就是一层一层地访问二叉树的各个节点,所以又叫层次周游。
- 二叉搜索树(BST)
- 二叉搜索树的特征:
二叉搜索树的特征是,对于每个节点都满足左子节点(如果有的话)的键值小于(或者大于)父节点的键值,每个右子节点(如果有的话) 的键值都大于(或者小于)父节点的键值。并且二叉搜索树的中根次序遍历的序列式完全有序的。
- 二叉搜索树的搜索:
二叉搜索树的搜索和二分搜索很类似。根结点的关键码等于查找的关键码,成功。否则,若小于根结点的关键码,查其左子树。大于根结点的关键码,查其右子树。二叉搜索树的高效率在于继续检索时只需要查找两棵子树之一。
如果二叉搜索树的左右子树比较平衡(左右子树的高度相差不大)的情况下,搜索效果比较好,如果二叉搜索树退化成线性表的结构,搜索效率就大大降低了。
平均搜索长度分析
- 二叉搜索树节点插入
实际上二叉搜索树的插入很简单,因为对于一个新的键值,总能在一颗二叉搜索树中把这个键值放到他的叶子节点处。也就是说所有的插入行为都是给这棵树增加一个叶子节点。
二叉搜索树的插入算法:
template<class T> void BinarySearchTree<T>::InsertNode(BinaryTreeNode<T> *root , BinaryTreeNode<T> *newpointer) { BinaryTreeNode<T> *pointer = NULL; if (root == NULL) { //如果是空树 Initialize(newpointer); //则用指针newpointer作为树根 return; } else pointer = root; while (pointer != NULL) { if (newpointer->value() == pointer->value())// 如果存在相等的元素则不用插入 return ; else if (newpointer->value() < pointer->value()) { // 如果待插入结点小于pointer的关键码值 if (pointer->leftchild() == NULL) { // 如果pointer没有左孩子 pointer->left = newpointer; // newpointer作为pointer的左子树 return; } else pointer = pointer->leftchild(); // 向左下降 } else { // 若待插入结点大于pointer的关键码值 if (pointer->rightchild() == NULL){//如果pointer没有右孩子 pointer->right = newpointer; // newpointer作为pointer的右子树 return; } else pointer = pointer->rightchild(); // 向右下降 } } }
- 二叉搜索树的节点删除
- 删除的节点是二叉搜索树中的一个叶子节点,直接删除。
- 删除的节点带有一颗子树。
- 删除节点拥有两个子节点:合并删除,复制删除
合并删除
复制删除
- AVL平衡树
上面提到过,如果当BST的左右子树的高度相差不大的时候BST的搜索效率是最高的。AVL平衡树,就是通过某种机制,使得每个节点的左右子树高度之差保持在0-1的范围内。
- AVL平衡树的性质
可以为空,具有n个结点的AVL树,高度为O(log n)
如果T是一棵AVL树,那么它的左右子树TL、TR也是AVL树,并且| hL-hR|≤1,hL、hR 是它的左右子树的高度 。
- AVL平衡树的平衡因子
平衡因子:bf(x)= x的右子树的高度 – x的左子树的高度,它的取值范围{0,-1,1}
平衡二叉树:每个结点的平衡因子都为 +1、-1、0 的二叉搜索树。或者说每个结点的左右子树的高度最多差一的二叉搜索树。、
- AVL平衡树的插入
首先按照BST的插入方式,插入新节点,然后检查是否处于不平衡状态,如果不平衡,进行调整。
不平衡情况:
- RR
调整:
- LL
调整:
- LR
调整:
- RL
调整:
关于调整:把树做任何一种旋转(RR、RL、LL、LR),新树保持了原来的中序周游顺序,旋转处理中仅需改变少数指针,而且新的子树高度为h+2,保持插入前子树的高度不变,原来二叉树在a结点上面的其余部分(若还有的话)总是保持平衡的 。
- Huffman树
Huffman树是一个具有n个外部节点的扩充二叉树,每个外部节点Ki都有一个权值Wi与之相对应。所有叶节点带权外部路径长度总和:
即每个叶节点到根节点的长度总和最小。带权路径长度WPL最小的二叉树称做最优二叉树,Huffman树是最优二叉树。 注意这里不用计算内部节点到根节点的距离。
在Huffman树中有这样的特点:
权越大的叶结点离根越近;如果某个叶结点的权较小,可能就会离根较远。
例如:
- Huffman树的构造过程
(1) 根据给定的 n 个权值 {w1,w2, … wn} 构成 n 棵二叉树的集合 F = {T1,T2, … Tn},其中每棵二叉树 Ti 中只有一个权值为 wi 的根结点。
(2) 在 F 中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且置新二叉树的根结点的权值为其左、右子树根结点的权值之和。
(3) 在 F 中删除这两棵树,同时将新得到的二叉树加入集合 F 中。
(4) 重复 (2) 和 (3) ,直到 F 中只含一棵树为止。
- Huffman的简单应用
这里描述Huffman树的简单编码应用。先简单解释下编码:
在长距离传输电文的过程中,可能需要将电文编成二进制代码进行传输,比如传送 ABACCD,四种字符,可以分别编码为 00,01,10,11;那么传输的二进制串是:000100101011。还可以编码为0,111,11,00,传输的二进制串是:01110111100,要比之前的编码少3个比特。但是问题是怎样进行编码,能够使得我再传输的比特数最少。最简单的一个想法就是,出现频率最高的字符,它的二进制比特串应该是最短的,相反频率最高的字符,它的二进制比特串就可以稍长些。
- 前缀编码
除此之外还有一个问题,那就是为了译码的方便,所有字符的编码前缀都不能是其他字符的编码。这样长度不等的编码,称之为前缀编码。而通过二叉树构造出来的编码树恰好就符合前缀编码的要求。(因为所有的字符都在叶子节点上,所以在到达叶子节点之前是不会译出字符的。)
通过分析:字符串ABACCD中各个字符的平率分布为:A2 B1 C2 D1。将频率越高的叶子节点放到越靠近根节点的位置,由此可以构造一个二叉树如下:
如果将每个字符出现在字符串中的频率作为叶子节点的权重的话,那么这颗二叉树就可以称之为最优二叉树了,即Huffman树。字符的编码就是从根节点到叶子节点的路径。传输比特为:011001010111,虽然这个编码串没有上面给的短,但它是符合前缀编码,并且当字符串的长度长到一定程度,这种编码的优势就体现出来的了。
如果Huffman编码可以压缩电文,肯定也可以压缩文本文件,这里用C++写了一个例子程序。
首先分析文本文件中所有的ASCII码字符种类和频率,然后根据各个字符的频率构建Huffman树,从而得到各个字符的编码串(二进制比特),最后将所有字符翻译成二进制比特,存到另外的一个文件里。
从文件大小来看,test.txt大小为29K,压缩后encode.txt大小为17K,压缩率大致可以达到50%。
从encode.txt中解压得到output.txt,是可以还原原来被压缩的文本文件的。
这个程序的工程文件可以到我的资源里下载。
二叉树和Huffman编码
最新推荐文章于 2022-10-31 20:15:48 发布