1、概要
最初学习二叉树是在学堂在线中清华大学邓俊辉的数据结构公开课上,老师讲课讲的很好,然后原理听懂了并不代表能实现算法。在邓俊辉教授提供的源代码中,仍然有部分代码令我难以理解,当时觉得二叉树真的很难。现在想想,主要是畏难心理在作祟。之后,我通过阅读博客,加以C++实现,感觉对二叉树了解了不少。总之,原理+实现,才是学习算法的最好方式!
本文将分以下三部分组成,分别为1)树的基本介绍;2)二叉树的概念和性质;3)二叉搜索树的C++实现过程。
2、树的介绍
客观世界之许多事物存在层次关系,这种层次关系不同于之前学习的线性结构如链、栈、表等,如:
- 人类社会家谱
- 社会组织结构
- 图书信息管理
往往,分层次组织结构在管理上具有更高的效率。
- 树的定义:
树是一种数据结构,是由
n(n≥1)
个有限节点组成的一个具有层次关系的集合。
1. 每个节点有零个或多个子节点;
2. 没有父节点的节点称为根节点;
3. 每一个非根节点有且只有一个父节点;
4. 除了根节点外,每个子节点可以分为多个不相交的子树。
- 树的基本术语:
- 结点的度(Degree):结点拥有的子树的数目;
- 叶子(Leaf):度为零的结点;
- 父结点(Parent):若一个结点有子树,那么该结点是子树根的父结点。
- 子结点(Child):若该结点有父结点,则该结点为父结点的字结点,也称孩子结点;
- 兄弟结点(Sibling):具有同一父结点的结点互相为兄弟结点;
- 祖先结点(Ancestor):沿着树根到某一结点路径上所有结点都是该结点的祖先结点;
- 子孙结点(Descendant):某一结点的子树中所有结点都是该结点子孙;
- 树的度:树中结点的最大的度;
- 结点层次(Level):根节点的层次为1,其余结点的层次等于该结点的父结点的层次加1;
- 树的深度(Depth):树中结点的最大层次;
- 无序树:如果树中结点的各子树之间次序无关,可以交换位置;
- 有序树:如果树中结点的各子树之间次序相关,不能交换位置;
- 森林:0个或多个不相交的树组成。对森林加上一个根,森林变成树;删去树的根,树即成为森林。
3、二叉树的介绍
- 二叉树的定义
二叉树是每个结点的度最多有2个的树结构。
1) 二叉树具有五种基本形态{空、单根、根+左子树、根+右子树、根+左右子树};
2) 二叉树的子树有左右顺序之分。
- 二叉树的性质
性质1:二叉树第i层上的结点数目最多为
2i−1 (i≥1)
;
性质2:深度为k的二叉树最大结点总数为
2k−1 (k≥1)
;
性质3:对于任何非空二叉树,若
n0
表示叶结点的数目,
n2
表示度为2的非叶结点的数目,那么两者满足关系
n0=n2+1
;
性质4:包含了n个结点的二叉树的高度至少为
log(n+1)2
;
- 满二叉树、完全二叉树和二叉搜索树
1. 满二叉树
定义:高度为h,并且有
2h−1
个结点的二叉树,即拥有最大结点总数,被称为满二叉树。
2. 完全二叉树
定义:除了最下面两层的度可以小于2,并且在最后一层的叶节点集中在靠左的若干位置上。
特点:叶子结点只能出现在最下层和次下层,且按照从上到下,从左到右的顺序。
3. 二叉搜索树
定义:二叉搜索树又称二叉查找树(Binary Search Tree)。设x是二叉搜索树的一个结点,x结点包含关键字key,key值记为key[x]。若y是x左子树的一个结点,则
key[x]≥key[y]
;若y是x右子树的一个结点,则
key[x]≤key[y]
。
在BST中:
- 若任意结点的左子树不空,则左子树的所有结点的值均小于其根节点的值;
- 任意结点的右子树不空,则右子树的所有结点的值均大于其根节点的值;
- 任意结点的左右子树也分别为二叉搜索树;
- 没有键值相等的结点。
4、BST的C++实现
1)结点模板类:
- 父节点(Parent);
- 左孩子(leftChild);
- 右孩子(rightChild);
- 键值(key);
#define BinNodePosi(T) BinNode<T>*
template <typename T> struct BinNode{
T key;//键值
BinNodePosi(T) leftChild;//左孩子
BinNodePosi(T) rightChild;//右孩子
BinNodePosi(T) parent;//父节点
BinNode(){}
BinNode(T value,BinNodePosi(T) p,BinNodePosi(T) left,BinNodePosi(T) right):
key(value),parent(p),leftChild(left),rightChild(right){}
BinNode(BinNodePosi(T) node):
key(node->key),parent(node->parent),leftChild(node->leftChild),rightChild(node->rightChild){}
bool operator!=(BinNode const& bn){return key!=bn.key;}
};
2)BinTree模板类:
template <typename T> class BinTree{
private:
BinNodePosi(T) mRoot;//根节点
public:
BinTree(){}//构造函数
~BinTree(){ destroy(); }//析构函数
void preOrder();//先序遍历
void inOrder();//中序遍历
void postOrder();//后序遍历
BinNodePosi(T) search(T key);//根据键值查找节点位置,递归版
BinNodePosi(T) iterativeSearch(T key);//非递归版
T min();//左分支中的最小值
T max();//右分枝中的最大值
BinNodePosi(T) successor(BinNodePosi(T) x);//后继,该节点右子树的最小节点
BinNodePosi(T) predecessor(BinNodePosi(T) x);//前驱,该节点左子树的最大节点
void insert(T key);//插入节点
void remove(T key);//删除节点
void destroy();//销毁二叉树
void print();//打印二叉树
private:
void preOrder(BinNodePosi(T) x) const;
void inOrder(BinNodePosi(T) x) const;
void postOrder(BinNodePosi(T) x) const;
BinNodePosi(T) search(BinNodePosi(T) x,T key) const;
BinNodePosi(T) iterativeSearch(BinNodePosi(T) x, T key) const;
BinNodePosi(T) max(BinNodePosi(T) x);
BinNodePosi(T) min(BinNodePosi(T) x);
void insert(BinNodePosi(T) &x,BinNodePosi(T) z);
BinNodePosi(T) remove(BinNodePosi(T) &x,BinNodePosi(T) z);
void destroy(BinNodePosi(T) &x);
void print(BinNodePosi(T) x, T key, int direction);
};
3)遍历的递归版本实现
前序遍历:
若二叉树非空,则执行以下操作:
1、访问根节点;
2、先序遍历左子树;
3、先序遍历右子树。
template <typename T>
void BinTree<T>::preOrder(BinNodePosi(T) x) const{
if(x != nullptr)
{
cout << x->key << " ";
preOrder(x->leftChild);
preOrder(x->rightChild);
}
}
template <typename T>
void BinTree<T>::preOrder()
{
preOrder(mRoot);
}
中序遍历:
若二叉树非空,则执行以下操作:
1、中序遍历左子树;
2、访问根结点;
3、中序遍历右子树。
template <typename T>
void BinTree<T>::inOrder(BinNodePosi(T) x) const{
if(x != nullptr){
inOrder(x->leftChild);
cout << x->key << " ";
inOrder(x->rightChild);
}
}
template <typename T>
void BinTree<T>::inOrder(){
inOrder(mRoot);
}
后序遍历:
若二叉树非空,则执行以下操作:
1、后序遍历左子树;
2、后序遍历右子树;
3、访问根节点。
template <typename T>
void BinTree<T>::postOrder(BinNodePosi(T) x) const{
if (x != nullptr){
postOrder(x->leftChild);
postOrder(x->rightChild);
cout << x->key << " ";
}
}
template <typename T>
void BinTree<T>::postOrder(){
postOrder(mRoot);
}
BST的插入操作:
- 当原树为空时,生成只有单根的二叉搜索树,Root = NewNode;
- 当原树非空时,通过判断键值key,查询新结点的父节点P;
- 比较新孩子与P的键值大小,插入新孩子操作。
template <typename T>
void BinTree<T>::insert(BinNodePosi(T) &x,BinNodePosi(T) z){
BinNodePosi(T) y = nullptr;
BinNodePosi(T) p = x;
//查询z的插入位置,当p指向空指针时说明找到了插入位置
while (p!=nullptr){
y = p;
if(z->key < p->key)
p = p->leftChild;
else
p = p->rightChild;
}
z->parent = y;//插入节点z的父节点
if(y==nullptr)//当原树为空,生成一个只有一个节点的二叉搜索树
x = z;
else if(z->key < y->key)//进行插入新孩子操作
y->leftChild = z;
else
y->rightChild = z;
}
template <typename T>
void BinTree<T>::insert(T key){
BinNodePosi(T) z = nullptr;
if( (z=new BinNode<T>(key,NULL,NULL,NULL)) == nullptr)
return;
insert(mRoot,z);
}
BST的查找:
- 从当前结点开始,若该结点为空或当前结点的键值与查找键值相等,则返回该结点位置。
- 若该结点键值大于查找键值,则执行左孩子查找操作;
- 若该结点键值小于查找键值,则执行右孩子查找操作。
template <typename T>
BinNodePosi(T) BinTree<T>::search(BinNodePosi(T) x,T key) const{
//递归基
if (x == nullptr || x->key == key)
return x;
if ( (x->key) > key ){
return search(x->leftChild,key);
}
else{
return search(x->rightChild,key);
}
}
template <typename T>
BinNodePosi(T) BinTree<T>::iterativeSearch(BinNodePosi(T) x, T key) const{
while( (x!=nullptr) && (x->key != key) ){
if(key < x->key)
x = x->leftChild;
else
x = x->rightChild;
}
return x;
}
template <typename T>
BinNodePosi(T) BinTree<T>::search(T key){
search(mRoot,key);
}
template <typename T>
BinNodePosi(T) BinTree<T>::iterativeSearch(T key){
iterativeSearch(mRoot,key);
}
BST的前驱:
/*
*节点x的前驱,即查找二叉树中该节点左子树的最大节点。
*原则:沿着左分支深入,沿途右节点入队列(先入先出)暂存
*/
template <typename T>
BinNodePosi(T) BinTree<T>::predecessor(BinNodePosi(T) x){
//若x存在左孩子
if(x->leftChild != nullptr)
return max(x->leftChild);
//若x没有左孩子
//1)x是一个右孩子,则x的前驱是它的父节点
//2)x是一个左孩子,则查找x的最低的父节点,且该父节点具有右孩子,则找到的这个最低的父节点就是x的前驱节点。
BinNodePosi(T) y = x->parent;
while( (y!=nullptr) && (x==y->leftChild) ){
x = y;
y = y->parent;
}
return y;
}
BST的后继:
/*
*节点x的后继,即查找二叉树中该节点右子树的最小节点。
*原则:沿着左分支深入,沿途右节点入队列(先入先出)暂存
*/
template <typename T>
BinNodePosi(T) BinTree<T>::successor(BinNodePosi(T) x){
//如果x存在右孩子
if(x->rightChild != nullptr)
return min(x->rightChild);
// 若x没有右孩子
// 1)x是一个左孩子,则x的后继节点为其父节点
// 2)x是一个右孩子,则查找x的最低的父节点且该父节点存在左孩子。
BinNodePosi(T) y = x->parent;
while( (y!=nullptr) && (x==y->rightChild) ){
x = y;
y = y->parent;
}
return y;
}
BST的删除操作:
//1)p为叶子节点,直接删除该节点,再修改其父节点的指针,置为null
//2)p只有一个孩子节点,将其父节点的指针指向要删除节点的孩子节点,即p->parent->child = p->child
//3)p有左右子树,用另一节点替代被删除节点->右子树的最小元素或左子树的最大元素,并修改p的键值
template <typename T>
BinNodePosi(T) BinTree<T>::remove(BinNodePosi(T) &x,BinNodePosi(T) z){
BinNodePosi(T) p1 = nullptr;//p1为p2的孩子指针
BinNodePosi(T) p2 = nullptr;//p2为删除位置
// 查找删除位置p2
//若z为叶节点
if((z->leftChild == nullptr) && (z->rightChild == nullptr))
p2 = z;
//若z只有一个孩子节点
else if( (z->leftChild == nullptr) || (z->rightChild == nullptr))
p2 = z;
else//否则,z有左右子树
p2 = successor(z);//右子树的最小节点
if(p2->leftChild != nullptr)
p1 = p2->leftChild;//p1为p2的左孩子
else
p1 = p2->rightChild;//p1为p2的右孩子
// 修改新父节点与孩子节点的双向指向关系
if(p1 != nullptr)//当p2为后继节点或p2为
p1->parent = p2->parent;//跳过待删除节点,修改新孩子-父指针
else if(p2 == p2->parent->leftChild)//若p2是其父节点的左孩子,修改新父-孩子指针
p2->parent->leftChild = p1;
else
p2->parent->rightChild = p1;//若p2是其父节点的右孩子,修改父-孩子指针
if (p2 != z) //若z不为叶节点,即p2为z的后继
z->key = p2->key;//将z的值修改为后继值
return p2;//返回被删除节点
}
template <typename T>
void BinTree<T>::remove(T key){
BinNodePosi(T) z = new BinNode<T>( search(mRoot, key) );
if(z!=nullptr)
{
BinNodePosi(T) node = new BinNode<T>( remove(mRoot, z) );
if(node!=nullptr)
delete node;
}
}
参考资料:
[1]http://www.cnblogs.com/skywang12345/p/3576328.html
[2]http://www.cnblogs.com/xpjiang/p/4569591.html