数据结构 - 二叉树 - 遍历算法代码
2019-04-21 17:30
543 查看
说明:
二叉树的常见遍历有四种:前、中、后、层序遍历。
它们是最基本的,学会这些遍历后才能使用二叉树来做一些任务。
下面是遍历的代码部分。大概思路为:首先建立二叉树,用数组保存下来,然后在数组上进行操作;其次将数组保存的二叉树导入链表中,然后在链表上进行操作。
另外,还有一些地方未完成,等完成后在加上。
注:
(1)main函数首先定义了数组的最大长度MaxSize=100;
(2)将数组初始化全为-1(其实初始化为0最好,就是后面保存二叉树时结点空的表示方法,原因看代码就知道了);
(3)二叉树保存到数组中时,保存的是完全二叉树的形式,没有结点的地方设置为0。输入-1表示保存完成并退出。
#include <iostream> #include <vector> #include <cmath> using namespace std; // 定义相关不变量 using ElemType = int; // 树结点保存值的类型,可以修改 enum Flage{ LEFT, RIGHT };// 两次会晤法的后序遍历中使用 const int ROOT = 0;// 根节点的序号 const int INIT = -1;// 数组初始化值 const int EMPTY = 0;// 某结点为空的表示 const int MaxSize = 100;//数组最大值 /*****************************************定义结构**********************************************/ template<class T>//定义二叉树结点类型 struct TreeNode{ TreeNode(T val) :data(val) { lchild = nullptr; rchild = nullptr; } T data; TreeNode<T> *lchild; TreeNode<T> *rchild; }; template<class T>//定义栈中的每个结点的类型 struct StackElem{ TreeNode<T> * pnode; Flage flage; }; template<class T>//栈:不能将模板函数的定义和声明放在不同文件中 struct Stack{ Stack():top(-1) {} inline bool empty(void) { return top == -1 ? true : false; // or return top == -1; } inline bool full(void) { return top >= MaxSize - 1; } bool push(T pNode) { if (!full()) { data[++top] = pNode; return true; } return false; } T getop(void) { return data[top]; } void pop(void) { if (!empty()) --top; return; } T data[MaxSize]; int top; }; template<class T> //非循环队列,建议用循环队列做,不然死的很惨!!!! struct Quene{ Quene() { first = 0; end = 0; } bool empty() { if (first == -1 || first == end) return true; else return false; } bool full() { return end >= MaxSize; } T getop() { if (first >= 0) return data[first]; else exit(-1); } bool pop() { if (empty()) return false; ++first; return true; } void push(T val) { if (full()) exit(-1); data[end++] = val; return; } T data[MaxSize]; int first; int end;//尾指针指向左后一个元素的下一个位置 }; /**********************************************generate tree***********************************************/ void InitSqBiTree(ElemType *tree)//初始化 { for (int i = ROOT; i < MaxSize; ++i) { tree[i] = EMPTY; } return; } void CreateSqBiTree(ElemType *tree)//将树存入数组中 { int i = ROOT; while (i < MaxSize && cin >> tree[i] && tree[i] != INIT) ++i; return; } inline bool IsEmpty(const ElemType *tree)//判断是否为空树 { if (tree[ROOT] == EMPTY || tree[ROOT] == INIT) return true; return false; } void ShowSqBiTree(const ElemType *tree)//按数组序号输出 { int i = ROOT; while (tree[i] != INIT && i < MaxSize) cout << tree[i++] << " "; cout << endl; return; } template<class T> inline void Show(TreeNode<T> *pNode) { cout << pNode->data << " "; return; } /**********************************************遍历二叉树***************************************************/ //遍历函数1 void Array_PreOrder_DiGui(const ElemType *tree, int n = 1);//递归算法;声明处写默认参数,定义处不能再写了,否则报错。 void Array_PreOrder_Stack(const ElemType *tree);//非递归算法 void Array_InOrder_DiGui(const ElemType *tree, int node = 1); void Array_InOrder_Stack(ElemType *tree); void Array_PostOrder_DiGui(ElemType *tree, int node = 1); void Array_LevelOrder_Quene(ElemType *tree); //遍历函数2,使用模板函数,使用范围是链表存储下的 template<class T> TreeNode<T> * array2link(const ElemType *tree, int n=1); //array convert to linklist template<class T> void LinkList_PreOrder_DiGui(TreeNode<T> *pNode); template<class T> void LinkList_PreOrder_Stack(TreeNode<T> *ptree); template<class T> void LinkList_InOrder_DiGui(TreeNode<T> *pNode); template<class T> void LinkList_InOrder_Stack(TreeNode<T> *pNode); template<class T> void LinkList_PostOrder_DiGui(TreeNode<T> *pNode); template<class T> void LinkList_PostOrder_Stack(TreeNode<T> *pNode); /*****************************************main()****************************************************/ int main() { cout << "--------------------生成树------------------------" << endl; ElemType tree[MaxSize]; // 初始化数组,初始化方法可以用尾指针代替,这样更方便 cout << "tree is init..." << endl; InitSqBiTree(tree); // 在数组里创建二叉树 cout << "tree is created." << endl; CreateSqBiTree(tree); // 按顺序输出数组里的值 cout << "tree is : "; ShowSqBiTree(tree); cout << "--------------------数组遍历------------------------" << endl; // 先序遍历+array+递归 cout << "Array_PreOrder_DiGui : "; Array_PreOrder_DiGui(tree); cout << endl; // 先序遍历+array+栈 cout << "Array_PreOrder_Stack : "; Array_PreOrder_Stack(tree); // 中序遍历+array+递归 cout << "Array_InOrder_DiGui : "; Array_InOrder_DiGui(tree); cout << endl; // 中序遍历+array+栈 cout << "Array_InOrder_Stack : "; Array_InOrder_Stack(tree); cout << endl; // 后序遍历+array+递归 cout << "Array_PostOrder_DiGui : "; Array_PostOrder_DiGui(tree); cout << endl; // 层序遍历+array+队列 cout << "Array_LevelOrder_Quene : "; Array_LevelOrder_Quene(tree); cout << endl; while (1); cout << "--------------------链表遍历------------------------" << endl; // array -> linklist TreeNode<int> * pTree = nullptr; cout << "array to linklist..." << endl; pTree = array2link<int>(tree); // 先序 + 链表 + 递归 cout << "LinkList_PreOrder_DiGui : "; LinkList_PreOrder_DiGui<int>(pTree); cout << endl; // 先序 + 链表 + 栈 cout << "LinkList+PreOrder+Stack : "; LinkList_PreOrder_Stack<int>(pTree); cout << endl; // 中序 + 链表 + 递归 cout << "LinkList+InOrder+DiGui : "; LinkList_InOrder_DiGui<int>(pTree); cout << endl; // 中序 + 链表 + 栈 cout << "LinkList+InOrder+Stack : "; LinkList_InOrder_Stack<int>(pTree); cout << endl; // 后序 + 链表 + 递归 cout << "LinkList_PostOrder_DiGui : "; LinkList_PostOrder_DiGui<int>(pTree); cout << endl; // 后序 + 链表 + 栈1 cout << "LinkList_PostOrder_Stack : "; LinkList_PostOrder_Stack<int>(pTree); cout << endl; while (1); return 0; } /****************************************************part1****************************************************/ //递归的说明:对于先序遍历,给你任意一个树中的结点 //(1)先访问输出这个结点值, //(2)如果...遍历它的左子树, //(3)如果...遍历它的右子树。 //先写出这三条命令,在添加细节。 void Array_PreOrder_DiGui(const ElemType *tree, int n) { //input: tree 树的根结点指针 // n 指第几个结点,根结点为第一结点 //output: none //notice: 先序遍历先输出结点,在看左孩子,最后看右孩子 //1.先输出结点 cout << tree[n - 1] << " "; //2.看左孩子 int lchild = 2 * n; //不为空0,且没有超出范围则访问孩子结点。注意这里区别开数组下标和第几个结点 if ((tree[lchild-1] != EMPTY) && (tree[lchild-1] != INIT)) Array_PreOrder_DiGui(tree, lchild); //3.看右孩子 int rchild = 2 * n + 1; if (tree[rchild-1] != EMPTY && tree[rchild-1] != INIT) Array_PreOrder_DiGui(tree, rchild); return; } void Array_PreOrder_Stack(const ElemType *tree) { int stack[MaxSize];//stack里面存放的是树的序号(不是数组的序号,他们差1),在链表里就是指向链表的指针。 const int empty = -1; int top = empty; int node = 1, lchild, rchild; if (IsEmpty(tree)) { cout << "empty" << endl; return ; } //先将根结点的序号1入栈, stack[++top] = node; while (top != empty) { //1.访问结点的值并出栈(访问时要减1) node = stack[top--]; cout << tree[node-1] << " "; //2.先入栈右孩子 rchild = 2 * node + 1; if ((tree[rchild - 1] != EMPTY) && (tree[rchild - 1] != INIT)) stack[++top] = rchild; //3.再入栈左孩子 lchild = 2 * node; if ((tree[lchild - 1] != EMPTY) && (tree[lchild - 1] != INIT)) stack[++top] = lchild; } cout << endl; return; } void Array_InOrder_DiGui(const ElemType *tree, int node) { //第一部分:先遍历左子树 int lchild = 2 * node; if (tree[lchild-1] != EMPTY && tree[lchild-1] != INIT) Array_InOrder_DiGui(tree, lchild); //第二部分:再输出结点值 cout << tree[node - 1] << " "; //第三部分:最后遍历右子树 int rchild = 2 * node + 1; if (tree[ 7ff7 rchild-1] != EMPTY && tree[rchild-1] != INIT) Array_InOrder_DiGui(tree, rchild); return; } void Array_InOrder_Stack(ElemType *tree) { Stack<ElemType> stack; const int empty = -1; int node = 1; //思路:用循环代替递归 while (stack.top != empty || (tree[node - 1] != EMPTY && tree[node - 1] != INIT)) { //1. 一直找左子树,直到左子树为空 while (tree[node-1]!=EMPTY && tree[node-1]!=INIT) { stack.data[++stack.top] = node; node = 2 * node; } //为啥要加if判断一下??? 感觉可以把if里面的写到外面来 if (stack.top != empty) { //2. 访问中间结点 //不能直接使用前面的node,因为它已经超出边界,或者是空,这里就需要将新的值(栈中值)赋给node,将指针指向以前的结点 node = stack.data[stack.top]; cout << tree[node-1] << " "; //中间结点出栈 stack.top--; //3. node置右结点 node = 2 * node + 1; } } return; } void Array_PostOrder_DiGui(ElemType *tree, int node) { if (tree[node - 1] == EMPTY || tree[node - 1] == INIT) { return; } Array_PostOrder_DiGui(tree, 2 * node); Array_PostOrder_DiGui(tree, 2 * node + 1); cout << tree[node - 1] << " "; return; } //void Array_PostOrder_Stack(ElemType *tree) //没有写 void Array_LevelOrder_Quene(ElemType *tree) { // 层序遍历的思路: // 先遇到的结点先访问,所以使用队列来完成操作。 // (1)从队列中抛出队首结点,访问结点的值 // (2)判断已抛出结点的孩子结点是否存在(不为空),存在,则按顺序先将左孩子入队,再将右孩子入队。 Quene<ElemType> quene; int node = 1;//这里结点指针从一开始是必须的,不然在第一次求孩子的时候始终为0。并且保存在队列里的指针也是node,它和访问结点时(tree[node-1])之间差了1!!! int lchild = 0; int rchild = 0; // 方法一: // 先将根结点入队,再进入循环,这样就可以利用队列是否为空作为循环结束的条件。 if (tree == nullptr)//先判断二叉树是否为空,是则返回;不是则继续。 return; quene.push(node);//先将根结点入队列,此时队里已经有了根结点。这也是第一种方法,后面第二种方法将不如队列。 while (!quene.empty())//判断队列是否为空,不空执行循环体,空则跳出。 { node = quene.getop();//先得到队首结点 cout << tree[node - 1] << " ";//并访问结点 quene.pop();//抛出队首结点 lchild = 2 * node;//前面已经保存了抛出结点的指针,这里计算出抛出结点的孩子 if (tree[lchild - 1] != EMPTY && tree[lchild - 1] != INIT)//有左孩子,则将孩子入队 { quene.push(lchild); } rchild = lchild + 1; if (tree[rchild - 1] != EMPTY && tree[rchild - 1] != INIT)//有右孩子,则将孩子入队 { quene.push(rchild); } } // 求深度or某个结点的层数都可以用层序遍历来做 // 二叉树若存储在数组中,则为完全二叉树的存储方法。完全二叉树有计算深度的公式 // 在上面while退出后node一定指向最后一个结点 int depth = log(node)/log(2) + 1; cout << endl << "Array_LevelOrder_Quene : depth is " << depth; return; } /******************************************************part2**************************************************/ //将数组转化为链表存储后,进行的遍历 template<class T> inline bool IsEmpty(const TreeNode<T> *ptree) { return ptree == nullptr ? true : false; //return ptree==nullptr; } template<class T> TreeNode<T> * array2link(const ElemType *tree, int n) { //产生结点并赋值,使用结构体的构造函数来初始化。这里有点坑,就是即用了面向对象有用了面向过程的思想,这里是为了熟练使用。 //注意内存的管理,即使删除new分配的内存,可以把new写到类或结构中,在析构函数里添加delete来释放动态内存。 TreeNode<T> * node = new TreeNode<T>(tree[n-1]); //产生左结点并挂载 int arr_lchild = 2 * n; if ((tree[arr_lchild - 1] != EMPTY) && (tree[arr_lchild - 1] != INIT)) { //先调用递归创建孩子,再将父亲结点的左指针指向它。 //理解的时候不需要看array2link函数怎么实现,只需要知道它返回的是指向孩子结点的指针。 //将父亲结点的左指针指向孩子结点即可。 //这里需要理解递归思想,而不是递归的过程。只关心某一个,其他的调用(递归)函数即可。 node->lchild = array2link<int>(tree, arr_lchild); } //产生右结点并挂载,同理。 int arr_rchild = 2 * n + 1; if ((tree[arr_rchild - 1] != EMPTY) && (tree[arr_rchild - 1] != INIT)) { node->rchild = array2link<int>(tree, arr_rchild); } return node; } template<class T> void LinkList_PreOrder_DiGui(TreeNode<T> *pNode) { if (pNode == nullptr) return; Show(pNode); LinkList_PreOrder_DiGui(pNode->lchild); LinkList_PreOrder_DiGui(pNode->rchild); return; } template<class T> void LinkList_PreOrder_Stack(TreeNode<T> *tree) { //不要使用这种方法,使用下面那个,目的是方便和中序和后序遍历一起记忆,形成一个整体,就像递归的三种方法一样。 #if 1 //使用数组栈,栈中保存指向结点的指针!!不是结点的值 Stack<TreeNode<T> *> stack; TreeNode<T> *pnode; if (IsEmpty<T>(tree)) { cout << "empty" << endl; return; } //先将指向根节点的指针入栈 stack.data[++stack.top] = tree; //如果栈不空,循环每一个结点 //只写某个结点的情况,利用循环遍历所有结点。 while (stack.top != -1) { //1.出栈 pnode = stack.data[stack.top--]; cout << pnode->data << " "; //2.先右入栈 if (pnode->rchild != nullptr) stack.data[++stack.top] = pnode->rchild; if (pnode->lchild != nullptr) stack.data[++stack.top] = pnode->lchild; } #endif #if 0 //未完成!!! #endif return; } template<class T> void LinkList_InOrder_DiGui(TreeNode<T> *pNode) { // 递归结束条件 if (pNode == nullptr) return; //开始递归 LinkList_InOrder_DiGui(pNode->lchild); Show(pNode); LinkList_InOrder_DiGui(pNode->rchild); return; } template<class T> void LinkList_InOrder_Stack(TreeNode<T> *pNode) { //使用类设计方法实现的,类和结构一样的用法 Stack<TreeNode<T> *> stack;//创建栈,用来保存指向二叉树的指针。 TreeNode<T> *pnode = pNode;//用来遍历二叉树用 while (!stack.empty() || pnode != nullptr) // 前者是终止判断条件,后者是起始判断条件 { // 1.遍历所有左子树的左边, 退出while时为左节点为空 while (pnode != nullptr)//判断结点是否为空,非空则进入,空则说明到头了 { stack.push(pnode);//入栈 pnode = pnode->lchild;//指向左节点 } if (!stack.empty())//没明白这里为啥要判断一下,不判断感觉也可以运行。??? { pnode = stack.getop();//拉回来,这里很关键,如果不从栈中取值,则pnode指的已经不是有效结点了,指的是空结点了,如果在输出就出错了。 Show(pnode);//2.访问 stack.pop();//访问完后就可以出栈了,即丢弃。但是丢弃后我们还需要它的右孩子,那怎么? //(1)若先让中间结点不出栈,先让右孩子指针进栈,那么要想让中间结点再出栈,已经出不去了。 //(2)如果先让中间结点出栈,那怎么才能访问它的右孩子呢,因为出栈相当于删除,都没有了该结点,就不能访问右孩子。 // 解决办法是,在出栈前,将中间结点的指针保存起来,这里对应pnode=stack.getop(),再让中间结点出栈,通过保存的指针来访问右孩子 pnode = pnode->rchild;// 3.指向右孩子,重新开始。 } } return; } template<class T> void LinkList_PostOrder_DiGui(TreeNode<T> *pNode) { if (pNode == nullptr) return; LinkList_PostOrder_DiGui(pNode->lchild); LinkList_PostOrder_DiGui(pNode->rchild); Show(pNode); return; } template<class T> void LinkList_PostOrder_Stack(TreeNode<T> *pNode) { #if 0 // 方法一:两个while, 一个rchild_cache一个flage Stack<TreeNode<T> *> stack; TreeNode<T> *pnode = pNode; //用来遍历二叉树的指针 TreeNode<T> *rchild_cache = nullptr; int flage=1; if (pnode == nullptr) return; //不能使用while (!stack.empty()||pnode!=nullptr)来做判断 //因为在后序遍历完成后,pnode指向root结点,虽然栈已经被置空,但是pnode指向root(不为空),所以循环又会执行,一直重复输出后序遍历。 //解决办法是(1)在while中去掉pnode!=nullptr,这句作用是判断一开始二叉树是否为空的,可以在while的前面加上判断空树的语句即可。 //(2)改成while(!stack.empty())后会发现一开始就跳过循环,因为一开始stack为空,进而使用了do-while()。 do //使用do-while;用while会一直循环下去 { // 1.先找左边 while (pnode != nullptr) { stack.push(pnode); pnode = pnode->lchild; } rchild_cache = nullptr;//将标记为置空。 flage = 1;//为1表示需要处理栈顶结点,好好理解这个标志位的设置 //2.左边找完后,使用if-else判断是遍历右边的结点,还是访问当前结点 while (!stack.empty() && flage == 1)//第一次必然满足,加while的目的是利用循环来处理当走到尽头后,之后就是一个一个的输出结点值(说的是if的情况) { //如果不加循环,程序会在叶结点附近死循环下去,不能向上访问结点。如果没有走到尽头,就继续走下去(说的是else情况) pnode = stack.getop(); //拉回 // 2.1 访问当前结点 if (pnode->rchild == nullptr || pnode->rchild == rchild_cache)//右孩子为空或已访问的右结点,则访问当前结点。空表示右边到头了 { Show(pnode);//访问结点 rchild_cache = pnode;//记录当前结点为已访问状态 stack.pop();//访问后就可以出栈了 } // 2.2 遍历右结点 else//右孩子不为空 且 未访问过,则将指针指向右孩子,继续向左遍历,故把flage置0,使得跳出循环。 { pnode = pnode->rchild;//指向右孩子 flage = 0;//为0表示栈顶结点处理完毕,也可以认为是 } } } while (!stack.empty()); //虽然后序遍历和前面两在代码形式上有很太的不同,但在后序遍历中还是存在先中遍历的思想,比如 //从头找向左一直找;else里面,左边找不下去,就往找它的右子树,但只找一次,找到之后又进行向左找到头的办法; //不同点是在后序中加了两个变量rchild_cache和flage。rchild_cache表示:是遍历右孩子,还是访问我自己。即起到控制作用。 //flage表时:控制循环,这里也可以替换为 break语句,将它写在else里面。 但是使用break语句有一些问题,能用其他语句代替最好。 // 问题:查break的后台执行了什么。 #endif #if 1 //方法二:两次会晤法(进栈) Stack< StackElem<T> > stack;//建立一个栈,栈中每个元素都有两个值,一个是结点指针,一个是flage;这里将结构传入栈中!!!! TreeNode<T> *pnode = pNode;//用来遍历二叉树的指针,它会不停的移动 StackElem<T> temp;//这个也是个关键点,每次将temp的值进栈,而不是将pnode的值进栈,进栈变量的类型必须和栈的类型一样!!!! if (pnode == nullptr) return; while (true) { //一直向左遍历,直到结点为空 while (pnode != nullptr) { // 错误进栈法 // stack.push(pnode);//这里细节不再和之前的入栈相同,不过总体一样。 // 正确法 temp.pnode = pnode;//先设置进栈结点里的值,再进栈 temp.flage = LEFT; stack.push(temp); pnode = pnode->lchild; } if (!stack.empty()) { temp = stack.getop(); stack.pop(); pnode = temp.pnode; //如果是右 while (temp.flage == RIGHT) //上面方法中的while一样,都是当flage=RIGHT时一直向上访问结点 { Show(pnode);// 访问 if (stack.empty()) // 相关处理,指向上一个点 return; else { temp = stack.getop();//拉向上一节点 stack.pop();//抛出栈顶结点 pnode = temp.pnode; } } //如果是左 if (temp.flage == LEFT) { temp.flage = RIGHT;//置右。没有修改栈里面的flage,temp是临时的变量而已。所以书上给出了抛出,输入。 stack.push(temp);//入栈。 pnode = pnode->rchild;//指向右孩子。 } } } #endif return; }
参考
- 数据结构-联考辅导教程(2013版) 李春葆
- 数据结构-基于c++模板类的实现 余腊生
相关文章推荐
- 数据结构与算法(十二)前/中/后遍历二叉树
- 【数据结构与算法】二叉树 遍历
- 【数据结构与算法】二叉树广度遍历
- 【算法学习笔记】10.数据结构基础 二叉树初步练习3(遍历与递归复习)
- 【算法学习笔记】10.数据结构基础 二叉树初步练习3(遍历与递归复习)
- 数据结构与算法——二叉树的前序遍历,中序遍历,后序遍历
- 算法与数据结构基础4:C++二叉树实现及遍历方法大全
- 【数据结构与算法】根据遍历结果构建二叉树
- 数据结构 算法面试100题 之 逐层遍历二叉树元素
- 【数据结构与算法】二叉树的层序遍历
- 数据结构例程——二叉树的层次遍历算法
- 算法系列(七)数据结构之树的基本结构和二叉树的遍历
- 【数据结构与算法】(六) c 语言实现简单的二叉树静态创建及先序、中序、后序遍历
- 数据结构中,二叉树用到的算法代码综合
- 数据结构与算法简记:按层次顺序遍历和存储二叉树
- 【数据结构与算法】二叉树的遍历(递归遍历、非递归遍历、层序遍历)
- 【数据结构与算法】二叉树前序、中序、后序遍历间关系
- 【数据结构与算法】二叉树的层序遍历
- 【数据结构与算法】二叉树的层序遍历
- 【数据结构与算法】二叉树的遍历