树
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空的树中:(1)有且仅有有个特定的称为根(root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,……,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
强调:
- 一棵树至多一个根,不可能存在多个根结点。
- 子树的个数没有限制,但是,子树之间一定是互不相交的。
树的相关概念
结点的度和树的度
- 结点拥有子树数称为结点的度(数分叉);
- 树的度是树内各结点的度的最大值。如上面的树的度为3。
结点类别
- 度为0的结点称为叶子结点或者终端结点;
- 度不为0的结点称为非终端结点或分支结点。除根结点外,分支结点也称为内部结点。
结点关系
结点层次与树的深度
结点的层次(Level)从根开始定义器,根为第一层(不过有的从0开始,这不影响),根的孩子为第二层。树中结点的最大层次称为数的深度(depth)或高度。
其他
如果将树中结点的各子树看成从左至右是有序的,不能互换的则该树为有序树,否则为无序树。问:3个结点组成的二叉树有几种形式?
森林(forest)是m(m>=0)棵互不相交的树的集合。
二叉树
二叉树(binary tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
特殊二叉树
- 斜树
顾名思义,斜树一定是倾斜的,分为左斜和右斜。斜树有很明显的特定,每一层只有一个结点,结点的个数和二叉树的深度相同。 - 满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
- 完全二叉树
对一棵具有n个结点的二叉树按层编号,如果编号i的结点与同样深度的满二叉树中的编号为i的结点在二叉树中未知完全相同,则这棵二叉树称为完全二叉树。就是满二叉树的从右至左删除叶子结点生成的二叉树,并且如果右边有结点没删除不能删除左边的结点。满二叉树是完全二叉树,但是完全二叉树不一定是满二叉树。
二叉树性质
- 性质1:在二叉树的第i层至多有 个结点(i>=1)。
- 性质2:深度为k的二叉树至多有 个结点(k>=1)。
- 性质3:对任何一棵二叉树T,如果其终端结点数为 ,度为2的结点数为 ,则 。
- 性质4:具有n个结点的完全二叉树的深度为 。
- 性质5:如果对一棵有n个结点的完全二叉树(其深度为
)的结点按层序编号(从第1层到第
层,每层从左至右),对任一结点i有:
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点 。
- 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
- 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
性质5用于创建完全二叉树。
二叉树遍历
先序遍历
若树为空,则直接返回,否则从根结点开始,然后前序遍历左子树,在前序遍历右子树。如下图的前序遍历结果为:ABDGHCEIF。
中序遍历
若树为空,则直接返回,否则首先从根结点开始(但是不访问),然后中序遍历左子树,再访问根结点,最后中序遍历右子树。如下图的中序遍历结果为:GDHBAEICF。
后序遍历
若树为空,则直接返回,否则首先从根结点开始(但是不访问),然后后序遍历左子树,再后序遍历右子树,最后访问根结点。如下图的后序遍历结果为:GHDBIEFCA。
层序遍历
若树为空,则直接返回,否则从树的第一层,也就是根结点开始访问,从上至下逐层遍历,在同一层中,从左至右对结点逐一访问。如下图的层序遍历结果为:ABCDEFGHI。
二叉树的创建
为了后序的遍历操作我们先创建一个二叉树。因为现在我的二叉树是无规律的,所以只能自己指定左孩子,右孩子和父结点。
定义结点:
public class Node {
public Node leftChild;//左孩子
public Node rightChild;//右孩子
public Node parentNode;//为了便于删除,我们也保存父节点的引用
public int data;//数据域
public Node(int i){
this.data = i;
}
public void displayNode(){
System.out.print(data+" ");
}
}
二叉树类:
public class BinaryTree {
public Node root=null;
public BinaryTree(){}
public BinaryTree(Node node){
root = node;
}
}
由于我们现在的这棵树没有规律,所以手动创建树。
public static void main(String[] args) {
Node node1 = new Node('A');
Node node2 = new Node('B');
Node node3 = new Node('C');
Node node4 = new Node('D');
Node node5 = new Node('E');
Node node6 = new Node('F');
Node node7 = new Node('G');
Node node8 = new Node('H');
Node node9 = new Node('I');
//由于我们现在的这棵树没有规律,所以手动创建
node1.leftChild = node2;
node2.parentNode = node1;
node1.rightChild = node3;
node3.parentNode = node1;
node2.leftChild = node4;
node4.parentNode = node2;
node4.leftChild = node7;
node7.parentNode = node4;
node4.rightChild = node8;
node8.parentNode = node4;
node3.leftChild = node5;
node5.parentNode = node3;
node3.rightChild = node6;
node6.parentNode = node3;
node5.rightChild = node9;
node9.parentNode = node5;
BinaryTree tree = new BinaryTree(node1);
}
前序遍历代码
递归实现
public void preOrderTraverse(){
System.out.println("先序遍历:");
preOrderTraverse(root);
System.out.println();
}
private void preOrderTraverse(Node root){
if(root==null)
return;
root.displayNode();
preOrderTraverse(root.leftChild);
preOrderTraverse(root.rightChild);
}
非递归实现
public void preOrderTraverseByStack(){
System.out.println("先序遍历:");
preOrderTraverseByStack(root);
System.out.println();
}
private void preOrderTraverseByStack(Node root){
Stack<Node> s = new Stack<Node>();
s.push(root);//首先将根结点入栈
Node current = root;
while (!s.empty()) {
current = s.pop();
if (current != null) {
current.displayNode();
//由于栈的FILO原则,先序遍历需要先左子树后右子树,所以这里需要将右子树先入栈
s.push(current.rightChild);
s.push(current.leftChild);
}
}
}
中序遍历代码
递归实现
public void inOrderTraverse(){
System.out.println("中序遍历:");
inOrderTraverse(root);
System.out.println();
}
private void inOrderTraverse(Node root){
if(root==null)
return;
inOrderTraverse(root.leftChild);
root.displayNode();
inOrderTraverse(root.rightChild);
}
非递归实现
public void inOrderTraverseByStack(){
System.out.println("中序遍历:");
inOrderTraverseByStack(root);
System.out.println();
}
private void inOrderTraverseByStack(Node root){
Stack<Node> s = new Stack<Node>();
Node current = root;
while(current!=null||!s.isEmpty()){
//将左子树放入栈
while(current!=null){
s.push(current);
current = current.leftChild;
}
current = s.pop();
current.displayNode();//访问根结点
current = current.rightChild;//遍历右子树,将右子树入栈
}
}
后序遍历代码
递归实现
public void postOrderTraverse(){
System.out.println("后序遍历:");
postOrderTraverse(root);
System.out.println();
}
private void postOrderTraverse(Node root){
if(root==null)
return;
postOrderTraverse(root.leftChild);//后序遍历左子树
postOrderTraverse(root.rightChild);//后序遍历右子树
root.displayNode();//最后访问根结点
}
非递归实现
public void postOrderTraverseByStack(){
System.out.println("后序遍历:");
postOrderTraverseByStack(root);
System.out.println();
}
private void postOrderTraverseByStack(Node root){
Stack<Node> s = new Stack<Node>();
Node curr = root;
Node last = null;//前一个已经访问的节点
while(curr != null || !s.empty()){ // 栈空时结束
while(curr != null){// 一直向左走直到为空
s.push(curr);
curr = curr.leftChild;
}
curr = s.peek();
// 当前节点的右孩子如果为空或者已经被访问,则访问当前节点
if(curr.rightChild == null || curr.rightChild == last){
curr.displayNode();
last = curr;
s.pop();
curr = null;
}
else
curr = curr.rightChild; // 否则访问右孩子
}
}
层序遍历代码
层序遍历使用队列实现,实现过程如下图所示:
public void levelOrderTraverse(){
System.out.println("层序遍历:");
levelOrderTraverse(root);
System.out.println();
}
private void levelOrderTraverse(Node root){
Queue<Node> queue = new ArrayDeque<>();
Node curr = root;
while(curr!=null||!queue.isEmpty()){
while(curr!=null){
if(queue.isEmpty())//第一次需要放入根结点
queue.add(curr);
if(curr.leftChild!=null)
queue.add(curr.leftChild);
if(curr.rightChild!=null)
queue.add(curr.rightChild);
curr=null;
}
curr = queue.poll();
curr.displayNode();
curr = queue.peek();
}
}
树是否为空
/**
* 判断树是否为空
*/
public boolean isEmpty(){
return root==null;
}
获取树的深度
如果这棵树只有一个根结点,深度为1。否则树的深度为左子树深度和右子树深度的较大值+1。采用递归实现。
/**
* 获取树的深度
*/
public int getDeepth(){
return getDeepth(root);
}
private int getDeepth(Node root){
if(root==null)
return 0;
int left = getDeepth(root.leftChild);
int right = getDeepth(root.rightChild);
return left>=right?left+1:right+1;//左右子树深度较大值+1
}
获取树的结点数
/**
* 结点数目
*/
public int size(){
return this.size(root);
}
private int size(Node root){
if(root==null)
return 0;
int nl = size(root.leftChild);
int nr = size(root.rightChild);
return nl+nr+1;//树的总结点数为左子树结点数+右子树结点数+1(根结点)
}
获取叶子结点数
/**
* 获取叶子结点数
*/
public int numberOfLeafs(){
return this.numberOfLeafs(root);
}
private int numberOfLeafs(Node root){
if(root==null)
return 0;//结点为null直接返回0
if(root.leftChild==null&&root.rightChild==null)
return 1;//如果结点没有左右结点,那么该结点就是叶子结点,返回1
return numberOfLeafs(root.leftChild)+numberOfLeafs(root.rightChild);//该节点不是叶子结点,计算它的左右子树的叶子结点之和
}
获取度为2的结点数
/**
* 计算满结点(度为2)的个数
*/
public int numberOfFulls(){
return this.numberOfFulls(root);
}
private int numberOfFulls(Node root){
if(root==null)
return 0;
else if(root.leftChild==null&&root.rightChild==null)
return 0;//叶子结点,不是满结点,直接返回0
else if(root.leftChild!=null&&root.rightChild==null)
return numberOfFulls(root.leftChild);//左孩子不为空,右孩子为空,满结点可以存在左子树中
else if(root.leftChild==null&&root.rightChild!=null)
return numberOfFulls(root.rightChild);//左孩子为空,右孩子不为空,满结点可以存在右子树中
else
return 1+numberOfFulls(root.leftChild)+numberOfFulls(root.rightChild);//左右孩子都不为空,当前结点是满结点,并且它的左右子树中都可能存在满结点
}
根据二叉树性质3,代码也可以写为:
public int numberOfFulls(){
return this.numberOfLeafs(root)-1;
}
查找指定结点
/**
* 查找指定值的结点
*/
public Node findValue(char value){
return findValue(root,value);
}
private Node findValue(Node node,char value){
if(node==null)
return null;
else if(node!=null&&node.data==value)
return node;
else{
Node nodeL = this.findValue(node.leftChild,value);//未找到则从左子树查找
Node nodeR = this.findValue(node.rightChild,value);//从右子树查找
if(nodeL!=null&&nodeL.data == value)
return nodeL;
else if(nodeR!=null&&nodeR.data == value )
return nodeR;
else
return null;
}
}
二叉树镜像
首先交换根结点的左右孩子(BC→CB),再分别以左右孩子为根结点执行同样的交换操作。
递归方法
/**
* 递归获取镜像
*/
public void getMirror(){
this.getMirror(root);
}
private void getMirror(Node node){
if(node==null)
return;
//交换左右孩子
Node temp = node.leftChild;
node.leftChild = node.rightChild;
node.rightChild = temp;
if(node.leftChild!=null)//以左孩子为根,翻转左子树
getMirror(node.leftChild);
if(node.rightChild!=null)
getMirror(node.rightChild);
}
非递归方法
按照先序遍历二叉树的顺序,每遇到一个节点,判断当前节点是否有孩子,如果有孩子,我们交换其左右孩子,然后把非空孩子入栈。直到栈为空。
/**
* 非递归获取镜像
*/
public void getMirrorByStack(){
this.getMirrorByStack(root);
}
private void getMirrorByStack(Node root) {
Stack<Node> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
Node top = stack.pop();
Node temp = top.leftChild;
top.leftChild = top.rightChild;
top.rightChild = temp;
if(top.leftChild!=null)
stack.push(top.leftChild);
if(top.rightChild!=null)
stack.push(top.rightChild);
}
}