二叉树学习笔记(一)之遍历二叉树
二叉树基础知识
概念
- 二叉树定义:含有n(n>=0)个节点的有限集合
- 非空二叉树中:
- 有且仅有一个根节点
- 其余节点划分为两个互不相交的子集L和R,L称为左子树,R称为右子树
- 任一结点的左、右子树的根称为该结点的左、右孩子,反过来,该结点称为孩子的双亲
- 度:结点的孩子个数
- 叶子结点:度为0 的结点
- 非叶子结点:度大于0,也称为内部结点或分支结点
- 二叉树的深度(或高度):结点的最大层次称为二叉树的深度(或高度)。(所谓层次,根节点即为第一层,以此类推)
二叉树的性质
- 在非空二叉树的第 i 层上最多右 2^(i-1) 个结点(i>=1)
- 深度为 k 的二叉树最多有 2^k - 1 个结点(k>=1)
- 对于任意一颗二叉树,如果度为 0 的结点个数为 n0 ,度为 2 的结点个数为 n2,则 n0 = n2+1
- 具有 n 个结点的完全二叉树的深度 k = [log2n] + 1
- 对于含 n 个结点的完全二叉树中编号为 i (1<=i<=n) 的结点
- 如果 i = 1,则 i 结点是这颗完全二叉树的根,没有双亲;否则其双亲的编号为 [i/2]
- 如果 2i>n,则 i 结点没有左孩子;否则其左孩子的编号为 2i
- 如果 2i+1>n,则 i 结点没有右孩子;否则其右孩子的编号为 2i+1
满二叉树与完全二叉树
-
满二叉树:深度为 k 且有 2^k - 1 个结点的二叉树
-
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。深度为K且含 n 个结点的二叉树,如果其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应,则称之为完全二叉树。换句话讲,在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。
二叉树的存储结构
顺序存储
// 一维数组实现
typedef char TElemType; // 假设结点元素类型为字符
typedef struct {
TElemType * elem; // 0号单元闲置
int lastIndex; // 二叉树最后一个结点的编号
} SqBiTree;
链式存储
- 二叉链表
typedef struct BiTNode{
TElemType data; // 数据域
struct BiTNode *lchild,*rchild; // 左、右孩子指针域
} BiTNode,*BiTNode;
- 三叉链表
typedef struct TriTNode{
TElemType data; // 数据域
struct TriTNode *parent,*lchild,*rchild;// 双亲左、右孩子指针域
} TriTNode,*TriTNode;
二叉树的遍历
首先来认识一下遍历
遍历:树的遍历(也称为树的搜索)是图的遍历的一种,指的是按照某种规则,不重复地访问某种树的所有结点,使得每个结点被且仅被访问的过程。具体的访问操作可能是检查节点的值、更新节点的值等。二叉树的遍历也适用于其它树的遍历。
遍历是二叉树的一类重要操作,也是二叉树的其他一些操作和各种应用算法的基本框架。
遍历有两种类别,一种是深度优先遍历,另一种是广度优先遍历
- 深度优先遍历:先访问子节点,再访问父节点,最后是第二个子节点
- 先序遍历:VLR,即根结点->左结点->右节点
- 中序遍历:LVR,即左结点->根结点->右节点
- 后序遍历:LRV,即左结点->右结点->根节点
- 广度优先遍历:先访问第一个子节点,再访问第二个子节点,最后访问父节点,二叉树的广度优先遍历就是层次遍历
由于从给定的某个节点出发,有多个可以前往的下一个节点(树不是线性数据结构),所以在顺序计算(即非并行计算)的情况下,只能推迟对某些节点的访问——即以某种方式保存起来以便稍后再访问。常见的做法是采用栈(LIFO)或队列(FIFO)。由于树本身是一种自我引用(即递归定义)的数据结构,因此很自然也可以用递归方式。
所以,下面我重点总结了这两种不同的遍历实现方式。
注:本文所讲的数据结构均为C语言版
先序遍历
递归遍历
Status PreOrderTraverse(BiTree T,Status(*visit)(TElemType e)){
if(NULL == T) return OK;
if(ERROR == visit(T->data))
return ERROR; //访问结点的数据域
if(ERROR == PreOrderTraverse(T->lchild,visit))
return ERROR; //递归遍历T的左子树
return PreOrderTraverse(T->rchild,visit);//递归遍历T的右子树
}
非递归遍历
1.使用栈的非递归先序遍历算法
/**********
二叉链表类型定义:
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild,*rchild;
} BiTNode, *BiTree;
栈类型Stack的相关定义:
typedef BiTree SElemType; // 栈的元素类型
Status InitStack(Stack &S); // 初始化栈
Status StackEmpty(Stack S); // 判栈空
Status Push(Stack &S, SElemType e); // 进栈
Status Pop(Stack &S, SElemType &e); // 出栈
Status GetTop(Stack S, SElemType &e); // 取栈顶元素
**********/
void PreOrder(BiTree T, void (*visit)(TElemType))
/*对每个结点的元素域data调用函数visit进行访问 */
{
Stack S; InitStack(S);
BiTree p = T;
// 先序访问根节点,遍历左节点 ,左节点入栈
// StackEmpty(Stack S);S为空返回true反之false;
// 当栈不空或 p非空时
while(!StackEmpty(S) || p!=NULL){
while(p!=NULL){
visit(p->data);
Push(S,p);
p = p->lchild;
}
if(!StackEmpty(S)){
Pop(S,p); // 执行完 p指向S出栈的元素
p = p->rchild;
}
}
}
2.不使用栈,使用三叉链表的非递归先序遍历算法
/**********
三叉链表类型定义:
typedef struct TriTNode {
TElemType data;
struct TriTNode *parent, *lchild, *rchild;
} TriTNode, *TriTree;
**********/
void PreOrder(BiTree T, void (*visit)(TElemType)){
TriTree p, pr;
if(T