一段来自百度百科的对二叉树的解释:

  在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
  一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有n个节点的完全二叉树的深度为floor(log2n)+1。深度为k的完全二叉树,至少有2k-1个叶子节点,至多有2k-1个节点。
  二叉树的结构:

  二叉树节点的声明:

static final class Entry<T extends Comparable<T>>{
//保存的数据
private T item;
//左子树
private Entry<T> left;
//右子树
private Entry<T> right;
//父节点
private Entry<T> parent;
Entry(T item,Entry<T> parent){
this.item = item;
this.parent = parent;
}
}

  类属性:

 //根节点
private Entry<T> root;
//数据量
private int size = ;

  二叉树添加数据:

/**
* 添加元素
* @param item 待添加元素
* @return 已添加元素
*/
public T put(T item){
//每次添加数据的时候都是从根节点向下遍历
Entry<T> t = root;
if (t == null){
//当前的叉树树的为空,将新节点设置为根节点,父节点为null
root = new Entry<>(item,null);
       size++; 
return root.item;
}
//自然排序结果,如果传入的数据小于当前节点返回-1,大于当前节点返回1,否则返回0
int ret = ;
//记录父节点
Entry<T> p = t;
while (t != null){
//与当前节点比较
ret = item.compareTo(t.item);
p = t;
//插入节点比当前节点小,把当前节点设置为左子节点,然后与左子比较,以此类推找到合适的位置
if (ret < )
t = t.left;
//大于当前节点
else if (ret > )
t = t.right;
else {
//相等就把旧值覆盖掉
t.item = item;
return t.item;
}
}
//创建新节点
Entry<T> e = new Entry<>(item,p);
//根据比较结果将新节点放入合适的位置
if (ret < )
p.left = e;
else
p.right = e;
     size++;
return e.item;
}

  在put的过程中,使用Comparable<T>中的compareTo来比较两个元素的大小的,所以在二叉树中存储的元素必须要继承Comparable<T> 类,覆写compareTo方法。

  二叉树删除数据

