关于二叉树的常见题型

一、二叉树相关概念

1.1 基本术语

  • 结点的度:一个结点的子结点的个数称为结点的度。
  • 树的度:树中结点的最大度数为树的度
  • 树的深度(高度):树中结点的最大层数,从1开始。

1.2 二叉树分类

  • 满二叉树:一颗高度为h,并且含有2^h-1个结点的二叉树称为满二叉树。即树中每一层都含有最多的节点。除叶子节点每个节点的度都为2。
  • 完全二叉树:当高度为h,具有n个结点的二叉树,与高度为h的满二叉树结点编号完全对应时,即为完全二叉树。(若存在度为1的节点,只能有一个,且只有左孩子无右孩子)
  • 二叉排序树:左子树上所有的结点的关键字均小于根节点的关键字,右子树上所有节点的关键字均大于根结点的关键字。(中序遍历可得到递增序列)
  • 平衡二叉树:树上任一结点的左子树和右子树深度之差不超过1。
  • 哈夫曼树:在含有n个带权叶子节点的二叉树中,其中带权路径长度最小的二叉树为哈夫曼树(最优二叉树)—哈夫曼编码
  • 红黑树:红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是查找树。最重要的性质,从根节点到叶子节点的最长的路径不多于最短路径的长度的2倍。红黑树插入、查找、删除的时间复杂度都为O(logn)

1.3 二叉树的几条重要性质:

  1. 非空二叉树上叶子节点的个数等于度数为2的节点数+1
  2. 结点i所在的层次(深度)为
    (logn)+1
  3. N个节点的二叉树的高度为(logn)+1或是log(n+1)

1.4 二叉树的常见面试题

注意事项:

  • 根节点是否为空(递归结点是否为null)
  • 是否只存在根节点(一个节点)
  • 二叉树通常使用递归,清楚递归结束的条件。例如找路径时是遇到叶子节点停止,大多数是当前结点为null

题目汇总

  1. 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)
  2. 根据中序遍历序列和前序遍历序列(或后序遍历)重建二叉树。
  3. 求二叉树的所有路径(根到叶子节点)
  4. 判断二叉树中是否存在一条路径使得节点之和为给定的值。
  5. 求二叉树的最大深度、最小深度
  6. 求二叉树第k层的节点个数(层次遍历:队列)
  7. 求二叉树中叶子节点的个数
  8. 交换二叉树的左右孩子
  9. 判断两棵二叉树是否相同(结构和值都相等)
  10. 判断二叉树是否为完全二叉树
  11. 判断二叉树是否为平衡二叉树
  12. 求二叉树的镜像
  13. 求二叉树中节点的最大距离
  14. 哈夫曼树的构建
  15. 将二叉查找树变为有序的双向链表
  16. 求二叉树中两个节点的最低公共祖先节点
  17. 输入两棵二叉树A和B,判断B是否为A的子树

详细代码

  1. 二叉树的遍历:前序遍历、中序遍历、后序遍历、层次遍历(队列)

前序遍历:

//递归:前序遍历
	public void preOrder(Node node){
		if(node!=null){
			System.out.print(node.getPerson().getKey()+"\t");
			preOrder(node.getLeftChild());
			preOrder(node.getRightChild());
		}
	}

	//非递归:前序遍历
	private void preOrder_2(){
		Node curNode = rootNode;
		while(curNode!=null){
			//打印当前节点
			System.out.print(curNode.getPerson().getKey()+"\t");
			//入栈
			stack.push(curNode);
			//指向左子节点
			curNode = curNode.getLeftChild();
			//查找最左边的子节点
			while(curNode == null&&!stack.isEmpty()){
				curNode = stack.peek();//取栈顶元素
				stack.pop();//出栈
				curNode = curNode.getRightChild();
			}
		}
	}

中序遍历:

<span style="white-space:pre">	</span>//递归:中序遍历
	public void midOrder(Node node){
		if(node!=null){
			midOrder(node.getLeftChild());
			System.out.print(node.getPerson().getKey()+"\t");
			midOrder(node.getRightChild());
		}
	}
	//非递归:中序遍历(左根右)
	public void midOrder_2(){
		Node curNode = rootNode;
		while(curNode!=null||!stack.isEmpty()){
			if(curNode.getLeftChild()!=null){
				stack.push(curNode);
				curNode = curNode.getLeftChild();
			}else {
				//打印最左端的节点
				System.out.print(curNode.getPerson().getKey()+"\t");
				curNode = curNode.getRightChild();//指向右子节点
				while(curNode == null&&!stack.isEmpty()){
					curNode = stack.peek();
					System.out.print(curNode.getPerson().getKey()+"\t");
					stack.pop();
					curNode = curNode.getRightChild();
				}
			}
		}
	}

