01、目录
文章目录
02、前言
有一说一,对于数据结构和算法,我从学编程以来,一只都是抱着一份敬畏或者说是畏惧的心情。
因为我实在没有把握学好他,想必很多人都跟我一样,曾经被数据结构与算法的外表吓住,不敢轻易去尝试,其实这真的很正常,如果你连最基本的语言本身都没有积累足够的自信,自然也没有自信去攀登数据结构这座屹立程序员心目中的这座大山了。
是什么让我有勇气开始接触他的呢?说来话长,这还得从一次公司内部的讨论开始,这里就不过多介绍,言而总之,就是准备咬着牙,去打这一场持久战,搞定数据结构。
但是确实如上所说,数据结构的外表太吓人了,但是呢,总有很多大佬(前辈)会做一些糖果炮弹,让我们去学习数据结构,今天给各位推荐一本书,自我感觉非常好,至少,看这本书,我不会一直想着数据结构有多吓人,甚至还会主动去接触,由于上传的资源需要各位破费,这里我贴上百度网盘链接,技术资源不应用收费来贬低它的价值,谢谢。
漫画算法————小灰的算法之旅
链接: https://pan.baidu.com/s/1sya4sLmP5GLk01uQLL0PgQ
提取码: c6h7
03、树的简介
由于本章出于遍历的介绍偏多,会用算法例子+概念,所以,数在这里只做简单了解,后面我们深入探讨。
区分:
线性表、栈、队列、串是一对一的数据结构,而树是一对多的数据结构。
定义:
树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。如下图所示:
对于树,我们注意两点:
- b>0时根结点是唯一的,不可能存在多个根结点,别和现实中的大树混在一起,现实中的大树有很多根须,那是真实的树,数据结构中的树只能有一个根结点。
- m>0时,子树的个数没有限制,但它们一定是互不相交的。像下图中的两个结构就不符合树的定义,因为它们有相交的子树。
OK,下面我们回归今天的主题,树的遍历。
04、树的遍历方式
树的遍历方式,主要在这里说三种:前序遍历,中序遍历,后续遍历。
4.1、 前序遍历
前序遍历:前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。
让我们一起来看树的前序遍历:
下面显示了按照前序遍历树之后得到的节点顺序。
4.2、 中序遍历
中序遍历:中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。
让我们一起来看树的中序遍历:
通常来说,对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。 我们将在数据结构介绍 – 二叉搜索树中再次提及。
4.3、 后续遍历
后续遍历:后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。
让我们一起来看树的中序遍历:
值得注意的是,当你删除树中的节点时,删除过程将按照后序遍历的顺序进行。 也就是说,当你删除一个节点时,你将首先删除它的左节点和它的右边的节点,然后再删除节点本身。
另外,后序在数学表达中被广泛使用。 编写程序来解析后缀表示法更为容易。 这里是一个例子:
您可以使用中序遍历轻松找出原始表达式。 但是程序处理这个表达式时并不容易,因为你必须检查操作的优先级。
如果你想对这棵树进行后序遍历,使用栈来处理表达式会变得更加容易。 每遇到一个操作符,就可以从栈中弹出栈顶的两个元素,计算并将结果返回到栈中。
递归和迭代
下面将举出三个例子,使用递归和迭代的思想解题,递归和迭代也是属于数据结构范畴,后续也会介绍的。
05、三种遍历方式的Code题(来自LeetCode)
这里呢,我都用C++两种实现,递归和迭代,略知一二,这里就尽量写好点。
5.1、 前序遍历(例题)
给定一个二叉树,返回它的 前序遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
思路:总体思路,前序遍历:根->左->右。利用递归和迭代,不懂的可以先看我写的,慢慢体会。
- C++递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> res;
vector<int> preorderTraversal(TreeNode* root) {
if(root){
res.push_back(root->val); //根
preorderTraversal(root->left); //左
preorderTraversal(root->right); //右
}
return res;
}
};
- C++迭代
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> tr;
TreeNode* cur = root; //得到临时根节点
while(cur || tr.size())
{//为了保证所有节点都遍历完,用while循环
while(cur){
tr.push(cur);
res.push_back(cur->val);
cur = cur -> left;
}
cur = tr.top(); //因为左节点为NULL,所以重新赋值栈中的值为头结点
tr.pop(); //弹栈,准备给右节点让位置
cur = cur ->right; //指向右节点,到此为止:根->左->右第一次执行完毕
}
return res;
}
};
本题用C++语言实现递归和迭代的前序遍历。想必语言不是困惑各位的。用C、java、go、python肯定也没问题,思路很关键。然后看上图也能看出,迭代是明显要优于递归的。而且递归确实相比于迭代要简单不少。
这里贴上一张过程图:
5.2、 中序遍历(例题)
给定一个二叉树,返回它的中序遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
思路:跟前序遍历思路差不多,只是遍历的顺序改变了:左->根->右。
C++递归:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
if(root != NULL) //判断是否是根节点
{//是:先遍历左,再根,最后右
inorderTraversal(root->left);
res.push_back(root->val);
inorderTraversal(root->right);
}
//不是根节点
return res;
}
};
C++迭代:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
TreeNode* cur = root; //保存当前节点
while(cur || st.size())
{
if(cur)
{
st.push(cur); //压栈
cur = cur -> left; //迭代的体现:重复整个过程的其中一部分,完成整体的要求
}
else
{
cur = st.top();
st.pop(); //弹栈
res.push_back(cur->val);
cur = cur -> right;
}
}
return res;
}
};
PS:当数据足够小的时候,递归和迭代是差不多的,但是如果这个树的深度很大,那么,迭代肯定是胜于递归的。
5.3、 后续遍历(例题)
给定一个二叉树,返回它的 后序遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
思路:同上,只是顺序不一样,具体看Code的体现。
C++递归:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> res;
vector<int> postorderTraversal(TreeNode* root) {
if(root != NULL)
{
//顺序:左->右->根
postorderTraversal(root->left);
postorderTraversal(root->right);
res.push_back(root->val);
}
return res;
}
};
C++迭代:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> t;
TreeNode* cur = root;
while(cur || !t.empty())
{
if(cur)
{
t.push(cur);
res.push_back(cur->val);
cur = cur -> right;
}
else
{
cur = t.top();
t.pop(); //弹栈
cur = cur -> left;
}
}
reverse(res.begin(),res.end());
return res;
}
};
后续遍历中我们用到了一个巧方法,利用前序遍历的模型,改一下,变为根->右->左。这样就是跟后续遍历完全相反,最后,我们用vector容器的翻转函数,reverse直接就得到了我们想要得结果。
这种方法借鉴来的,我也想了好久,在后续遍历的规则中绕进去了。没解出来。
06、层序遍历(了解内容)
层序遍历就是逐层遍历树结构。
广度优先搜索是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。
当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。
这是一个层序顺序遍历的例子:
通常,我们使用一个叫做队列的数据结构来帮助我们做广度优先搜索。
队列的话,简单来说就跟我们排队买奶茶一样,按照顺序来的结构,我们后面再说。加油,攻克数据结构,做一个上进的程序猿鸭!下面我们来看一个例题,仅供了解。因为队列不熟悉,可能理解有点困难。
6.1、 层序遍历(了解例题)
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
DFS:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> levelOrder(TreeNode* root)
{
addVector(root,0); //调用递归函数
return res;
}
void addVector(TreeNode* root,int level)
{
if(root == NULL) return;
if(res.size() == level) res.resize(level+1); //level表示层数,也对应二维数组的第一层索引,
res[level].push_back(root->val);
addVector(root->left,level+1);
addVector(root->right,level+1);
}
};
BFS:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (root == NULL) return {};
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
vector<int>level; //存放每一层的元素值
int count=q.size(); //队列大小表示当前层数的元素个数
while(count--) //count--逐个对该层元素进行处理
{
TreeNode *t=q.front(); q.pop();
level.push_back(t->val);
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
res.push_back(level); //将当层元素的vector加入res中
}
return res;
}
};
关于DFS和BFS,由于博主也搞得不是很懂,将网上的Code拿了下来,大家看看吧。当然,写文章的时候不明白,说不定你看的时候博主已经ok了,可以评论区评论出来一起讨论鸭!
07、小结
关于树的遍历,和其他数据结构如数组是有很大的不同的,链表这种基础一定要有,不然学的时候一定是似懂非懂的状态,那样,说实话,真的是在浪费时间,不如往前走,学好基础,这是博主给大家的建议哈。
版权声明:转载请注明出处,谢谢!