/**
* 删除元素
* 删除元素如果细分的话,可以分为4中:没有子节点,只有左节点,只有右节点,有两个子节点
* 1)没有子节点这种情况比较简单,直接删除就可以了
* 2)只有左节点或右节点,这两种情况处理方式是一致的,只是方向相反,所以在一起讲了,
* 将删除节点的父节点的左节点(右节点)指向删除节点的子节点,将左节点(右节点)指向删除节点的父节点
* 3)有两个子节点,这种情况相对来说比较复杂一点:
* 找到删除节点的前驱节点或后继节点,然后将前驱或后继节点的值赋给删除节点,然后将前驱或后继节点删除。本质是删除前驱或后继节点
* 前驱节点的特点:
* 1)删除的左子节点没有右子节点,那么左子节点即为前驱节点
* 2)删除节点的左子节点有右子节点,那么最右边的最后一个节点即为前驱节点
* 后继节点的特点:
* 与前驱节点刚好相反,总是右子节点,或则右子节点的最左子节点;例如:删除节点为c ,那么前驱节点为 m,后继节点为n
* a
* / \
* b c
* / \ / \
* d e f g
* / \ / \ / \ / \
* @param item 删除元素 h i j k l m n o
* @return 删除结果
*/
public boolean remove(T item){
//获取删除节点
Entry<T> delEntry = getEntry(item);
if (delEntry == null) return false;
//删除节点的父节点
Entry<T> p = delEntry.parent;
size--;
//情况1:没有子节点
if (delEntry.left == null && delEntry.right == null){
//删除节点为根节点
if (delEntry == root){
root = null;
}else {//非根节点
//删除的是父节点的左节点
if (delEntry == p.left){
p.left = null;
}else {//删除右节点
p.right = null;
}
}
//情况2:删除的节点只有左节点
}else if (delEntry.right == null){
Entry<T> lc = delEntry.left;
//删除的节点为根节点,将删除节点的左节点设置成根节点
if (p == null) {
lc.parent = null;
root = lc;
} else {//非根节点
if (delEntry == p.left){//删除左节点
p.left = lc;
}else {//删除右节点
p.right = lc;
}
lc.parent = p;
}
//情况3:删除节点只有右节点
}else if (delEntry.left == null){
Entry<T> rc = delEntry.right;
//删除根节点
if (p == null) {
rc.parent = null;
root = rc;
}else {//删除非根节点
if (delEntry == p.left)
p.left = rc;
else
p.right = rc;
rc.parent = p;
}
//情况4:删除节点有两个子节点
}else {//有两个节点,找到后继节点,将值赋给删除节点,然后将后继节点删除掉即可
Entry<T> successor = successor(delEntry);//获取到后继节点
delEntry.item = successor.item;
//后继节点为右子节点
if (delEntry.right == successor){
//右子节点有右子节点
if (successor.right != null) {
delEntry.right = successor.right;
successor.right.parent = delEntry;
}else {//右子节点没有子节点
delEntry.right = null;
}
}else {//后继节点必定是左节点
successor.parent.left = null;
}
return true;
}
//让gc回收
delEntry.parent = null;
delEntry.left = null;
delEntry.right = null;
return true;
} /**
* 获取节点节点
* @param item 获取节点
* @return 获取到的节点,可能为null
*/
private Entry<T> getEntry(T item){
Entry<T> t = root;
int ret;
//从根节点开始遍历
for (;t != null;){
ret = item.compareTo(t.item);
if (ret < )
t = t.left;
else if (ret > )
t = t.right;
else
return t;
}
return null;
} /**
* 查找后继节点
* @param delEntry 删除节点
* @return 后继节点
*/
private Entry<T> successor(Entry<T> delEntry) {
Entry<T> r = delEntry.right;//r节点必定不为空
while (r.left != null){
r = r.left;
}
return r;
}

  二叉树获取节点

    /**
* 判断是否存在该元素
* @param item 查找元素
* @return 结果
*/
public boolean contains(T item){
return getEntry(item) != null;
} /**
* 获取节点节点
* @param item 获取节点
* @return 获取到的节点,可能为null
*/
private Entry<T> getEntry(T item){
Entry<T> t = root;
int ret;
//从根节点开始遍历
for (;t != null;){
ret = item.compareTo(t.item);
if (ret < )
t = t.left;
else if (ret > )
t = t.right;
else
return t;
}
return null;
}

  因为我这个例子是存储一个元素,获取到的元素和传进去的元素是一致的,所以我用contains方法来判断返回true即表示获取成功了,不返回获取到的结果了。当然,如果将entry存储的元素改为kv形式的话,就可以使用get方法了。

  二叉树的遍历

  二叉树的遍历可以分为三种:前序遍历、中序遍历和后续遍历,其中中序遍历是最常用的遍历方式,因为它遍历出来的结果的升序的。

  前序遍历:

     /**
* 前序遍历
* @param e 开始遍历元素
*/
public void prevIterator(Entry<T> e){
if (e != null) {
System.out.print(e.item + " ");
prevIterator(e.left);
prevIterator(e.right);
}
}

  中序遍历:

   /**
* 中序遍历
* @param e
*/
public void midIterator(Entry<T> e){
if (e != null){
midIterator(e.left);
System.out.print(e.item + " ");
midIterator(e.right);
}
}

  后序遍历:

    /**
* 后续遍历
* @param e 开始遍历元素
*/
public void subIterator(Entry<T> e){
if (e != null) {
subIterator(e.left);
subIterator(e.right);
System.out.print(e.item + " ");
}
}

  demo完整的代码:也可以到我的github中下载代码:https://github.com/rainple1860/MyCollection

package com.rainple.collections;