后序遍历:

//递归:后序遍历
	public void behOrder(Node node){
		if (node!=null) {
			behOrder(node.getLeftChild());
			behOrder(node.getRightChild());
			System.out.print(node.getPerson().getKey()+"\t");
		}
	}
	//非递归:后序遍历(左根右)
	public void behOrder_2(){
		Node curNode = rootNode;
		Node preNode = null;
		//先将根入栈
		stack.push(curNode);
		while(!stack.isEmpty()){
			curNode = stack.peek();//当前节点设置为栈顶节点
			if(curNode.getLeftChild()==null&&curNode.getRightChild()==null
			||(preNode!=null&&(curNode.getLeftChild()==preNode||curNode.getRightChild()==preNode))){
				//当前节点无左右节点,或者有左节点或右节点,但已经被访问过
				//则直接输出该节点,将其出栈,将其设为上一个访问的节点
				System.out.print(curNode.getPerson().getKey()+"\t");
				stack.pop();
				preNode = curNode;//已被访问过
			}else {
				//如果不满足上面两种情况,则将其右孩子左孩子依次入栈(先右节点再左节点)
				if (curNode.getRightChild()!=null) {
					stack.push(curNode.getRightChild());
				}
				if(curNode.getLeftChild()!=null){
					stack.push(curNode.getLeftChild());
				}
			}
		}
	}

层次遍历:

<span style="white-space:pre">	</span>public int levelOrder(TreeNode root,int k) {
		Queue  queue = new Queue(1024);
        List<List<Integer>> list = new ArrayList<List<Integer>>();
		if(root==null){
			return 0;
		}

        queue.push(root);
        //每层节点集
        List<Integer> level = new ArrayList<Integer>();
        //每层的节点个数
        int number = 0;
        int levelCount = 0;
        do{
        	Queue  queue2 = new Queue(1024);
        	while(!queue.isEmpty()){
            	//取队首节点
            	TreeNode head = queue.peek();
            	level.add(head.val);
            	number++;//结点个数加1
            	//弹出队头
            	queue.pop();

            	if(head.left!=null){
            		queue2.push(head.left);
            	}
            	if(head.right!=null){
            		queue2.push(head.right);
            	}
            }
        	levelCount++;//层级加1
        	if(levelCount==k){
        		return number;
        	}

        	list.add(level);
        	level = new ArrayList<Integer>();
        	number = 0;
        	queue = queue2;
        }while(!queue.isEmpty());
        return number;
    }

	public class Queue {
		/**
		 * 实现队列
		 */
		int first,last,maxSize;
		TreeNode[] queue;
		public Queue(int size){
			first = last = -1;
			maxSize = size;
			queue = new TreeNode[maxSize];
		}
		public boolean isEmpty(){
			if(first==-1){
				return true;
			}else {
				return false;
			}
		}
		public boolean isFull(){
			if(first==(last+1)%maxSize){
				return true;
			}else {
				return false;
			}
		}
		//push
		public boolean push(TreeNode num){
			if(this.isFull()){
				System.out.println("queue is full!");
				return false;
			}if(this.isEmpty()){
				first = last = 0;
			}else{
				last = (last+1)%maxSize;
			}
			queue[last] = num;

			return true;
		}

		public TreeNode pop(){
			TreeNode num = queue[first];
			if(this.isEmpty()){
				queue = new TreeNode[maxSize];
				return null;
			}
			if(first==last){
				//到达队尾,清空数组
				queue = new TreeNode[maxSize];
				first = last = -1;
				return num;
			}

			first=(first+1)%maxSize;
			return num;
		}

		public TreeNode peek(){
			if(first==-1) return null;
			else return queue[first];
		}
	}
  1. 根据中序遍历序列和前序遍历序列(或后序遍历)重建二叉树。
  2. 求二叉树的所有路径(根到叶子节点)
  3. 判断二叉树中是否存在一条路径使得节点之和为给定的值。
  4. 求二叉树的最大深度、最小深度
  5. 求二叉树第k层的节点个数(层次遍历:队列)
  6. 求二叉树中叶子节点的个数
  7. 交换二叉树的左右孩子
  8. 判断两棵二叉树是否相同(结构和值都相等)

  9. 判断二叉树是否为完全二叉树
  10. 判断二叉树是否为平衡二叉树
  11. 求二叉树的镜像
  12. 求二叉树中节点的最大距离
  13. 哈夫曼树的构建
  14. 将二叉查找树变为有序的双向链表
  15. 求二叉树中两个节点的最低公共祖先节点
  16. 输入两棵二叉树A和B,判断B是否为A的子树
时间: 2024-08-10 01:55:06

关于二叉树的常见题型的相关文章

栈和队列常见题型(java版)

