文章目录
这是动脑学院的"java版数据结构和算法"的学习笔记
1 树的基本定义
- 树(Tree)是n(n>=0)个结点的有限集。
- n = 0时为空树
- 在任意一棵非空树中:
- 有且仅有一个特定的称为根(root)的结点
- 当n>1时候,其余结点可分为m(m>0)个互不相交的有限集T1、T2、… Tm,
- 其中每一个集合本身又是一棵树,并且称为根的子树(subTree)
2 树和结点的度
- 结点拥有的子树数称为结点的度
- 度为0的结点称为
叶子结点
(或终端结点),度不为0的结点称为分支结点
(或非终端结点) - 树的度是树内各结点的度的最大值
3 树层次与深度
- 结点的层次(Level)从根开始定义,根为第一层,根的孩子为第二层。
- 双亲在同一层的结点互为堂兄弟结点,如下图
D,E
,D,F
互为堂兄弟,G,J
,H,J
,I,J
也互为堂兄弟。 - 树中结点的最大层次称为
树的深度(Depth)或高度
,当前树的深度为4
4 有序树和无序树
- 如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称为有序树,否则称为无序树。
5 森林
森林是m棵互不相交的树的集合
6 树的存储结构
- 简单的顺序存储不能满足树的实现
- 结合顺序存储和链式存储来实现
对于上面的树有下面几种表示方法
6.1 双亲表示法
取一块连续的内存空间,在存储每个结点时,附加一个parent
字段表明其双亲结点的位置
对于下面的树,双亲法顺序存储结构进行表示
当算法中需要在树结构中频繁地查找某结点的父结点时,使用双亲表示法最合适。当频繁地访问结点的孩子结点时,双亲表示法就很麻烦,采用孩子表示法就很简单。
6.2 孩子表示法
将树中的每个结点的孩子结点排列成一个线性表,用链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在一个线性表中,这样的表示方法就是孩子表示法。
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点
6.3 孩子双亲表示法
孩子表示法和双亲表示法的结合。
6.4 孩子兄弟表示法
- 任意一棵树它的结点的第一个孩子如果存在,那么就是唯一的
- 对于任意一个结点,它的右兄弟存在也是唯一的。
- 因此对于结点的数据结构,除了数据外,我们设置两个指针,分别指向
该结点的第一个孩子
和此结点的右兄弟
。
7 二叉树
- 二叉树(Binary Tree)是n个结点的有限集合
- 该集合或者为空集,则称为空二叉树
- 或者由一个根结点和两棵互不相交,分别称为根结点的左子树和右子树的二叉树组成。
7.1 斜树
- 所有的结点都只有左子树的二叉树叫左斜树。
- 所有结点都是只有右子树的二叉树叫右斜树。
- 这两者统称为斜树。
A A
B B
C C
D D
左斜树 右斜树
线性表结构可以理解为树的斜树表达
7.2 满二叉树
在一棵二叉树,如果所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树
7.3 完全二叉树
对一棵具有n个节点的二叉树按层序编号,如果编号为i(1<=i<=n)与同样深度的满二叉树编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
7.4 二叉树的性质
- 在二叉树的第i层上至多有2i-1个结点(i>=1)
- 深度为k的二叉树至多有2k-1个结点(k>=1)
- 对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1
- 具有n个结点的完全二叉树深度为[log2n]+1 ([a]表示不 大于 a的最大整数)
- 如果对一颗有n个结点的完全二叉树(其深度为[log2n]+1) 的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任意一个结点i(1<=i<=n)有
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]
- 如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
证明性质3:
设n为总结点数,n1为度为1的结点数,n2为度为2的结点数,n0为终端结点数。
分支线总数=n-1= n1 + 2n2。 总结点数n = n0 + n1 + n2
所以: n0 = n2 + 1
7.5 二叉树的顺序存储结构
-
完全二叉树
其存储结构为
完全二叉树,顺序存储在线性表中,则从线性表中存储的结点,能反推出的原来的二叉树。 -
一般二叉树
一般二叉树,如果直接存储在线性表中,则从线性表中的结点顺序,不能反推出原来的二叉树,所以要把一般二叉树补全为完全二叉树然后进行插入。
D, F, H, I为补全的结点,此时一般二叉树的顺序存储为
7.6 二叉链表
结点除了数据之外,有一个左孩子指针,一个右孩子指针。
整个链表的结构为
7.7 二叉树的遍历
7.7.1 前序遍历
- 若二叉树为空,则空操作返回
- 否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
前序遍历结果为:A,B,D,G,H,C,E,I,F
c代码实现
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
printf(“%c”,T->data);
ProOrderTraverse(T->lchild);
ProOrderTraverse(T->rchild);
}
7.7.2 中序遍历
- 若树为空,则空操作返回
- 否则从根结点开始(注意并不是先访问根结点), 中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
中序遍历结果为: G,D,H,B,A,E,I,C,F
’
c代码实现
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
ProOrderTraverse(T->lchild);
printf(“%c”,T-data);
ProOrderTraverse(T->rchild);
}
7.7.3 后序遍历
- 若树为空,则空操作返回
- 否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
后序遍历结果为:G,H,D,B,I,E,F,C,A
c代码实现
void ProOrderTraverse(Tree T){
if(T == null){
return;
}
ProOrderTraverse(T->lchild);
ProOrderTraverse(T->rchild);
printf(“%c”,T-data);
}
7.7.4 层序遍历
- 规则是若树为空,则空操作返回
- 否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问
层序遍历结果: ABCDEFGHI
使用队列进行实现:
- 先将A入队,然后把
A出队
,然后把B,C入队 - 然后
B出队
,D入队。C出队
,E,F入队 D出队
,G,H入队,E出队
,I入队。F,G,H,I依次出队
出队顺序即为层序遍历结果: A,B,C,D,E,F,G,H
7.7.5 java实现前序,中序,后序遍历
- 定义结点
public class TreeNode{
private int index;
private String data;
private TreeNode leftChild;
private TreeNode rightChild;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public TreeNode(int index,String data){
this.index = index;
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
}
- 创建BinaryTree
public class BinaryTree {
private TreeNode root = null;
public BinaryTree(){
root = new TreeNode(1, "A");
}
/**
* 构建二叉树
* A
* B C
* D E F
*/
public void createBinaryTree(){
TreeNode nodeB = new TreeNode(2, "B");
TreeNode nodeC = new TreeNode(3, "C");
TreeNode nodeD = new TreeNode(4, "D");
TreeNode nodeE = new TreeNode(5, "E");
TreeNode nodeF = new TreeNode(6, "F");
root.leftChild = nodeB;
root.rightChild = nodeC;
nodeB.leftChild = nodeD;
nodeB.rightChild = nodeE;
nodeC.rightChild = nodeF;
}
- 获取二叉树的高度
/**
* 求二叉树的高度
* @author Administrator
*
*/
public int getHeight(){
return getHeight(root);
}
private int getHeight(TreeNode node) {
if(node == null){
return 0;
}else{
int i = getHeight(node.leftChild);
int j = getHeight(node.rightChild);
return (i<j)?j+1:i+1;
}
}
二叉树的高度=Max(左子树高度,右子树高度) + 1
- 获取二叉树结点数
/**
* 获取二叉树的结点数
* @author Administrator
*
*/
public int getSize(){
return getSize(root);
}
private int getSize(TreeNode node) {
if(node == null){
return 0;
}else{
return 1+getSize(node.leftChild)+getSize(node.rightChild);
}
}
树的总的结点数=左子树的结点数+右子树的结点数+1(根结点)
- 前序遍历二叉树
/**
* 前序遍历——迭代
*/
public void preOrder(TreeNode node){
if(node == null){
return;
}else{
System.out.println("preOrder data:"+node.getData());
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
/**
* 前序遍历——非迭代
*/
public void nonRecOrder(TreeNode node){
if(node == null){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(node);
while(!stack.isEmpty()){
//弹出栈顶结点
TreeNode n = stack.pop();
//打印出栈顶结点
System.out.println("nonRecOrder data"+n.getData());
//先将右子结点入栈
if(n.rightChild!=null){
stack.push(n.rightChild);
}
//再将左边结点入栈,此时左边结点在栈顶,下次循环时,先左子节点出栈
if(n.leftChild!=null){
stack.push(n.leftChild);
}
}
}
- 中序遍历
/**
* 中序遍历——迭代
* @author Administrator
*
*/
public void midOrder(TreeNode node){
if(node == null){
return;
}else{
midOrder(node.leftChild);
System.out.println("midOrder data:"+node.getData());
midOrder(node.rightChild);
}
}
- 后序遍历
/**
* 后序遍历——迭代
* @author Administrator
*
*/
public void postOrder(TreeNode node){
if(node == null){
return;
}else{
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println("postOrder data:"+node.getData());
}
}