/**
* 二叉树
* @param <T>
*/
public class BinaryTree<T extends Comparable<T>> { //根节点
private Entry<T> root;
//数据量
private int size = ; public BinaryTree(){} /**
* 添加元素
* @param item 待添加元素
* @return 已添加元素
*/
public T put(T item){
//每次添加数据的时候都是从根节点向下遍历
Entry<T> t = root;
size++;
if (t == null){
//当前的叉树树的为空,将新节点设置为根节点,父节点为null
root = new Entry<>(item,null);
return root.item;
}
//自然排序结果,如果传入的数据小于当前节点返回-1,大于当前节点返回1,否则返回0
int ret = ;
//记录父节点
Entry<T> p = t;
while (t != null){
//与当前节点比较
ret = item.compareTo(t.item);
p = t;
//插入节点比当前节点小,把当前节点设置为左子节点,然后与左子比较,以此类推找到合适的位置
if (ret < )
t = t.left;
//大于当前节点
else if (ret > )
t = t.right;
else {
//相等就把旧值覆盖掉
t.item = item;
return t.item;
}
}
//创建新节点
Entry<T> e = new Entry<>(item,p);
//根据比较结果将新节点放入合适的位置
if (ret < )
p.left = e;
else
p.right = e;
return e.item;
} public void print(){
midIterator(root);
} /**
* 中序遍历
* @param e
*/
public void midIterator(Entry<T> e){
if (e != null){
midIterator(e.left);
System.out.print(e.item + " ");
midIterator(e.right);
}
} /**
* 获取根节点
* @return 根节点
*/
public Entry<T> getRoot(){return root;} /**
* 前序遍历
* @param e 开始遍历元素
*/
public void prevIterator(Entry<T> e){
if (e != null) {
System.out.print(e.item + " ");
prevIterator(e.left);
prevIterator(e.right);
}
} /**
* 后续遍历
* @param e 开始遍历元素
*/
public void subIterator(Entry<T> e){
if (e != null) {
subIterator(e.left);
subIterator(e.right);
System.out.print(e.item + " ");
}
} /**
* 获取节点节点
* @param item 获取节点
* @return 获取到的节点,可能为null
*/
private Entry<T> getEntry(T item){
Entry<T> t = root;
int ret;
//从根节点开始遍历
for (;t != null;){
ret = item.compareTo(t.item);
if (ret < )
t = t.left;
else if (ret > )
t = t.right;
else
return t;
}
return null;
} /**
* 判断是否存在该元素
* @param item 查找元素
* @return 结果
*/
public boolean contains(T item){
return getEntry(item) != null;
} /**
* 删除元素
* 删除元素如果细分的话,可以分为4中:没有子节点,只有左节点,只有右节点,有两个子节点
* 1)没有子节点这种情况比较简单,直接删除就可以了
* 2)只有左节点或右节点,这两种情况处理方式是一致的,只是方向相反,所以在一起讲了,
* 将删除节点的父节点的左节点(右节点)指向删除节点的子节点,将左节点(右节点)指向删除节点的父节点
* 3)有两个子节点,这种情况相对来说比较复杂一点:
* 找到删除节点的前驱节点或后继节点,然后将前驱或后继节点的值赋给删除节点,然后将前驱或后继节点删除。本质是删除前驱或后继节点
* 前驱节点的特点:
* 1)删除的左子节点没有右子节点,那么左子节点即为前驱节点
* 2)删除节点的左子节点有右子节点,那么最右边的最后一个节点即为前驱节点
* 后继节点的特点:
* 与前驱节点刚好相反,总是右子节点,或则右子节点的最左子节点;例如:删除节点为c ,那么前驱节点为 m,后继节点为n
* a
* / \
* b c
* / \ / \
* d e f g
* / \ / \ / \ / \
* @param item 删除元素 h i j k l m n o
* @return 删除结果
*/
public boolean remove(T item){
//获取删除节点
Entry<T> delEntry = getEntry(item);
if (delEntry == null) return false;
//删除节点的父节点
Entry<T> p = delEntry.parent;
size--;
//情况1:没有子节点
if (delEntry.left == null && delEntry.right == null){
//删除节点为根节点
if (delEntry == root){
root = null;
}else {//非根节点
//删除的是父节点的左节点
if (delEntry == p.left){
p.left = null;
}else {//删除右节点
p.right = null;
}
}
//情况2:删除的节点只有左节点
}else if (delEntry.right == null){
Entry<T> lc = delEntry.left;
//删除的节点为根节点,将删除节点的左节点设置成根节点
if (p == null) {
lc.parent = null;
root = lc;
} else {//非根节点
if (delEntry == p.left){//删除左节点
p.left = lc;
}else {//删除右节点
p.right = lc;
}
lc.parent = p;
}
//情况3:删除节点只有右节点
}else if (delEntry.left == null){
Entry<T> rc = delEntry.right;
//删除根节点
if (p == null) {
rc.parent = null;
root = rc;
}else {//删除非根节点
if (delEntry == p.left)
p.left = rc;
else
p.right = rc;
rc.parent = p;
}
//情况4:删除节点有两个子节点
}else {//有两个节点,找到后继节点,将值赋给删除节点,然后将后继节点删除掉即可
Entry<T> successor = successor(delEntry);//获取到后继节点
delEntry.item = successor.item;
//后继节点为右子节点
if (delEntry.right == successor){
//右子节点有右子节点
if (successor.right != null) {
delEntry.right = successor.right;
successor.right.parent = delEntry;
}else {//右子节点没有子节点
delEntry.right = null;
}
}else {//后继节点必定是左节点
successor.parent.left = null;
}
return true;
}
//让gc回收
delEntry.parent = null;
delEntry.left = null;
delEntry.right = null;
return true;
} /**
* 查找后继节点
* @param delEntry 删除节点
* @return 后继节点
*/
private Entry<T> successor(Entry<T> delEntry) {
Entry<T> r = delEntry.right;//r节点必定不为空
while (r.left != null){
r = r.left;
}
return r;
} public int size(){return size;} public boolean isEmpty(){return size == ;} public void clear(){
clear(getRoot());
root = null;
} private void clear(Entry<T> e){
if (e != null){
clear(e.left);
e.left = null;
clear(e.right);
e.right = null;
}
} static final class Entry<T extends Comparable<T>>{
//保存的数据
private T item;
//左子树
private Entry<T> left;
//右子树
private Entry<T> right;
//父节点
private Entry<T> parent;
Entry(T item,Entry<T> parent){
this.item = item;
this.parent = parent;
}
} }

   

  测试代码示例:

