目录
一.树形结构
1.1概念
树是一种非线性的数据结构,是由n(n>=0)个有限结点组成一个具有层次关系的集合。
1.2树的术语
- 结点的度:一个结点拥有子树的个数称为该节点的度;如下图,A的度为6.
- 树的度:一棵树中,所有结点度的最大值称为树的度;如下图,树的度为6.
- 叶子结点或终端结点:度为0的结点称为叶子结点;如下图,B,C,H,I,P,D等都为叶子结点。
- 双亲节点或父结点:若一个结点含有子节点,则这个结点称为其子节点的父节点;如下图,D是H的父结点。
- 孩子节点或子结点:一个节点含有的子树的根结点称为该节点的子节点;如下图:H是D的子节点。
- 根结点:一棵树中,没有双亲节点的节点;如下图,A是根节点。
- 结点的层次:从根开始,根为第一层,根的子节点为第二层,以此类推。
- 树的高度或深度:树中节点的最大层次;如下图,树的高度为4.
- 森林:由m(m>=0)棵互不相交的树组成的集合称为森林。
1.3树的表示形式
双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟法
class Node {
int value; // 树中存储的数据
Node firstChild; // 第一个孩子引用
Node nextBrother; // 下一个兄弟引用
}
二.二叉树
2.1概念
二叉树是节点的一个有限集合:
1.或者为空;
2.或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
注意:
1.二叉树不存在度大于2的结点
2.二叉树有左右之分,次序不能颠倒,因此二叉树是有序树
二叉树的5种基本形态:
2.2二叉树的性质:
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^{i-1}(i>0)个节点
- 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^{k}-1(k>=0)
- 对任何一棵二叉树,如果其叶结点个数为n0,度为2的非叶结点个数为n2,则有n0=n2+1
- 具有n个结点的完全二叉树的深度k为log2(n+1)上取整
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有: 若i>0,双亲序号:(i-1)/2i=0,i为根结点编号,无双亲结点
若2i+1<n,左孩子序号为:2i+1,否则无左孩子
若2i+2<n,右孩子序号为2i+2,否则无右孩子
2.3两种特殊的二叉树
1.满二叉树
一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树,即若一棵二叉树的层数为k且节点总数为2^{k}-1,它就是满二叉树。
2.完全二叉树
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。
满二叉树是一种特殊的完全二叉树
2.3二叉树的存储
二叉树的存储结构分为:顺序存储和链式存储。
1.链式存储
链式存储类似于链表,通过一个个结点引用起来常见的表示方式有二叉和三叉表示方式。
// 孩子表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; //当前节点的根节点
}
2.顺序存储
二叉树的顺序存储其实就是用数组,按照层次顺序依次存储。
2.4二叉树的基本操作
1.二叉树的构建
我们进行操作前,需要先构造一棵二叉树,按照下图,构建一棵满二叉树,如下:
class TreeBinary{
class treeNode{
char val;
treeNode left;
treeNode right;
public treeNode(char val){
this.val=val;
}
}
public treeNode root;//根节点
public treeNode creatBinaryTree(){
treeNode A=new treeNode('A');
treeNode B=new treeNode('B');
treeNode C=new treeNode('C');
treeNode D=new treeNode('D');
treeNode E=new treeNode('E');
treeNode F=new treeNode('F');
treeNode H=new treeNode('H');
A.left=B;
A.right=C;
B.left=D;
B.right=E;
C.left=F;
C.right=H;
return A;
}
}
2.二叉树的遍历(递归&&非递归实现)
遍历就是沿着某条搜索路径,依次对树中的每个结点进行有且仅有一次的访问。
二叉树的遍历有前中后遍历
主要应用:打印结点内容。
2.1前序遍历
前序遍历的顺序:根节点-->左子树-->右子树(根左右)
前序遍历打印流程图
代码实现(递归)
public void preOder(treeNode root){
if(root==null){//节点为空返回
return;
}
//打印节点
System.out.print(root.val+" ");
//进入左子树
preOder(root.left);
//进入右子树
preOder(root.right);
}
/**
* 使用栈实现二叉树的前序遍历。
* @param root 二叉树的根节点
*/
public static void preOder1(treeNode root){
Stack<treeNode> stack=new Stack<>();
treeNode cur=root;
// 遍历二叉树直至所有节点都被访问
while(cur!=null||!stack.isEmpty()){
// 将当前节点及其左子节点全部入栈
while(cur!=null){
stack.push(cur);
System.out.print(cur.val+" ");
cur=cur.left;
}
// 弹出栈顶节点,并访问其右子树
treeNode top=stack.pop();
cur=top.right;
}
}
前序遍历递归过程图:
前序遍历是根左右,所以当打印完根节点后,会进入左子树,以此类推,当走到null时,会返回到前一个根节点,再进入其右子树。
2.2中序遍历
中序遍历的顺序:左子树-->根节点-->右子树(左根右)
(递归)
public void inOder(treeNode root){
if(root==null){
return;
}
//进入左子树
inOder(root.left);
//打印节点
System.out.print(root.val+" ");
//进入右子树
inOder(root.right);
}
(非递归)
/**
* 使用栈实现二叉树的中序遍历。
* @param root 二叉树的根节点
*/
public static void inOder1(treeNode root){
Stack<treeNode> stack=new Stack<>();
treeNode cur=root;
// 遍历二叉树直至所有节点都被访问
while(cur!=null||!stack.isEmpty()){
// 将当前节点及其左子节点全部入栈
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
// 弹出栈顶节点,并访问其值和右子树
treeNode top=stack.pop();
System.out.print(top.val+" ");
cur=top.right;
}
}
中序遍历打印流程图:
2.3.后序遍历
后序遍历顺序:左子树-->右子树-->根节点(左右根)
(递归)
public void postOder(treeNode root){
if(root==null){
return;
}
//进入左子树
postOder(root.left);
//进入右子树
postOder(root.right);
//打印节点
System.out.print(root.val+" ");
}
(非递归)
/**
* 使用栈实现二叉树的后序遍历。
* @param root 二叉树的根节点
*/
public static void postOder1(treeNode root) {
Stack<treeNode> stack = new Stack<>();
treeNode cur = root;
treeNode prev = null;
// 遍历二叉树直至所有节点都被访问
while (cur != null || !stack.isEmpty()) {
// 将当前节点及其左子节点全部入栈
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
// 获取栈顶节点
treeNode top = stack.peek();
// 如果右子节点为空或已经访问过,则弹出栈顶节点并访问
if (top.right == null || prev == top.right) {
stack.pop();
System.out.print(top.val + " ");
prev=top;
} else {
// 否则,转向右子节点
cur = top.right;
}
}
}
后序遍历打印流程图:
2.4.层序遍历
层序遍历:从左往右、从上往下依次遍历
public void levelOder(treeNode root){
Queue<treeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
treeNode node=queue.poll();
System.out.print(node.val+" ");
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
实现完前中后及层次遍历的代码,我们可以运行一遍是否如我们所想的一样
public static void main(String[] args) {
TreeBinary treeBinary=new TreeBinary();
TreeBinary.treeNode root=treeBinary.creatBinaryTree();
System.out.print("前序遍历:");
treeBinary.preOder(root);
System.out.println();
System.out.print("中序遍历:");
treeBinary.inOder(root);
System.out.println();
System.out.print("后序遍历:");
treeBinary.postOder(root);
}
3.求节点个数
对于求节点个数
1:我们可以借助上述中的前序遍历(前序遍历过程中必定会经过每一个节点)的基础上进行定义一个成员变量size进行计算,在遍历的过程中,只要根节点不为空,size就加1。
2:求节点个数,也就是左子树的节点个数+右子树的节点个数+1=整棵树的节点个数
法一:
/**
* 法一:定义一个变量,记录节点个数
*/
public int nodesize;
public int size1(treeNode root){
if(root==null){
return 0;
}
nodesize++;
size1(root.left);
size1(root.right);
return nodesize;
}
法二:
/**
* 法二:递归求节点个数
* @param root 根节点
* @return
*/
public int size(treeNode root){
if(root==null){
return 0;
}
//左子树的节点个数➕右子树的节点个数+1=整棵树的节点个数
return size(root.left)+size(root.right)+1;
}
4.求叶子节点个数
对于求叶子节点个数,我们知道叶子节点是没有左子树和右子树的,所以我们只需要在求节点个数的基础上加上一个条件:如果左子树为空且右子树也为空。
两种方法:
/**
*
* @param root
* @return 叶子节点个数
*/
public int leafCount(treeNode root){
if(root==null){
return 0;
}
if(root.left==null&&root.right==null){
return 1;
}
return leafCount(root.left)+leafCount(root.right);
}
/**
* 递归求叶子节点个数
*/
public int leaffnode;
public int getLeafnode(treeNode root) {
if(root==null){
return 0;
}
if(root.left==null&&root.right==null){
leaffnode++;
}
getLeafnode(root.left);
getLeafnode(root.right);
return leaffnode;
}
5.求第k层的节点个数
我们依旧是以上面的二叉树为例:
假如我们现在要求第3层的节点个数,我们从图中可以知道,第3层的节点个数为4.
那么如果我们求第3层的节点个数,其实也就是求第2层节点的左子树和右子树之和。假设k为层数,我们要求第k层的节点数,也就是求(k-1)层的左子树和右子树之和。
代码如下:
/**
* 获取第k层的节点个数
*/
public int getKLevelNodeCount(treeNode root,int k){
if(root==null){
return 0;
}
if(k==1){
return 1;
}
return getKLevelNodeCount(root.left,k-1)
+getKLevelNodeCount(root.right,k-1);
}
6.求值是否存在二叉树中
我们可以利用前面几种遍历,如果找到对应的节点则直接返回,否则进入左子树和右子树中进行查找。
public treeNode find(treeNode root, char val) {
// 如果根节点为空,说明树为空,无法继续查找,直接返回null
if (root == null) {
return null;
}
// 如果根节点的值等于要查找的值,直接返回根节点
if (root.val == val) {
return root;
}
// 在左子树中递归查找指定值的节点
treeNode node = find(root.left, val);
// 如果在左子树中找到了指定值的节点,直接返回该节点
if (node != null) {
return node;
}
// 在右子树中递归查找指定值的节点
node = find(root.right, val);
// 如果在右子树中找到了指定值的节点,直接返回该节点
if (node != null) {
return node;
}
// 如果左右子树中都没有找到指定值的节点,返回null
return null;
}
7.判断一棵树是不是完全二叉树
判断一棵树是不是完全二叉树,我们可以利用队列,我们把根节点先放在队列中,在根节点出队的时候,把根节点的左右子树都放到队列中。在入队的过程中,我们可以判断是否遇到null的情况,如果遇到null,说明我们已经入队结束。
当我们入完队之后,我们要判断队列还存在的节点是否都为空,如果有一个不为空,说明这棵树不是完全二叉树。
/**
* 检查二叉树是否为完全二叉树。
* 完全二叉树定义:除最后一层外,每一层的节点数都达到最大值,并且最后一层的节点尽量都集中在左边。
*
* @param root 二叉树的根节点
* @return 如果二叉树是完全二叉树,则返回true;否则返回false。
*/
public boolean isCompleteTree(TreeNode root) {
// 使用队列来进行层次遍历
Queue<TreeNode> queue = new LinkedList<>();
// 根节点为空,直接判断为完全二叉树
if (root == null) {
return true;
}
// 将根节点入队
queue.offer(root);
// 遍历队列中的所有节点,直到队列为空
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
// 遇到空节点,说明当前层已经遍历完毕,跳出循环
if (cur == null) {
break;
}
// 左右子节点入队
queue.offer(cur.left);
queue.offer(cur.right);
}
// 继续遍历队列中剩余的节点,检查是否存在非空节点,判断是否为完全二叉树
while (!queue.isEmpty()) {
TreeNode node = queue.peek();
// 如果存在非空节点,则不是完全二叉树
if (node != null) {
return false;
} else {
// 移除队列头部的空节点
queue.poll();
}
}
// 遍历结束,队列为空,说明二叉树是完全二叉树
return true;
}
相关的二叉树题目:
若有不足之处,欢迎指正~