>>数据结构_树
1、树简单介绍
1.1概述及图示
树是一种一对多
的数据结构,非线性数据结构
,下图就是一颗普通的树。
1.2常用术语
根节点
就是上图中的粉红节点,它是一颗树的根父节点
就是一个节点的前驱节点 一个节点只有一个父节点,比如5 6 7的父节点是2号节点
8号节点的父节点是3号节点
子节点
就是一个节点的后继节点,一个节点的后继节点可以有多个,比如2号节点的子节点是5 6 7
叶子节点
没有子节点的节点成为叶子节点,如上图中的5 6 7 8 9 10
子树
子树就是一个节点的子节点构成的数,比如根节点的一颗最左子树就是2作为根节点构成的树节点的度
一个节点拥有子树的数目成为节点的度树的高度
即数的层数,如上图所示,有三层,所以树的高度是3森林
,多颗子树构成森林,最简单的,一颗树去掉根节点剩余的所有子树就构成森林,比如上图删除根节点1,剩下了三颗新的树,这三颗树就构成了森林
2、二叉树
2.1概述
树结构中使用最多的就是二叉树,二叉树就是一颗树的每一个节点最多有两个子节点,
且这两个节点有左右之分,分别称为左节点和右节点
2.2满二叉树
满二叉树首先是一颗二叉树,并且满足 所有的叶子节点都在最后一层
,并且节点总数 sum = 2 ^ n -1
,n是二叉树的层数
如下图所示,一共有三层,节点总数为2^3-1 = 7
,满足上面的条件,且所有的叶子结点都在最后一层,所有下图就是一颗满二叉树
满二叉树的每一层的节点数是 2 ^ (k-1)
k是第几层
2.3完全二叉树
完全二叉树也是一颗二叉树,它的叶子节点都分布在最后两层,且倒数第二层的节点数必须是 2 ^ (k-1)
,即倒数第二层的节点数必须是满的,并且最后一层的节点从左边
开始连续
所以满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树
3、二叉树的遍历
3.1代码定义二叉树节点
public class TreeNode {
//左子节点
public TreeNode left;
//右子节点
public TreeNode right;
//data域
public int val;
public TreeNode() {}
public TreeNode(int val) {
this.val = val;
}
public TreeNode(TreeNode left, TreeNode right, int val) {
this.left = left;
this.right = right;
this.val = val;
}
}
3.2前序遍历(DLR)
3.2.1概述图示DLR
所谓的前序遍历,也叫先根遍历,即先遍历获取根节点的值,然后遍历获取左子节点的值,最后遍历获取右子节点的值,即(data->left->right)
3.2.2递归写法
class Solution {
//用一个集合保存保存节点的值
List<Integer> res = new ArrayList();
public List<Integer> preorderTraversal(TreeNode root) {
dlr(root);
return res;
}
public void dlr(TreeNode root){
if(root == null) return;
//先保存根节点的值
res.add(root.val);
//保存左节点的值
dlr(root.left);
//保存右节点的值
dlr(root.right);
}
}
3.2.3迭代写法
根据上面的图示可以发现,前序遍历会先一直访问左子树,当左子树为空时(遍历到最后一层时),依次向上访问父节点的右节点
,所以我们可以先用一个栈保存左节点,当遍历到最后一层时,取出栈顶元素(就是父节点),使其出栈然后访问其右节点即可。
具体的过程(以上图的二叉树为例):
① 先将 1号节点的值存入集合
然后将1号节点入栈
② 2 4 8 重复①的过程
此时集合中的值 1 2 4 8
栈中(TreeNode类型)
8
4
2
1
--------------
③ 由于8号节点的left是null,所以停止本次查找,此时8号节点(栈顶元素)就是父节点,然后使其出栈,遍历它的右节点,由于它没有右节点,此时栈顶元素时4号节点,然后遍历4号节点的右节点,是9
,将9存入集合并将9号节点入栈,因为9号节点没有left,使其出栈,遍历其右节点为null,然后此时栈顶时2号节点,使其出栈遍历其右节点,。。。。
代码
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
Stack<TreeNode> stk = new Stack();
while(root != null || !stk.isEmpty()){
//一直访问左节点
while(root != null){
res.add(root.val);
stk.push(root);
root = root.left;
}
//此时栈顶元素就是父节点 访问其右节点
root = stk.pop().right;
}
return res;
}
}
3.3中序遍历(LDR)
3.3.1概述
中序遍历即先输出左子节点的值,然后输出父节点的值,最后输出右子节点的值,类似先序遍历
3.3.2递归写法
class Solution {
List<Integer> res = new ArrayList();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null) return res;
inorderTraversal(root.left);
res.add(root.val);
inorderTraversal(root.right);
return res;
}
}
3.3.3迭代写法
也是借助栈,先一直遍历左节点将其入栈,然后从下向上输出值然后遍历右节点即可
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
Stack<TreeNode> stk = new Stack();
while(root != null || !stk.isEmpty()){
while(root != null){
stk.push(root);
root = root.left;
}
TreeNode node = stk.pop();
res.add(node.val);
root = node.right;
}
return res;
}
}
3.4后序遍历(LRD)
递归写法
class Solution {
List<Integer> res = new ArrayList();
public List<Integer> postorderTraversal(TreeNode root) {
if(root == null) return res;
postorderTraversal(root.left);
postorderTraversal(root.right);
res.add(root.val);
return res;
}
}
迭代写法
后序遍历是LRD 我们只需要前序遍历DRL,然后结果反转即可
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
Stack<TreeNode> stack = new Stack();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
res.add(root.val);
root = root.right;
}
root = stack.pop().left;
}
//反转
Collections.reverse(res);
return res;
}
}