二叉树的各种遍历,二叉树改链表,二叉树复习
2015-08-31 19:29
295 查看
二叉树的一个节点的结构如下:
struct BinaryTree { int m_value; BinaryTree* left; BinaryTree* right; };
二叉树的递归遍历
用递归来表达二叉树的遍历,算法思想最明白:void pre_order_access(BinaryTree* pRoot)//先序遍历 { if (pRoot != nullptr) { cout << pRoot->m_value <<" "; //先访问根节点 pre_order_access(pRoot->left); //左子树 pre_order_access(pRoot->right); //右子树 } } void mid_order_access(BinaryTree* pRoot) //中续遍历 { if (pRoot != nullptr) { //1、如果在这里定义int i; mid_order_access(pRoot->left); //先访问左子树 cout << pRoot->m_value <<" "; //访问根节点 //2、在这里给i赋值 ipRoot->m_value mid_order_access(pRoot->right); //访问右子树 //3、在末尾为i赋值,那么最终返回的i的值将是根节点的值 i } //4、虽然最内层的i 最先被赋值 } <pre name="code" class="cpp">void post_order_access(BinaryTree* pRoot) //后续遍历 { if (pRoot != nullptr) { post_order_access(pRoot->left); //先访问左子树 post_order_access(pRoot->right); //然后访问右子树 cout << pRoot->m_value <<" "; //最后根节点 } }
用数组元素来构建一棵二叉树
对于遍历的测试,需要先构建一棵二叉树,一般的,可以用一个数组来存储一个完全二叉树(按层序来存储),对于索引为 i 的节点,其左右孩子节点的索引分别为 2*i + 1 和 2*i + 2;BinaryTree* creatBinaryTree(int arr[], int i, int n) //递归的来构造一棵完全二叉树 { BinaryTree* pRoot; if (i >= n) return nullptr; pRoot = (BinaryTree*)malloc(sizeof(BinaryTree)); pRoot->m_value = arr[i]; pRoot->left = creatBinaryTree(arr, 2 * i + 1, n); pRoot->right = creatBinaryTree(arr, 2 * i + 2, n); return pRoot; }调用时,这样就行
int arr[] = { 8, 4, 10, 2, 6, 9, 11, 1, 3, 5, 7}; BinaryTree* proot = creatBinaryTree(arr, 0, sizeof(arr) / sizeof(int));对于非完全二叉树,可以将左右节点为空的节点扩展出子节点来,相当于虚节点,节扩展点值为特殊的值,用来标记其父节点的对应左右为空。
BinaryTree* creatPreBinaryTree(int arr[], int& i) //注意这里的第二个参数用来表示索引 必须是个引用类型 { //表示每层递归使用的是同一个索引变量 不至于混乱 BinaryTree* proot = nullptr; if (arr[i] == 0) return nullptr; proot = (BinaryTree*)malloc(sizeof(BinaryTree)); proot->m_value = arr[i]; //先创建根节点 ++i; proot->left = creatPreBinaryTree(arr, i); //然后创建左子树 ++i; proot->right = creatPreBinaryTree(arr, i); //然后创建右子树 return proot; //实际相当于一个前序遍历的创建 }调用时
int a[] = { 8, 4, 2, 1,0, 0, 3, 0, 0, 6, 5, 0, 0, 7, 0, 0,10, 9, 0, 0, 11, 0, 0 }; int i = 0; BinaryTree* root = creatPreBinaryTree(a, i);
层序遍历二叉树
层序遍历借助一个队列即可,从根节点开始入列之后,当队列不为空,循环的的操作(注意循环的必须保持一致性,每个节点的操作相同):出列,将左右子节点入列即可
void level_access(BinaryTree* pRoot) //分层遍历 相当于广度优先搜索 { if (pRoot != nullptr) { queue<BinaryTree*> queueBinaryNode; queueBinaryNode.push(pRoot); BinaryTree* curNode = nullptr; while (!queueBinaryNode.empty()) //每次访问队列的首元素 同时把其连个孩子放入队列中 { curNode = queueBinaryNode.front(); cout << curNode->m_value << " "; queueBinaryNode.pop(); if (curNode->left != nullptr) queueBinaryNode.push(curNode->left); if (curNode->right != nullptr) queueBinaryNode.push(curNode->right); } } }
二叉树的非递归遍历
先序,中序,后续的遍历需要借助栈来实现,既然是栈,每次就只能对栈顶的元素进行操作,也就是每次访问输出都只能是栈顶的元素(当然要满足输出条件),而且必须保持操作的一致性,也就是循环体内对每个节点的处理相同。前序遍历的非递归遍历:
void pre_deep_access(BinaryTree* pRoot) //深度优先搜索 在这里相当于前序遍历 { if (nullptr != pRoot) { stack<BinaryTree*> stackBinaryTree; stackBinaryTree.push(pRoot); //根节点入栈 BinaryTree* curNode = nullptr; while (!stackBinaryTree.empty()) { curNode = stackBinaryTree.top(); //每次对栈顶元素进行处理 stackBinaryTree.pop(); cout << curNode->m_value <<" "; if (curNode->right != nullptr) stackBinaryTree.push(curNode->right); //先右子节点入栈 if (curNode->left != nullptr) stackBinaryTree.push(curNode->left); //再左子节点入栈 这样保证左子节点先于右子节点访问 } } }中序遍历的非递归遍历:
void mid_deep_access(BinaryTree* pRoot) { if (nullptr != pRoot) { stack<BinaryTree*> stackBinaryTree; BinaryTree* curNode = pRoot; while (!stackBinaryTree.empty() || (curNode != nullptr)) { //根节点弹出之后 变空了 但是还有右边的子树 curNode用来标记的 while (nullptr != curNode) //沿着左下方向,将节点入栈 { stackBinaryTree.push(curNode); curNode = curNode->left; } if (!stackBinaryTree.empty()) { curNode = stackBinaryTree.top(); cout << curNode->m_value << " "; stackBinaryTree.pop(); //如果将根节点弹出 此时栈为空但是右子树还没遍历所以wile 的循环条件中 需要或上curNode != nullptr curNode = curNode->right; } } //对于跟节点 最后弹出来 但是右子树还是不为空 所以这里需要用个或运算 } }后续遍历逻辑相对麻烦一些,先要沿着左下方向把节点入栈,不过最左下的子节点(左子节点为空),也就是栈顶的节点A,若右子节点非空,需要对右子节点进行沿着左下方向压入,最后返回到栈顶的节点又是A时,由于A的右子树已经遍历过,所以不能再重复,于是引入一个变量,指示当前访问(这里的访问在程序里就是输出节点值)节点的上一个访问的节点,如果栈顶的元素是A,但是上一个访问的节点是 A的右子节点,说明A的右子节点已经访问过,代码如下
void post_deep_access(BinaryTree* pRoot) { if (nullptr != pRoot) { stack<BinaryTree*> stackBinaryTree; BinaryTree* curNode = pRoot; BinaryTree* preNode = nullptr; //指向前一个被访问的节点 也就是输出过的 while (curNode != nullptr || !stackBinaryTree.empty()) { while (curNode != nullptr) { stackBinaryTree.push(curNode); curNode = curNode->left; } curNode = stackBinaryTree.top(); //栈顶节点 if (curNode->right == nullptr || curNode->right == preNode) //栈顶元素可以马上访问的条件 没有右子树 或者右子树刚访问过 { cout << curNode->m_value << " "; stackBinaryTree.pop(); preNode = curNode; curNode = nullptr; } else curNode = curNode->right; //栈顶元素不能马上访问 } } }还有一种使用双栈来进行后序遍历的,将根节点入栈1,然后每次弹出时,压入到栈2当中,且把弹出的节点的左,右子节点压入栈1当中;这样保证最终所有的节点都会入栈2,且栈2中,所有的右子树在左子树的下边。
void post_deep_access2stack(BinaryTree* pRoot) { if (nullptr != pRoot) { stack<BinaryTree*> stackBinaryTree1, stackBinaryTree2; BinaryTree* curNode = nullptr; stackBinaryTree1.push(pRoot); //将根节点入栈1 while (!stackBinaryTree1.empty()) { curNode = stackBinaryTree1.top(); stackBinaryTree1.pop(); stackBinaryTree2.push(curNode); if (curNode->left != nullptr) stackBinaryTree1.push(curNode->left); if (curNode->right != curNode) stackBinaryTree1.push(curNode->right); } while (!stackBinaryTree2.empty()) { curNode = stackBinaryTree2.top(); stackBinaryTree2.pop(); cout << curNode->m_value << " "; } } }总结:沿着左下方向把节点入栈的,在循环内压入根节点,且循环判断的条件会多一个。循环外压入根节点的,会在循环里弹栈顶元素,然后压入左,右子节点。
对于队列,每次访问输出的只能队列的首元素;对于栈,每次访问输出的只能是栈顶元素。
将二叉树按中续遍历顺序改成链表
每访问到一个节点cur,将该节点的指向左子节点的指针指向前一个访问的节点 pre,所以这里需要保存下来前一个访问的节点,同时将pre 的指向右节点的指针指向当前节点即可,即cur->left = pre. pre->right = cur. 代码如下:void binary2list(BinaryTree* pRoot, BinaryTree*& pre, BinaryTree* &result) { if (pRoot != nullptr) { //BinaryTree* pHead = nullptr; binary2list(pRoot->left, pre, result); cout << pRoot->m_value << " "; pRoot->left = pre; //返回时赋值 if (nullptr == result) result = pRoot; //对于非静态成员 pHead = pRoot; //最外层的递归执行到这里 当前的pRoot是根节点了 此时左节点已经访问完毕 //static BinaryTree* result = pRoot; if (nullptr != pre) pre->right = pRoot; pre = pRoot; //pre最后指向尾节点 //空行这之间的代码第一次执行是左下边的那个节点 也就是中序遍历访问的第一个节点才执行 //而最外层的 binary2list() 执行到这里时,左子树已经遍历玩,所以想要使用一个局部变量pHead保存pRoot //结果保存下来的这个pRoot实际指向的是根节点 而不是第一个访问的节点 binary2list(pRoot->right, pre, result); } }
这里需要说明的是在不能在函数内部使用 static BinaryTree* pre = nullptr; 静态变量在静态存储区,只会赋值一次,也就是第一次调用该函数进入该函数的第一层会赋值,这样虽然每层函数使用的也是同一个pre,不过在外部调用完成之后,由于pre 不再为空,所以第二次在外部调用该函数,会发生错误(链表的头节点的左指针不为空)
该函数的调用如下:
BinaryTree* pTail = nullptr; BinaryTree* pHead = nullptr; binary2list(root, pTail, pHead); //在这里 pHead一定要传一个空指针进去 否则会出错 //pHead为指向链表表头 pTail指向链表的最后一个元素
相关文章推荐
- SwipeListView实现QQ消息侧滑删除功能
- 第六周第一天
- php curl 简介
- MongoDB学习十一 --MongoDB的Replication实践
- Google地图数据算法
- Powerdesigner15-用jdbc链接MySQL实现逆向工程步骤
- HDU 2196 Computer(树形dp)
- UVA784简单dfs
- #2 create and populate a database && realistic and practical applications (PART 2)
- 前端数据存储
- Spring项目启动报错
- java学习个人笔记---容器之Vetor与ArrayList的区别
- MySQL命令行导入导出数据
- java建造者模式
- html的一个有趣的图层+图片滑动
- python中有序集合的索引遍历
- 函数的记忆
- Ajax总结
- Java 字符串连接
- dispay属性详解