10.1 树结构
树是一种层次化的结构,各个元素存储在树枝上,树枝从根开始向下延伸。
树节点按照所在的高度被划分为不同的层,根节点在最高层。任何节点都可以有多个后继,这些后继比该节点低一个层次。所以,树是一种非线性结构。在广义树中,每个节点可以有多于两个的后继节点,但他们的应用有限。二叉树中每个节点最多有两个后继节点。
在一棵树中,有一个唯一的起始节点,称为根节点。其余的节点都从根节点延伸出来。每一个节点包含一个值和零个或多个指向后继节点的指针。
从父节点P到其子树中某一个节点N的路径由一系列节点组成,由于每一个非根节点只有一个父节点,这样保证了从任何节点到他或其子节点只有唯一的一条路径。
从根节点到任何节点的路径长度为每一个节点定义了一种度量,称为层次。根节点的层次是零。根节点的子节点的层次是1 , 子节点的子节点的层次是2 , 以此类推 。
树的高度定义为树中所有节点层次的最大值。
二叉树中每个节点都可以通过两个指针来访问到他的子节点。我们用左指针和右指针来区分它们。
二叉树的密度::二叉树的第n层的节点数可以从1 到 2^n 变化。每层平均的节点数定义为树的密度。
把只有一个叶节点,且每一个非叶节点只有一个子节点的树称为退化树。n节点退化树的深度为n-1 。可以看出,退化的树和链表是等价的。
高度为n的完全二叉树的第0层到第n-1层的节点是满的,而在第n层,叶子节点则占据了最左边的位置。
2^d <= n <= 2^(d+1) – 1 <2^(d+1) ;
====> d<= log2 n <d+1 ;n为完全二叉树的节点数,d为深度。所以,d = int(log 2 n) ;
10.2 二叉树节点
节点是tnode的对象。每个数节点包含一个数据nodeValue , 以及两个指针:left 和right 。空值(null)表示空子树。
#include <iostream>
using namespace std;
template<typename T>
class tnode
{
public:
T nodeValue ;
tnode<T> *left , *right ;
tnode():left(NULL),right(NULL){}
tnode(const T& item , tnode<T> *lptr = NULL , tnode<T> *rptr = NULL)
{
nodeValue = item ;
left = lptr ;
right = rptr ;
}
};
注意::我们自底向上建立一棵树,先是分配叶子节点,然后分配父节点。
10.3 二叉树的遍历算法
中序遍历::遍历左子树,访问节点,遍历右子树 。
template<typename T>
void inorderOutput(tnode<T> *t , const string& separator = " ")
{
if (t != NULL)
{
inorderOutput(t->left , separator) ;
cout<<char(t->nodeValue) <<separator ;
inorderOutput(t->right , separator ) ;
}
}
后序遍历::遍历左子树,遍历右子树,访问节点 。
template<typename T>
void PostorderOutput(tnode<T> *t , const string& separator = " ")
{
if (t != NULL)
{
PostorderOutput(t->left , separator) ;
PostorderOutput(t->right , separator ) ;
cout<<char(t->nodeValue) <<separator ;
}
}
迭代层次遍历::层次遍历是迭代的过程,他用一个队列作为中间的存储容器。开始,根节点进入这个队列。下面的迭代操作就是不断的弹出节点,对该节点执行一些操作,然后将其子节点加入到队列尾。因为子节点是在访问其父节点时加入队列的,所以,同一层的子节点会按递归顺序退出队列。
层次遍历算法:这个算法包括一个初始化的步骤以及一个while循环,通过这个循环不断地从队列中逐层弹出节点。当队列为空时循环结束。
10.4 使用树遍历算法
10.4.1 叶节点计数
template<typename T>
void countLeaf(tnode<T> *t, int& count)
{
if (t != NULL)
{
if (t->left == NULL && t->right == NULL)
{
count ++ ;
}
countLeaf(t->left , count) ;
countLeaf(t->right , count) ;
}
}
10.4.2 计算树的深度::用后序遍历,函数depth()接受一个树节点指针为参数,返回树的深度。该函数采用后序遍历的算法,首先计算左右子树的深度,然后整颗树的深度就是左右子树深度较大的值加1。
template<typename T>
int depth(tnode<T> *t)
{
int depthVal, depthLeft ,depthRight ;
if (t == NULL)
{
depthVal = -1 ;
}
else
{
depthLeft = depth(t->left) ;
depthRight = depth(t->right) ;
depthVal = 1 + (depthLeft > depthRight ? depthLeft : depthRight ) ;
}
return depthVal ;
}
10.4.3 复制二叉树
copyTree()函数以一颗初始树为参数,然后创建一颗一模一样的树。该函数使用后序遍历来访问树的所有节点。这样的遍历顺序保证先创建左右子树的所有节点。当左右子树都建立了以后,再创建根节点,并让根节点指向其子树。该函数自下而上构建新树。
template<typename T>
tnode<T> * copyTree(tnode<T> *t)
{
tnode<T> *newLeft , *newRight ,*newNode ;
if (t == NULL)
{
return NULL ;
}
newLeft = copyTree(t->left) ;
newRight = copyTree(t->right) ;
newNode = new tnode<T>(t->nodeValue , newLeft , newRight) ;
return newNode ;
}
10.4.4 删除树节点
采用后序遍历来遍历树节点。这样的顺序保证先删除子节点再删除当前父节点。
template<typename T>
void deleteTree(tnode<T> *t)
{
//后序扫描,删除t的左子树和右子树,然后删除根节点t
if (t != NULL)
{
deleteTree(t->left);
deleteTree(t->right);
delete t ; //删除子根节点
} //所有树节点都被删除,根节点指向不确定位置。
}
template<typename T>
void clearTree(tnode<T> * & t)
{ //删除所有节点,并使根节点指针指向null 。
deleteTree(t) ;
t = NULL ;
}
10.4.5 显示二叉树
displayTree(),她输出二叉树的图,输出地树是竖直的,同一层的节点在同一行上。
10.5 二叉搜索树
我们将二叉树设计成在能够存储大量数据的同时,又能为程序员提供方便高效的访问和更新元素的操作。最终得到的容器结构为二叉搜索树,她包含了一些操作,是所有关联容器的原型。
与顺序容器相似,二叉搜索树也提供了迭代器来遍历元素,并且为搜索和更新操作也提供了便利。
10.5.1 二叉搜索树概述::二叉树是由各个节点组成的数据结构,从根节点到其他节点之间只有唯一的路径。为了将二叉树设计成有效的存储容器,我们必须实现一种次序,把节点的值与她所处的路径联系起来。这种次序是的我们可以根据值来找到对应元素的位置。由于这种原因,这种存储容器称为二叉搜索树。
二叉搜索树按模板类型T定义的关系运算符< 为元素排序。排序过程不断比较节点和子树中元素的值:排序:对于每个节点,其左子树中的所有节点的值都比他小,而其右子树中所有节点的值都比她大。
二叉搜索树的这种结构保证了对他的中序遍历可以按照升序列出所有元素。
二叉搜索树是按元素值来定位元素的结构。这个类为stree类,函数size和empty来计算容器中的元素个数。默认的构造函数创建一个空树,而第二种形式的构造函数用预定义数组中的元素使用地址范围来初始化树。二叉搜索树类支持常量或非常量的迭代器。