目录
前言:
一:前序遍历
二:中序遍历
三:后序遍历
四:层序遍历
前言:
二叉树的非递归遍历需要借助栈和队列以及二叉树的一些基础接口,这些在之前的文章中有讲过,这里就不赘述,不清楚的家人们可以点链接,这里直接给出所有需要的接口。
栈和队列:https://blog.csdn.net/2301_76269963/article/details/129823215?spm=1001.2014.3001.5501
二叉树基础接口:https://blog.csdn.net/2301_76269963/article/details/130231257?spm=1001.2014.3001.5501
代码:
//队列 struct BinaryTreeNode; //重定义,方便更改存储类型 typedef struct BinaryTreeNode* QDataType; //结点的结构体 typedef struct QueueNode {struct QueueNode* next;QDataType data; }QNode; //队列的结构体(头用来开辟链接,尾用来查找) typedef struct Queue {//头QNode* head;//尾QNode* tail; }Queue;//初始化 void QueueInit(Queue* pq) {//断言,不能传空的结构体指针assert(pq);//初始化,把头和尾都指向空pq->head = pq->tail = NULL; }//入队列 void QueuePush(Queue* pq,QDataType x) {//断言,不能传空的结构体指针assert(pq);//申请新结点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){printf("malloc error\n");exit(-1);}newnode->data = x;newnode->next = NULL;//如果队列为空,单独处理if (pq->head == NULL){pq->head = pq->tail = newnode;}else{//原尾指向新结点(链接)pq->tail->next = newnode;//更新尾pq->tail = newnode;} }//队列销毁 void QueueDestroy(Queue* pq) {//断言,不能传空的结构体指针assert(pq);//先保存下一个,再释放QNode* cur = pq->head;while (cur){//记录QNode* next = cur->next;//释放free(cur);//迭代cur = next;}//头尾都置空pq->head = pq->tail = NULL; }//出队列(删除) void QueuePop(Queue* pq) {//断言,不能传空的结构体指针assert(pq);//断言,队列为空不能删除assert(!QueueEmpty(pq));//保存原头的下一个结点位置QNode* newhead = pq->head->next;//释放free(pq->head);//迭代pq->head = newhead;//如果删除结束了,要把tail指向空(避免野指针)if (pq->head == NULL)pq->tail = NULL; }//判断队列是否为空 bool QueueEmpty(Queue* pq) {//断言,不能传空的结构体指针assert(pq);/*if (pq->head == NULL)return true;elsereturn false;*///依据判断语句的指直接返回return pq->head == NULL; }//查找队列的头数据 QDataType QueueFront(Queue* pq) {//断言,不能传空的结构体指针assert(pq);//断言,队列为空不能查找assert(!QueueEmpty(pq));return pq->head->data; }//栈 struct BinaryTreeNode; //重定义数据类型,方便更改 typedef struct BinaryTreeNode* STDataType;typedef struct stack {//存储数据STDataType* a;//栈顶(位置)int top;//容量int capacity; }ST; //初始化 void StackInit(ST* ps) {//断言,不能传空指针进来assert(ps );//一开始指向NULLps->a = NULL;//把栈顶和容量都置为空ps->top = ps->capacity = 0; }//销毁 void StackDestroy(ST* ps) {//断言,不能传空指针进来assert(ps );//栈顶和容量置为空ps->top = ps->capacity = 0;//释放空间free(ps->a);ps->a = NULL; }//入栈 void StackPush(ST* ps, STDataType x) {//断言,不能传空指针进来assert(ps);//先判断是否扩容if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity) * 2;//扩容STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);//扩容失败if (tmp == NULL){printf("realloc error\n");exit(-1);}//更新ps->capacity = newcapacity;ps->a = tmp;}//存储数据ps->a[ps->top] = x;ps->top++; }//出栈(删除) void StackPop(ST* ps) {//断言,不能传空指针进来assert(ps);//如果栈为空,不能出栈assert(!StackEmpty(ps));ps->top--; }//取顶部数据 STDataType StackTop(ST* ps) {//断言,不能传空指针进来assert(ps);//如果栈为空,不能进行访问assert(!StackEmpty(ps));//返回栈顶数据return ps->a[ps->top-1]; } //判断栈是否为空 bool StackEmpty(ST* ps) {//断言,不能传空指针进来assert(ps);//依据top来判断/*if (ps->top == 0)return true;return false;*///更简洁的写法,一个判断语句的值要么为true,要么falsereturn ps->top == 0; } //求树的节点数 int BinaryTreeSize(BTNode* root) {/*if (root == NULL){return 0;}return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;*///更加简洁的写法return root == NULL ? 0 :BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; }//手动建立一个二叉树 int main() {BTNode* nodeA = BuyNewNode('A');BTNode* nodeB = BuyNewNode('B');BTNode* nodeC = BuyNewNode('C');nodeA->left = nodeB;nodeA->right = nodeC;BTNode* nodeD = BuyNewNode('D');BTNode* nodeE = BuyNewNode('E');BTNode* nodeF = BuyNewNode('F');nodeB->left = nodeD;nodeC->left = nodeE;nodeC->right = nodeF; }
一:前序遍历
思路(配合代码和后面的图解看):
- 将根节点入栈,设计一个指针p遍历节点
- 只要栈不为空或者p不为空,就进行循环
- 节点不为空,先打印节点数据,再入栈,让指针指向节点左孩子
- 节点为空,取到栈顶数据(节点父亲),出栈,让指针指向父亲右孩子
- p为空并且栈为空,结束
代码:
//非递归前序遍历 void NotRecPreOrder(BTNode* root) {ST s;StackInit(&s);BTNode* p = root;while (!StackEmpty(&s) || p){//不为空if (p){//入栈StackPush(&s, p);printf("%c ", p->data);p = p->left;}//为空else{//拿到栈顶p = StackTop(&s);StackPop(&s);p = p->right;}}StackDestroy(&s); }
图解:
一开始p不为空,我们把A打印,入栈,p指向B
p又不为空,我们把B打印,入栈,p指向D
p又不为空,我们把D打印,入栈,p指向空
然后就是重点了,p为空, 我们拿到栈顶数据,也就是D(地址),然后出栈,p指向D的右孩子
右孩子也为空,再拿到栈顶(B地址),出栈,p指向B的右孩子,这个时候以B为根的树的左子树就遍历完了,开始遍历B的右子树
B的右子树为空,再拿到栈顶(A地址),出栈,这个时候以A为根的树的左子树就遍历完了,开始遍历右子树,重复这个过程一直到栈空p空,就可以遍历整棵树。
二:中序遍历
思路(配合代码和图解):
- 将根节点入栈,设计一个指针p用来遍历节点
- 只要栈不为空或者p不为空,就进行循环
- 节点不为空,入栈,让指针指向节点左孩子
- 节点为空(这个时候父亲节点的左子树一定遍历完了),取到栈顶数据(节点父亲),出栈,打印,让指针指向父亲右孩子
- p为空并且栈为空,结束
代码:
//非递归中序遍历 void NotRecInOrder(BTNode* root) {BTNode* p = root;ST s;StackInit(&s);while (p || !StackEmpty(&s)){//不为空if (p){StackPush(&s, p);p = p->left;}//为空else{//拿到栈顶p = StackTop(&s);//出栈StackPop(&s);printf("%c ", p->data);p = p->right;}}StackDestroy(&s);}
图解:
一开始p不为空,我们把A入栈,p指向B
p又不为空,我们把B入栈,p指向D
p又不为空,我们把D入栈,p指向空
接下来就是关键点了,p为空,这个时候以D为根的树的左子树完成遍历,拿到栈顶(D地址),出栈,然后打印数据,p指向D右孩子。
D右孩子也为空,以B为根的树的左子树完成遍历,拿到栈顶数据(B地址),出栈,打印数据,p指向B右孩子
B的右孩子为空,再拿到栈顶(A地址),出栈,打印数据,这个时候以A为根的树的左子树就遍历完了,开始遍历右子树,重复这个过程一直到栈空p空,就可以遍历整棵树。
三:后序遍历
思路(配合代码和图解):
- 将根节点入栈,设计一个指针p用来遍历节点
- 只要栈不为空或者p不为空,就进行循环
- 节点不为空,入栈,让指针指向节点左孩子
- 和前序中序不同,节点为空的时候我们不知道父亲的左右子树是否完成遍历,我们需要设计一个标记数组来判断栈顶节点左右子树是否完成遍历
- 第一次进入else内部一定是左子树完成遍历,取到栈顶数据(节点父亲),让指针指向父亲右孩子,标记为1
- 如果进入else内部时标记为1,取到栈顶数据(节点父亲),出栈,打印,循环这个过程一直到标记为0的节点,取到这个节点地址,p指向这个节点右孩子
- tag数组下标为0的位置是没有被使用的,如果根部节点的左右子树遍历完成,这个时候再访问根部遍历就结束了,我们把下标1的位置给根部节点,这样遍历完成后下标0的位置数据为0,可以跳出循环
- while循环结束后如果栈为空,代表根部节点已经访问,由于后序遍历根部是最后访问的,这个时候遍历结束,应该跳出循环
代码:
//非递归后序遍历 void NotRecPostOrder1(BTNode* root) {ST s;StackInit(&s);BTNode* p = root;//作为遍历了左子树的依据//根据节点数量开辟足够空间char* tag = (char*)malloc(sizeof(char) * BinaryTreeSize(root));if (tag == NULL){printf("malloc error\n");exit(-1);}while (p || !StackEmpty(&s)){if (p){StackPush(&s, p);tag[s.top] = '0';//标志结点是否遍历右子树p = p->left;}else{//如果已经遍历了左子树while(tag[s.top] == '1'){p = StackTop(&s);StackPop(&s);printf("%c ", p->data);}if (StackEmpty(&s)){break;}//没遍历右子树的情况取到栈顶,遍历右子树//遍历了右子树,取得栈顶,遍历上一层的右子树p = StackTop(&s);p = p->right;tag[s.top] = '1';}}StackDestroy(&s); }
图解:
一开始p指向A,不为空,A(地址)入栈,p指向A左孩子,栈顶标记为0
p指向B,不为空,B入栈,p指向B的左孩子,栈顶标记为0
p指向D,不为空,D入栈,p指向D的左孩子,栈顶标记为0
接下来就是比较关键的地方了,p为空,进入else
但是栈顶(D)的标记为0,代表它的左右子树还没有完成遍历
我们不进入while循环,取到栈顶数据(D地址),然后p指向D的右孩子,将标记修改为1
p为空,进入else
这个时候D的左右子树完成遍历,栈顶的标记为1,进入while循环
取到栈顶数据(D地址),出栈,打印
这个时候tag[top]为0,代表B的左右子树还没有完成遍历,跳出循环
取到栈顶数据(B地址),p指向B的右孩子,标记修改为1
p为空,进入else
这个时候B的左右子树完成遍历,栈顶的标记为1,进入while循环
取到栈顶数据(D地址),出栈,打印
这个时候tag[top]为0,代表A的左右子树还没有完成遍历,跳出循环
取到栈顶数据(A地址),p指向A的右孩子,标记修改为1
p不为空,C(地址)入栈,p指向C左孩子,栈顶标记为0
p指向E,不为空,E入栈,p指向E的左孩子,栈顶标记为0
p为空,进入else
但是栈顶(E)的标记为0,代表它的左右子树还没有完成遍历
我们不进入while循环,取到栈顶数据(E地址),然后p指向E的右孩子,将标记修改为1
重复上述步骤
跳出while循环后如果栈为空,代表整棵树已经遍历完成,结束循环
四:层序遍历
思路(配合代码和图解):
- 将根节点插入队列中,设计一个指针p来遍历节点
- 只要队列不为空,就进行循环
- 循环中先取到队头数据(地址),然后打印节点数据,最后出队列
- 出队列后若该节点有左节点,则将其左节点插入队列中;若该节点有右节点,则将其右节点插入队列中
- 队列为空,循环结束(简单来说就是父亲带出孩子)
代码:
//层序遍历 void LevelOrder(BTNode* root) {assert(root);Queue Q;QueueInit(&Q);BTNode* p = root;//放入首节点QueuePush(&Q, p);while (!QueueEmpty(&Q)){p = QueueFront(&Q);printf("%c ", p->data);QueuePop(&Q);//左入if (p->left != NULL){QueuePush(&Q, p->left);}//右入if (p->right != NULL){QueuePush(&Q, p->right);}}//销毁队列QueueDestroy(&Q); }
图解:
一开始把根节点放入队列中(如果为空树直接报错)
队列不为空,进入循环,取到A地址
出队列,打印数据,A的左右孩子都不为空,入队列
队列不为空,进入循环,取到B地址
出队列,打印数据,B的左孩子不为空,入队列
队列不为空,进入循环,取到C地址
出队列,打印数据,C的左右孩子都不为空,入队列
就这样循环一直到队列为空,层序遍历就完成了。