二叉树-树形结构-天然的查找语义
目录
二叉树-树形结构-天然的查找语义
二叉树的主要难点
什么时候能用递归?
如何写递归程序:
一,为何要有树结构?
二,数据结构常用的树结构 logN
三,关于树的基础概念
概念
树与非树?
树中需要了解的概念
其余:
四,二叉树***
概念:
二叉树的特点:
两种特殊的二叉树
五,二叉树的性质
六,二叉树的存储方式
七,二叉树的遍历
遍历:
深度优先遍历
广度优先遍历
八,二叉树的其他基础操作
获取结点个数:
求一颗二叉树中叶子结点个数:
注意:
判断当前二叉树中是否含有寻找元素:
求二叉树的高度:
九,用代码实现一颗完整的二叉树,并实现其基础功能进行测试
二叉树的主要难点
二叉树的主要难点在于递归的使用。
什么时候能用递归?
1.大问题可以拆分为小问题
2.拆分后小问题和原问题除了规模不同,解决思路完全一样
3.存在递归的终止条件,拆分拆到底的条件
如何写递归程序:
千万不要纠结递归具体怎么运行的,要会使用递归的语义来解决问题。
一,为何要有树结构?
因为树结构可以高效的进行查找和搜索。
比如
电脑中的文件系统,就是一个树形结构。
为什么不用线性结构?
线性结构是构成文件逻辑上连续,成一条直线排列。如果检索特定的某个文件,就要遍历这个集合复杂度为O(n)。
而用树结构查找特定文件其时间复杂度实际上就是文件数的深度logN。
因此,将数据使用树形结构来存储后,再次进行检索或查找,效率比线性结构高得多!
二,数据结构常用的树结构 logN
(回溯算法解决树形问题时,也是logN级别;排序:nlogN的排序算法,堆排序、快速排序、归并排序)以后看到logN就要首先想到树结构
BST(二分搜索树)- 二叉树的元素查找
平衡二分搜索树 - AVL(严格平衡树),红黑树(非严格平衡)
堆
并查集
字符串 - 线段树;字典树(Trie)
三,关于树的基础概念
概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的具有层次关系的集合,把它叫做树是因为它看起来像一颗倒挂着的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
- 有一个特殊的节点,成为根节点(A),根节点没有前驱节点。
- 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1、T2、…….、Tm,其中每一个集合T又是一颗与树类似的子树,每棵子树的根节点有且只有一个前驱,可以有0个或多个后继。
- 树是递归定义的。
树与非树?
子树是不相交的。
除了根节点外,每个结点有且仅有一个父结点。
一颗N个结点的树有N-1条边。
上图中,图中每个单元称为节点,A,B都称为节点
树中需要了解的概念
a,节点的度:该节点含有的子树的个数就称为节点的度
A节点的度6
D节点的度1
F节点的度3
b,树的度:该树中最大的结点的度就是该树的度
上图中的树度为6,成为6叉树
c,叶子结点(终端节点):度为零的节点称为叶子结点
上图中的BCHIPQKLMN都属于叶子结点
d,对于A和B这两个结点而言
双亲结点/父节点 A
孩子节点/子节点 B
e,根节点:树中没有父节点的结点称为根节点,A就是根节点
f,节点的层次:从根节点开始计算,根节点是第一层,A的层次就是1,以此类推
g,树的高度:结点的最大层次,上图中树的高度就为4
其余:
- 非终端节点:度不为0的节点
- 兄弟节点:具有相同的父节点互称为兄弟节点
- 唐兄弟节点:父节点在同一层的节点
- 节点的祖先:从根到该节点所经分支的所有节点
- 以某节点为根的子树中任一节点都称为该节点的子孙
- 森林: 由 m ( m>=0 )棵互不相交的树的集合
四,二叉树***
概念:
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
1. 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。
2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。
两种特殊的二叉树
满二叉树:满二叉树中,所有非叶子结点的度都为2。
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果 一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
对于一颗二叉树来讲,
1,高度为k,则该二叉树最多有2^k-1个结点(最多的情况就是满二叉树)
2,层次为k,第k层最多有2^(k-1)个结点,上图中,第三层最多为2^(3-1) = 4
3,边长和节点个数关系
边长 = 结点个数 - 1
设度为0的节点个数为n0,度为1的结点个数为n1,度为2的结点个数为n2。
= > n0 = n2 + 1 (叶子结点的个数 = 度为2的结点个数 + 1)
设总的结点个数为n
= > 边长 = n - 1
完全二叉树:
实际上就是满二叉树缺了个右下角(只能缺叶子结点的最右边,不能中间有隔开空缺)不存在只有右子树没有左子树的情况
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全 二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
五,二叉树的性质
- 若规定 根节点的层数为 1 ,则一棵 非空二叉树的第 i 层上最多有 (i>0) 个结点
- 若规定只有 根节点的二叉树的深度为 1 ,则 深度为 K 的二叉树的最大结点数是 (k>=0)
- 对任何一棵二叉树 , 如果其 叶结点个数为 n0, 度为 2 的非叶结点个数为 n2, 则有 n0 = n2 + 1
- 具有 n 个结点的完全二叉树的深度 k 为 上取整
- 对于具有 n 个结点的完全二叉树 ,如果按照 从上至下从左至右的顺序对所有节点从 0 开始编号 ,则对于 序号为 i 的结点有 :
- 若i>0,双亲序号:(i-1)/2; i=0,i为根节点编号,无双亲节点
- 若2i+1<n,左孩子序号: 2i+1,否则无左孩子
- 若2i+2<n,右孩子序号: 2i+2,否则无右孩子
六,二叉树的存储方式
和链表一样,有顺序存储和链式存储(引用)
顺序存储,将二叉树采用数组方式存储(只能存储完全二叉树,在堆里使用)
普通的二叉树我们采用引用方式来存储
class TreeNode<E> {
E val;
TreeNode left;//左子树根节点
TreeNode right;//右子树根节点
//还可以定义父节点,平衡树会用到
TreeNode parent;//父节点的地址
}
七,二叉树的遍历
遍历:
按照一定的顺序“访问”(根据不同的场景,访问的需求是不同的,打印结点值或是计算结点个数)这个集合的所有元素,不重复,不遗漏。
对于二叉树这种非线性结构而言,遍历比线性结构复杂的多,四大遍历方式。对于二叉树而言,遍历是其他操作的基础
深度优先遍历
前序遍历(preOrder):根左右
先访问根节点,递归访问左子树,递归访问右子树
以上图为例前序遍历结果为:ABDEHCFG。
结论:
前序遍历结果集中,第一个输出的一定是当前树的根节点。
代码实现
public static void preOrder(TreeNode root) {
if (root == null) {
return;
}
//先访问根节点
System.out.print(root.val + " ");
//递归访问左树
preOrder(root.left);
//递归访问右树
preOrder(root.right);
}
中序遍历(inOrder):左根右
先递归访问左子树,然后访问根节点,最后递归访问右子树
以上图为例中序遍历结果为:DBEHAFCG
结论:
在中序遍历结果中,左子树的遍历结果在根节点的左侧,右子树的遍历结果在根节点的右侧
代码实现
public static void inOrder(TreeNode root) {
if (root == null) {
return;
}
//先递归访问左树
inOrder(root.left);
//然后访问根节点
System.out.print(root.val + " ");
//最后递归访问右树
inOrder(root.right);
}
后序遍历(postOrder):左右根
先递归访问左子树,然后递归访问右子树,最后访问根节点
以上图为例后序遍历结果为:DHEBFGCA
结论:
后序结果集中,最后一个才是根节点,后序结果集的转置输出恰好是前序遍历的镜像 - 根右左
代码实现
public static void postOrder(TreeNode root) {
if (root == null) {
return;
}
//先递归访问左树
postOrder(root.left);
//然后递归访问右树
postOrder(root.right);
//最后访问根节点
System.out.print(root.val + " ");
}
广度优先遍历
层序遍历(leveOrder):
按照二叉树的层次一层层访问结点,先左再右
代码实现
public static void leveOrder(TreeNode root) {
if (root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
//此时树不为空,将根节点入队
queue.offer(root);
while (!queue.isEmpty()) {
//取出队首元素
TreeNode cur = queue.poll();
//输出队首元素的的值
System.out.print(cur.val + " ");
//当前节点还有子节点,将子节点从左到右依次入队
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
八,二叉树的其他基础操作
获取结点个数:
getNodes(TreeNode root)传入根节点获取二叉树节点个数
代码实现
/**
* 传入根节点,统计出节点个数
* @param root
* @return 结点个数
*/
public static int getNodes(TreeNode root) {
//当前节点为空,边界条件
if (root == null) {
return 0;
}
//当前节点不为空,统计当前节点,然后加上左右子树节点数
return 1 + getNodes(root.left) + getNodes(root.right);
}
求一颗二叉树中叶子结点个数:
getLeafNodes(TreeNode root)入根节点获取二叉树叶子节点个数
/**
* 传入根节点,统计叶子结点个数
* @param root
* @return
*/
public static int getLeafNode(TreeNode root) {
if (root == null) {
return 0;
}
//当前为叶子结点,加一
if (root.left == null && root.right == null) {
return 1;
}
//当前树不为空,且存在子树
return getLeafNode(root.left) + getLeafNode(root.right);
}
注意:
递归函数实际上具体的处理过程都在终止条件,递归过程只是将多个结果拼接起来而已
判断当前二叉树中是否含有寻找元素:
Contains(TreeNode root, char val)传入二叉树和寻找值
/**
* 传入一个树和一个值,判断当前树中是否含有该元素
* @param root
* @param val
* @return true/false
*/
public static boolean contains(TreeNode root,char val) {
if (root == null) {
return false;
}
//当前节点值等于所传入值
if (root.val == val) {
return true;
}
//二叉数不为空,且当前节点值不等于所传入值,在子树中继续寻找
return contains(root.left,val) || contains(root.right,val);
}
求二叉树的高度:
height(TreeNode root)传入二叉树根节点
/**
* 传入一个以root为根节点的二叉树,就能求出该树的高度
*
* @param root
* @return 树的高度
*/
public static int height(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(height(root.left), height(root.right));
}