二叉树的遍历都是 先左后右,实际是一路遍历左结点,直到孩子为空,返回的时候访问其他结点(如右结点),继续以右节点为基础一路访问左结点。
总体是 一路向左,没有了返回访问其他结点顺便换路线,再向左。
NLR中的N—node,其实也就是自己,而对于那个子树来说,他是根。
L—left subtree左子树 ,R—rigth subtree右子树,均是递归进入,遍历完了才返回。
访问到自己的时候,直接输出自己(先序),或者先递归完左子树L再输出自己(中序),或者递归完左子树L、右子树R再输出自己(后序)。
1.前序遍历—NLR
void PreOrder(BiTree T){
if(T!=NULL){ //每一次递归都会进入一个新子树,T为子树的根
cout<<T->data; //每访问一个子树先访问根结点
PreOrder(T->lchild); //一路向左
PreOrder(T->rchild); //左走完了,访问右+换路线,下一次继续一路向左
}
}
基本过程是:输出根,向左(进入左子树)、输出根,向左、输出根,向左…
直到没了左子,返回,有右就向右(进入右子树),再输出根,向左,…
先序的特点:根是第一个访问到的,并且先访问完左子树,再访问右子树(无法区分分界点)
2.中序遍历—LNR
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild); //一路向左,直到进入最后一个子树向下执行
cout<<T->data; //只有到最后一个左子树(叶)输出,然后返回的时候会输出子树根
InOrder(T->rchild); //进入右结点,继续一路向左,在其子树中输出左,根
}
}
基本过程:一路向左,触底输出,再返回不断输出根,是否有右——调换路线,
有:右向右(进入右子树),再一路向左,直到叶子输出,返回输出根…
没有:就再返回输出根
如图也可知,中序序列可以用投影法快速求得。
中序的特点:第一个访问的是最左端的结点。先访问完根的左子树,再访问根,再访问根的右子树。所以知道子树的根就可以划分左右子树。
3.后序遍历—LRN
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild); //一路向左
PostOrder(T->rchild); //再一路向右
cout<<T->data; //直到最左节点输出,然后输出最右节点,返回时输出根
}
}
后序有点奇怪,根节点是最后返回的时候才输出的,每次输出处在最底端的左右结点,返回输出根结点,有点像下图的摘葡萄.
适合用来销毁二叉树,从底部慢慢销毁,最后销毁根。
后序的特点:根是最后访问的。第一个访问最靠左的叶(可能是最左端的叶,也可能是最左端结点的右子树上最左端的叶)
我们遍历中不断调用Order(T->left)
、Oreder(T->rigth)
就是在不断地进入左子树,没有子再进入右子树,同时也代表了上一个T的左子结点、右子结点。
每次调用后,我们的T就是根结点。返回也会返回到根结点,这也就是为什么根的位置是cout的位置。
Order(T->left)
是一路向左。
如果在前面输出T(先序),就是每向左一个进入了新的子树,就把子树的根输出。
如果在其后面输出T,就直接一路向左走到了最后的子树(叶结点),再输出叶子。
Oreder(T->rigth)
后面没有输出,每次调用进入函数还是向左/输出,这个主要是做调换路线用的。
那怎么输出右结点呢?当进入了右结点的路线,右结点也就成了子树的根,然后继续递归输出。
Ex.从单个结点看待遍历
对于每个结点,以他自己为参数的函数在整个递归过程中会"路过"三次
- 第一次是以其为参数T进入这个函数,这个时候要是输出,就是先序遍历。
- 接着进入他的左子树
Order(T->left)
,然后一路递归,遍历完左子树最终返回到这个结点,这是第二次来到这个结点的函数,这个时候如果输出,就是中序遍历。 - 接着进入他的右子树
Oreder(T->rigth)
,然后一路递归,遍历完右子树最终返回到这个结点,这是第三次来到这个结点的函数,这个时候如果输出,就是后序遍历。
这三次结束,这个结点的函数就会彻底结束,也就是直接出栈了。返回到上一个结点继续进行。
对于B来说
第一次是A调用 Order(T->left)
,进入了B为参数的函数——然后调用Order(T->left)
第二次是B的Order(T->left)
运行结束返回的时候——然后调用Order(T->right)
第三次是B的Order(T->right)
运行结束返回的时候。
对于一个结点,会先将其左子树遍历,再遍历其右子树,遍历的方法也是先左子树再右子树(毕竟递归,我这话讲的也递归了…)
所以,NLR中的N是node,其实也就是自己,而对于那个子树来说,他是根。LR分别是 left subtree , rigth subtree左子树和右子树
先序:进入左子树前输出自己。
中序:进入左子树后,进入右子树前输出自己。
后序:进入右子树后输出自己。
4.层次遍历(队列)
需要用队列来辅助进行,先将根入队,队头出队,再将队头的左子入队,右子入队,然后继续队头出队…
辅助队列如下:
typedef struct LiNode {
BiNode* node; //存结点的指针
LiNode* next;
}LiNode,*Linklist;
typedef struct LinkQueue {
Linklist front, rear;
}LinkQueue;
void InitQueue(LinkQueue& Q) { //初始化队列,带头结点
Q.front = new LiNode;
Q.front->next = NULL;
Q.rear = Q.front;
}
void EnQueue(LinkQueue& Q,BiNode* x) {
LiNode* p = new LiNode;
p->node = x;
p->next = Q.rear->next;
Q.rear->next = p;
Q.rear = p;
}
bool DeQueue(LinkQueue& Q, BiNode* &x) {
if (Q.rear == Q.front) //队空
return false;
x = Q.front->next->node;
LiNode* p = Q.front->next;
if (p == Q.rear) //只有一个结点
Q.rear = Q.front;
else
Q.front->next = p->next;
delete(p);
}
层序遍历
void LevelOrder(BiTree T, LinkQueue& Q) {
InitQueue(Q);
EnQueue(Q, T); //根进队
BiNode* p; //工作指针
while (Q.rear != Q.front) { //队非空
DeQueue(Q, p); //队头出队
cout << p->data;
if (p->lchild != NULL) //左子入队
EnQueue(Q, p->lchild);
if (p->rchild != NULL) //右子入队
EnQueue(Q, p->rchild);
}
}
用顺序啊,BiNode *data[Maxsize],就是个可以存结点的指针的数组!!!
我居然没想到数组可以存储各种结构!!!!
居然用链栈,链队!
难怪王道基本上都不介绍链栈链队。。。
记住!栈、队列,基本上都可以用顺序(顺序栈、循环队列)
//顺序队列:
typedef struct SqQueue {
BiNode* val[Maxsize];
int front, rear;
}SqQueue;
void InitQueue(SqQueue& Q) { //rear指向队尾的下一个
Q.front = Q.rear = 0;
}
bool isEmpty(SqQueue Q) {
return Q.front == Q.rear ? 1 : 0;
}
bool EnQueue(SqQueue &Q, BiNode* x) {
if ((Q.rear - Q.front) % Maxsize == Maxsize - 1) //队满
return 0;
Q.val[Q.rear] = x;
Q.rear = (Q.rear + 1) % Maxsize;
return 1;
}
bool DeQueue(SqQueue &Q, BiNode*& x) {
if (Q.front == Q.rear)
return 0;
x = Q.val[Q.front];
Q.front = (Q.front + 1) % Maxsize;
return 1;
}