数据结构系列文章 |
---|
数据结构图文解析之:数组、单链表、双链表介绍及C++模板实现 |
数据结构图文解析之:栈的简介及C++模板实现 |
数据结构图文解析之:队列详解与C++模板实现 |
数据结构图文解析之:树的简介及二叉排序树C++模板实现. |
树是一种数据结构,它是n(n>=0)个节点的有限集。n=0时称为空树。n>0时,有限集的元素构成一个具有层次感的数据结构。
区别于线性表一对一的元素关系,树中的节点是一对多的关系。树具有以下特点:
树有许多相关的术语与概念,在学习树的结构之前,我们要熟悉这些概念。
二叉树或者为空集,或者由一个根节点和两棵互不相交的、分别称为左子树和右子树的二叉树组成。从定义可以看出一棵二叉树:
根据定义,一棵二叉树有5中基本形态:
所有节点都只有左子树的二叉树叫做左斜树,所有节点都只有右子树的二叉树叫做右斜树。左斜树和右子树统称为斜树。
斜树已经退化成线性结构,二叉树在查找上表现出来优异性能在斜树得不到体现。
注意:为了只关注重点,我们所有的节点都采用统一浅绿色着色,若有特殊节点将在图中备注
满二叉树要满足两个条件:
在同样深度的二叉树中,满二叉树的节点数目是最多的,叶子数也是最多的。
在一棵二叉树中,只有最下两层的度可以小于2,并且最下一层的叶子节点集中出现在靠左的若干位置上。
或者这样定义:对一棵具有n个节点的二叉树按层序从左到右编序,二叉树树某个节点的编序与同样位置的满二叉树节点的编序相同如果所有节点都满足这个条件,则二叉树为完全二叉树。
从定义可以看出: 满二叉树一定是完全二叉树;完全二叉树不一定是满二叉树。
二叉排序树也称为二叉搜索树或二叉排序树。二叉排序树的节点包含键值key。二叉排序树或者是一棵空树,否则要求:
根据定义,二叉查找树中没有重复key的节点。
在实际的应用中,二叉排序树的应用比较多,我们后面要讲的AVL树本身也是一种二叉排序树。
证明:利用数学归纳法进行证明
证明:二叉树节点数最多时,每层的节点树都必须最多。
根据性质一,深度为k的二叉树的节点数最多为: 2^0 + 2^1 +....+2^(k-1) = 2 ^ k -1
证明:二叉树节点度数最大为2,则 : n = n0 + n1 + n2 (等式一)
从孩子个数角度出发: 度为0的节点没有孩子, 度为1的节点没有1个孩子,度为2的节点有2个孩子,孩子总数为 n00 + n11 +n2 2 = n1+2n2;树的所有节点中,只有根不是任何节点的孩 子,因此有 n -1 = n1 + 2* n2 ,即 n = n1 + 2* n2 + 1. (等式二)
由等式一等式而可以推出 n0 = n2 +1
证明:高度为h的二叉树最多有2{h}–1个结点。反之,对于包含n个节点的二叉树的高度至少为log2(n+1)。
二叉查找树的定义我们已经知道。要维护二叉查找树的特性,比较复杂的是删除节点操作,我们将进行重点的解析。不过我们先来看看二叉查找树的节点结构定义与类定义。
//二叉查找树的节点结构
template <typename T>
struct BSNode
{
BSNode(T t)
: value(t), lchild(nullptr), rchild(nullptr){}
BSNode() = default;
T value;
BSNode<T>* lchild;
BSNode<T>* rchild;
BSNode<T>* parent;
};
//二叉查找树类
template <typename T>
class BSTree
{
public:
BSTree();
~BSTree();
void preOrder(); //前序遍历二叉树
void inOrder(); //中序遍历二叉树
void postOrder(); //后序遍历二叉树
void layerOrder(); //层次遍历二叉树
BSNode<T>* search_recursion(T key); //递归地进行查找
BSNode<T>* search_Iterator(T key); //迭代地进行查找
T search_minimun(); //查找最小元素
T search_maximum(); //查找最大元素
BSNode<T>* successor (BSNode<T>* x); //查找指定节点的后继节点
BSNode<T>* predecessor(BSNode<T>* x); //查找指定节点的前驱节点
void insert(T key); //插入指定值节点
void remove(T key); //删除指定值节点
void destory(); //销毁二叉树
void print(); //打印二叉树
private:
BSNode<T>* root; //根节点
private:
BSNode<T>* search(BSNode<T>* & p, T key);
void remove(BSNode<T>* p, T key);
void preOrder(BSNode<T>* p);
void inOrder(BSNode<T>* p);
void postOrder(BSNode<T>* p);
T search_minimun(BSNode<T>* p);
T search_maximum(BSNode<T>* p);
void destory(BSNode<T>* &p);
};
这里我们定义了二叉排序树的类型BSTree。它包含了:
提供的其他接口都有相应的备注说明。
假设我们要为数组 a[] = {10 , 5 , 15 , 6 , 4 , 16 }构建一个二叉排序树,我们按顺序逐个插入元素。
插入过程是这样的:
从这个过程我们可以总结出插入新元素的步骤:
该过程的实现代码:
/*插入函数*/
template <typename T>
void BSTree<T>::insert(T key)
{
BSNode<T>* pparent = nullptr;
BSNode<T>* pnode = root;
while (pnode != nullptr) //寻找合适的插入位置
{
pparent = pnode;
if (key > pnode->value)
pnode = pnode->rchild;
else if (key < pnode->value)
pnode = pnode->lchild;
else
break;
}
pnode = new BSNode<T>(key); //以元素的值构建新节点
if (pparent == nullptr) //如果是空树
{
root = pnode; //则新节点为根
}
else
{
if (key > pparent->value)
{
pparent->rchild = pnode;//否则新节点为其父节点的左孩
}
else
pparent->lchild = pnode; //或右孩
}
pnode->parent = pparent; //指明新节点的父节点
};
将构建出来的新节点插入二叉排序树时,需要修改链接指针的指向。
遍历平衡二叉树,就是以某种方式逐个“访问”二叉树的每一个节点。“访问”是指对节点的进行某种操作,例如输出节点的值。
平衡二叉树是有序树,严格区分左子树与右子树,如果规定左子树先于右子树的次序,我们有三种方式遍历二叉树:
若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。(简记为:VLR)
/*前序遍历算法*/
template <typename T>
void BSTree<T>::preOrder()
{
preOrder(root);
};
template <typename T>
void BSTree<T>::preOrder(BSNode<T> *p)
{
if (p != nullptr)
{
cout << p->value << endl;
preOrder(p->lchild);
preOrder(p->rchild);
}
};
前序遍历树a:10 5 4 3 6 15 16
前序遍历树b:5 3 2 4 8 7 9
若二叉树为空,则空操作返回,否则从根节点开始,中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树。(简记为:LVR)
/*中序遍历算法*/
template <typename T>
void BSTree<T>::inOrder()
{
inOrder(root);
};
template<typename T>
void BSTree<T>::inOrder(BSNode<T>* p)
{
if (p != nullptr)
{
inOrder(p->lchild);
cout << p->value << endl;
inOrder(p->rchild);
}
};
前序遍历树a:3 4 5 6 10 15 16
前序遍历树b:2 3 4 5 7 8 9
二叉排序树的中序遍历刚好输出一个非递减的有序序列。
若树为空,则返回空操作,否则从左到右先叶子后节点的方式遍历访问左右子树,左右子树都访问结束,才访问根节点。(简称LRV)
/*后序遍历算法*/
template <typename T>
void BSTree<T>::postOrder()
{
postOrder(root);
};
template <typename T>
void BSTree<T>::postOrder(BSNode<T>* p)
{
if (p != nullptr)
{
postOrder(p->lchild);
postOrder(p->rchild);
cout << p->value<<endl;
}
};
后序遍历树a:3 4 6 5 16 15 10
后序遍历树b:2 4 3 7 9 8 5
对于一棵二叉排序树,中序遍历时刚好可以输出一个非递减的序列。例如前序遍历图九树a:3 4 5 6 10 15 16,则可称:
一个节点的前驱节点有3种情况:
它没有左子树,且它本身为左子树,则它的前驱节点为“第一个拥有右子树的父节点”
/*寻找其前驱节点*/
template <typename T>
BSNode<T>* BSTree<T>::predecessor(BSNode<T>* pnode)
{
if (pnode->lchild != nullptr)
{
pnode = pnode->lchild;
while (pnode->rchild != nullptr)
{
pnode = pnode->rchild;
}
return pnode;
}
BSNode<T>* pparent = pnode->parent;
while (pparent != nullptr && pparent->lchild == pnode)//如果进入循环,则是第三种情况;否则为第二种情况
{
pnode = pparent;
pparent = pparent->parent;
}
return pparent;
};
同样的,一个节点的后继节点也有三种情况:
/*寻找其后继节点*/
template <typename T>
BSNode<T>* BSTree<T>::successor(BSNode<T>* pnode)
{
if (pnode->rchild != nullptr)
{
pnode = pnode->rchild;
while (pnode->lchild != nullptr)
{
pnode = pnode->lchild;
}
return pnode;
}
BSNode<T>* pparent = pnode->parent;
while (pparent!=nullptr&& pparent->rchild == pnode)
{
pnode = pparent;
pparent = pparent->parent;
}
return pparent;
};
删除二叉排序树的某个节点有三种情况:
对于第一种情况,我们的处理方式是将前驱节点的值保存在当前结点,继而删除前驱节点。
对于第二种情况,我们直接用子树替换被删节点。
对于第三种情况,我们可以直接删除节点。
删除节点的代码:
/*删除指定节点*/
template <typename T>
void BSTree<T>::remove(T key)
{
remove(root, key);
};
/*删除指定节点*/
/*内部使用函数*/
template <typename T>
void BSTree<T>::remove(BSNode<T>* pnode, T key)
{
if (pnode != nullptr)
{
if (pnode->value == key)
{
BSNode<T>* pdel=nullptr;
if (pnode->lchild == nullptr || pnode->rchild == nullptr)
pdel = pnode; //情况二、三:被删节点只有左子树或右子树,或没有孩子
else
pdel = predecessor(pnode); //情况一:被删节点同时有左右子树,则删除该节点的前驱
//此时,被删节点只有一个孩子(或没有孩子).保存该孩子指针
BSNode<T>* pchild=nullptr;
if (pdel->lchild != nullptr)
pchild = pdel->lchild;
else
pchild = pdel->rchild;
//让孩子指向被删除节点的父节点
if (pchild != nullptr)
pchild->parent = pdel->parent;
//如果要删除的节点是头节点,注意更改root的值
if (pdel->parent == nullptr)
root = pchild;
//如果要删除的节点不是头节点,要注意更改它的双亲节点指向新的孩子节点
else if (pdel->parent->lchild==pdel)
{
pdel->parent->lchild = pchild;
}
else
{
pdel->parent->rchild = pchild;
}
if (pnode->value != pdel->value)
pnode->value = pdel->value;
delete pdel;
}
//进行递归删除
else if (key > pnode->value)
{
remove(pnode->rchild, key);
}
else remove(pnode->lchild, key);
}
};
我们可以递归或非递归地进行元素的查找。元素的查找过程与元素的插入过程一致,也是在不断地与当前结点进行比较,若值比当前节点的值大,则在右子树进行查找,若值比当前节点的值小,则在左子树进行查找,可以看到这是一个很适合递归操作的过程。而由于二叉排序树这种左小右大的节点特征,也很容易进行非递归查找。
/*查找指定元素的节点(非递归)*/
template <typename T>
BSNode<T>* BSTree<T>::search_Iterator(T key)
{
BSNode<T> * pnode = root;
while (pnode != nullptr)
{
if (key == pnode->value) //找到
return pnode;
if (key > pnode->value) //关键字比节点值大,在节点右子树查找
pnode = pnode->rchild;
else
pnode = pnode->lchild; //关键字比节点值小,在节点左子树查找
}
return nullptr;
};
/*查找指定元素的节点(递归)*/
template <typename T>
BSNode<T>* BSTree<T>::search_recursion(T key)
{
return search(root, key);
};
/*private:search()*/
/*递归查找的类内部实现*/
template <typename T>
BSNode<T>* BSTree<T>::search(BSNode<T>* & pnode, T key)
{
if (pnode == nullptr)
return nullptr;
if (pnode->value == key)
return pnode;
//cout << "-->" << pnode->value << endl; //可以输出查找路径
if (key > pnode->value)
return search(pnode->rchild, key);
return search(pnode->lchild, key);
};
二叉排序树的最小值位于其最左节点上;最大值位于其最右节点上:
/*寻找最小元素*/
template <typename T>
T BSTree<T>::search_minimun()
{
return search_minimun(root);
};
template <typename T>
T BSTree<T>::search_minimun(BSNode<T>* p)
{
if (p->lchild != nullptr)
return search_minimun(p->lchild);
return p->value;
};
/*寻找最大元素*/
template <typename T>
T BSTree<T>::search_maximum()
{
return search_maximum(root);
};
template <typename T>
T BSTree<T>::search_maximum(BSNode<T>*p)
{
if (p->rchild != nullptr)
return search_maximum(p->rchild);
return p->value;
};
使用后序遍历递归销毁二叉树
/*销毁二叉树*/
template<typename T>
void BSTree<T>::destory()
{
destory(root);
};
template <typename T>
void BSTree<T>::destory(BSNode<T>* &p)
{
if (p != nullptr)
{
if (p->lchild != nullptr)
destory(p->lchild);
if (p->rchild != nullptr)
destory(p->rchild);
delete p;
p = nullptr;
}
};
int main()
{
BSTree<int> t;
t.insert(62);
t.insert(58);
t.insert(47);
t.insert(51);
t.insert(35);
t.insert(37);
t.insert(88);
t.insert(73);
t.insert(99);
t.insert(93);
t.insert(95);
cout << endl << "中序遍历:" << endl;
t.inOrder();
cout << "最大元素:" << t.search_maximum() << endl;
cout << "最小元素:" << t.search_minimun() << endl;
cout << "删除元素99" << endl;
t.remove(99);
cout << "最大元素:" << t.search_maximum() << endl;
t.destory();
getchar();
return 0;
}
运行结果:
中序遍历:
35
37
47
51
58
62
73
88
93
95
99
最大元素:99
最小元素:35
删除元素99
最大元素:95
在github上存放了二叉排序树的vs项目工程。这是二叉排序树的源代码:
https://github.com/huanzheWu/Data-Structure/blob/master/BSTree/BSTree/BSTree.h
原创文章,转载请注明出处:http://www.cnblogs.com/QG-whz/p/5168620.html#_label0