满二叉树:深度为 k 且有 2^k - 1 个结点的二叉树web
彻底二叉树:彻底二叉树是效率很高的数据结构,彻底二叉树是由满二叉树而引出来的。深度为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;
遍历:树的遍历(也称为树的搜索)是图的遍历的一种,指的是按照某种规则,不重复地访问某种树的全部结点,使得每一个结点被且仅被访问的过程。具体的访问操做多是检查节点的值、更新节点的值等。二叉树的遍历也适用于其它树的遍历。数组
遍历是二叉树的一类重要操做,也是二叉树的其余一些操做和各类应用算法的基本框架。数据结构
遍历有两种类别,一种是深度优先遍历,另外一种是广度优先遍历框架
因为从给定的某个节点出发,有多个能够前往的下一个节点(树不是线性数据结构),因此在顺序计算(即非并行计算)的状况下,只能推迟对某些节点的访问——即以某种方式保存起来以便稍后再访问。常见的作法是采用栈(LIFO)或队列(FIFO)。因为树自己是一种自我引用(即递归定义)的数据结构,所以很天然也能够用递归方式。svg
因此,下面我重点总结了这两种不一样的遍历实现方式。函数
注:本文所讲的数据结构均为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.使用栈的非递归先序遍历算法spa
/********** 二叉链表类型定义: 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!=NULL){ p = T; while(p!=NULL){ visit(p->data);//输出当前的结点 if(p->lchild!=NULL){ p = p->lchild;//如有左孩子,继续访问 } else if(p->rchild!=NULL){ p = p->rchild;//如有右孩子,继续访问 } else{ //沿双亲指针链查找,找到第一个由右孩子的p结点 //找不到则结束 do{ pr = p; p = p->parent; }while(p!=NULL&&(p->rchild==pr||NULL==p->rchild)) //p有右孩子但不是pr,结束循环 if(p) p = p->rchild;//找到后,p指向右孩子结点 } } } }
Status InOrderTraverse(BiTree T,Status(*visit)(TElemType e)){ if(NULL == T) return OK; if(ERROR == InOrderTraverse(T->lchild,visit)) return ERROR; //递归遍历T的左子树 if(ERROR == visit(T->data)) return ERROR; //访问结点的数据域 return InOrderTraverse(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); // 取栈顶元素 **********/ BiTNode *GoFarLeft(BiTree T,Stack &S){ //从T结点出发,沿左分支走到底,沿途结点的指针入栈S,返回左上结点的指针 if(!T) return NULL; while(T->lchild!=NULL){ Push(S,T); T = T->lchild; } return T; } void InOrderTraverse(BiTree T,void(*visit)(TElemType e)){ Stack S; InitStack(S); BiTree p; p = GoFarLeft(T, S);//找到最左下的结点,并将沿途结点的指针入栈S while(p!=NULL){ visit(p->data);//访问结点 if(p->rchild!=NULL){ //令p指向其右孩子为根的子树的最左下结点 p = GoFarLeft(p->rchild, S); } else if(!StackEmpty(S)){ //栈不空时退栈 Pop(S,p); } else p = NULL;//栈空代表遍历结束 } }
2.不使用栈,使用三叉链表的非递归中序遍历算法
/********** 三叉链表类型定义: typedef struct TriTNode { TElemType data; struct TriTNode *parent, *lchild, *rchild; } TriTNode, *TriTree; **********/ void InOrder(TriTree PT, void (*visit)(TElemType)){ TriTree p=PT, pr; while(NULL != p) { if (p->lchild != NULL) { p = p->lchild; //寻找最左下结点 } else { visit(p->data); //当前节点左子树为空,但右子树不必定为空, //因此该节点多是这个子树的根节点,要先访问, //若是有右节点,才能在右节点以前访问 if (p->rchild != NULL) { //如有右子树,转到该子树,继续寻找最左下结点 p =p->rchild; } else { //执行到这里 //p 要不是最左下节点,要不是没有孩子的右节点 //其实也就是叶子节点 pr = p; //不然返回其父亲 p = p->parent; while (p != NULL && (p->lchild != pr || NULL == p->rchild))//若其不是从左子树回溯来的,或左结点的父亲并无右孩子 { if (p->lchild == pr) {//若最左结点的父亲并无右孩子 visit(p->data);//直接访问父亲 } pr = p; //父亲已被访问,故返回上一级 p = p->parent; //该while循环沿双亲链一直查找,若无右孩子则访问 //直至找到第一个有右孩子的结点为止 //(但不访问该结点,留给下步if语句访问 } if (NULL != p) { //访问父亲,并转到右孩子 //(经上步while处理,能够肯定此时p有右孩子) visit(p->data); p = p->rchild; } } } } }
Status PostOrderTraverse(BiTree T,Status(*visit)(TElemType e)){ if(NULL == T) return OK; if(ERROR == PostOrderTraverse(T->lchild,visit)) return ERROR; //递归遍历T的左子树 if(ERROR == PostOrderTraverse(T->rchild,visit)) return ERROR; //递归遍历T的右子树 return visit(T->data);//访问结点的数据域 }
1.使用栈的非递归后序遍历算法
/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; 为分辨后序遍历时两次进栈的不一样返回点,需在指针进栈时同时将一个标志进栈 typedef struct { struct BiTNode *ptr; // 二叉树结点的指针类型 int tag; // 标志 值为 0 或 1 } SElemType; // 栈的元素类型 栈类型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 PostOrder(BiTree T, void (*visit)(TElemType)) /* 使用栈,非递归后序遍历二叉树T, */ /* 对每一个结点的元素域data调用函数visit */ { Stack S; InitStack(S); SElemType e; BiTree p=T; e.tag=0; while((!StackEmpty(S)||p==T)&&T!=NULL){ while(p){// 这个小 while循环的目的是先遍历每一个子树的左节点到底,并将沿途的左节点入栈 e.ptr=p; e.tag=0; Push(S,e); // 入栈 p=p->lchild; }// 小while结束,此时的栈顶元素也就是要访问的起始节点 if(!StackEmpty(S)){ // tag==1,e为第二次出如今栈顶 Pop(S,e); // 取出栈顶元素,并返回给e,e指向出栈元素,方便下面的tag值判断 if(e.tag == 1) { p=e.ptr; visit(p->data);// 访问该结点 p = NULL; } else { p=e.ptr; if(p->rchild){ // 若是存在右子树,设tag == 1 e.tag=1; Push(S,e); // 从新入栈,它是根节点,留着等下再访问 p=p->rchild; // 使 p指向右子树 } else{//没有右子树 ,直接访问该节点 p=e.ptr; visit(p->data); p = NULL; } } } else{//栈空则 p为NULL p=NULL; } }//大while结束 }
2.不使用栈,使用三叉链表的非递归后序遍历算法
/********** 在三叉链表的结点中增设一个标志域 (mark取值0,1或2)以区分遍历过程当中到达该结点时应继续向左或向右或访问该结点 带标志域的三叉链表类型定义: typedef struct TriTNode { TElemType data; struct TriTNode *lchild, *rchild, *parent; int mark; // 标志域 } TriTNode, *TriTree; **********/ void PostOrder(TriTree T, void (*visit)(TElemType)) /* 不使用栈,非递归后序遍历二叉树T, */ /* 对每一个结点的元素域data调用函数visit */ { //mark == 0 向左访问,1 访问自身,2 向右访问 TriTree p= T, pr; if(p==NULL) return ; while(true){ while(p!=NULL&&p->mark!=1){ if(p->rchild!=NULL) //如有右孩子 p->mark=2; else p->mark=1; pr=p; p=p->lchild; } p=pr; // p为最左下节点,可能有右孩子 pr=pr->parent; if(p->mark==2) { // p有右孩子 p->mark=1; // p访问完右边后下次要访问本身 p=p->rchild; } else{ visit(p->data); // 访问 p->mark=1; } if(p->data==T->data) break; } }
层次遍历是按二叉树的层次从小到大且每层从左到右的顺序依次访问结点
对于顺序存储结构的二叉树,其结点在数组中的顺序下标序列与层次遍历的访问顺序一致,所以可直接根据数组获得层次遍历的结果。
对于链式存储结构的二叉树,难以得到结点的同一层的下一层结点,从层末结点也难以获得下一层的层首结点。因此可以使用一个辅助队列存储当前层被访问过的结点。
算法以下:
/********** 二叉链表类型定义: typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode, *BiTree; **********/ void LevelOrderTraverse(BiTree T,Status(*visit)(TEleType e)){ if(T!=NULL){ Queue Q; InitQueue(Q); BiTree p = T; //初始化 visit(p->data); //访问根节点 EnQueue(Q,p); //并将根节点入队 } while(OK==DeQueue(Q,p))//当队非空时重复执行出队操做 { if(p->lchild!=NULL)//访问左孩子并入队 { visit(p->lchild->data); EnQueue(Q,p->lchild); } if(p->rchild!=NULL)//访问右孩子并入队 { visit(p->rchild->data); EnQueue(Q,p->rchild); } } }
利用后序遍历框架递归求左、右子树深度,而后取二者的较大值加 1(根节点)做为深度值返回。代码以下:
int BiTreeDepth(BiTree T){ int depthLeft,depthRight; if(NULL==T) return 0; //二叉树深度为0 else{ depthLeft = BiTreeDepth(T->lchild); depthRight = BiTreeDepth(T->rchild); return 1+((depthLeft>depthRight)?depthLeft:depthRight); } }
利用先序遍历框架递归实现,算法以下:
void CountLeaf(BiTree T, int &count){ if(T!=NULL){ if(NULL == T->lchild && NULL == T->rchild){ count++; // 对叶子结点计数 } CountLeaf(T->lchild, count); // 对左子树递归计数 CountLeaf(T->rchild, count); // 对右子树递归计数 } }
代码以下:
void DestroyBiTree(BiTree &T){ if(T!=NULL){ DestroyBiTree(T->lchild);// 递归销毁左子树 DestroyBiTree(T->rchild);// 递归销毁右子树 free(T); // 释放根节点 } }
代码以下:
void ReleaseX(BiTree &T, char x) /* 对于二叉树T中每个元素值为x的结点, */ /* 删去以它为根的子树,并释放相应的空间 */ { if(!T) return ; ReleaseX(T->lchild,x); ReleaseX(T->rchild,x); if(T->data == x) free(T); }
代码以下:
void CopyBiTree(BiTree T, BiTree &TT) /* 递归复制二叉树T获得TT */ { TT = (BiTree)malloc(sizeof(BiTNode)); if(T){ TT->data = T->data; TT->lchild = T->lchild; TT->rchild = T->rchild; if(T->lchild) CopyBiTree(T->lchild,TT->lchild); if(T->rchild)CopyBiTree(T->rchild,TT->rchild); } else free(TT); }