结点的度:结点所拥有的子树棵数。
结点的层次:从根到该结点所经路径上的分支条数。
叶结点:度为0的结点。
树的深度:树中距离根结点最远的结点所处层次即为树的深度。
树的度:树中结点的度的最大值。
有序树:树中结点的各棵子树是有次序的。
二叉树
特点:每个结点最多有两个子女,并且二叉树的子树有左、右之分,其子树的次序不能颠倒,因此是有根有序树。
二叉树的性质
- 在二叉树的第i(i>=1)层最多有2^(i-1)个结点。
- 深度为k(k>=0)的二叉树,最少有k个结点,最多有2^k-1个结点。
- 对任何一棵非空二叉树,如果其叶结点数为n0,度为2的非叶结点数为n2,则n0=n2+1。
- 完全二叉树:当深度为k时,从第1层到第k-1层的所有各层的结点数都是满的,只有最下面第k层或是满的,或从右向左连续缺若干结点。
- 具有n个结点的完全二叉树的深度为log2^(n+1)取上。
- 如果将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号。则有如下性质:
- 若i==1,则结点i为根;若i>1,则结点i的父节点为结点i/2取下。
- 若2i <= n,则结点i的左子女结点2i。
- 若2i+1<=n,则结点i的右子女为结点2i+1。
- 若结点编号i为奇数,且i!=1,它处于右兄弟位置。
- 若结点编号i为偶数,且i!=n,它处于左兄弟位置。
- 结点i所处层次为log2^i取下再加1。
二叉树的存储表示
数组存储表示
适用于二叉树的大小和形态不发生剧烈动态变化的场合,例如完全二叉树。
链表存储表示
二叉树的结点至少有三个域:数据data、左子女结点指针、右子女结点指针。要找结点的父结点可以增加一个父指针域。
二叉树遍历及其应用
L、R、V分别代表遍历一个结点的左子树、右子树和该结点。
三种遍历方式:
//前序遍历VLR
template<class T>
void BinaryTree<T>::PreOreder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)){//前序遍历以subTree为根的子树,第二个参数是访问函数visit名当指针传入。
if(subTree !=NULL){
visit(subTree);//访问根结点
PreOrder(subTree->leftChild,visit);
PreOrder(subTree->right,visit);
}
};
//中序遍历算法
template<class T>
void BinaryTree<T>::InOreder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)){
if(subTree !=NULL){
InOrder(subTree->leftChild,visit);
visit(subTree);//访问根结点
InOrder(subTree->right,visit);
}
};
//后续遍历
template<class T>
void BinaryTree<T>::PostOreder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)){
if(subTree !=NULL){
PostOrder(subTree->leftChild,visit);
PostOrder(subTree->right,visit);
visit(subTree);//访问根结点
}
};
利用二叉树前序遍历建立和输出二叉树
//前序遍历建立二叉树的算法
template<class T>
void BinaryTree<T>::CreateBinTree(ifstream& in, BinTreeNode<T> *& subTree){
T item;
if(!in.eof()){//未读完,读入并建树
in>>item;//读入根结点
if(item!=RefValue){//不是终止值
subTree = new BinTreeNode<T>(item);
if(subTree==NULL){
cerr<<"存储分配错"<<endl;
exit(1);}
CreateBinTree(in, subTree->leftChild);
CreateBinTree(in, subTree->rightChild);
}
else subTree = NULL;//建立空子树结束递归
}
}
//以广义表形式输出二叉树的算法
#include “binaryTree.h”
template<class T>
void PrintBTree(BinTreeNode<T> *BT){
if(BT != NULL){
cout<<BT->data;
if(BT->leftChild != NULL || BT->rightChild != NULL){
cout<<'(';
PrintBTree(BT->leftChild);
cout<<',';
if(BT->rightChild != NULL)
PrintBTree(BT->rightChild);
cout<<')';
}
}
}
二叉树遍历的非递归算法
利用栈记录遍历时的回退路径
//前序遍历,访问一个结点后向左子树遍历下去,利用栈记录该结点的右子女。
#include"stack.h"
template<class T>
void BinaryTree<T>::PreOrder(void(*visit)(BinTreeNode<T>*p)){
stack<BinTreeNode<T>*> S;//声明一个栈,栈的元素是指向结点的指针
BinTreeNode<T> *p = root;
S.Push(NULL);//压入NULL,设置为遍历结束条件
while(p != NULL){
visit(p);
if(p->rightChild != NULL) S.push(p->rightChild);
if(p->leftChild !+ NULL) p=p->leftChild;
else S.Pop(p);//将栈顶元素弹出赋给p
}
}
//另一种前序遍历的非递归算法
#include"stack.h"
template<class T>
void BinaryTree<T>::PreOrder(void(*visit)(BinTreeNode<T>*p)){
stack<BinTreeNode<T>*> S;
BinTreeNode<T> *p;
S.Push(root);//将根结点压入栈中
while(! S.IsEmpty()){
S.Pop(p);visit(p);//退栈,访问
if(p->rightChild != NULL) S.push(p->rightChild);//先压入右子树再左子树,退栈访问刚好相反
if(p->leftChild != NULL) S.push(p->leftChild);
}
}
//中序遍历非递归算法
#include"stack.h"
template<class T>
void BinaryTree<T>::InOrder(void(*visit)(BinTreeNode<T>*p)){
stack<BinTreeNode<T>*> S;
BinTreeNode<T> *p = root;
do{
while(p!=NULL){//遍历到最左下的结点
S.Push(p);//该子树沿途结点进展,其实把父结点和左子女都压入了
p=p->leftChild;
}
if(!S.IsEmpty()){
S.Pop(p);visit(p);
p=p->rightChild;//转到右子女结点
}
}while(p != NULL || !S.IsEmpty());//结束条件是栈为空同时遍历指针也为空,若栈为空,而指针不为空说明右子树非空。
}
//后续遍历所用的栈的结点定义
template<class T>
struct stkNode{
BinTreeNode<T> *ptr;
enum tag{L,R};//栈暂存根结点,当向左子树遍历下去,tag为L,当向右子树遍历为R,从右子树退出时才访问位于栈顶的根结点的值。
sktNode(BinTreeNode<T> *N = NULL):ptr(N),tag(L){}
}
后续遍历的非递归算法
#include "stack.h"
template<class T>
void BinaryTree<T>::PostOrder(void(*visit)(BinTreeNode<T>*p)){
Stack<sktNode<T>> S;stkNode<T> w;
BinTreeNode<T> *p=root;
do{
while(p!=NULL){
w.ptr=p;w.tag=L;S.Push(w);
p=p->leftChild;
}
int continue1=1;//
while(continue1 && !S.IsEmpty()){
S.Pop(w);p=w.ptr;
switch(w.tag){
case L:
w.tag=R;S.Push(w);//从左子树退回,修改栈顶tag
continue1=0;
p=p->rightChild;
break;//这里是对应switch,否则不会自动停止,会继续执行下面的case
case R: visit(p); break;
}
}
}while(!S.IsEmpty()):
cout<<endl;
}
由给定的前序序列和中序序列能够唯一确定一棵二叉树。