直接上干货..... 栈和队列常见题型: 实现栈和实现队列. 两个栈实现一个队列. 设计栈,使得pop,push和min时间复杂度为O(1). 滑动窗口的最大值. 栈的进出序列. 实现栈和实现队列 主要包括:栈,队列,循环队列. package com.sywyg; /** * 实现栈 * 数组应该是Object类型的 * 注意top的设置影响出栈和获取栈顶元素. * size也可以用top代替 */ class MyStack<E>{ // 栈元素个数 private int size; /

链表常见题型(java版)

直接上干货..... 链表常见题型: 找到单链表的倒数第k个节点. 删除单链表中的某个结点(O(1)). 反转链表. 两个链表的第一个公共结点. 有环链表返回环路的开头节点(及判断是否有环). 合并两个排序的链表. 删除链表中重复的结点. 先给出链表的定义: /** * 单链表定义 */ public static class Node<E>{ private E element;//节点保存的元素 private Node<E> next;//指向下一个节点的引用 public

java实现二叉树的常见操作

本文转自:红客联盟 解释:程序调用自身的编程技巧叫做递归. 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量.递归的能力在于用有限的语句来定义对象的无限集合. 递归的三个条件: 边界条件 递归前进段 递归返回段 当边界条件不

栈和队列的常见题型

一.常见题型如下: 1. 实现一个栈,要求实现Push(出栈).Pop(入栈).Min(返回最小值的操作)的时间复杂度为O(1) 2. 使用两个栈实现一个队列 3. 使用两个队列实现一个栈 4. 元素出栈.入栈顺序的合法性.如入栈的序列(1,2,3,4,5),出栈序列为(4,5,3,2,1) 5. 一个数组实现两个栈. 二.解法分析及示例代码 1.第一题前两种操作Push(出栈).Pop(入栈)比较好实现,也是栈中比较基本的操作,题目没有明确要求所以我们可以直接用STL容器,进行个封装即可,st

有关链表的常见题型

一.线性表基本概念 1.1 顺序存储-顺序表: 线性表的顺序存储又称为顺序表,用一组地址连续的存储单元,一次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素物理位置也相邻. 优点:可以进行随机访问,只要知道下标索引即可在O(1)时间内找到指定元素. 缺点:插入和删除需要移动大量元素. 基本操作:插入.删除.查找(二分查找).排序(归并排序)下一篇重点讲关于数组的常见题型 1.2 链式存储-单链表.双链表.循环链表 单链表:每个链表节点除了存放自身信息之外,还携带一个指向后继的指针.只能从头节

重温数据结构:二叉树的常见方法及三种遍历方式 Java 实现

读完本文你将了解到: 什么是二叉树 Binary Tree 两种特殊的二叉树 满二叉树 完全二叉树 满二叉树 和 完全二叉树 的对比图 二叉树的实现 用 递归节点实现法左右链表示法 表示一个二叉树节点 用 数组下标表示法 表示一个节点 二叉树的主要方法 二叉树的创建 二叉树的添加元素 二叉树的删除元素 二叉树的清空 获得二叉树的高度 获得二叉树的节点数 获得某个节点的父亲节点 二叉树的遍历 先序遍历 中序遍历 后序遍历 遍历小结 总结 树的分类有很多种,但基本都是 二叉树 的衍生,今天来学习下二

二叉树的常见操作

[输出二叉树中的叶子结点] 无论前序.中序.后序遍历,叶子结点的输出顺序都是一样的吗?都是一样的,输出顺序为:从树的左边到右边叶子!!在二叉树的遍历算法中增加检测结点的“左右子树是否都为空”. 1 void PreOrderPrintLeaves(BinTree Bt) 2 { 3 if(Bt) 4 { 5 if(Bt->Left == NULL && Bt->Right == NULL) 6 printf("%c ", Bt->Data); 7 Pr

4.单向链表的常见题型

本篇主要是单向链表题型的实战,比如反转单向链表.查找单向链表的中间节点.判断一个链表是否有环.合并两个有序链表.判断一个单向链表是否是回文链表. /** * Node为链表结点对象 */ class Node { public Integer data; public Node next; public Node(Integer data){ this.data = data; } } public class LinkedListUtil { /** * 反转一个单向链表 * 思路:申请两个变

JS中for循环的常见题型

for循环示例: // 1-10000以内的完数 // 完数:因子之和相加等于这个数 // 例如:6的因子为1,2,3:1+2+3=6 let i ,j; let sum ; for(i=1;i<=10000;i++){ sum = 0; for(j=1;j<i;j++){ if(i%j == 0){ sum +=j; } } if(i == sum){ console.log(i) } } // 让用户输入行数,使用for循环嵌套打出正着的星星出来,行数等于用户输入的数字 let readl