数据结构笔记四
文章目录
前言
这一节是二叉树的代码,主要包括二叉树的递归遍历,非递归遍历,层序遍历,最大宽度等等
二叉树的前中后序遍历
递归遍历
原理很简单代码如下
public class TreeNode {//先放上基本的树结构
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public void PreOrder(TreeNode root,List<Integer> list){//前序:头左右
//运行到这里第一次经过这个节点
if(root==null)return;
list.add(root.val);
PreOrder(root.left,list);
//运行到这里第二次经过这个节点,从左树返回
PreOrder(root.right,list);
//运行到这里第三次经过这个节点,从右树返回
}
public void InOrder(TreeNode root,List<Integer> list){//中序:左头右
//运行到这里第一次经过这个节点
if(root==null)return;
PreOrder(root.left,list);
//运行到这里第二次经过这个节点,从左树返回
list.add(root.val);
PreOrder(root.right,list);
//运行到这里第三次经过这个节点,从右树返回
}
public void PostOrder(TreeNode root,List<Integer> list){//后序:左右头
//运行到这里第一次经过这个节点
if(root==null)return;
PreOrder(root.left,list);
//运行到这里第二次经过这个节点,从左树返回
PreOrder(root.right,list);
//运行到这里第三次经过这个节点,从右树返回
list.add(root.val);
}
非递归遍历
先序遍历:准备一个栈,先把头节点放到栈里,然后是固定流程:
1.弹出一个节点并打印(如果此时栈空,没有元素可以弹出就结束)
2.先把右孩子入栈,再把左孩子入栈(如果有的话,因为从栈里弹出相当于逆序,所以要先右再左)
3.回到步骤一
(原理的话脑子里面自行模拟一下,把左右孩子入栈相当于是往下遍历,然后在栈里是逆序)
代码如下:
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if (root==null)return list;
Stack<TreeNode>stack=new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode a=stack.pop();
list.add(a.val);
if(a.right!=null)stack.push(a.right);
if(a.left!=null)stack.push(a.left);
}
return list;
}
后序遍历:
我们根据先序遍历可以得到头左右的顺序,那么按上面所写的代码,我左右孩子进栈的顺序调整一下就可以变成头右左的顺序,这时我不直接打印,我使用一个栈来将顺序颠倒就变成了左右头代码如下:
public List<Integer> PostorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if (root==null)return list;
Stack<TreeNode>stack=new Stack<>();
Stack<TreeNode>stack2=new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode a=stack.pop();
stack2.push(a);
if(a.right!=null)stack.push(a.right);
if(a.left!=null)stack.push(a.left);
}
while (!stack2.isEmpty()){
list.add(stack2.pop().val);
}
return list;
}
中序遍历:方法如下:
1.先将整颗树的左边界进栈
2.顺序弹出,但是在弹出的过程中如果该节点有右子树的话,在弹出该节点之后将该节点的右子树的整个左边界进栈
3.重复2过程直到栈空。
代码如下:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
if (root==null)return list;
Stack<TreeNode>stack=new Stack<>();
while (root!=null){//步骤一
stack.push(root);
root=root.left;
}
while (!stack.isEmpty()){//步骤二
TreeNode a=stack.pop();
list.add(a.val);
TreeNode b=a.right;
while (b!=null){
stack.push(b);
b=b.left;
}
}
return list;
}
二叉树的层序遍历
层序遍历也就是按照二叉树的结构一层一层的输出,实现的思路呢也就是和宽度优先遍历相似,使用队列来遍历,每一个节点进队列,到弹出的时候需要把这个节点的左右孩子也放到队列里面,先进先出,但是再次之外我们还需要几个变量来记录打印的信息
代码如下提供两种思路,一种是用额外的变量记录队列现在遍历到了第几层(记录该层的节点数,每循环一次就减一,当为0时也就说明这这一层遍历完了),一种是在每遍历完一层时先的到队列的长度,记录下下一层共有几个节点
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list=new ArrayList<List<Integer>>();
if (root==null)return list;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int num=1;//记录这一层有几个节点
while (!queue.isEmpty()){
int nextnum=0;//记录下一层有几个结点
List<Integer> tem=new ArrayList<>();
while (num!=0){
TreeNode a=queue.poll();
tem.add(a.val);
if(a.left!=null){
nextnum++;
queue.add(a.left);
}
if(a.right!=null){
nextnum++;
queue.add(a.right);
}
num--;
}
list.add(tem);
num=nextnum;
nextnum=0;
}
return list;
}
public List<List<Integer>> levelOrder(TreeNode root) {//第二种
List<List<Integer>> list=new ArrayList<List<Integer>>();
if(root==null){
return list;
}
Queue<TreeNode> queue=new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()){
List<Integer> res=new ArrayList<>();
int currentLeversize= queue.size();//在这里获得该层有几个节点
for(int i=1;i<=currentLeversize;i++){
TreeNode t=queue.poll();
res.add(t.val);
if(t.left!=null){
queue.add(t.left);
}
if(t.right!=null){
queue.add(t.right);
}
}
list.add(res);
}
return list;
}
二叉树的宽度、每一层的最大值
二叉树的宽度使用上一节的代码稍微改改就行
宽度只需要保持一个变量,当这一层的节点数大于这个值时更新
代码如下:
public int GetTreewidth(TreeNode root) {
if (root==null)return 0;
int width=1;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int num=1;//记录这一层有几个节点
while (!queue.isEmpty()){
int nextnum=0;
while (num!=0){
TreeNode a=queue.poll();
if(a.left!=null){
nextnum++;
queue.add(a.left);
}
if(a.right!=null){
nextnum++;
queue.add(a.right);
}
num--;
}
width=width<nextnum?nextnum:width;
num=nextnum;
nextnum=0;
}
return width;
}
每一层的最大值也是同理,在遍历这一层的节点时维持一个最大值即可
代码如下:
public List<Integer> largestValues(TreeNode root) {
List<Integer>list=new ArrayList<>();
if (root==null)return list;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int num=1;//记录这一层有几个节点
while (!queue.isEmpty()){
int nextnum=0,tem=Integer.MIN_VALUE;
while (num!=0){
TreeNode a=queue.poll();
tem=tem>a.val?tem:a.val;
if(a.left!=null){
nextnum++;
queue.add(a.left);
}
if(a.right!=null){
nextnum++;
queue.add(a.right);
}
num--;
}
list.add(tem);
num=nextnum;
nextnum=0;
}
return list;
}
判断一棵树是否为搜索二叉树
搜索二叉树:对于一棵树,他的左树上的节点都比他小,右树上的节点都比他大
那么根据搜索二叉树的特征我们可以使用中序遍历的方法,如果在遍历的过程中,每个节点的值都是严格递增的,那么这个树就是搜索二叉树,这个代码很简单,这里就不演示了
判断一棵树是否为完全二叉树
完全二叉树:完全二叉树是由满二叉树而引出来的,若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
判断的方法,使用宽度优先遍历,1.如果一个节点有右孩子但是没有左孩子就直接返回false 2.在1的前提下,如果出现了第一个左右孩子不双全的节点(左右孩子同时存在),那么接下来遍历到的每一个节点都要求是叶节点,原理的话建议自己画一棵树感觉一下。代码如下:
public boolean panduan(TreeNode root) {
if (root==null)return false;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode a=queue.poll();
if(a.left==null&&a.right!=null)return false;
if(a.left!=null&&a.right!=null){
queue.add(a.left);
queue.add(a.right);
}else break;
}
while (!queue.isEmpty()){
TreeNode a=queue.poll();
if(a.left!=null||a.right!=null) return false;
}
return true;
}
判断一棵树是否为平衡二叉树
平衡二叉树:树上的每一个树左右子树的高度差不超过一
判断方法:使用递归的思想,对于一颗二叉树我们可以抽象为包括根节点,左子树,右子树,那么根节点向左右子树询问是否为平衡二叉树,如果是,那么返回高度,如果不是就返回false,根节点得到左右子树的回复之后计算左右子树的高度差,如果符合高度差不查过1,那么就继续向根节点的上级节点返回高度,如果不符合,那么相应的也就返回false。代码如下:
public boolean isBalanced(TreeNode root) {
if(isBalanced1(root)<0)return false;
else return true;
}
public int isBalanced1(TreeNode root){
if (root==null)return 0;
int left=isBalanced1(root.left);
int right=isBalanced1(root.right);
if(left<0||right<0||Math.abs(left-right)>1)return -1;
else return Math.max(left,right)+1;
}
寻找一棵树上的两个节点的最低公共祖先
给一张网课里面的图片做示例吧
d和e的最低公共祖先是b,b和d的最低公共祖先是b
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||root==p||root==q) return root;
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
if(left!=null&&right!=null) return root;
return left!=null?left:right;
}
代码的原理和思路:我们使用递归函数可以从孩子节点得到消息,设我们要寻找公共祖先的两个节点为p和q,那么我们随机的从树上挑出一个节点,现在我们关注这个节点:假设p和q分别在他的两个子树上,而且在此之前没有其他的公共祖先,那么该节点肯定是返回自己,如果p,q已经有公共祖先了在这个节点的左子树或者右子树上,子树返回的是一个节点,对于该子树不必关注这个节点是pq还是公共祖先,只需要向上继续返回就行了,如果他的子树什么也不返回或者这个节点本身为空,返回值就为空,从前面的流程中我们发现只有左右子树同时有返回值才意味着pq的最近公共祖先就是这个节点,而且,对于每一个节点而已,返回的值要么是他自己,要么是空,要么就是子树传递的值。