前言:
我们平时写的关于树的算法都是直接写算法,就是比如写一个前序遍历就是
public void preOrder(TreeNode root)
{
//前序遍历
}
但是很少有写一个完整的测试用例的,即写一个函数按照图的样子创建一棵树,然后让这个函数返回根节点,然后就可以在主函数中定义一个树节点获取这个根节点,剩下的就是调用这些树的算法就好了。简而言之就是这篇博客不仅有常见的树的算法,还有真实的测试用例。
一、创建一棵树
首先就是定义树节点这个类
class TreeNode
{
Object data;
public TreeNode left;
public TreeNode right;
public TreeNode(Object data)
{
this.data=data;
this.left=null;
this.right=null;
}
}
1、首先讲最复杂情况,就是这个树的节点值五花八门,比如有数字(1 2 3),字母(h k D)还有可能有一些其他的乱七八糟的(*—/+?)这些都可以作为接节点值(所以前面把树节点的类型定义为Object),所以这样的情况下这些值是不可能放在数组里的(因为数组里的都要求同一类型),所以这种情况下把树节点的值放在字符串里,然后每个值用空格格开。
比如下面的这个树的节点值就可以用这个字符串表示:
String str="A - + 2 / 0 D 0 3 0 0 0 0 *";
解释为什么树的节点值没有0,但是字符串里确有0的,这是因为要按照完全二叉树的顺序写进字符串,按照完全二叉树的顺序如果在没有节点的地方就用0表示,这些0很关键,在后面的打印节点时候要判断。那为什么*后面的0没写进去,因为是按照完全二叉树的形式写,所以D是没有右节点的,所以不写最后一个0也是完全二叉树(这部分是完全二叉树的概念,理解这里对后面构造左右孩子有帮助。)
现在我们只是有了节点的值,还不是树节点的类型,那么接下来的思路是定义一个泛型是TreeNode类型的List,然后通过新建每一个树节点,然后把相应的节点值传给新建节点的方式把树节点都存在list中,然后在list中指明节点的左右孩子。完整代码如下(不看这些文字直接看代码也可以)
public class BianliTree {
public TreeNode create(String str)
{
List<TreeNode> list=new LinkedList<TreeNode>();
for(int i=0;i<str.split(" ").length;i++)
{
list.add(new TreeNode(str.split(" ")[i]));
}
for(int i=0;i<str.split(" ").length/2-1;i++)
{
list.get(i).left=list.get(2*i+1);
list.get(i).right=list.get(2*i+2);
}
//最后一个父节点,因为可能没有右孩子,所以单独拿出来处理。
int temp=str.split(" ").length/2-1;
list.get(temp).left=list.get(2*temp+1);
//如果节点数量是奇数,才有右孩子
if(str.split(" ").length%2==1)
{
list.get(temp).right=list.get(2*temp+2);
}
return list.get(0);
}
public void visit(TreeNode root)
{
if(!root.data.equals("0"))
{
System.out.print(root.data + " ");
}
}
public static void main(String[] args) {
BianliTree tree=new BianliTree();
String str=String str="A - + 2 / 0 D 0 3 0 0 0 0 *";
TreeNode root=tree.create(str);
}
}
(1)这样在主函数得到的这个root就是上面这棵树的根节点(A),以后就可以把这颗节点作为参数来进行我们最常见比如剑指OFFER里面的算法写法了。
(2)str.split(" “)得到的是将字符串按照空格分离把每一个字符作为一个字符串存入到字符串数组中,所以str.split(” “)得到的是一个字符串数组,比如str.split(” “)[0]是"A”。所以在Visit函数中进行判断的时候必须是!root.data.equals(“0”)。这也是为什么之前定义的树节点data类型是Object。
上面所说这是最复杂的建立树的情况了(我们要用下面的简单的)
2、因为我们一般做题书的节点值都是固定一样的,一般是数字或者字母。所以可以用数组来表示,就不用字符串了(由于类都一样,树节点的data类型就可以不定义为Object了)。这还不是最方便的情况了,最方便的情况是这棵树还是完全二叉树,那么这样的话,不但可以用数组表示,还可以不用填充0了,所以输出的时候也不需要Visit函数判断了。所以我们以后都用完全二叉树测试
这里声明一下:
无论这棵树是什么样的,
只是在构建书的方法上有简易,比如上面说的最复杂方法适合所有树构建。下面的这个简单就是针对完全二叉树。
但是在树的算法上是一样的,无论什么树。所以算法都一样,我们当然选择最简单的树构建方法来测试了。
我们测试用的完全二叉树如下:
下面给出创建树的代码以及三种遍历方式(递归与非递归)还有测试代码,剩下的关于图的算法就不写这么复杂了,就直接写像牛客网那样的算法函数了,至于测试,和下面这个一样。
package Tree;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
public class BianliTree {
//树节点类型的创建
class TreeNode
{
char data;
public TreeNode left;
public TreeNode right;
public TreeNode(char data)
{
this.data=data;
this.left=null;
this.right=null;
}
}
//输出节点值
public void visit(TreeNode root)
{
System.out.print(root.data + " ");
}
//创建节点
public TreeNode create(char[] arr)
{
List<TreeNode> list=new LinkedList<TreeNode>();
for(int i=0;i<arr.length;i++)
{
list.add(new TreeNode(arr[i]));
}
for(int i=0;i<arr.length/2-1;i++)
{
list.get(i).left=list.get(2*i+1);
list.get(i).right=list.get(2*i+2);
}
int index=arr.length/2-1;
list.get(index).left=list.get(2*index+1);
if(arr.length%2==1)
{
list.get(index).right=list.get(2*index+2);
}
return list.get(0);
}
//递归前序
public void preOrder(TreeNode root)
{
if(root==null)
{
return;
}
visit(root);
preOrder(root.left);
preOrder(root.right);
}
//非递归前序
public void preOrder1(TreeNode root)
{
Stack<TreeNode> stack=new Stack<>();
while(root!=null || !stack.empty())
{
while(root!=null)
{
visit(root);
stack.push(root);
root=root.left;
}
if(!stack.empty())
{
root=stack.pop();
root=root.right;
}
}
}
//递归中序
public void inOrder(TreeNode root)
{
if(root==null)
{
return;
}
inOrder(root.left);
visit(root);
inOrder(root.right);
}
//非递归中序
public void inOrder1(TreeNode root)
{
Stack<TreeNode> stack=new Stack<>();
while(root!=null || !stack.empty())
{
while(root!=null)
{
stack.push(root);
root=root.left;
}
if(!stack.empty())
{
root=stack.pop();
visit(root);
root=root.right;
}
}
}
//递归后序
public void postOrder(TreeNode root)
{
if(root==null)
{
return;
}
postOrder(root.left);
postOrder(root.right);
visit(root);
}
//非递归后序
public void postOrder1(TreeNode root)
{
Stack<TreeNode> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
int i = 1;
while(root != null || !stack1.empty())
{
while (root!= null)
{
stack1.push(root);
stack2.push(0);
root = root.left;
}
while(!stack1.empty() && stack2.peek() == i)
{
stack2.pop();
visit(stack1.pop());
}
if(!stack1.empty())
{
stack2.pop();
stack2.push(1);
root = stack1.peek();
root = root.right;
}
}
}
public static void main(String[] args) {
BianliTree tree=new BianliTree();
char[] arr= {'A','B','C','D','E','F','G','H','I','J'};
TreeNode root=tree.create(arr);
System.out.println("递归前序遍历:");
tree.preOrder(root);
System.out.println();
System.out.println("非递归前序遍历:");
tree.preOrder1(root);
System.out.println();
System.out.println("递归中序遍历:");
tree.inOrder(root);
System.out.println();
System.out.println("非递归中序遍历:");
tree.inOrder1(root);
System.out.println();
System.out.println("递归后序遍历:");
tree.postOrder(root);
System.out.println();
System.out.println("非递归后序遍历:");
tree.postOrder1(root);
}
}
二、树的层次遍历
思路:把头节点进队列,只要队列不为空,就把队列的头节点取出访问,并且如果头节点有左右孩子的话都入队列,然后再取出此时的队列头元素,访问,判断有无左右孩子,有的话入队列,一直循环到队列为空为止。
public void levelOrder(TreeNode root)
{
Queue<TreeNode> queue=new LinkedList<TreeNode>();
if(root==null)
return;
queue.add(root);
while(!queue.isEmpty())
{
TreeNode node=queue.poll();
visit(node);
if(node.left !=null)
queue.add(node.left);
if(node.right!=null)
queue.add(node.right);
}
}
三、求二叉树的高度,宽度和结点数量
1、高度:
递归,求出左子树高度,再求出右子树高度,然后取二者大的那一个+1。
public int Hight(TreeNode root)
{
int L,R;
if(root==null)
return 0;
L=Hight(root.left);
R=Hight(root.right);
return (L>R?L:R)+1;
}
2、数量:
递归,和高度一个思路,求出左子树的个数,右子树的个数,然后+1。
public int nums(TreeNode root)
{
if(root==null)
return 0;
return nums(root.left)+nums(root.right)+1;
}
3、树的层数和宽度(都是利用层次遍历)
public int Width(TreeNode root)
{
if(root==null)
return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int maxnums=1;
int len=0;//记录每层节点的个数
int layer=0;
while(!queue.isEmpty())
{
layer++;
len=queue.size();//当前层数的节点
while(len>0)//len>0说明此层还有节点没出队列
{
root=queue.poll();
if(root.left!=null)
queue.add(root.left);
if(root.right!=null)
queue.add(root.right);
len--;
}
maxnums=Math.max(maxnums, queue.size());
}
System.out.println("层数是:" + layer);
return maxnums;
}