树
一、基本概念
1、什么是树?
树是一中类似于链表的数据结构,不过链表的结点是以线性方式简单地指向其后继节点,而树的一个结点可以指向许多结点;
树是一种典型的非线性结构,树结构是表达具有层次特性的图结构的一种方法;
对于树ADT(抽象数据类型),元素的顺序不是重点。如果需要用到用到元素的顺序信息,可以使用链表,栈,队列等线性数据结构。
2、树之术语
- 根节点:没有双亲结点的结点,一个树最多只能有一个根节点;
- 边:双亲结点到孩子结点的链接;
- 叶子结点:没有孩子结点的结点;
- 兄弟结点:拥有相同双亲结点的所有孩子结点;
- 祖先结点:如果存在一条从根结点到孩子结点Q的路径,且结点P出现在这条路径上,则结点P就是结点Q的祖先结点,同时Q也是结点P的子孙结点;
- 结点大小:结点大小是指其子孙的个数(包括其自身);
- 树的层:位于相同深度的所有结点集合叫做树的层,从开始0层,则根节点位于0层;
- 结点的深度:从根节点到该结点的路径长度;和结点位于的层数一样;
- 结点的高度:从该结点到最深结点的路径长度,结点的高度和深度不一定相等;
- 树的高度:树中所有结点高度的最大值;
- 树的深度:树中所有结点深度的最大值,且树的高度等于树的深度;
- 斜树:树中除了叶子结点外,每一个结点都只有一个孩子结点;
- 只有左孩子结点的叫左斜树;
- 只有右孩子结点的叫右斜树;
- 其余的叫斜树;
- 结点的度数:一个结点含有的孩子结点的个数;
二、二叉树
1、什么是二叉树?
如果一颗树中的每个结点有0,1或者2个孩子结点,那么这棵树就是二叉树;
空树也是一颗二叉树;
一颗二叉树可以看做由根结点和两颗不相交的子树组成;
2、二叉树类型
严格二叉树:二叉树中的每个结点要么有两个孩子结点,要么没有孩子结点;
满二叉树:二叉树中的每个结点恰好都有两个孩子结点,且所有的叶子结点都位于同一层;
完全二叉树:如果一颗二叉树,扣除其最大层次后是一颗满二叉树,且最后一层的所有节点均向左靠齐,则称该树为完全二叉树;
3、二叉树的性质
假设输的高度为h,根结点的深度为0,
满二叉树的结点个数为2^(h+1) - 1;
完全二叉树结点个数为2^h 到 2^(h+1) - 1;
满二叉树的叶子结点个数为2^h;
对于n的结点的完全二叉树,空指针个数为n+1???;
4、二叉树的结构
表示一个结点的方法为:定义两个指针,一个指向左孩子结点,一个指向右孩子结点,中间为数据字段;
public class BinaryTreeNode{
private int data;
private BinaryTreeNode left;
private BinaryTreeNode right;
public int getData(){
return data;
}
public void setData(int data){
this.data = data;
}
public BinaryTreeNode getLeft(){
return left;
}
public void setLeft(BinaryTreeNode left){
this.left = left;
}
public BinaryTreeNode getRight(){
return right;
}
public void setRight(BinaryTreeNode right){
this.right = right;
}
}
5、二叉树的遍历
访问树所有结点的过程称为遍历;
遍历的分类:
前序遍历:DLR
中序遍历:LDR
后序遍历:LRD
层次遍历:
前序遍历:
//时间复杂度O(n),空间复杂度O(n)
void preOrder(BinaryTreeNode root){
if(root!=null){
System.out.println(root.getData());
preOrder(root.getLeft());
preOrder(root.getRight());
}
}
非递归前序遍历:
//时间复杂度O(n),空间复杂度O(n)
void preOrderNonRecursive(BinaryTreeNode root){
if(root==null) return null;
LStack s = new LStack();
while(true){
while(root != null){
System.out.println(root.getData());
s.push(root);
root = root.getLeft();
}
if(s.isEmpty()) break;
root = (BinaryTreeNode) s.pop();
root = root.getRight();
}
return;
}
//中序遍历
//时间复杂度O(n),空间复杂度O(n)
void InOrder(BinaryTreeNode root){
if(root != null){
InOrder(root.getLeft());
System.out.println(root.getData());
InOrder(root.getRight())
}
}
//非递归中序遍历
//时间复杂度O(n),空间复杂度O(n)
void InOrderNonRecursive(BinaryTreeNode root){
if(root == null) return null;
LStack s = new LStack();
while(true){
while(root != null){
s.push(root);
root = root.getLeft();
}
if(s.isEmpty()) break;
root = (BinaryTreeNode) s.pop();
System.out.println(root.getData());
root = root.getRight();
}
return;
}
//后续遍历
//时间复杂度O(n),空间复杂度O(n)
void PostOrder(BinaryTreeNode root){
if(root != null){
PostOrder(root.getLeft());
PostOrder(root.getRight());
System.out.println(root.getData());
}
}
//非递归后序遍历
//时间复杂度O(n),空间复杂度O(n)
//在前序和中序遍历中,当元素出栈后就不需要再次访问该结点啦。但是后续遍历时,
//每个结点需要访问两次。这就意味着,在遍历完左子树后,需要访问该结点,
//之后遍历完右子树时,还需要访问该结点,此时也才能输出该结点。
//所以需要进行判断,是第几次返回该结点,即让最新出栈的元素和栈顶元素的
//右孩子结点比较,如果相同,则是第二次访问,可以输出;反之,不行。
void PostOrderNonRecursive(BinaryTreeNode root){
if(root == null) return null;
LStack s = new LStack();
while(true){
if(root != null){
s.push(root);
root = root.getLeft();
}else{
if(s.isEmpty()){
System.out.println("Stack is Empty");
return;
}else{
if(s.top().getRight() == null){
root = s.top();
System.out.println(root.getData());
if(root == s.top().getRight()){
System.out.println(s.top().getData());
s.pop();
}
}
if(!s.isEmpty()){
root = s.top().getRight();
}else
root = null;
}
}
}
s.deleteStack();//清空栈
}
//层次遍历
//利用队列,在访问第l层的时,将其相关的l+1层按顺序保存在队列中。
//时间复杂度O(n),空间复杂度O(n)
void LevelOrder(BinaryTreeNode root){
BinaryTreeNode temp;
LQueue q = new LQueue();
if(root == null){
return;
}
q.enQueue(root);
while(!q.isEmpty()){
temp = q.deQueue();
System.out.println(temp.getData());
//输出该结点后,将其下一层的左右孩子结点存入队列中
if(temp.getLeft())
q.enQueue((temp.getLeft()));
if(temp.getRight())
q.enQueue((temp.getRight()));
}
q.deleteQueue();
}