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

  在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(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. Android Studio使用手记

    1.官方的Tip and Tricks:http://developer.android.com/intl/zh-cn/sdk/installing/studio-tips.html 2.项目的目录树 ...

  2. AQS分析(AbstractQueuedSynchronizer)(三)

    1.AQS是什么 AQS同步器是Java并发编程的基础,从资源共享的角度分成独占和共享两种模式,像ReentrantLock.ThreadPoolExecutor.CountDownLatch等都是基 ...

  3. 学习Android过程中的一些博客或工具收集

    android studio中使用SlidingMenu: 超简单Android Studio导入第三方库(SlidingMenu)教程绝对傻瓜版 android 更新sdk23以后,报错提示Floa ...

  4. vue 使用踩坑 note

    1. 如图,假如large那一行错写成 'large': item.ext_data.isLarge + '' === 'true',, 那么,编译不报错,控制台无提示,模板不输出. 2. vue的t ...

  5. [ SSH框架 ] Struts2框架学习之四(自定义拦截器)

    一.Struts2的拦截器 1.1 拦截器概述 拦截器,在AOP( Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作.拦截 ...

  6. C语言 > 字符串和字符串函数

    输入 gets() 函数 : 1.gets() 从标准输入设备读取字符串,以回车结束读取,使用'\0'结尾,回车符'\n'被舍弃没有遗留在缓冲区. 2.可以用来输入带空格的字符串. 3.可以无限读取, ...

  7. 云计算大数据:Xen、KVM、VMware、hyper-v等虚拟化技术的比较

    1.Xen.KVM.VMware.hyper-v等虚拟化技术的比较,xen和kvm,是开源免费的虚拟化软件. vmware是付费的虚拟化软件. hyper-v比较特别,是微软windows 2008 ...

  8. vue-quasar-admin 一个包含通用权限控制的后台管理系统

    vue-quasar-admin   Quasar-Framework 是一款基于vue.js开发的开源的前端框架, 它能帮助web开发者快速创建以下网站:响应式网站,渐进式应用,手机应用(通过Cor ...

  9. 在Windows下同时安装Python2.x和Python3.x

    前言: Python现在是两个版本共存,Python2.x和Python3.x都同时在更新.但是Python2.x和Python3.x的区别还是很多的(以后我可能会写一篇文章列举一下Python2.x ...

  10. OAuth 2 开发人员指南(Spring security oauth2)

    https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md 入门 这是支持OAuth2.0的 ...