文章目录
树
树是由一个集合以及在该集合上定义的一种关系构成的,集合中的元素称为树的结点,所定义的关系称为父子关系。
父子关系在数的结点之间建立了一个层次结构。树的结点包含一个数据元素及若干指向其子树的若干分叉。在这种层次结构中由一个结点具有特殊的地位,这个结点称为该树的根节点,或简称为树根。
我们可以形式地给出树的递归定义如下:
**树(tree)是n(n>=0)个结点的有限集。**它
- 或者是一棵空树(n=0),空树中不包含任何系欸但。
- 或者是一棵非空树(n>0),此时有且仅有一个特定的称为根(root)的结点;
当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,。。。,Tm其中每一个本身又是一棵树,并且称为根的子树(sub tree)
图a时一棵空树、b还是只有一个根结点的树,c时一棵由10个结点的树,其中A时根,其余的结点分成3个不相交的集合:T1=(B,E,F)、T2=(C,G)、T3=(D,H,I,J)每个集合都构成一棵树,且都是根A的子树。
结点的度和树的度
结点拥有的子树的数目称为结点的度(Degree)。
**度为0的结点称为叶子(leaf)**或终端结点。度不为0的结点称为非终端结点或分支结点。除根之外的分支结点也称为内部结点,树内各结点的度的最大值称为树的度。
结点的层次和树的深度
结点的层次(level)从根开始定义,层次数为1的结点时根结点,其子树的根的层次数为2。数中结点的最大层次数称为树的深度(Depth)或高度
父亲、儿子、兄弟
父亲(parent):一个结点的直接前驱结点
儿子(child):一个结点的直接后继结点
兄弟(sibling):同一个读取结点的其他结点。
结点A时结点B、C、D的父亲,结点B、C、D是结点A的孩子,由于系欸但H、I、J有同一个父节点D,因此他们互为兄弟。
祖先、子孙、堂兄弟
将父子关系进行扩展,就可以得到祖先、子孙、堂兄弟等关系。
结点的祖先是从根到该结点路径上的所有结点,以某节点为根的树中的任一结点都称为该结点的子孙。
父亲在同一层次的结点互为堂兄弟。
有序树、m叉树、森林
如果将树中结点的各子树看成是从左至右是有次序的,则称该树为有序树;若不考虑子树的顺序则称为无序树。对于有序树,我们可以明确的定义每个结点的第一个孩子、第二个孩子等,直到最后一个孩子。若不特别指明,一般讨论的树都是有序树
树中所有结点最大度数为m的有序树成为m叉树。
森林(forest)是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。树和森林的概念很近,删去一棵树的根,就得到一个森林;反之加上一个结点作为树根,森林就变为一棵树。
二叉树
每个结点的度均不超过2的有序树,称为二叉树(binary tree)。与树的递归定义类似,二叉树的递归定义如下:
二叉树或者是一棵空树,或者是一棵由一个根节点和两棵互不相交的分别称为左子树和右子树所组成的非空树。
由以上定义可以看出,二叉树中每个结点的孩子数只能是0、1或2个,并且每个孩子都有左右之分。位于左边的孩子称为左孩子,位于右边的孩子称为右孩子;以左孩子为根的树称为左子树,以右孩子为根的子树称为右子树。
满二叉树:
高度为k并且右2**(k-1)-1个结点的二叉树。在满二叉树中,每层结点都达到最大数,即每层结点都是满的,因此称为满二叉树。
完全二叉树:
若在一棵满二叉树中,在最下层从最右侧起去掉相邻的若干子节点,得到的二叉树即为完全二叉树。
满二叉树比为完全二叉树,而完全二叉树不一定是满二叉树
二叉树的性质
- 在二叉树的第i层最多有2**(i-1)个结点(根是第一层)
- 高度为h的二叉树最多有2**h - 1个结点
- 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点树为n2,则n0 = n2 + 1
- 有n个结点的完全二叉树的高度为【log2 n】+1,其中【log2 n】是向下取整
- 含有n>=1个系欸但的二叉树的高度至多为n-1;高度至少为【log2 n】+1,其中【log2 n】是向下取整
- 如果对一棵有n个结点的完全二叉树的结点进行编号,则对任一结点i(1<=i<=n),有
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲结点PARENT(i)是结点 i/2向下取整。
- 如果2i>n,则结点i无左孩子;否则其左孩子是结点2i。
- 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
二叉树的存储结构
二叉树的存储结构有两种:顺序存储链式存储。
顺序存储结构
对于满二叉树和完全二叉树来捉,可以将其数据元素逐层存放到一组连续的存储单元中,如图所示。用以作为数组来实现顺序存储结构时,将二叉树中编号为i的结点存放到数组中的第i个分量中。如此根据二叉树性质,可以得到结点的i的父节点、左右孩子结点分别存放在、2i以及2i+1分量中
这种存储方式对于满二叉树和完全而擦函数是非常合适也是高效方便的。因为满二叉树和完全二叉树采用顺序存储结构既不浪费空间,也可以根据公式很快的确定结点之间的额关系。但是对于一般的二叉树而言,必须用“虚结点”将一棵二叉树补成一个完全二叉树来存储,否则无法确定结点之间的前驱后续关系,但是这样一来就会造成空间的浪费。
链式存储结构
设计不同的结点结构可构成不同的链式存储结构。
在二叉树中每个结点都有两个孩子,则可设计每个结点至少包括3个域:数据域、左孩子域和右孩子域。数据域存放数据元素,左孩子域存放指向左孩子结点的指针,右孩子域存放指向右孩子结点的指针。如图a所示。利用此结点得到的二叉树存储结构称为二叉链表 。
为了方便找到父结点,可以在上述结点结构中增加一个指针域,指向结点的父节点。如图b所示。采用此结点捷尔沟得到的二叉树存储结构称为三叉链表。
二叉树遍历概念和算法
遍历(Traverse)
就是按照某种次序访问树中的所有结点,且每个结点恰好访问一次。也就是说,按照被访问的次序,可以得到由树中所有结点排成的一个序列。树的遍历也可以看成是认为的键非线性结构线性化。
这里的访问时广义的,可以时对结点作各种处理,例如输出结点信息,更新结点信息等。在我们的现实中,并不真正的访问这些结点,而是得到一个结点的线性序列,以先行白哦的形式输出。
将整个二叉树看作三部分:根、左子树、右子树。如果规定先遍历左子树、再遍历右子树,那么根据根的遍历顺序就有三证遍历方式左子树、右子树、根
先序/根遍历DLR:根 左子树 右子树
中序/根遍历LDR:左子树 根 右子树
后序/根遍历LRD:左子树 右子树 根
注意:由于树的递归定义,其实对三种遍历的概念其实也是一个递归的描述过程
先序/根遍历DLR:1 4 5 2 3 6 7
中序/根遍历LDR:4 5 1 3 2 6 7
后序/根遍历LRD:5 4 3 7 6 2 1
面试题:已知一棵二叉树的后续遍历的序列为5 4 3 7 6 2 1,中序遍历的序列为4 5 1 3 2 6 7,则其先序遍历的序列是什么?
由后续遍历得:1为根,由中序遍历得:4 5为左子树3 2 6 7 为右子树,再由后续遍历得5在4前而中序4在5前,所以由LDR和LRD得4和5分别为D和R,所以4为根,5为R,以此类推,先序为1 4 5 2 3 6 7
package DataStructure.Btree;
/**
* @Description :二叉树的接口,可以由不同的实现类,每个类可以使用不同的存储结构
* @Date 2019/5/29 19:28
* @Param
* @Return
*/
public interface BinaryTree {
/**
* @Description :是否为空树
* @Date 2019/5/29 19:25
*/
public boolean isEmpty();
/**
* @Description :树的结点数量
* @Date 2019/5/29 19:26
*/
public int size();
/**
* @Description :获取二叉树的高度
* @Date 2019/5/29 19:26
*/
public int getHeight();
/**
* @Description :查询指定值的结点
* @Date 2019/5/29 19:27
* @Param value
* @Return
*/
public Node findKey(int value);//查找
/**
* @Description :前序递归遍历
* @Date 2019/5/29 19:28
*/
public void preOrderTraverse();
/**
* @Description :中序递归遍历
* @Date 2019/5/29 19:30
*/
public void inOrderTraverse();
/**
* @Description :后序递归遍历
* @Date 2019/5/29 19:31
*/
public void postOrderTraverse();
/**
* @Description :前序非递归操作
* 1. 对于任意节点current,若该节点不为空则访问该结点后将再将结点压栈,并将左子树结点置为current,重复此操作,直到current为空。
* 2. 若左子树为空,栈顶结点出栈,将该结点的右子树置为current
* 3. 重复1、2步骤,直到current为空且栈内结点为空
* @Date 2019/5/29 19:39
*/
public void preOrderByStack();
/**
* @Description :中序非递归操作
* 1. 对于任意节点current,若该节点不为空则将该节点压栈,并将左子树结点置为current,重复此操作,直到current为空。
* 2. 若左子树为空,栈顶结点出栈没访问结点后将该结点的右子树置为current
* 3. 重复1、2步骤,直到current为空且栈内结点为空
* @Date 2019/5/29 19:39
*/
public void inOrderByStack();
/**
* @Description :后序非递归操作
* 1. 对于任意节点current,若该节点不为空则访问该结点后再将该节点压栈,并将左子树结点置为current,重复此操作,直到current为空。
* 2. 若左子树为空,取栈顶结点的右子树,如果右子树为空或右子树刚访问过,则访问该节点,并将preNode置为该结点
* 3. 重复1、2步骤,直到current为空且栈内结点为空
* @Date 2019/5/29 19:39
*/
public void postOrderByStack();
/**
* @Description :按照层次遍历
* @Date 2019/5/29 19:53
*/
public void levelOrderByStack();
}
package DataStructure.Btree;
/**
* @Description TODO 二叉链表得结点
* @Author Matthew
* @Date 2019/5/29 19:17
* @Version 1.0
*/
public class Node {
private Object value;//结点值
private Node leftChild;//左子树得引用
private Node rightChild;//右子树得引用
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
", leftChild=" + leftChild +
", rightChild=" + rightChild +
'}';
}
public Node(Object value) {
this.value = value;
}
public void setValue(Object value) {
this.value = value;
}
public Node getLeftChild() {
return leftChild;
}
public void setLeftChild(Node leftChild) {
this.leftChild = leftChild;
}
public Node getRightChild() {
return rightChild;
}
public void setRightChild(Node rightChild) {
this.rightChild = rightChild;
}
public Node() {
}
public Node(Object value, Node leftChild, Node rightChild) {
this.value = value;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
package DataStructure.Btree;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Description TODO
* @Author Matthew
* @Date 2019/5/29 19:56
* @Version 1.0
*/
public class LinkedBinaryTree implements BinaryTree {
private Node root;//根节点
public LinkedBinaryTree(Node root) {
this.root = root;
}
public LinkedBinaryTree() {
}
@Override
public boolean isEmpty() {
return root == null;
}
@Override
public int size() {
System.out.print("二叉树的结点个数:");
return this.size(root);
}
private int size(Node root) {
if (root == null) {
return 0;
} else {
//获取左子树的size
int nl = this.size(root.getLeftChild());
//获取右子树的size
int nr = this.size(root.getRightChild());
//返回左子树加右子树加1
return nl+nr+1;
}
}
@Override
public int getHeight() {
System.out.print("二叉树的高度:");
return this.getHeight(root);
}
private int getHeight(Node root){
if (root == null) {
return 0;
} else {
//获取左子树的高度
int nl = this.getHeight(root.getLeftChild());
//获取右子树的高度
int nr = this.getHeight(root.getRightChild());
//返回左子树、右子树较大高度加1
return nl > nr ? nl+1 : nr+1;
}
}
@Override
public Node findKey(int value) {
System.out.print("按照层次遍历二叉树:");
return this.findKey(value, root);
}
private Node findKey(Object value, Node root) {
if (root == null) {//递归结束条件1:结点为空,说明此二叉树没有结点,也可能是递归调用中节点的左孩子或右孩子。
return null;
} else if (root != null && root.getValue() == value) {//递归结束条件2:找到了
return root;
} else {//没找到
Node node1 = this.findKey(value, root.getLeftChild());//看左孩子
Node node2 = this.findKey(value, root.getRightChild());//看右孩子
if (node1 != null && node1.getValue() == value) {//左孩子满足条件返回
return node1;
} else if (node2 != null && node2.getValue() == value) {//右孩子满足条件返回
return node2;
} else {//都不满足返回空
return null;
}
}
}
@Override
public void preOrderTraverse() {
//输出根节点的值
if (root != null) {
System.out.print(root.getValue()+" ");
//对左子树进行先序遍历
//构建一个二叉树,根是左子树的根
BinaryTree leftTree = new LinkedBinaryTree(root.getLeftChild());
leftTree.preOrderTraverse();
//对右子树进行先序遍历
//构建一个二叉树,根是左子树的根
BinaryTree rightTree = new LinkedBinaryTree(root.getRightChild());
rightTree.preOrderTraverse();
}
}
@Override
public void inOrderTraverse() {
System.out.print("中序遍历:");
this.inOrderTraverse(root);
System.out.println();
}
private void inOrderTraverse(Node root) {
if (root != null) {
//遍历左子树
this.inOrderTraverse(root.getLeftChild());
//输出根的值
System.out.print(root.getValue() + " ");
//遍历右子树
this.inOrderTraverse(root.getRightChild());
}
}
@Override
public void postOrderTraverse() {
System.out.print("后序遍历:");
this.postOrderTraverse(root);
System.out.println();
}
private void postOrderTraverse(Node root) {
if (root != null) {
//遍历左子树
this.postOrderTraverse(root.getLeftChild());
//遍历右子树
this.postOrderTraverse(root.getRightChild());
//输出根的值
System.out.print(root.getValue() + " ");
}
}
@Override
public void preOrderByStack() {
System.out.print("先序非递归遍历:");
//创建栈
Deque<Node> stack = new LinkedList<Node>();
Node current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
System.out.print(current.getValue() + " ");
if (current.getRightChild() != null) {
stack.push(current.getRightChild());
}
current = current.getLeftChild();
}
if (!stack.isEmpty()) {
current = stack.pop();
}
}
System.out.println();
}
@Override
public void inOrderByStack() {
System.out.print("中序非递归遍历:");
//创建栈
Deque<Node> stack = new LinkedList<Node>();
Node current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.getLeftChild();
}
if (!stack.isEmpty()) {
current = stack.pop();
System.out.print(current.getValue() + " ");
current = current.getRightChild();
}
}
System.out.println();
}
@Override
public void postOrderByStack() {
System.out.print("后序非递归遍历:");
//创建栈
Deque<Node> stack = new LinkedList<Node>();
Node current = root;
Node flag = null;
if (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.getLeftChild();
}
while (!stack.isEmpty()) {
current = stack.pop();
if (current.getRightChild() == null || current.getRightChild() == flag) {
System.out.print(current.getValue() + " ");
flag = current;
} else {
stack.push(current);
current = current.getRightChild();
while (current != null) {
stack.push(current);
current = current.getLeftChild();
}
}
}
}
while (!stack.isEmpty()) {
current = stack.pop();
}
System.out.println();
}
@Override
public void levelOrderByStack() {
System.out.print("按照层次遍历二叉树:");
if (root == null) {
return;
} else{
Queue<Node> queue = new LinkedList<Node>();
queue.add(root);
while (queue.size() != 0) {
int len = queue.size();
for (int i = 0; i < len; i++) {
Node temp = queue.poll();
System.out.print(temp.getValue() + " ");
if (temp.getLeftChild() != null) {
queue.add(temp.getLeftChild());
}
if (temp.getRightChild() != null) {
queue.add(temp.getRightChild());
}
}
}
System.out.println();
}
}
}
package DataStructure.Btree;
/**
* @Description TODO
* @Author Matthew
* @Date 2019/5/29 19:56
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
//创建一个二叉树
Node node5 = new Node(5, null, null);
Node node4 = new Node(4, null, node5);
Node node3 = new Node(3, null, null);
Node node7 = new Node(7, null, null);
Node node6 = new Node(6, null, node7);
Node node2 = new Node(2, node3, node6);
Node node1 = new Node(1, node4, node2);
BinaryTree btree = new LinkedBinaryTree(node1);
//判断二叉树是否为空
System.out.println(btree.isEmpty());
//先序遍历递归 1 4 5 2 3 6 7
System.out.print("先序遍历:");
btree.preOrderTraverse();
System.out.println();
//中序遍历递归 4 5 1 3 2 6 7
btree.inOrderTraverse();
//后续遍历递归 5 4 3 7 6 2 1
btree.postOrderTraverse();
//先序非递归遍历 1 4 5 2 3 6 7
btree.preOrderByStack();
//中序非递归遍历(借助栈)4 5 1 3 2 6 7
btree.inOrderByStack();
//后序非递归遍历
btree.postOrderByStack();
//按照层次遍历(借助队列)1 4 2 5 3 6 7
btree.levelOrderByStack();
//在二叉树中查找某个值
System.out.println(btree.findKey(7));
//二叉树的高度
System.out.println(btree.getHeight());
//二叉树的结点数量
System.out.println(btree.size());
}
}