数据结构 二叉树
二叉树
二叉树(BinaryTree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此理解二叉树显得特别重要。二叉树由一个节点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。图中展现了五种不同基本形态的二叉树。
概念
术语
-
结点:表示树中的元素,包括数据项及若干指向其子树的分支。
-
结点的度:结点所拥有的子树的个数称为该结点的度。
-
叶子结点:度为0的结点称为叶子结点,或者称为终端结点。
-
分支结点:度不为0的结点称为分支结点,或者称为非终端结点。一棵树的结点除叶子结点外,其余的都是分支结点。
-
孩子、双亲、兄弟:若在树中一个结点A的子树的根结点是B,则称B为A的孩子(也称子结点),称A为B的双亲(也称父节点)。具有同一个双亲的子结点互称为兄弟。
-
路径、路径长度:如果一棵树的一串结点n1,n2,…,nk有如下关系,即结点ni是ni+1的父结点(1≤i<k),就把n1,n2,…,nk称为一条由n1至nk的路径。这条路径的长度是k-1。
-
祖先、子孙:在树中,如果有一条路径从结点M到结点N,那么M就称为N的祖先,而N称为M的子孙。
-
结点的层数:规定树的根结点的层数为1,其余结点的层数等于它的双亲结点的层数加1。
-
树的深度:树中所有结点的最大层数称为树的深度。
-
树的度:树中各结点度的最大值称为该树的度
-
有序树和无序树:如果一棵树中结点的各子树从左到右是有次序的,即若交换了某结点各子树的相对位置,则构成不同的树,称这棵树为有序树;反之,则称为无序树。
-
森林:零棵或有限棵不相交的树的集合称为森林。自然界中树和森林是不同的概念,但在数据结构中,树和森林只有很小的差别。任何一棵树,删去根结点就变成了森林。
满二叉树
如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵二叉树称作满二叉树
完全二叉树
完全二叉树是一种叶子结点只能出现在最下层和次下层且最下层的叶子结点集中在树的左边的特殊二叉树
性质
- 一颗非空二叉树的第 i 层最多有 2i-1个节点
- 一棵深度为 k 的二叉树中,最多具有 2k-1 个结点
- 对于一棵非空的二叉树,如果叶子结点数为 n0,度数 1 为 2 的结点数为 n2,则有:n0=n2+1
- 具有 n 个结点的完全二叉树的深度 k 为⌊㏒2n⌋+12
- 对于具有 n 个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉树中的所有结点从 0 开始顺序编号,则对于任意的序号为 i 的结点,有:
- 如果 i>1,则序号为 i 的结点的父结点的序号为 ⌊(i-1)/2⌋;如果 i=0,则该结点是根结点,无父结点
- 如果 2i≤n,则序号为 i 的结点的左子结点的序号为 2i+1;如果 2i+1>n,则序号为 i 的结点无左子结点
- 如果 2i+1≤n,则序号为 i 的结点的右子结点的序号为 2i+2;如果 2i+2>n,则序号为 i 的结点无右子结点
存储结构
顺序存储
用一组连续的存储单元存放二叉树中的结点。一般是按照二叉树结点从上至下、从左到右的顺序存储
依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适
对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储
一棵深度为k的右单支树,只有k个结点,却需分配2k-1个存储单元
链式存储
链表中每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来给出该结点左孩子和右孩子所在的链结点的存储地址
class Node<T>{
public Node<T> lChild;
private T data;
public Node<T> rChild;
public Node(){
this.data = null;
this.lChild = null;
this.rChild = null;
}
public Node(T x){
this.data = x;
this.lChild = null;
this.rChild = null;
}
}
在Java中描述二叉链表的关键是确定二叉树的根,代码如下
class BinaryTree<T>{
public Node<T> root; //根节点
public BinaryTree(){
this.root = new Node<T>();
}
public BinaryTree(T x){
this.root = new Node<T>(x);
}
}
三叉链表存储
每个结点由四个域组成
这种存储结构既便于查找孩子结点,又便于查找双亲结点,但是,相对于二叉链表存储结构而言,它增加了空间开销
尽管在二叉链表中无法由结点直接找到其双亲,但由于二叉链表结构灵活,操作方便,对于一般情况的二叉树,甚至比顺序存储结构还节省空间。因此,二叉链表是最常用的二叉树存储方式
基本操作
class BinaryTree<T>{
private Node<T> root;
// 创建一颗空二叉树
public BinaryTree(){
}
// 创建一颗以数据元素x为根节点的二叉树
public BinaryTree(x){
}
// 在当前二叉树的父节点中插入一个新的左子节点,若已存在左子树,则将该左子树变成新左子节点的左子树
public boolean insertLeft(T x, Node<T> parent){
}
// 在当前二叉树的父节点中插入一个新的右子节点,若已存在右子树,则将该右子树变成新右子节点的右子树
public boolean insertRight(T x, Node<T> parent){
}
// 删除在当前二叉树的父节点中的左子树
public boolean deleteLeft(Node<T> parent){
}
// 删除在当前二叉树的父节点中的右子树
pulbic boolean deleteRight(Node<T> parent){
}
// 在当前二叉树中查找数据x
public boolean search(T x){
}
// 按某种方式遍历当前二叉树的全部节点
public void traversal(int i){