引言
由于自己的算法基础比较薄弱,特此记录一下学习过程中遇见的算法题,从简单算法题开始做了一个梳理,希望我的梳理能对跟我一样的小白起到一点作用,写的不好的地方请在评论区多多提建议。本节记录的是有关二叉树算法题,我们先从最基础的地方开始入门:
- 将链表转换成二叉树(根左右顺序、层序)
- 对二叉树进行先序遍历、中序遍历、后序遍历。
常规题
0.基础数据准备
- 二叉树的基本结构
/**
* @author 欢迎关注【编程开发分享者】公众号,领取新人编程大礼包
* @Date 2020/12/18 18:31
* @Description
*/
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
TreeNode(int x) { val = x; }
}
- 准备一个链表,作为二叉树的每个节点的值,如果值为空,则使用
null
表示
/**
* 构建一个链表
*
* @return
*/
public LinkedList<String> generateList() {
String[] array = {"3", "8", "7", null, null, "9", null, null, "5", null, "1"};
LinkedList<String> linkedList = new LinkedList<>(Arrays.asList(array));
return linkedList;
}
1.将链表转换成二叉树
如果没有做特殊要求,将链表转换成二叉树可以有很多种办法,你想怎么转就怎么转。
第一种:按照根、左、右的顺序构建
我们可以采用先序的方法,按照根节点、左节点、右节点的顺序依次添加。如下图所示,红色节点是按照顺序进行增加节点。
对应的代码如下,主要采用了递归的思想,每次先取出链表的头结点,然后插入到左节点和右节点中。
/**
* 先序生成一个树,根节点、左节点、右节点的顺序
*
* @param list
* @return
*/
public TreeNode generateTree(LinkedList<String> list) {
TreeNode node;
if (list == null || list.size() == 0 || list.isEmpty()) return null;
String val = list.removeFirst();
if (val != null) {
node = new TreeNode(Integer.parseInt(val));
node.left = generateTree(list);
node.right = generateTree(list);
return node;
}
return null;
}
第二种:层序构建(这种方式比较常用):
按照层次的顺序依次添加,为null
的节点是不能添加左右节点的,看效果图。
这种方式是最常用的构建二叉树的方式,学过二叉树知识的同学们应该都知道,确定一棵二叉树,最少需要两种遍历的方式(先序+中序或者后序+中序)。所以第一种方式并不能唯一的确定一个二叉树。这种层序的构建是可以做到的,虽然看着代码很多,但其实非常好理解,其核心思想是,从链表中取值封装成TreeNode
对象,放入到队列中,然后从队列中取出TreeNode,判断一下是不是要给其添加左右节点。对应的代码如下,
/**
* 先序生成一个树,层序
* 使用一个队列来保存已经遍历过的节点。
*
* @param list
* @return
*/
public TreeNode generateTreeLayer(LinkedList<String> list) {
if (list == null && list.size() == 0) return null;
Queue<TreeNode> queue = new LinkedList<>();
//取出第一个值,封装成TreeNode后放入到队列中
TreeNode root = new TreeNode(Integer.parseInt(list.removeFirst()));
queue.add(root);
while (queue != null && queue.size() != 0) {
//取出队列中的第一个值
TreeNode node = queue.poll();
//给当前节点添加左节点
if (list != null && list.size() != 0) {
String val = list.removeFirst();
if (val != null) {
node.left = new TreeNode(Integer.parseInt(val));
queue.add(node.left);
}
}
//给当前节点添加右节点
if (list != null && list.size() != 0) {
String val = list.removeFirst();
if (val != null) {
node.right = new TreeNode(Integer.parseInt(val));
queue.add(node.right);
}
}
}
return root;
}
小小练习题
学会了如何构建一个二叉树我们就可以试着做一下力扣的第297道题:传送门,这虽然是一道hard题,但不要被其表象所迷惑,你可以的!这道题是要对一颗二叉树进行序列化和反序列化,通俗的讲就是将一棵二叉树转换成一个字符串,然后在通过字符串可以转换成二叉树。
//思路:将树序列化成一个字符串,如果节点为空,则用#号表示
public class Q297 {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
String result = "";
while (queue != null && queue.size() != 0) {
TreeNode node = queue.poll();
if (node.val != -9999999) {
result = result + node.val + ",";
} else {
result = result + "null,";
continue;
}
if (node.left != null) {
queue.add(node.left);
} else {
queue.add(new TreeNode(-9999999));
}
if (node.right != null) {
queue.add(node.right);
} else {
queue.add(new TreeNode(-9999999));
}
}
return result;
}
// Decodes your encoded data to tree. [1,2,3,null,null,4,5]
public TreeNode deserialize(String data) {
if (data == null) return null;
String[] datas = data.split(",");
LinkedList<String> linkedList = new LinkedList<>(Arrays.asList(datas));
if (linkedList == null || linkedList.size() == 0) return null;
TreeNode root = new TreeNode(Integer.parseInt(linkedList.removeFirst()));
Queue<TreeNode> queue = new LinkedList();
queue.add(root);
while (queue != null && queue.size() != 0) {
TreeNode node = queue.poll();
if (linkedList != null && linkedList.size() != 0) {
String val = linkedList.removeFirst();
if (!val.equals("null")) {
node.left = new TreeNode(Integer.parseInt(val));
queue.add(node.left);
}
}
if (linkedList != null && linkedList.size() != 0) {
String val = linkedList.removeFirst();
if (!val.equals("null")) {
node.right = new TreeNode(Integer.parseInt(val));
queue.add(node.right);
}
}
}
return root;
}
@Test
public void Test() {
String data = "1,2,3,null,null,4,5";
TreeNode root = deserialize(data);
TreeOperation.show(root);
String result = serialize(root);
System.out.println(result);
}
}
测试结果如下:第一个红框是我们的原始数据,第二个红框是序列化之后的数据,我们发现比原始数据多了四个null
,这其实是无关紧要的,这一道题考察的不是序列化结果要跟原始数据一样,只要将二叉树变成字符串格式,然后在原封不动的变回来即可。
2.对二叉树进行遍历
对二叉树主要有三种遍历的方式:先序遍历、中序遍历和后序遍历。主要的区别就是对于根节点、左节点、右节点的打印顺序不同,使用了递归的思想,代码如下:
/**
* 对二叉树进行先序遍历
*
* @param node
*/
public void preorderTraverse(TreeNode node) {
if (node == null) return;
System.out.print(node.val+" ");
preorderTraverse(node.left);
preorderTraverse(node.right);
}
/**
* 对二叉树进行中序遍历
*
* @param node
*/
public void inorderTraverse(TreeNode node) {
if (node == null) return;
inorderTraverse(node.left);
System.out.print(node.val+" ");
inorderTraverse(node.right);
}
/**
* 对二叉树进行后序遍历
*
* @param node
*/
public void postorderTraverse(TreeNode node) {
if (node == null) return;
postorderTraverse(node.left);
postorderTraverse(node.right);
System.out.print(node.val+" ");
}
不难发现,其实只是打印的先后顺序不一样,代码结构一点没变,至于打印的有什么不一样,我们来测试一下:
工具
为了方便测试,大家可以将二叉树以树形图的形式打印出来,更加直观的看到自己生成的二叉树是否正确,我参考的是这一篇文章中的代码:https://www.cnblogs.com/liulaolaiu/p/11744409.html,感谢分享。
// TreeOperation.java
public class TreeOperation {
/*
树的结构示例:
1
/ \
2 3
/ \ / \
4 5 6 7
*/
// 用于获得树的层数
public static int getTreeDepth(TreeNode root) {
return root == null ? 0 : (1 + Math.max(getTreeDepth(root.left), getTreeDepth(root.right)));
}
private static void writeArray(TreeNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
// 保证输入的树不为空
if (currNode == null) return;
// 先将当前节点保存到二维数组中
res[rowIndex][columnIndex] = String.valueOf(currNode.val);
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth) return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
if (currNode.left != null) {
res[rowIndex + 1][columnIndex - gap] = "/";
writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
if (currNode.right != null) {
res[rowIndex + 1][columnIndex + gap] = "\\";
writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
}
}
public static void show(TreeNode root) {
if (root == null) System.out.println("EMPTY!");
// 得到树的深度
int treeDepth = getTreeDepth(root);
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth];
// 对数组进行初始化,默认为一个空格
for (int i = 0; i < arrayHeight; i ++) {
for (int j = 0; j < arrayWidth; j ++) {
res[i][j] = " ";
}
}
// 从根节点开始,递归处理整个树
// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
writeArray(root, 0, arrayWidth/ 2, res, treeDepth);
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line: res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i ++) {
sb.append(line[i]);
if (line[i].length() > 1 && i <= line.length - 1) {
i += line[i].length() > 4 ? 2: line[i].length() - 1;
}
}
System.out.println(sb.toString());
}
}
}