基础定义
一个树t是一个非空的有限元素的集合,其中一个元素为根(root),其余的元素(如果有的话)组成t的子树(subtree)
树的另一常用术语为级(level)。树根是1级,其孩子(如果有)是2级,孩子的孩子是3级,等等。
一棵树的高度(height)或深度(depth)是树中级的个数
一个元素的度(degree of an element)是指其孩子的个数。
一棵树的度是其元素的度的最大值
二叉树
定义
定义:一棵二叉树(binary tree)t是有限个元素的集合(可以为空)。当二叉树非空时,其中有一个元素称为根,余下的元素(如果有的话)被划分为两颗二叉树,分别称为t的左子树和右子树
特点:
1)二叉树的每个元素都恰好有两棵子树(其中一个或两个可能为空)
2)在二叉树中,每个元素的子树都是有序的,也就是有左子树和右子树之分
3)二叉树可以为空,而树不能为空
特性
1)一棵二叉树有n个元素,n>0,它有n-1条边
2)一棵二叉树的高度为h,h>=0,它最少有h个元素,最多有2^h-1个元素
3)一棵二叉树有n个元素,n>0,它的高度最大为n,最小高度为log2(n+1)向上取整
当高度为h的二叉树恰好有2^h-1个元素时,称其为满二叉树(full binary tree)
假设从满二叉树中删除k个其编号为2^h-i(最后一层)元素,1<=i<=k<2^h,所得到的二叉树被称为完全二叉树(complete binary tree)
设完全二叉树的一元素编号为i,1<=i<=n,有以下关系:
1)如果i=1,则该元素是二叉树的根。若i>1,则其父节点的编号为i/2向下取整
2)如果2i>n,则该元素无左孩子。否则,其左孩子的编号为2i
3)如果2i+1>n,则该元素无右孩子。否则,其右孩子的编号为2i+1
二叉树的链表描述
每个元素用一个节点表示,节点有两个指针域,分别称为leftChile和rightChile。除此之外,还有一个element域。
template<class T>
struct binaryTreeNode
{
T element;
binaryTreeNode<T> *leftChild;//左子树
binaryTreeNode<T> *rightChild;//右子树
binaryTreeNode() {leftChild=rightChile=NULL;}
binaryTreeNode(const T& theElement)
{
element(theElement);
leftChild=rightChild=NULL;
}
binaryTreeNode(const T& theElement,binaryTreeNode *theLeftChild,binaryTreeNode *theRightChild)
{
element(theElement);
leftChild=theLeftChile;
rightChild=theRightChild;
}
};
二叉树的遍历
前序遍历(先根遍历)
顾名思义,先访问根节点,然后前序遍历左子树,最后前序遍历右子树
递归程序:
template<class T>
void preOrder(binaryTreeNode<T> *t)
{
//递归前序遍历
if(t!=NULL)
{
visit(t);//访问根节点
preOrder(t->leftChild);//前序遍历左子树
preOrder(t->leftChild);//前序遍历右子树
}
}
非递归程序:用栈来模拟递归过程
template<class T>
void preOrder(binaryTreeNode<T> *t)
{
//非递归前序遍历
stack<binaryTreeNode<T>*> s;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
visit(p);
p=p->leftChild;
}
p=s.top();
s.pop();
p=p->rightChild;
}
}
中序遍历(中根遍历)
顾名思义,先中序遍历左子树,然后访问根节点,最后中序遍历右子树
递归程序:
template<class T>
void inOrder(binaryTreeNode<T> *t)
{
//递归中序遍历
if(t!=NULL)
{
inOrder(t->leftChild);//中序遍历左子树
visit(t);//访问根节点
inOrder(t->rightChild);//中序遍历右子树
}
}
非递归程序:
template<class T>
void inOrder(binaryTreeNode<T> *t)
{
//非递归中序遍历
stack<binaryTreeNode<T>*> s;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
p=p->leftChild;
}
p=s.top();
visit(p);
s.pop();
p=p->rightChild;
}
}
后序遍历(后根遍历)
顾名思义:先后序遍历左子树,然后后序遍历右子树,最后访问根节点
递归程序:
template<class T>
void postOrder(binaryTreeNode<T> *t)
{
//递归后序遍历
if(t!=NULL)
{
postOrder(t->leftChild);//后序遍历左子树
postOrder(t->rightChild);//后序遍历右子树
visit(t);//访问根节点
}
}
非递归程序:后序遍历的非递归程序比较难,因为根节点要在最后访问,如果用栈来模拟递归,根节点会被先出栈。我们可以考虑:只有当第二次将根节点出栈时,才访问它。可以用一个辅助栈来同步计数
template<class T>
void postOrder(binaryTreeNode<T> *t)
{
//非递归后序遍历
stack<binaryTreeNode<T>*> s;
stack<int> index;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
index.push(0);//计数一次
p=p->leftChild;
}
if(index.top()==1)
{
//第二次出栈则访问根节点
visit(s.top());
s.pop();
index.pop();
}
else
{
p=s.top();
p=p->rightChild;
index.top()=1;//计数两次
}
}
}
层次遍历
层次遍历是从顶层到底层,在同一层中,从左到右,依次访问树的元素。因为层次遍历需要队列而不是栈,因此很难编写递归程序。
非递归程序:
template<class T>
void levelOrder(binaryTreeNode<T> *t)
{
//层次遍历
arrayQueue<binaryTreeNode<T>*> q;
while(t!=NULL)
{
visit(t);//访问t
//将t的孩子插入队列
if(t->leftChild!=NULL)
q.push(t->leftChild);
if(t->rightChild!=NULL)
q.push(t->rightChild);
//提取下一个要访问的节点
try
{
t=q.front();
}
catch(queueEmpty)
{
return;
}
q.pop();
}
}