基本概念
- 二叉树(binary tree):结点的有限集合,一般为根结点+左右子树
- 根结点(root)
- 子树(subtree)
- 父结点(parent)
- 子结点(child)
- 兄弟结点(sibling)
- 结点的度(degree):结点的子树的个数
- 叶结点(leaf ):度为0的结点
- 内部结点(internal node),除叶结点以外的非终端结点
- 边(edge):有向线段 <k,k′> <script type="math/tex" id="MathJax-Element-1"> </script>
- 路径(path)
- 路径长度(length)
- 祖先(ancestor)
- 子孙(descendant)
- 结点的层数(level):根结点为0层
- 满二叉树(full binary tree):除了叶子都满,或结点的度为0或2
- 完全二叉树(complete binary tree):宽搜时叶子都在最后
- 扩充二叉树(extended binary tree):在所有度数为0和1的结点后增加空树叶,一定是满二叉树。新增的空叶子结点(外部结点)个数是原来结点数+1
- 外部长度路径 E :从扩充的二叉树的根到每个外部结点(新增叶子个数)的路径长度之和
- 内部路径长度
I :从扩充的二叉树的根到每个内部结点(原来结点个数)的路径长度之和
E+B=2n(归纳法证明)
主要性质
- 第
i
层上最多有
2i 个结点 - 深度(depth)为
k
的二叉树至多有
2k+1−1 个结点 - 其终端结点数为
n0
,度为
2
的结点数为
n2 ,则 n0=n2+1 - 非空满二叉树树叶数目等于其分支结点数加1。
- 一个非空二叉树的空子树数目等于其结点数加1。
- 有
n
个结点(
n>0 )的完全二叉树的高度为 [log2(n+1)] (深度为 [log2(n+1)]−1 )。 其中二叉树的高度(height )定义为二叉树中层数最大的叶结点的层数加1。
抽象数据类型
结点
template<class T>
class BinaryTreeNode{
friend class BinaryTree<T>; //把BinaryTree设为友元类
private:
T Value;
public:
BinaryTreeNode();
BinaryTreeNode(const T& val);
BinaryTreeNode(const T& val, BinaryTreeNode<T>* left, BinaryTreeNode<T>* right);
T getValue();
BinaryTreeNode<T>* getLeftChild();
BinaryTreeNode<T>* getRightChild();
void setValue(const T& val);
void setLeftChild(BianryTreeNode<T>* left);
void setRightChild(BinaryTreeNode<T>* right);
bool isLeaf() const; //why const?
BinaryTreeNode<T>& operator=(BinaryTreeNode<T>& node);
};
二叉树
template<class T>
class BinaryTree{
private:
BinaryTreeNode<T>* root;
public:
BinaryTree(){root = NULL};
~BinaryTree(){DeleteBinaryTree();};
bool isEmpty() const;
BinaryTreeNode<T>* getRoot(return root;);
void setRoot(BinaryTreeNode<T>*);
BinaryTreeNode<T>* Parent(BinaryTreeNode<T>* current);
BinaryTreeNode<T>* LeftSibling(BinaryTreeNode<T>* current);
BinaryTreeNode<T>* RightSibling(BinaryTreeNode<T>* current);
void PreOrder(BinaryTreeNode<T>* root);
void InOrder(BinaryTreeNode<T>* root);
void PostOrder(BinaryTreeNode<T>* root);
void LevelOrder(BinaryTreeNode<T>* root);
void DeleteBinaryTree(BinaryTreeNode<T>* root);
};
遍历
- 遍历,也叫周游(traversal),按一定顺序访问所有结点一遍,实际上就是把二叉树线性化的过程。
- 分为深度优先和广度优先,深度优先分为前序、中序、后序三种。
深度优先遍历
- 递归方法
- 分为前序、中序、后序三种,前中后说的是访问根节点的时刻。都采用
- 递归定义。
- 前序(tLR次序,preorder traversal)先根结点、再左、再右;
- 中序(LtR次序,inorder traversal)先左结点、再根、再右;
- 后序(LRt次序,posorder traversal)先左结点、再右、再根;
代码
//前序
template<T>
void BinaryTree<T>::PreOrder(BinaryTreeNode<T>* root)
{
if(root->LeftChild() != NULL)
{
Visit(root);
PreOrder(root->getLeftChild());
PreOrder(root->getRightChild());
}
}
//中序
template<T>
void BinaryTree<T>::InOrder(BinaryTreeNode<T>* root)
{
if(root->LeftChild() != NULL)
{
InOrder(root->getLeftChild());
Visit(root);
InOrder(root->getRightChild());
}
}
//后序
template<T>
void BinaryTree<T>::PostOrder(BinaryTreeNode<T>* root)
{
if(root->LeftChild() != NULL)
{
PostOrder(root->getLeftChild());
PostOrder(root->getRightChild());
Visit(root);
}
}
非递归方法
- 有时程序不允许递归(when?),所以要用非递归方法
- 用栈
- 举例:非递归前序周游
- 每遇到一个结点,先访问该结点,将非空右子结点推入栈,周游左子树;
- 周游不下去时就出栈;
- 一开始推入一个空指针(监视哨),当这个空指针出栈时程序结束。
//非递归前序周游
template<T>
void BinaryTree<T>::PreOrderWithoutRecursion(BinaryTreeNode<T>* root)
{
using std::stack;
stack<BinaryTreeNode<T>*> S;
S.push(NULL);
if(root == NULL)
return;
S.push(root);
while(S.front() != NULL)
{
BinaryTreeNode<T>* node = S.top();
S.pop();
Visit(node);
if(node->getRightChild() != NULL)
S.push(node->getRightChild());
if(node->getLeftChild() != NULL)
S.push(node->getLeftChild());
}
}
//非递归中序周游
template<T>
void BinaryTree<T>::InOrderWithoutRecursion(BinaryTreeNode<T>* root)
{
using std::stack;
stack<BinaryTreeNode<T>*> S;
BinaryTreeNode<T>* pointer = root;
while(!S.empty() || pointer)
{
if(pointer)
{
S.push(pointer);
pointer = pointer->getLeftChild();
}
else
{
BinaryTreeNode<T>* node = S.top();
S.pop();
Visit(node);
pointer = node->getRightChild();
}
}
}
//非递归后序周游
template<T>
void BinaryTree<T>::PostOrderWithoutRecursion(BinaryTreeNode<T>* root)
{
using std::stack;
stack<BinaryTreeNode<T>*> S;
BinaryTreeNode<T>* pointer = root;
while(!S.empty() || pointer)
{
if(pointer)
{
S.push(pointer);
if(pointer->getRightChild())
{
S.push(pointer->getRightChild());
}
if(pointer->getLeftChild())
pointer = pointer->getLeftChild();
else
pointer = pointer->getRightChlid();
}
else
{
BinaryTreeNode<T>* node = S.top();
S.pop();
Visit(node);
pointer = node->S.top();
}
}
}
广度优先遍历
存储方式
链式存储结构
- 分为二叉链表和三叉链表,前者有左右子结点两个指针域,后者多一个父结点指针域。区别在找父结点时,二叉链表要从根结点开始,三叉链表直接找。
- 完全二叉树的线性存储结构:第
i
个结点的两个子结点为
2i+1 , 2i+2
二叉搜索树
- 每个结点的数据是一个关键值码;
- 左子结点的关键值码小于根结点,右子结点的关键值码大于根结点
- 按中序周游得到从小到大的序列
- 搜索时只需要查两棵子树之一,时间复杂度为 O(nlogn)
- 插入算法,在树形较平衡的时候效率高;
- 删除算法:把子结点较小者提到根结点,再删去该子结点(递归);
堆
- 最小值堆
- 一个关键码序列, Ki<=K2i+1,Ki<=K2i+2 ,对应一个完全二叉树,根结点的关键码不大于两个子结点。
- 局部有序
- 最小值在根结点
堆的实现
- 筛选法:从倒数第一个度为不为0的结点开始,进行它和子结点之间的微调
- 建堆的效率是 O(n) ,查找等操作的效率是 O(logn) , 排序的效率是 O(nlogn)
优先队列
就是堆?
Huffman树
- 用于通信的编码和译码,对使用频率不同(权值不同)的字符分配码字,要求达到无歧义和效率高两个目的。
- 实现方法是扩充二叉树的外部路径的加权外部路径