一段來自百度百科的對二叉樹的解釋:sql
在計算機科學中,二叉樹是每一個結點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。bash
一棵深度爲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 = 0;
複製代碼
二叉樹添加數據:性能
/**
* 添加元素
* @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 = 0;
//記錄父節點
Entry<T> p = t;
while (t != null){
//與當前節點比較
ret = item.compareTo(t.item);
p = t;
//插入節點比當前節點小,把當前節點設置爲左子節點,而後與左子比較,以此類推找到合適的位置
if (ret < 0)
t = t.left;
//大於當前節點
else if (ret > 0)
t = t.right;
else {
//相等就把舊值覆蓋掉
t.item = item;
return t.item;
}
}
//建立新節點
Entry<T> e = new Entry<>(item,p);
//根據比較結果將新節點放入合適的位置
if (ret < 0)
p.left = e;
else
p.right = e;
     size++;
return e.item;
}
複製代碼
在put的過程當中,使用Comparable中的compareTo來比較兩個元素的大小的,因此在二叉樹中存儲的元素必需要繼承Comparable 類,覆寫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 < 0)
t = t.left;
else if (ret > 0)
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;
}
複製代碼
二叉樹獲取節點ui
/**
* 判斷是否存在該元素
* @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 < 0)
t = t.left;
else if (ret > 0)
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 + " ");
}
}
複製代碼
package com.rainple.collections;
/**
* 二叉樹
* @param <T>
*/
public class BinaryTree<T extends Comparable<T>> {
//根節點
private Entry<T> root;
//數據量
private int size = 0;
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 = 0;
//記錄父節點
Entry<T> p = t;
while (t != null){
//與當前節點比較
ret = item.compareTo(t.item);
p = t;
//插入節點比當前節點小,把當前節點設置爲左子節點,而後與左子比較,以此類推找到合適的位置
if (ret < 0)
t = t.left;
//大於當前節點
else if (ret > 0)
t = t.right;
else {
//相等就把舊值覆蓋掉
t.item = item;
return t.item;
}
}
//建立新節點
Entry<T> e = new Entry<>(item,p);
//根據比較結果將新節點放入合適的位置
if (ret < 0)
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 < 0)
t = t.left;
else if (ret > 0)
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 == 0;}
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(73);
binaryTree.put(22);
binaryTree.put(532);
binaryTree.put(62);
binaryTree.put(72);
binaryTree.put(243);
binaryTree.put(42);
binaryTree.put(3);
binaryTree.put(12);
binaryTree.put(52);
System.out.println("size: " + binaryTree.size());
binaryTree.put(52);
System.out.println("添加相同元素後的size: " + binaryTree.size());
//判斷數據是否存在
System.out.println("數據是否存在:" + binaryTree.contains(12));
//中序遍歷
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(62);
System.out.println("刪除數據後判斷是否存在:" + binaryTree.contains(62));
//清空二叉樹
binaryTree.clear();
System.out.print("清空數據後中序遍歷: ");
binaryTree.midIterator(binaryTree.getRoot());
}
複製代碼
測試結果:
size: 10
添加相同元素後的size: 10
數據是否存在:true
中序遍歷結果: 3 12 22 42 52 62 72 73 243 532
前遍歷結果: 73 22 3 12 62 42 52 72 532 243
後續遍歷結果: 12 3 52 42 72 62 22 243 532 73
刪除數據後判斷是否存在:false
清空數據後中序遍歷:
複製代碼
純手寫的demo,有什麼錯誤的地方歡迎指正,謝謝你們的閱讀!!!
分享免費學習資料
針對於還會準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料) 爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
資料領取方式:加入Java技術交流羣963944895
,私信管理員便可免費領取