树的遍历及二叉树实现
来源:互联网 发布:石头人升级数据 编辑:程序博客网 时间:2024/05/16 23:44
一、树的遍历
树的遍历有两大类四种方式,分别是深度优先遍历和广度优先遍历。
(一)深度优先遍历
- 先序遍历
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树; - 中序遍历(二叉树独有)
(1)中序遍历左子树;
(2)访问根结点;
(3)中序遍历右子树; - 后序遍历
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根结点;
(二)广度优先遍历
- 层次遍历:一层一层的访问,从上到下,从左到右的顺序。
(三)C语言实现
使用递归实现这些遍历比较简单,重点理解递归的用法即可,而使用非递归方式则比较麻烦,这里递归与非递归方式都会给出代码,为了方便,被遍历的树为一颗二叉搜索树,而对树结点的操作为打印输出操作。
(1)递归实现
//头文件:Tree.h#ifndef TREE_H_INCLUDED#define TREE_H_INCLUDEDstruct BinarySearchTree{ int element; struct BinarySearchTree* left; struct BinarySearchTree* right;};typedef struct BinarySearchTree* Tree;Tree insertEle(Tree, int);void preOrderByRec(Tree);void inOrderByRec(Tree);void postOrderByRec(Tree);void levelOrder(Tree);#endif // TREE_H_INCLUDED
//遍历实现文件:Tree.c#include <stdio.h>#include <stdlib.h>#include "Tree.h"//插入新结点Tree insertEle(Tree tree, int data){ if(tree){ if(tree->element > data){ tree->left = insertEle(tree->left, data); }else if(tree->element < data){ tree->right = insertEle(tree->right, data); }else{ printf("树中已经有该元素,不再插入\n"); } }else{ tree = (Tree)malloc(sizeof(struct BinarySearchTree)); tree->element = data; tree->left = tree->right = NULL; } return tree;//注意这里一定要返回该指针!!}/* 递归实现三种深度优先遍历时,代码基本一致 不同的是对元素操作的时机不同*/void preOrderByRec(Tree tree){ if(tree){ printf("%d\n", tree->element);//最先访问 preOrderByRec(tree->left);//遍历左子树 preOrderByRec(tree->right);//遍历右子树 }}void inOrderByRec(Tree tree){ if(tree){ inOrderByRec(tree->left);//先遍历左子树 printf("%d\n", tree->element);//访问结点 inOrderByRec(tree->right);//最后遍历右子树 }}void postOrderByRec(Tree tree){ if(tree){ postOrderByRec(tree->left);//先遍历左子树 postOrderByRec(tree->right);//再遍历右子树 printf("%d\n", tree->element);//最后访问结点 }}/*层次遍历需要使用一个队列,这里只给出伪代码*//*void levelOrder(Tree tree){ Queue Q; Tree T; if(!tree) return;//空树则直接返回 Q = createQueue();//创建空队列Q addQ(Q, tree); //利用了队列先进先出的性质 //往队列里添加第k层时,第k层由第k-1层的结点生出来 //依次添加k-1层每个结点的左右子结点 //比如,一颗树按层划分为(12)-(5,14)-(4,8,17)-(23) //那么往队列添加删除的顺序为(加号为添加,减号为删除): //+12, -12, +5, +14, -5, +4, +8, -14, +17, -4, -8, -17, +23, -23 while(!isEmpty(Q)){ T = deleteQ(Q); printf("%d\n", T->element); if(T->left){ addQ(Q, T->left); } if(T->right){ addQ(Q, T->right); } }}*/
//main入口:main.c#include <stdio.h>#include <stdlib.h>#include "Tree.h"int main(){ Tree tree = NULL; tree = insertEle(tree, 12); tree = insertEle(tree, 5); tree = insertEle(tree, 14); tree = insertEle(tree, 4); tree = insertEle(tree, 8); tree = insertEle(tree, 23); tree = insertEle(tree, 17); printf("先序遍历结果:\n"); preOrderByRec(tree); printf("\n"); printf("中序遍历结果:\n"); inOrderByRec(tree); printf("\n"); printf("后序遍历结果:\n"); postOrderByRec(tree); printf("\n"); return 0;}//先序遍历结果://12//5//4//8//14//23//17//中序遍历结果://4//5//8//12//14//17//23//后序遍历结果://4//8//5//17//23//14//12
(2)非递归实现
因为递归本质上使用了一个栈,所以将递归化为非递归,一般额外需要使用一个栈,用该栈来模拟递归,此时循环的条件除了递归里的条件,还有一个就是栈不为空,二叉树的先序遍历和中序遍历化为非递归的主要代码差不多,所以先说这两个,这里也只给出伪代码。
void traverse(Tree tree){ Stack stack = createStack(Maxsize); Tree T = tree;//注意,这里需要用一个局部指针来代替传进来的tree,以免把原树的结构改变了 //循环的条件二选一:一是结点不为空,二是栈不为空,任何一个满足都可以继续循环 while(T || !isEmpty(stack)){ if(T){ //printf("%d\n", T->element);//先序遍历 push(stack, T); T = T->left; }else{ pop(stack, T); printf("%d\n", T->element);//中序遍历 T = T->right; } }}
后序遍历的非递归实现稍微麻烦一点,这里需要使用一个栈、一个记录当前访问的结点,一个记录上次访问的结点,后序遍历里,对于任意一个结点,只有其右子结点被访问过了,该结点才会被访问,所以我们可以利用这个条件,先出栈一个结点,如果该节点的右子结点没有被访问,那么这个结点再进栈,然后进入右子结点遍历。
后序遍历说明图
如上图,先依次将个左子结点入栈,此时栈Stack = [1,2],然后开始出栈,结点2没有右结点,因此打印输出,然后继续出栈,结点1有右子结点并且此时右子结点并没有访问,所以结点1再次进栈,并将结点3入栈,此时栈Stack = [1,3],此时没有结点可以入栈了,于是结点3出栈,结点3没有右子结点,因此打印输出,然后结点1出栈,因为此时上一次访问的结点是右子节点,因此此时结点1打印,此时栈空,停止程序。伪代码如下。
//后序遍历非递归实现伪代码void postTraverse(Tree tree){ Tree curTree = tree;//当前操作的结点 Tree lastTree = NULL;//上次打印输出的结点 Stack stack = createStack(MaxSize);//建栈 //向左遍历进入最左边的元素 while(curTree){ push(stack, curTree); curTree = curTree->left; } //考虑上图的思路过程,判断条件为栈非空 while(!isEmpty(stack)){ pop(stack, curTree); //访问根结点的条件是该结点没有右子结点或者右子结点已经访问过 if(curTree->right == NULL || curTree->right == lastTree){ printf("%d\n", curTree->element); lastTree = curTree; }else{ push(stack, curTree);//根结点再次入栈 //右子树入栈 curTree = curTree->right; while(curTree){ push(stack, curTree); curTree = curTree->left; } } }}
(3)总结
二叉树的非递归遍历程序里关于循环条件有点绕,因为每个结点既可以看作是根结点、也可以看作子结点,这种灵活性增加了思考的难度,但总的来说,不管哪种遍历方式,都需要在整棵树的前提下,先向左找到最左的结点,这里形成了一条路径,然后沿着该路径回溯,判断出栈操作或向右遍历,而向右遍历的时候看起来只操作了一次(T = T->right)就又开始向左迭代找最左的元素,但这也符合逻辑,即先左子树后右子树的规定,因为把右子树单独看作一棵树,这颗树也有自己的左子树,如果想不清楚的话,按照程序多画画树,就好理解了。
阅读全文
5 0
- 树的遍历及二叉树实现
- 二叉树的建立及遍历实现
- 二叉树的创建及遍历实现
- 二叉树的遍历及实现方法
- 二叉树的创建、遍历及搜索(C实现)
- 二叉树的遍历方法及递归实现
- 二叉树的建立删除及三种遍历实现
- C++实现 二叉树的建立及4种遍历
- 递归实现二叉树的建立及前中后序遍历 c
- 二叉树及按层遍历的算法实现
- java实现二叉树的建立及遍历方法
- 二叉树的建立删除及三种遍历实现
- 二叉树的递归实现及递归遍历
- 二叉树创建及各种遍历的实现
- java实现二叉树的创建及遍历
- 二叉树的各种运算及遍历实现
- java实现二叉树的创建及5种遍历
- 二叉树的遍历思想及核心代码实现
- (0062)iOS开发之Xcode自带单元测试UnitTest
- HDU 6124 Euler theorem
- 神经网络和深度学习(二)——一个简单的手写数字分类网络
- 第一行代码(四)Fragment
- i2c 协议解析
- 树的遍历及二叉树实现
- OJ 中,提交代码时,C++ 和 G++的区别
- Hdu6124 Euler theorem(2017多校第7场)
- POJ3170Knights of Ni
- hdu 4561 模拟题
- HTTP协议:工作原理
- 新建git项目,没有项目内容,怎样创建项目并上传
- java包的命名规则
- 2017 搜狗校招c++工程师笔试试卷