public static void main(String[] args) {
BinaryTree<Integer> binaryTree = new BinaryTree<>();
//放数据
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put();
binaryTree.put(); System.out.println("size: " + binaryTree.size());
binaryTree.put();
System.out.println("添加相同元素后的size: " + binaryTree.size());
//判断数据是否存在
System.out.println("数据是否存在:" + binaryTree.contains());
//中序遍历
System.out.print("中序遍历结果: ");
binaryTree.midIterator(binaryTree.getRoot());
System.out.println();
//前序遍历
System.out.print("前遍历结果: ");
binaryTree.prevIterator(binaryTree.getRoot());
System.out.println();
//后序遍历
System.out.print("后续遍历结果: ");
binaryTree.subIterator(binaryTree.getRoot());
//删除数据
System.out.println();
binaryTree.remove();
System.out.println("删除数据后判断是否存在:" + binaryTree.contains());
//清空二叉树
binaryTree.clear();
System.out.print("清空数据后中序遍历: ");
binaryTree.midIterator(binaryTree.getRoot());
}

  测试结果:

size:
添加相同元素后的size:
数据是否存在:true
中序遍历结果:
前遍历结果:
后续遍历结果:
删除数据后判断是否存在:false
清空数据后中序遍历:

  纯手写的demo,有什么错误的地方欢迎指正,谢谢大家的阅读!!!

  欢迎大家关注公众号: 【java解忧杂货铺】,里面会不定时发布一些技术博客;关注即可免费领取大量最新,最流行的技术教学视频:

