二叉树的先序遍历
就是每棵子树,都是先出现头结点、再左子树、再右子树。
1、递归实现先序中序后序遍历
!!! 一定会到达这个Node节点三次,也表明了有三个位置进行Node数据的处理,前中后
每个节点都是这样。
先序就是第一次到达某个节点就打印
中序就是第二次到达某个节点就打印
后序就是第三次到达某个节点再打印。
二叉树可以先去左边树上转一圈,再回到中间节点,再去右边树上转一圈,可以进行信息的整合。
f(Node)
{
if(Node==NULL)
return;
// 1
f(Node->left);
// 2
f(Node->left);
//3
}
2、非递归实现先序中序后序遍历
压栈的方式实现非递归
1、先序遍历
设计栈 头-左-右
- 栈弹出元素就打印
- 如有右,就压入右
- 如有左,就压入左
因为栈先进后出,所以先压右,再压左,那么弹出的时候就先弹出左,弹出即打印,就是先序了。
2、后序遍历
头-右-左 那么把弹出来的元素倒个序就是左-右-头,也就是倒序遍历了
- 栈弹出元素就打印
- 如有左,就压入左
- 如有右,就压入右
所以处理元素的时候,另设一个新栈放处理元素,等放节点的栈处理完了 ,把新栈里的元素弹出,那么就是后序遍历的结果了。
后序遍历通过一个栈实现
在没有打印的时候,h的含义没有动过。h始终指向上次打印的位置。c指向当前栈顶。
c.left=h就说明 c节点的左子树处理完了,c.right=h就说明 c节点的右子树处理完了,当然左树也处理完了。
if...else if...else 分别对应节点的左树、右树都没有处理完;左树处理完了;右树处理完,左右树同时处理完了。
3、中序遍历
1)head不为零,整条左边界依次入栈
2)条件1)无法满足,就弹出,右树继续执行条件1)
理解:整棵树会被左边界(头左)分解,弹出的时候就是左头。且每次处理都是先左树的左头,再右树的左头,把整棵树分解,也就遍历完了树上的每个节点。
3、层序遍历
刷爆LeetCode很难?你错了!左神(左程云)算法大牛,熬夜1个月,肝出数据结构与算法基础-中级-高级全家桶教程,直击BATJ算法面试题真题,一线大厂一网打尽_哔哩哔哩_bilibili
4、二叉树的序列化和反序列化
二叉树结该是一个二维平面内的结构,而序列化出来的字符串是一个线性的一维结构。所谓的序列化不过就是把结构化的数据「打平」,其实就是在考察二叉树的遍历方式。
保存节点,包括空节点,那么可以通过打印出来的东西进行二叉树的复原。
序列化和二叉树的结构一一对应。
不能忽略空。
层序遍历反序列化
序列化是在前、中、后序遍历的基础上,把二维平面内的二叉树结构元素拿出来变成线性的一维结构。
下面的是参考labuladong算法小抄的内容
该链接中同时包含前中后序遍历二叉树的序列化和反序列化,同时有层级遍历的序列化和反序列化。
4.1基于前序遍历的框架实现序列化和反序列化
序列化:在前序遍历的框架上,遍历元素并且保存元素
String SEP = ",";
String NULL = "#";
/* 主函数,将二叉树序列化为字符串 */
String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root, sb);
return sb.toString();
}
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
/****** 前序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
serialize(root.left, sb);
serialize(root.right, sb);
}
反序列:如何通过二叉树的前序遍历结果还原一棵二叉树?
if (nodes.isEmpty()) return null;
/****** 前序遍历位置 ******/
// 列表最左侧就是根节点
String first = nodes.removeFirst();
if (first.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(first));
/***********************/
root.left = deserialize(nodes);
root.right = deserialize(nodes);
理解:借助二叉树的递归就是先处理中间的节点,再处理左子树,最后处理右子树。
注意这里的返回值用一维结构元素数组中取出来的元素构造节点,分别先赋值给根节点,再赋值给根节点的左、右树节点。
根据树的递归性质,nodes 列表的第一个元素就是一棵树的根节点,所以只要将列表的第一个元素取出作为根节点,剩下的交给递归函数去解决即可。
!!!注意:这里的两次返回null,一次是数组元素为空,说明所有的节点都处理完了;一次是遇到数组中有null,在先序遍历中说明某节点的某颗子树处理完了。
4.2基于中序遍历的框架无法实现反序列化
序列化:
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
serialize(root.left, sb);
serialize(root.right, sb);
/****** 后序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
}
反序列化:
/* 主函数,将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
LinkedList<String> nodes = new LinkedList<>();
for (String s : data.split(SEP)) {
nodes.addLast(s);
}
return deserialize(nodes);
}
/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
if (nodes.isEmpty()) return null;
// 从后往前取出元素
String last = nodes.removeLast();
if (last.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(last));
// 限构造右子树,后构造左子树
root.right = deserialize(nodes);
root.left = deserialize(nodes);
return root;
}
4.3基于中序遍历的框架无法实现反序列化
可以实现序列化
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
serialize(root.left, sb);
/****** 中序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
serialize(root.right, sb);
}
但是,我们刚才说了,要想实现反序列方法,首先要构造 root
节点。前序遍历得到的 nodes
列表中,第一个元素是 root
节点的值;后序遍历得到的 nodes
列表中,最后一个元素是 root
节点的值。
你看上面这段中序遍历的代码,root
的值被夹在两棵子树的中间,也就是在 nodes
列表的中间,我们不知道确切的索引位置,所以无法找到 root
节点,也就无法进行反序列化。
4.4基于层序遍历的框架实现反序列化
可以看到,每一个非空节点都会对应两个子节点。
我的困惑就是怎么在一、二层完成以后,怎么将第三层和上两层的联系起来?是通过把元素构成的节点放到队列中,如果遍历到的元素不为空就压入队列中,意味着通过它构造出来的节点不为空,就会对应两个子节点。
/* 将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
if (data.isEmpty()) return null;
String[] nodes = data.split(SEP);
// 第一个元素就是 root 的值
TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));
// 队列 q 记录父节点,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
for (int i = 1; i < nodes.length; ) {
// 队列中存的都是父节点
TreeNode parent = q.poll();
// 父节点对应的左侧子节点的值
String left = nodes[i++];
if (!left.equals(NULL)) {
parent.left = new TreeNode(Integer.parseInt(left));
q.offer(parent.left);
} else {
parent.left = null;
}
// 父节点对应的右侧子节点的值
String right = nodes[i++];
if (!right.equals(NULL)) {
parent.right = new TreeNode(Integer.parseInt(right));
q.offer(parent.right);
} else {
parent.right = null;
}
}
return root;
}