树结构
关于树的一些特性:
每个树都是由根节点及一些“子树”构成。非空树至少有一个节点,根节点。否则,没有任何节点,为空树。
子树之间是互不相交的
除了根节点外,每个节点有且仅有一个父节点
-
一颗N个节点的树有N-1条边
关于树的一些概念:
节点的度:节点的子树个数
树的度:树中所有节点的度的最大值
叶子节点的度为0
节点的层次:规定根节点所在的层次为1,其他任意节点是其父节点的层次加1
树的深度:树中所有节点的最大层次就是这棵树的深度
树的表示
儿子-兄弟表示法
将表示的节点树右转45°,可以看到如下:
由此我们可以知道,一般的树都可以由左右两个指针的节点所表示,也就是我们常说的二叉树
二叉树:若为非空树,则由根节点及左子树、右子树两个不相交的二叉树组成
子树有左右之分
一些特殊的二叉树:
一个二叉树第i层的最大节点数是:2^(i-1),i>=1
深度为K的二叉树的最大节点数是2^K-1
2^0+2^1+2^3+….+2^(K-1) = 2^K -1
对二叉树的操作:
1.创建一个二叉树
2.遍历:按某个顺序访问每个节点
——遍历方法:
先序遍历:根,左子树,右子树
中序遍历:左子树,根,右子树
后序遍历:左子树,右子树,根
层序遍历:从上到下,从左到右
树的存储结构
- 数组
完全二叉树可以用数组存储,如果是一般二叉树,用数组存储就会造成空间的浪费。
比如上图中的完美二叉树
如果用数组存储的话,如下表:
节点 | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
其中非根节点i的父节点为:i/2取整
节点i的左孩子为2I(如果2i>n,则该节点没有左孩子)
节点i的右孩子为2i+1(如果2i+1>n,则该节点没有右孩子)
- 链表
//节点结构
class TreeNode{
private Object data;
TreeNode leftChild;
TreeNode rightChild;
}
前面提到二叉树的遍历有四种遍历方式,下面我们来说一下其中的三种遍历方式,前序中序及后序遍历
由于树就是一种递归的结构,所以我们可以利用递归实现二叉树的遍历
- 前序遍历
public void PreTraverse(TreeNode root){
if(root != null){
System.out.println(root.data);
PreTraverse(root.leftChild);
PreTraverse(root.rightChild);
}
}
- 中序遍历
public void MidTraverse(TreeNode root){
if(root != null){
MidTraverse(root.leftChild);
System.out.println(root.data);
MidTraverse(root.rightChild);
}
}
- 后序遍历
public void PostOrderTraverse(TreeNode root){
if(root != null){
PostOrderTraverse(root.leftChild);
PostOrderTraverse(root.rightChild);
System.out.println(root.data);
}
}
利用递归我们可以很简单的实现二叉树的遍历,利用非递归的方式如何实现二叉树的遍历呢?
思路:使用堆栈
- 中序遍历的非递归实现:
思路:
1.遇到一个节点,就将其压栈,并去遍历它的左子树
2.当左子树遍历完毕后,就从栈顶弹出一个节点并去访问它
3.然后再去按其右指针再去中序遍历该节点的右子树
public void InorderTraverse(TreeNode root){
TreeNode r = root;
Stack s = new Stack();
while(r != null || !s.isEmpty()){ //树不空或者栈不空
while(r != null){ //一直向左并将遍历的节点入栈
s.push(r);
r = r.leftChild;
}
if(!s.isEmpty){ //左孩子遍历完,弹出栈顶节点,并访问,然后再遍历该节点的右子树
TreeNode node = s.pop();
System.out.println(node.data);
r = node.rightChild;
}
}
}
-
先序遍历的非递归算法:
思路:
无论是哪种遍历方法,它们最终所走的路线都是一样的,之所以输出的节点的顺序不同,是因为不同的遍历决定了是在第一次碰到该节点的时候访问还是第二次碰到该节点的时候进行访问。中序遍历是在第二次走到该节点的时候去访问,而先序遍历呢?自然就是第一次遇到该节点的时候就访问,所以上述的中序遍历的非递归算法我们修改一条语句的位置就是先序遍历的非递归算法了
public void InorderTraverse(TreeNode root){
TreeNode r = root;
Stack s = new Stack();
while(r != null || !s.isEmpty()){ //树不空或者栈不空
while(r != null){ //一直向左并将遍历的节点入栈
System.out.println(node.data); //!!!!!!!压栈之前先访问
s.push(r); //第一次遇到节点,入栈
r = r.leftChild;
}
if(!s.isEmpty){ //左孩子遍历完,弹出栈顶节点,并访问,然后再遍历该节点的右子树
TreeNode node = s.pop(); //出栈,第二次遇到节点
//System.out.println(node.data);
r = node.rightChild;
}
}
}
未完待续……
上一篇: 一颗二叉搜索树
下一篇: 记又一次解决生产环境宕机问题(业务系统)