使用Java实现二叉树的添加,删除,获取以及遍历的更多相关文章

  1. JavaScript Dom基础-9-Dom查找方法; 设置DOM元素的样式; innerHTML属性的应用; className属性的应用; DOM元素上添加删除获取属性;

    JavaScript Dom基础 学习目标 1.掌握基本的Dom查找方法 domcument.getElementById() Domcument.getElementBy TagName() 2.掌 ...

  2. Java ArrayList正确循环添加删除元素方法及分析

    在阿里巴巴Java开发手册中,有这样一条规定: 但是手册中并没有给出具体原因,本文就来深入分析一下该规定背后的思考. 一.foreach循环 foreach循环(Foreach loop)是计算机编程 ...

  3. 《剑指Offer》-004 -Java版二叉树先序和中序遍历返回原二叉树

    如题 (总结要点) 注意空值 假定数据是没有问题的 前序(根左右) ,中序(左根右), 故每次的第一个节点就是根节点 没用数组的库函数,自己手写了两个方法 用Java代码写二叉树很舒服, 没有啥指针, ...

  4. Java数据结构——二叉树 增加、删除、查询

    //二叉树系统 public class BinarySystem { public static void main(String[] args) { BinaryDomain root = nul ...

  5. jQuery08源码 (5140 , 6057) DOM操作 : 添加 删除 获取 包装 DOM筛选

    jQuery.fn.extend({ //$('ul').find('li').css('background','red'); //$('ul').find( $('li') ).css('back ...

  6. JAVA连接MYSQL,查询 ,添加,删除,语句

        package com; import Java.sql.*;/** *//** * @author Administrator */public class ggg {    private ...

  7. js+jquery动态设置/添加/删除/获取元素属性的两种方法集锦对照(动态onclick属性设置+动态title设置)

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html140 ...

  8. jquery17 DOM操作 : 添加 删除 获取 包装 DOM筛选

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  9. jquery16 DOM操作 : 添加 删除 获取 包装 DOM筛选

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

随机推荐

  1. Django升级1.8的一些问题

    1.最明显的问题当然是Settings设置中关于模板的设置数据结构发生变化,这个就不细说了,你开个Django的1.8的新项目就知道怎么改了 2.migrations问题,这个问题是1.8最主要的修改 ...

  2. 浏览器选择最新IE渲染

    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

  3. 物流的纯css实现方法

    首先我们来看看UI给出的设计图. 为什么到达是最前面,为什么物流顺序是倒叙的,这是什么用户习惯,这是我拿到设计稿的问题,但是这里不谈设计,因为审美这个东西无法评估.那么这里我就做一个顺序的来对比一下吧 ...

  4. invalid bound statement (not found)

    invalid bound statement (not found) mybatis 错误: 一般是Mapepr.xml文件中文nameapce没有和mapper接口发生映射,导致mybatis绑定 ...

  5. .NET开发微信小程序-获取OpenId

    注:获取当前用户信息只需要用GetUserInfo这个方法就行.这里就不需要提了 前端代码: CallBack:回调函数 function GetOpenID(CallBack){ var appIn ...

  6. maven工程,java代码加载resources下面资源文件的路径

    1 通过类加载器加载器, 1. URL resource = TestMain.class.getResource("/18500228040.txt");File file = ...

  7. Unix系统的常用信号

    编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号).不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信 ...

  8. 关于内核转储(core dump)的设置方法

    原作者:http://blog.csdn.net/wj_j2ee/article/details/7161586 1. 内核转储作用 (1) 内核转储的最大好处是能够保存问题发生时的状态. (2) 只 ...

  9. 2017年的golang、python、php、c++、c、java、Nodejs性能对比[续]

    2017年的golang.python.php.c++.c.java.Nodejs性能对比[续] 最近忙,这个话题放了几天,今天来个续集.   上篇传送门: 2017年的golang.python.p ...

  10. python函数注释, :与 ->

    python函数注释, :与 -> 如图:add1函数中的:意思是:函数中的参数说明    add2函数中:->意思是:函数的返回值为整型 这两种方法都是函数的注释方法,具体使用时要别人能 ...