Java数据结构之二叉树
一、 现在开始学习新的数据结构——树(树形结构)
它是重要的非线性数据结构
,区别之前学习的链表和栈者(这些是一对一的线性结构),树是一对多
结构。
1、从树的定义开始
树(Tree)
是n(n>=0)个节点的有限集合。
在任意一棵非空树中有
- 有且只有一个特定的节点称为
根(Root)
- 当 n 大于1的时候,其余节点可以分为m(m>0)个互不相交的有限集合T1,T2,T3,… Tm;其中每个集合本身又可以是一棵树,称为子树。
2、有关树(Tree)的的相关术语:
- 节点(Node):包一个数据元素以及指向其他子树的指针(分支)。
- 节点的度(Degree):该节点拥有的子树的数量就称之为节点的度。
- 叶子(Leaf):度为0 的节点称之为叶子(终端节点)。
- 分支节点:度不为0 的节点称之为分支节点(非终端节点)。
- 孩子(child):节点的子树称之孩子(子节点)。
- 双亲(parent):节点的上一级节点称之该节点的双亲节点(父节点)。
- 祖先:从根节点到该节点的所经分支上的所有节点都称之为该节点的祖先节点。
- 子孙:属于节点的子树中的节点都叫子孙节点
- 节点的层次(Level):从根节点开始为第一层,根节点的的孩子为第二层。
- 堂兄弟:在同一层级的节点称之堂兄弟。
- 深度(depth):树中节点的最大层级称之为树的深度(或高度)。
(上图:解释了根节点、分支节点、叶子节点、节点的度)
(上图:解释了孩子节点、双亲节点、兄弟节点)
(上图:解释了兄弟节点,树的深度)
二、特殊的树之二叉树
在研究树形数据结构的时候,我们通常研究的是一种称为二叉树的抽象的数据类型。
3、二叉树(Binary Tree)的定义:
二叉树是一种特殊的树形结构,特点是每个节点最多只有两颗子树(也就是它的的度不能超过2),同时它也有左右之分,次序不能任意颠倒。
4、一些特殊的二叉树:
满二叉树
:该二叉树的所有节点都在最后一层上,同时节点总数 = 2^n-1(n为层数)。
(上图为满二叉树)
完全二叉树
:所有的节点树都在最后一层或者倒数第二层,其最后一层的子节点在左边连续,倒数第二层的节点在右边连续,这种情况称之为完全二叉树。
(上图为完全二叉树)
三、用Java实现二叉树
5、现在我们现在看看再java中是如何定义二叉树
5.1先定义二叉树中的节点:
public class BinaryNode <T>{
// 节点中存放的数据
private T data;
// 左子节点
private BinaryNode<T> left;
// 右子节点
private BinaryNode<T> right;
// 构造方法
public BinaryNode() {
}
public BinaryNode(T data) {
this.data = data;
this.left = null;
this.right = null;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public BinaryNode<T> getLeft() {
return left;
}
public void setLeft(BinaryNode<T> left) {
this.left = left;
}
public BinaryNode<T> getRight() {
return right;
}
public void setRight(BinaryNode<T> right) {
this.right = right;
}
@Override
// 只答应当前节点,避免对节点进行递归打印
public String toString() {
return "BinaryNode{" +
"data=" + data +
'}';
}
}
5.2 定义二叉树
// 定义树形结构的二叉树对象BinaryTree
public class BinaryTree<T> {
// 定义根节点
private BinaryNode<T> root;
// 构造方法
public BinaryTree() {
}
public BinaryTree(BinaryNode<T> root) {
this.root = root;
}
}
6、二叉树的遍历
二叉树的遍历方式有:前序遍历、中序遍历和后序遍历。
前序遍历:
- 先访问根节点
- 遍历左子节点
- 遍历右子节点
中序遍历:
- 遍历左子节点
- 先访问根节点
- 遍历右子节点
后序遍历:
- 遍历左子节点
- 遍历右子节点
- 先访问根节点
代码实现:
public class BinaryNode <T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 遍历的方法:
// 前序遍历:
public void preOrder(){
// 前序遍历 首先遍历的当前节点,现在的操作是输出当前节点
System.out.println(this.data);
// 接下处理左子节点,如果不为空递归调用该节点
if(this.left != null){
left.preOrder();
}
// 接下处理右边子节点,如果不为空递归调用该节点
if(this.right != null){
right.preOrder();
}
}
// 中序遍历
public void infixOrder(){
// 首先处理左子节点,如果不为空递归调用该节点
if(this.left != null){
left.infixOrder();
}
// 接着处理的当前节点,现在的操作是输出当前节点
System.out.println(this.data);
// 接下处理右边子节点,如果不为空递归调用该节点
if(this.right != null){
right.infixOrder();
}
}
// 后序遍历
public void postOrder(){
// 首先处理左子节点,如果不为空递归调用该节点
if(this.left != null){
left.postOrder();
}
// 接下处理右边子节点,如果不为空递归调用该节点
if(this.right != null){
right.postOrder();
}
// 最后处理当前节点,现在的操作是输出当前节点
System.out.println(this.data);
}
}
在BinaryTree编写对整个树结构进入遍历的方法
public class BinaryTree<T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 二叉树的前序遍历
public void pre(){
if( root != null){
// 调用的是BinaryNode的preOrder()方法
root.preOrder();
}
}
// 二叉树的中序遍历
public void infix(){
if( root != null){
// 调用的是BinaryNode的infixOrder()方法
root.infixOrder();
}
}
// 二叉树的中序遍历
public void post(){
if( root != null){
// 调用的是BinaryNode的postOrder()方法
root.postOrder();
}
}
}
7、二叉树的查找
二叉树的查找
说明:说到查找,一般都是全部的元素中查到指定的元素,这里就离不开对节点的遍历,所以在查找操作也有前序查找、中序查找、后续查找。都是在遍历的过程中添加比较的操作。
因为这里的data使用的是泛型的数据类型,所以这里需要我们传入一个Predicate接口实现类,该实现类必须提供判断条件。
public class BinaryNode <T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 查找的方法:
// 前序查找:
public BinaryNode<T> preOrderFind(Predicate<T> predicate){
BinaryNode<T> result = null;
// 前序查找: 首先比较的当前节点,现在符合查找的条件,就直接返回
if(predicate.test(this.data)){
result = this;
return result;
};
// 接下遍历左子节点进行查找,如果不为空递归调用该节点
// 如果进行查找
if(this.left != null){
result = left.preOrderFind(predicate);
}
// 在左子节点没有找到情况下才进行右子节点的查找
if(result == null && this.right != null){
result = right.preOrderFind(predicate);
}
// 返回查找的结果
return result;
}
// 中序查找:
public BinaryNode<T> infixOrderFind(Predicate<T> predicate){
BinaryNode<T> result = null;
// 先遍历左子节点进行查找,如果不为空递归调用该节点
// 如果进行查找
if(this.left != null){
result = left.infixOrderFind(predicate);
}
// 如果没有找到就比较当前节点,如果不为空递归调用该节点
if(result == null && predicate.test(this.data)){
result = this;
};
// 在左子节点没有找到情况下才进行右子节点的查找
if(result == null && this.right != null){
result = right.infixOrderFind(predicate);
}
// 返回查找的结果
return result;
}
// 后序查找:
public BinaryNode<T> postOrderFind(Predicate<T> predicate){
BinaryNode<T> result = null;
// 先遍历左子节点进行查找,如果不为空递归调用该节点
// 如果进行查找
if(this.left != null){
result = left.postOrderFind(predicate);
}
// 在左子节点没有找到情况下才进行右子节点的查找
if(result == null && this.right != null){
result = right.postOrderFind(predicate);
}
// 如果没有找到就比较当前节点,如果不为空递归调用该节点
if(result == null && predicate.test(this.data)){
result = this;
};
// 返回查找的结果
return result;
}
}
在BinaryTree编写对整个树结构进入遍历的方法
public class BinaryTree<T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 二叉树的前序遍历
public void pre(){
if( root != null){
// 调用的是BinaryNode的preOrder()方法
root.preOrder();
}
}
// 二叉树的中序遍历
public void infix(){
if( root != null){
// 调用的是BinaryNode的infixOrder()方法
root.infixOrder();
}
}
// 二叉树的中序遍历
public void post(){
if( root != null){
// 调用的是BinaryNode的postOrder()方法
root.postOrder();
}
}
}
8、删除指定的节点
删除指定节点,例如我们我们需要删除的节点是
删除节点里其实也是遍历整个整棵树,查找需要删除的节点,然后进行删除,需要注意的是在二叉树中的关系都是单向的,因此我们需要获取到该节点的父节点,然后将指向该节点的指针置空就可以。
首先看看再BinaryNode 类的操作:
public class BinaryNode <T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 删除节点,在删除的节点的时候也是对整棵树进行遍历的过程,这里有前序遍历比较容易一些
// 需要找到被删除节点的父节点
// 如果是也叶子节点就直接删除,如果是非叶子节点,就删除整棵子树
public void deleteNode(Predicate<T> predicate){
// 进入该方法,说明当前节点是不等被删除的数据的
// 存在左子节点
if(this.left != null){
// 如果符合条件
if(predicate.test(this.left.data)){
// 将其置空
this.left = null;
return;
}else{
this.left.deleteNode(predicate);
}
}
// 存在右子节点
if(this.right !=null){
// 如果符合条件
if(predicate.test(this.right.data)){
// 将其置空
this.right = null;
return;
}else{
this.right.deleteNode(predicate);
}
}
}
}
接下来就是在BinaryTree的操作了,这相当来说比较简单:
public class BinaryTree<T> {
// 省略了属性、seter、geter、构造方法、和其他无关的方法
// 删除指定的节点
public void delNode(Predicate<T> predicate){
// 如果为空,直接打返回
if(isEmpty()){
return;
}
// 如果根节点负荷被删除的条件,那么将整棵树置空,直接返回
if(predicate.test(root.getData())){
this.root = null;
return;
}else{
// 调用BinaryNode的deleteNode方法
root.deleteNode(predicate);
}
}
public boolean isEmpty(){
if(this.root == null){
return true;
}
return false;
}
}
9、二叉树的测试
public class BinaryTreeDemo {
public static void main(String[] args) {
// 创建一棵二叉树
BinaryTree<Integer> tree = new BinaryTree<>();
// 创建节点:
BinaryNode<Integer> node1 = new BinaryNode<>(1);
BinaryNode<Integer> node2 = new BinaryNode<>(2);
BinaryNode<Integer> node3 = new BinaryNode<>(3);
BinaryNode<Integer> node4 = new BinaryNode<>(4);
BinaryNode<Integer> node5 = new BinaryNode<>(5);
BinaryNode<Integer> node6 = new BinaryNode<>(6);
BinaryNode<Integer> node7 = new BinaryNode<>(7);
BinaryNode<Integer> node8 = new BinaryNode<>(8);
BinaryNode<Integer> node9 = new BinaryNode<>(9);
BinaryNode<Integer> node10 = new BinaryNode<>(10);
// 构建二叉树
tree.setRoot(node1);
node1.setLeft(node2);
node1.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
node3.setRight(node7);
node4.setLeft(node8);
node4.setRight(node9);
node5.setLeft(node10);
// 前序遍历:1、2、4、8、9、5、10、3、6、7
System.out.println("测试前序遍历");
tree.pre();
// 中序遍历:8、4、9、2、10、5、1、6、3、7
System.out.println("测试中序遍历");
tree.infix();
// 后续遍历:8、9、4、10、5、2、6、7、3、1
System.out.println("测试后序遍历");
tree.post();
// 测前序查找
System.out.println("测试前序查找");
BinaryNode<Integer> resultNode = tree.preFind((data)->{
return data.equals(8);});
BinaryNode<Integer> resultNodeNull = tree.preFind((data)->{
return data.equals(11);});
System.out.println("查找结果:"+resultNode);
System.out.println("查找结果:"+resultNodeNull);
// 测中序查找
System.out.println("测试中序查找");
BinaryNode<Integer> resultNode2 = tree.infixFind((data)->{
return data.equals(8);});
BinaryNode<Integer> resultNodeNull2 = tree.infixFind((data)->{
return data.equals(11);});
System.out.println("查找结果:"+resultNode2);
System.out.println("查找结果:"+resultNodeNull2);
// 测中序查找
System.out.println("测试后序查找");
BinaryNode<Integer> resultNode3 = tree.postFind((data)->{
return data.equals(8);});
BinaryNode<Integer> resultNodeNull3 = tree.postFind((data)->{
return data.equals(11);});
System.out.println("查找结果:"+resultNode3);
System.out.println("查找结果:"+resultNodeNull3);
// 测试删除节点
// 删除前
System.out.println("删除前:");
tree.pre();
tree.delNode((data)->data.equals(7));
System.out.println("删除后");
tree.pre();
//
System.out.println("删除根节点");
tree.delNode((data)->data.equals(1));
tree.pre();
System.out.println("二叉树"+(tree.isEmpty()?"为空":"不为空"));
}
}