数据结构是所有的程序员就业过程中无法回避的知识,最近在回顾数据结构的内容,因此会不定期的推出一些数据结构的文章,分享自己的笔记。
树是数据结构中的重点,由于二叉树又是树中的重中之重。二叉树的应用也非常的多,比如二叉排序树、平衡二叉树、红黑树、B树、堆等。本篇文章是关于二叉树的第一篇文章,主要介绍二叉树的几种遍历方式以及代码实现。
一、二叉树的基本概念
二叉树的定义:一棵二叉树节点的有限集合,该集合或者为空,或者由一个根节点加上两棵左子树和右子树组成,其左子树和右子树又分别是一棵二叉树。
二叉树的特点:
- 每个节点最多只有两棵子树,即二叉树不存在度数大于2的节点
- 二叉树的子树有左、右之分,其子树的次序不能颠倒
- 二叉树的典型遍历方式有四种:先序遍历、中序遍历、后序遍历、层序遍历
几种特殊的二叉树:
- 满二叉树:在一棵二叉树中,所有分支节点都存在左子树和右子树,并且所有的叶节点都在同一层上。如图1。
- 完全二叉树:一棵具有N个节点的完全二叉树的结构与满二叉树的前N个节点的结构相同。如图2。
二、二叉树的遍历
在开始介绍遍历之前,首先规定一下二叉树的存储和遍历的接口,定义如下:
typedef struct BinaryTreeNode{ int data; BinaryTreeNode* leftChild; BinaryTreeNode* rightChild;}BinaryTreeNode, BinaryTree;class BinaryTreeOperate{public: static void createBinaryTree(BinaryTree** root); /* 递归方式操作 */ static void preOrderTraverse(const BinaryTree* root); static void inOrderTraverse(const BinaryTree* root); static void postOrderTraverse(const BinaryTree* root); /* 非递归方式操作 */ static void preOrderTraverseNonRecurve(const BinaryTree* root); static void inOrderTraverseNonRecurve(const BinaryTree* root); static void postOrderTraverseNonRecurve(const BinaryTree* root); /* 宽度优先遍历 */ static void widthFirstTraverse(const BinaryTree* root);};
测试二叉树结构如图3所示:
1、二叉树的先序遍历
二叉树的先序遍历:先访问根节点,再访问左子树,最后访问右子树。
上述的二叉树的先序遍历结果为:10 6 4 8 14 12 16
递归方式实现:
void BinaryTreeOperate::preOrderTraverse(const BinaryTree* root){ if(root != nullptr) { cout << root->data << ""; BinaryTreeOperate::preOrderTraverse(root->leftChild); BinaryTreeOperate::preOrderTraverse(root->rightChild); }}
非递归方式实现:
思考一下如何实现:首先访问根节点,然后访问它的左子树;当左子树为空时,我们要退回到上一个访问的节点(根节点),访问它的右子树。如何实现退回到上一个访问的节点的操作?有数据结构基础的人很容易就会想到栈了。
过程:
- 访问根节点,然后将根节点入栈
- 遍历根节点的左子树
- 当左子树为空,获取栈顶元素并出栈,然后访问栈顶元素的右子树
void BinaryTreeOperate::preOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack; //root用了const修饰,直接将const赋值为non-const是不可以的 //需要先使用const_cast去除root的const限定 BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack.empty()) { if(p != nullptr) { cout << p->data << ""; nodes_stack.push(p); p = p->leftChild; } else { p = nodes_stack.top(); nodes_stack.pop(); p = p->rightChild; } }}
2、二叉树的中序遍历
二叉树的中序遍历:先遍历左子树,再访问根节点,最后访问右子树。
上述的二叉树的中序遍历结果为:4 6 8 10 12 14 16
递归方式实现:
void BinaryTreeOperate::inOrderTraverse(const BinaryTree* root){ if(root != nullptr) { BinaryTreeOperate::inOrderTraverse(root->leftChild); cout << root->data << ""; BinaryTreeOperate::inOrderTraverse(root->rightChild); }}
非递归方式实现:
中序遍历的非递归实现和先序遍历类似:
- 先将根节点入栈
- 遍历根节点的左子树
- 当左子树为空时,获取栈定元素并出栈,访问栈顶元素,然后遍历栈顶元素的右子树
void BinaryTreeOperate::inOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack; BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack.empty()) { if(p != nullptr) { nodes_stack.push(p); p = p->leftChild; } else { p = nodes_stack.top(); nodes_stack.pop(); cout << p->data << ""; p = p->rightChild; } }}
3、二叉树的后序遍历
二叉树的后序遍历:先遍历左子树,再遍历右子树,最后访问根节点。
上述二叉树的后序遍历结果为:4 8 6 12 16 14 10
递归方式实现:
void BinaryTreeOperate::postOrderTraverse(const BinaryTree* root){ if(root != nullptr) { BinaryTreeOperate::postOrderTraverse(root->leftChild); BinaryTreeOperate::postOrderTraverse(root->rightChild); cout << root->data << ""; }}
非递归方式实现:
后序遍历的非递归实现稍微复杂一点,先看一下下面的分析:
对于下图所示的这一棵二叉树:
后序遍历的结果为:4 8 6 12 16 14 10
后序遍历的逆序结果为:10 14 16 12 6 8 4
想一下后序遍历的逆序结果是如何得到的:
- 先遍历根节点
- 然后遍历右子树
- 最后遍历左子树
思路出来了:
- 模仿先序遍历的过程,但是把左右节点的访问顺序颠倒,同时把访问的结果加到栈中(因为后序遍历是它的一个逆序的过程)
- 遍历栈中的所有节点,即为后序遍历的结果
void BinaryTreeOperate::postOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack_1, nodes_stack_2; BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack_1.empty()) { if(p != nullptr) { nodes_stack_2.push(p); nodes_stack_1.push(p); p = p->rightChild; } else { p = nodes_stack_1.top(); nodes_stack_1.pop(); p = p->leftChild; } } while(!nodes_stack_2.empty()) { p = nodes_stack_2.top(); nodes_stack_2.pop(); cout << p->data << ""; }}
4、二叉树的层序遍历
二叉树的层序遍历:层序遍历也可以被称为宽度优先遍历,先访问树的第一层节点,再访问树的第二层节点······。再同一层节点中,以从左到右的顺序依次访问。
上述二叉树的层序遍历结果为:10 6 14 4 8 12 16
代码实现(借助队列):
void BinaryTreeOperate::widthFirstTraverse(const BinaryTree* root){ queue nodes_queue; BinaryTreeNode* p = const_cast(root); nodes_queue.push(p); while(!nodes_queue.empty()) { p = nodes_queue.front(); nodes_queue.pop(); if(p != nullptr) { cout << p->data << ""; if(p->leftChild != nullptr) nodes_queue.push(p->leftChild); if(p->rightChild != nullptr) nodes_queue.push(p->rightChild); } }}
关于二叉树的遍历操作:递归实现简单,但是效率就比较低,因为编译器帮你做了很多的现场保护和现场恢复的工作;非递归实现都借用了栈,其实也是在模仿递归。两种方式都很重要。
完整代码(包含二叉树创建、遍历的各种实现、main函数测试)如下:
#include #include #include #include using namespace std;// 二叉树的每个节点的值,0表示该节点为空vector tree_data = {10, 6, 4, 0, 0, 8, 0, 0, 14, 12, 0, 0, 16, 0, 0};typedef struct BinaryTreeNode{ int data; BinaryTreeNode* leftChild; BinaryTreeNode* rightChild;}BinaryTreeNode, BinaryTree;class BinaryTreeOperate{public: static void createBinaryTree(BinaryTree** root); /* 递归方式操作 */ static void preOrderTraverse(const BinaryTree* root); static void inOrderTraverse(const BinaryTree* root); static void postOrderTraverse(const BinaryTree* root); /* 非递归方式操作 */ static void preOrderTraverseNonRecurve(const BinaryTree* root); static void inOrderTraverseNonRecurve(const BinaryTree* root); static void postOrderTraverseNonRecurve(const BinaryTree* root); /* 宽度优先遍历 */ static void widthFirstTraverse(const BinaryTree* root);};void BinaryTreeOperate::createBinaryTree(BinaryTree **root){ static int id = 0; int tmp_data = tree_data[id++]; if (tmp_data > 0) { BinaryTreeNode *newNode = new BinaryTreeNode(); newNode->data = tmp_data; newNode->leftChild = nullptr; newNode->rightChild = nullptr; *root = newNode; BinaryTreeOperate::createBinaryTree(&((*root)->leftChild)); BinaryTreeOperate::createBinaryTree(&((*root)->rightChild)); }}void BinaryTreeOperate::preOrderTraverse(const BinaryTree* root){ if(root != nullptr) { cout << root->data << ""; BinaryTreeOperate::preOrderTraverse(root->leftChild); BinaryTreeOperate::preOrderTraverse(root->rightChild); }}void BinaryTreeOperate::preOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack; //root用了const修饰,直接将const赋值为non-const是不可以的 //需要先使用const_cast去除root的const限定 BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack.empty()) { if(p != nullptr) { cout << p->data << ""; nodes_stack.push(p); p = p->leftChild; } else { p = nodes_stack.top(); nodes_stack.pop(); p = p->rightChild; } }}void BinaryTreeOperate::inOrderTraverse(const BinaryTree* root){ if(root != nullptr) { BinaryTreeOperate::inOrderTraverse(root->leftChild); cout << root->data << ""; BinaryTreeOperate::inOrderTraverse(root->rightChild); }}void BinaryTreeOperate::inOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack; BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack.empty()) { if(p != nullptr) { nodes_stack.push(p); p = p->leftChild; } else { p = nodes_stack.top(); nodes_stack.pop(); cout << p->data << ""; p = p->rightChild; } }}void BinaryTreeOperate::postOrderTraverse(const BinaryTree* root){ if(root != nullptr) { BinaryTreeOperate::postOrderTraverse(root->leftChild); BinaryTreeOperate::postOrderTraverse(root->rightChild); cout << root->data << ""; }}void BinaryTreeOperate::postOrderTraverseNonRecurve(const BinaryTree* root){ stack nodes_stack_1, nodes_stack_2; BinaryTreeNode* p = const_cast(root); while(p != nullptr || !nodes_stack_1.empty()) { if(p != nullptr) { nodes_stack_2.push(p); nodes_stack_1.push(p); p = p->rightChild; } else { p = nodes_stack_1.top(); nodes_stack_1.pop(); p = p->leftChild; } } while(!nodes_stack_2.empty()) { p = nodes_stack_2.top(); nodes_stack_2.pop(); cout << p->data << ""; }}void BinaryTreeOperate::widthFirstTraverse(const BinaryTree* root){ queue nodes_queue; BinaryTreeNode* p = const_cast(root); nodes_queue.push(p); while(!nodes_queue.empty()) { p = nodes_queue.front(); nodes_queue.pop(); if(p != nullptr) { cout << p->data << ""; if(p->leftChild != nullptr) nodes_queue.push(p->leftChild); if(p->rightChild != nullptr) nodes_queue.push(p->rightChild); } }}int main(){ BinaryTree* root; BinaryTreeOperate::createBinaryTree(&root); cout << "先序遍历结果: " << endl; BinaryTreeOperate::preOrderTraverse(root); cout << ""; BinaryTreeOperate::preOrderTraverseNonRecurve(root); cout << "中序遍历结果: " << endl; BinaryTreeOperate::inOrderTraverse(root); cout << ""; BinaryTreeOperate::inOrderTraverseNonRecurve(root); cout << "后序遍历结果: " << endl; BinaryTreeOperate::postOrderTraverse(root); cout << ""; BinaryTreeOperate::postOrderTraverseNonRecurve(root); cout << "宽度优先遍历结果:" << endl; BinaryTreeOperate::widthFirstTraverse(root); return 0;}
运行结果如图4所示:
今天的内容就到这儿了。如果对我的推、文有兴趣,欢迎转、载分、享。也可以推荐给朋友关、注哦。只推干货,宁缺毋滥。