数据结构(java)之二叉树
Posted hsiaolung
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构(java)之二叉树相关的知识,希望对你有一定的参考价值。
1. 二叉树的逻辑结构
a) 定义:二叉树是一种非线性结构,是n个有限元素的集合,由一个根节点与两个不相交的左子树和右子树组成,当集合为空又称为空二叉树,一个元素也成为一个节点。二叉树是有序的。
b) 基本概念
i. 节点的度:节点下子树的个数,范围为0-2
ii. 叶节点:度为0的节点
iii. 分支节点:度不为0的节点
iv. 路径、路径长度:设n(i+1)是n(i)的父节点,则n(1)…n(k)称为一条路径,路径长度为k-1
v. 节点层数:从根节点开始到叶节点,每个节点层数等于其父节点的节点层数+1,根节点的节点层数位1
vi. 数的深度:叶节点的最大节点层数
vii. 数的度:各节点度的最大值,数的度最大位2
viii. 满二叉树:所有分支节点都有左子树和右子树且叶节点都在同一层的二叉树
ix. 完全二叉树:一棵深度为k有n个节点的二叉树,对树中的节点按照从上到下,从左到右的顺序进行编号,树中编号为i的节点与满二叉树编号为i的节点位置相同则称为完全二叉树。特点是:叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在左边。(可以理解为在一棵深度为k满二叉树的第k层(共有2^(k-1)个节点)的最右边第一个节点开始向左去掉0或(2^(k-1))或0-2^(k-1)之间的奇数个节点)
c) 主要性质
i. 非空二叉树的第k层最多有2^(k-1)个节点
ii. 一棵深度为k的二叉树最有有2^k-1个节点
iii. 对于一棵非空二叉树:叶子节点数=度为2的节点数+1
iv. 具有n个节点的满二叉树的深度为log(n+1),具有n个节点的完全二叉树的深度为log(n)+1
v. 对于具有n个节点的完全二叉树,按照从上到下从左到右的顺序进行排序,对于序号为i的节点
[1] 若i>1,则双亲的序号为i/2的整数部分,i=1,则该节点是树的根节点;
[2] 若2i<n,则i的左孩子为2i,若2i>n,则该节点无左孩子
[3] 若2i+1<n,则i的右孩子为2i+1,若2i+1>n,则该节点无右孩子
d) 二叉树的抽象数据类型
i. 数据元素:具有相同类型的数据集合
ii. 数据结构:除根节点外,每个节点都有一个直接前驱,除叶节点外,每个节点都有1-2个直接后驱
iii. 数据操作:一般二叉树的操作定义在IBiTree接口中
publicinterface IBiTree<E> {
//返回根节点
TreeNode<E> getRoot();
//返回fnode的左子树
TreeNode<E> getLeftChild(TreeNode<E> fnode);
//返回fnode的右子树
TreeNode<E> getRightChild(TreeNode<E> fnode);
//向fnode左子树添加节点
void insertLeftChild(TreeNode<E> lcnode,TreeNode<E> fnode);
//向fnode的右子树添加节点
void insertRightChild(TreeNode<E> rcnode,TreeNode<E> fnode);
//删除fnode的左子树
TreeNode<E> deleteLeftChild(TreeNode<E> fnode);
//删除fnode的右子树
TreeNode<E> deleteRightChild(TreeNode<E> fnode);
//查找指定节点
TreeNode<E> find(TreeNode<E> target);
//以第前序方式遍历二叉树
void pretraverse(TreeNode<E> root);
//以中序方式遍历二叉树
void intraverse(TreeNode<E> root);
//以后序遍历二叉树
void posttraverse(TreeNode<E> root);
//以层序方式遍历二叉树
void leveltraverse();
}
2. 二叉树的实现之顺序存储
a) 顺序存储结构:用一组连续的数组来存储二叉树数据,通常按照从左到右、从上到下的顺序进行存储,对于一般的二叉树存储在数组中,下标不能很好的反映二叉树中的节点逻辑关系,需要在数组中添加空节点,这样比较浪费存储空间。因此,一般用数组存储满二叉树或者完全二叉树比较合适。
3. 二叉树的实现之链式存储
a) 链式存储结构
i. 二叉链式存储结构:每个节点包括存储数据的元素,指向左子树的left和指向右子树的right,当左子树或右子树不存在时,将left或者right设置为null
ii. 三叉链式存储结构:相比二叉链式存储结构增加了指向父节点的parent,最常用的时二叉链式存储结构
b) 代码实现
//定义树节点,包括数据,权值,左子树,右子树
publicclass TreeNode<E> {
private E e;
privateintweight;
private TreeNode<E> left;
private TreeNode<E> right;
//不带权值的构造方法
public TreeNode(E e) {
super();
this.e = e;
this.weight=0;
this.left = null;
this.right = null;
}
//带权值的构造方法
public TreeNode(E e, intweight) {
super();
this.e = e;
this.weight = weight;
}
public E getE() {
returne;
}
publicvoid setE(E e) {
this.e = e;
}
public TreeNode<E> getLeft() {
returnleft;
}
publicvoid setLeft(TreeNode<E> left) {
this.left = left;
}
public TreeNode<E> getRight() {
returnright;
}
publicvoid setRight(TreeNode<E> right) {
this.right = right;
}
}
//创建普通二叉树类
publicclass MyLinkTree<E> implements IBiTree<E> {
private TreeNode<E> root;
//初始化二叉树
public MyLinkTree(TreeNode<E> root) {
super();
this.root = root;
}
//返回树的根节点
@Override
public TreeNode<E> getRoot() {
returnthis.root;
}
//返回指定节点的左孩子
@Override
public TreeNode<E> getLeftChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
returntemp.getLeft();
}
//返回指定节点的右孩子
@Override
public TreeNode<E> getRightChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
returntemp.getRight();
}
//在指定节点插入左孩子,如过其左孩子已经存在,将原近年来的左孩子作为新建左孩子的左子树
@Override
publicvoid insertLeftChild(TreeNode<E> lcnode, TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
if(temp.getLeft()!=null) {
TreeNode<E> left=temp.getLeft();
temp.setLeft(lcnode);
lcnode.setLeft(left);
}
else
temp.setLeft(lcnode);
}
//在指定节点插入右孩子,如果其右孩子存在,将其原来的右孩子作为新建右孩子的右子树
@Override
publicvoid insertRightChild(TreeNode<E> rcnode, TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode); //找到指定节点
if(temp.getRight()!=null) {
TreeNode<E> right=temp.getRight();
temp.setRight(rcnode);
rcnode.setRight(right);
}
else
temp.setRight(rcnode);
}
//删除指定节点的左子树(将其左子树下的所有节点删除)
@Override
public TreeNode<E> deleteLeftChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
TreeNode<E> lcnode=temp.getLeft();
temp.setLeft(null);
returnlcnode;
}
//删除指定节点的右子树(将其右子树下的所有节点删除)
@Override
public TreeNode<E> deleteRightChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
TreeNode<E> rcnode=temp.getRight();
temp.setRight(null);
returnrcnode;
}
//利用层序遍历找到指定节点
public TreeNode<E> find(TreeNode<E> target){
TreeNode<E> temp=this.root;
IQueue<TreeNode> queue=new MylinkQueue<>();
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
if(temp==target)
returntemp;
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
returnnull;
}
//前序遍历
@Override
publicvoid pretraverse(TreeNode<E> root) {
if(root!=null) {
System.out.println(root.getE());
pretraverse(root.getLeft());
pretraverse(root.getRight());
}
}
//中序遍历
@Override
publicvoid intraverse(TreeNode<E> root) {
if(root!=null) {
intraverse(root.getLeft());
System.out.println(root.getE());
intraverse(root.getRight());
}
}
//后续遍历
@Override
publicvoid posttraverse(TreeNode<E> root) {
if(root!=null) {
posttraverse(root.getLeft());
posttraverse(root.getRight());
System.out.println(root.getE());
}
}
//层序遍历
@Override
publicvoid leveltraverse() {
IQueue<TreeNode<E>> queue=new MylinkQueue<>();
TreeNode<E> temp=this.root;
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
System.out.println(temp.getE());
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
}
}
4. 二叉排序树(二叉查找树)
a) 性质:左子树的值要小于其根节点的值,右子树的值要大于其根节点值
b) 代码实现如下
//创建二叉查找树,一般二叉查找树的数据元素类型都是Integer
publicclass MyBinaryTree {
private TreeNode<Integer> root;
//初始化二叉查找树
public MyBinaryTree(inte) {
super();
this.root = new TreeNode<Integer>(e);
}
//返回树的根节点
public TreeNode<Integer> getRoot() {
returnthis.root;
}
//将数据插入到二叉查找树的指定位置
publicvoid insert(inte) {
TreeNode<Integer> temp=this.root;
TreeNode<Integer> node=new TreeNode<Integer>(e);
while(temp!=null) {
//先比较数值与节点大小,比节点值大向节点的右子树搜索,比节点小向左子树搜索
if(e>temp.getE()) {
//如果节点的右子树为空,则在该节点将新建节点插入作为右子树并返回
if(temp.getRight()==null) {
temp.setRight(node);
return;
}
else
temp=temp.getRight();
}
else {
//如果节点的左子树为空,则在该节点将新建节点插入作为左子树并返回
if(temp.getLeft()==null) {
temp.setLeft(node);
return;
}
else
temp=temp.getLeft();
}
}
}
//先序遍历
publicvoid pretraverse(TreeNode<Integer> root) {
if(root!=null) {
System.out.println(root.getE());
pretraverse(root.getLeft());
pretraverse(root.getRight());
}
}
//中序遍历
publicvoid intraverse(TreeNode<Integer> root) {
if(root!=null) {
intraverse(root.getLeft());
System.out.println(root.getE());
intraverse(root.getRight());
}
}
//后续遍历
publicvoid posttraverse(TreeNode<Integer> root) {
if(root!=null) {
posttraverse(root.getLeft());
posttraverse(root.getRight());
System.out.println(root.getE());
}
}
//层序遍历
publicvoid leveltraverse() {
IQueue<TreeNode<Integer>> queue=new MylinkQueue<>();
TreeNode<Integer> temp=this.root;
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
System.out.println(temp.getE());
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
}
}
5. 哈夫曼树
a) 概念:也叫最优二叉树,指由根节点到各个节点的路径长度乘以节点的权值,使得带权路径之和最小。
b) 要使得带权路径之和最小,则应该使权值大的节点离根节点近,权值小的节点离根节点远
c) 步骤:
i. 首先以每一个节点为根节点建立一棵树,形成一个集合
ii. 选出这些树中根节点权值最小的和次最小的两棵树作为左子树和右子树,两棵子树权值和为根节点形成一棵新的树
iii. 在原集合中删除作为左右子树的两棵树,并将新的树加入集合
iv. 重复ii,iii直到集合中只剩下一棵树
v. 假设每棵左子树到其父节点的边为0,右子树到其父节点的边1,按照先序遍历或者层序遍历求根节点到每个节点边的序列作为字符编码
1. 二叉树的逻辑结构
a) 定义:二叉树是一种非线性结构,是n个有限元素的集合,由一个根节点与两个不相交的左子树和右子树组成,当集合为空又称为空二叉树,一个元素也成为一个节点。二叉树是有序的。
b) 基本概念
i. 节点的度:节点下子树的个数,范围为0-2
ii. 叶节点:度为0的节点
iii. 分支节点:度不为0的节点
iv. 路径、路径长度:设n(i+1)是n(i)的父节点,则n(1)…n(k)称为一条路径,路径长度为k-1
v. 节点层数:从根节点开始到叶节点,每个节点层数等于其父节点的节点层数+1,根节点的节点层数位1
vi. 数的深度:叶节点的最大节点层数
vii. 数的度:各节点度的最大值,数的度最大位2
viii. 满二叉树:所有分支节点都有左子树和右子树且叶节点都在同一层的二叉树
ix. 完全二叉树:一棵深度为k有n个节点的二叉树,对树中的节点按照从上到下,从左到右的顺序进行编号,树中编号为i的节点与满二叉树编号为i的节点位置相同则称为完全二叉树。特点是:叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在左边。(可以理解为在一棵深度为k满二叉树的第k层(共有2^(k-1)个节点)的最右边第一个节点开始向左去掉0或(2^(k-1))或0-2^(k-1)之间的奇数个节点)
c) 主要性质
i. 非空二叉树的第k层最多有2^(k-1)个节点
ii. 一棵深度为k的二叉树最有有2^k-1个节点
iii. 对于一棵非空二叉树:叶子节点数=度为2的节点数+1
iv. 具有n个节点的满二叉树的深度为log(n+1),具有n个节点的完全二叉树的深度为log(n)+1
v. 对于具有n个节点的完全二叉树,按照从上到下从左到右的顺序进行排序,对于序号为i的节点
[1] 若i>1,则双亲的序号为i/2的整数部分,i=1,则该节点是树的根节点;
[2] 若2i<n,则i的左孩子为2i,若2i>n,则该节点无左孩子
[3] 若2i+1<n,则i的右孩子为2i+1,若2i+1>n,则该节点无右孩子
d) 二叉树的抽象数据类型
i. 数据元素:具有相同类型的数据集合
ii. 数据结构:除根节点外,每个节点都有一个直接前驱,除叶节点外,每个节点都有1-2个直接后驱
iii. 数据操作:一般二叉树的操作定义在IBiTree接口中
publicinterface IBiTree<E> {
//返回根节点
TreeNode<E> getRoot();
//返回fnode的左子树
TreeNode<E> getLeftChild(TreeNode<E> fnode);
//返回fnode的右子树
TreeNode<E> getRightChild(TreeNode<E> fnode);
//向fnode左子树添加节点
void insertLeftChild(TreeNode<E> lcnode,TreeNode<E> fnode);
//向fnode的右子树添加节点
void insertRightChild(TreeNode<E> rcnode,TreeNode<E> fnode);
//删除fnode的左子树
TreeNode<E> deleteLeftChild(TreeNode<E> fnode);
//删除fnode的右子树
TreeNode<E> deleteRightChild(TreeNode<E> fnode);
//查找指定节点
TreeNode<E> find(TreeNode<E> target);
//以第前序方式遍历二叉树
void pretraverse(TreeNode<E> root);
//以中序方式遍历二叉树
void intraverse(TreeNode<E> root);
//以后序遍历二叉树
void posttraverse(TreeNode<E> root);
//以层序方式遍历二叉树
void leveltraverse();
}
2. 二叉树的实现之顺序存储
a) 顺序存储结构:用一组连续的数组来存储二叉树数据,通常按照从左到右、从上到下的顺序进行存储,对于一般的二叉树存储在数组中,下标不能很好的反映二叉树中的节点逻辑关系,需要在数组中添加空节点,这样比较浪费存储空间。因此,一般用数组存储满二叉树或者完全二叉树比较合适。
3. 二叉树的实现之链式存储
a) 链式存储结构
i. 二叉链式存储结构:每个节点包括存储数据的元素,指向左子树的left和指向右子树的right,当左子树或右子树不存在时,将left或者right设置为null
ii. 三叉链式存储结构:相比二叉链式存储结构增加了指向父节点的parent,最常用的时二叉链式存储结构
b) 代码实现
//定义树节点,包括数据,权值,左子树,右子树
publicclass TreeNode<E> {
private E e;
privateintweight;
private TreeNode<E> left;
private TreeNode<E> right;
//不带权值的构造方法
public TreeNode(E e) {
super();
this.e = e;
this.weight=0;
this.left = null;
this.right = null;
}
//带权值的构造方法
public TreeNode(E e, intweight) {
super();
this.e = e;
this.weight = weight;
}
public E getE() {
returne;
}
publicvoid setE(E e) {
this.e = e;
}
public TreeNode<E> getLeft() {
returnleft;
}
publicvoid setLeft(TreeNode<E> left) {
this.left = left;
}
public TreeNode<E> getRight() {
returnright;
}
publicvoid setRight(TreeNode<E> right) {
this.right = right;
}
}
//创建普通二叉树类
publicclass MyLinkTree<E> implements IBiTree<E> {
private TreeNode<E> root;
//初始化二叉树
public MyLinkTree(TreeNode<E> root) {
super();
this.root = root;
}
//返回树的根节点
@Override
public TreeNode<E> getRoot() {
returnthis.root;
}
//返回指定节点的左孩子
@Override
public TreeNode<E> getLeftChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
returntemp.getLeft();
}
//返回指定节点的右孩子
@Override
public TreeNode<E> getRightChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
returntemp.getRight();
}
//在指定节点插入左孩子,如过其左孩子已经存在,将原近年来的左孩子作为新建左孩子的左子树
@Override
publicvoid insertLeftChild(TreeNode<E> lcnode, TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
if(temp.getLeft()!=null) {
TreeNode<E> left=temp.getLeft();
temp.setLeft(lcnode);
lcnode.setLeft(left);
}
else
temp.setLeft(lcnode);
}
//在指定节点插入右孩子,如果其右孩子存在,将其原来的右孩子作为新建右孩子的右子树
@Override
publicvoid insertRightChild(TreeNode<E> rcnode, TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);//找到指定节点
if(temp.getRight()!=null) {
TreeNode<E> right=temp.getRight();
temp.setRight(rcnode);
rcnode.setRight(right);
}
else
temp.setRight(rcnode);
}
//删除指定节点的左子树(将其左子树下的所有节点删除)
@Override
public TreeNode<E> deleteLeftChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
TreeNode<E> lcnode=temp.getLeft();
temp.setLeft(null);
returnlcnode;
}
//删除指定节点的右子树(将其右子树下的所有节点删除)
@Override
public TreeNode<E> deleteRightChild(TreeNode<E> fnode) {
TreeNode<E> temp=find(fnode);
TreeNode<E> rcnode=temp.getRight();
temp.setRight(null);
returnrcnode;
}
//利用层序遍历找到指定节点
public TreeNode<E> find(TreeNode<E> target){
TreeNode<E> temp=this.root;
IQueue<TreeNode> queue=new MylinkQueue<>();
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
if(temp==target)
returntemp;
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
returnnull;
}
//前序遍历
@Override
publicvoid pretraverse(TreeNode<E> root) {
if(root!=null) {
System.out.println(root.getE());
pretraverse(root.getLeft());
pretraverse(root.getRight());
}
}
//中序遍历
@Override
publicvoid intraverse(TreeNode<E> root) {
if(root!=null) {
intraverse(root.getLeft());
System.out.println(root.getE());
intraverse(root.getRight());
}
}
//后续遍历
@Override
publicvoid posttraverse(TreeNode<E> root) {
if(root!=null) {
posttraverse(root.getLeft());
posttraverse(root.getRight());
System.out.println(root.getE());
}
}
//层序遍历
@Override
publicvoid leveltraverse() {
IQueue<TreeNode<E>> queue=new MylinkQueue<>();
TreeNode<E> temp=this.root;
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
System.out.println(temp.getE());
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
}
}
4. 二叉排序树(二叉查找树)
a) 性质:左子树的值要小于其根节点的值,右子树的值要大于其根节点值
b) 代码实现如下
//创建二叉查找树,一般二叉查找树的数据元素类型都是Integer
publicclass MyBinaryTree {
private TreeNode<Integer> root;
//初始化二叉查找树
public MyBinaryTree(inte) {
super();
this.root = new TreeNode<Integer>(e);
}
//返回树的根节点
public TreeNode<Integer> getRoot() {
returnthis.root;
}
//将数据插入到二叉查找树的指定位置
publicvoid insert(inte) {
TreeNode<Integer> temp=this.root;
TreeNode<Integer> node=new TreeNode<Integer>(e);
while(temp!=null) {
//先比较数值与节点大小,比节点值大向节点的右子树搜索,比节点小向左子树搜索
if(e>temp.getE()) {
//如果节点的右子树为空,则在该节点将新建节点插入作为右子树并返回
if(temp.getRight()==null) {
temp.setRight(node);
return;
}
else
temp=temp.getRight();
}
else {
//如果节点的左子树为空,则在该节点将新建节点插入作为左子树并返回
if(temp.getLeft()==null) {
temp.setLeft(node);
return;
}
else
temp=temp.getLeft();
}
}
}
//先序遍历
publicvoid pretraverse(TreeNode<Integer> root) {
if(root!=null) {
System.out.println(root.getE());
pretraverse(root.getLeft());
pretraverse(root.getRight());
}
}
//中序遍历
publicvoid intraverse(TreeNode<Integer> root) {
if(root!=null) {
intraverse(root.getLeft());
System.out.println(root.getE());
intraverse(root.getRight());
}
}
//后续遍历
publicvoid posttraverse(TreeNode<Integer> root) {
if(root!=null) {
posttraverse(root.getLeft());
posttraverse(root.getRight());
System.out.println(root.getE());
}
}
//层序遍历
publicvoid leveltraverse() {
IQueue<TreeNode<Integer>> queue=new MylinkQueue<>();
TreeNode<Integer> temp=this.root;
queue.enqueue(temp);
while(!queue.isEmpty()) {
temp=queue.dequeue();
System.out.println(temp.getE());
if(temp.getLeft()!=null)
queue.enqueue(temp.getLeft());
if(temp.getRight()!=null)
queue.enqueue(temp.getRight());
}
}
}
5. 哈夫曼树
a) 概念:也叫最优二叉树,指由根节点到各个节点的路径长度乘以节点的权值,使得带权路径之和最小。
b) 要使得带权路径之和最小,则应该使权值大的节点离根节点近,权值小的节点离根节点远
c) 步骤:
i. 首先以每一个节点为根节点建立一棵树,形成一个集合
ii. 选出这些树中根节点权值最小的和次最小的两棵树作为左子树和右子树,两棵子树权值和为根节点形成一棵新的树
iii. 在原集合中删除作为左右子树的两棵树,并将新的树加入集合
iv. 重复ii,iii直到集合中只剩下一棵树
v. 假设每棵左子树到其父节点的边为0,右子树到其父节点的边1,按照先序遍历或者层序遍历求根节点到每个节点边的序列作为字符编码
以上是关于数据结构(java)之二叉树的主要内容,如果未能解决你的问题,请参考以下文章