前面介紹學習的大可能是線性表相關的內容,把指針搞懂後其實也沒有什麼難度,規則相對是簡單的,後面會講解一些比較常見的數據結構,用多圖的方式讓你們更容易吸取。java
在數據結構與算法中,樹是一個比較大的家族,家族中有不少厲害的成員,這些成員有二叉樹和多叉樹(例如B+樹等),而二叉樹的你們族中,二叉搜索樹(又稱二叉排序樹)是最最基礎的,在這基礎上才能繼續拓展學習AVL(二叉平衡樹)、紅黑樹等知識。node
對於二叉排序樹而言,本章重點關注其實現方式以及插入、刪除步驟流程,咱們會手寫一個二叉排序樹,二叉樹遍歷部分的內容比較多會單獨詳細講解。算法
樹是一種數據結構,它是由n(n>=1)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。
數組
樹是遞歸的,將樹的任何一個節點以及節點下的節點都能組合成一個新的樹,因此樹的不少問題都是使用遞歸去完成。數據結構
根節點: 最上面的那個節點(root),根節點沒有父節點,只有子節點(0個或多個均可以)函數
層數: 通常認爲根節點是第1層(有的也說第0層),而樹的高度就是層數最高(上圖層數開始爲1)節點的層數學習
節點關係:this
節點的度: 就是節點擁有孩子節點的個數(是直接鏈接的孩子不是子孫).3d
樹的度: 就是全部節點中最大 (節點的度)。同時,若是度大於0的節點是分支節點,度等於0的節點是葉子節點(沒有子孫)。指針
相關性質:
二叉樹是一樹的一種,但應用比較多,因此須要深刻學習,二叉樹的每一個節點最多隻有兩個子節點(但不必定非得要有兩個節點)。
二叉樹與度爲2的樹的區別:
一、度爲2的的樹必須有三個節點以上(不然就不叫度爲二了,必定要先存在),二叉樹能夠爲空。
二、二叉樹的度不必定爲2,好比斜樹。
三、二叉樹有左右節點區分,而度爲2的樹沒有左右節點的區分。
幾種特殊二叉樹:
滿二叉樹:高度爲n的滿二叉樹有(2^n) -1個節點
徹底二叉樹:上面一層所有滿,最下一層從左到右順序排列
二叉排序樹:樹按照必定規則插入排序(本文詳解)。
平衡二叉樹:樹上任意節點左子樹和右子樹深度差距不超過1(後文詳解).
二叉樹性質:
一、二叉樹有用樹的性質
二、非空二叉樹葉子節點數=度爲2的節點數+1.原本一個節點若是度爲1.那麼一直延續就一個葉子,但若是出現一個度爲2除了延續原來的一個節點,會多出一個節點須要維繫。因此到最後會多出一個葉子。
三、非空第i層最多有2^(i-1)個節點。
四、高爲h的樹最多有(2^h)-1個節點(等比求和)。
二叉樹通常用鏈式存儲,這樣內存利用更高,但二叉樹也能夠用數組存儲的(常常會遇到),各個節點對應的下標是能夠計算出來的,就拿一個徹底二叉樹若從左往右,從上到下編號如圖:
前面鋪墊那麼多,我們言歸正傳,詳細講解並實現一個二叉排序樹,二叉搜索樹擁有二叉樹的性質,同時有一些本身的規則:
首先要了解二叉排序樹的規則:從任意節點開始,節點左側節點值總比節點右側值要小。
例如一個二叉排序樹依次插入15,6,23,7,4,71,5,50
會造成下圖順序
二叉排序樹是由若干節點(node)構成的,對於node須要這些屬性:left,right,和value。其中left和right是左右指針指向左右孩子子樹,而value是儲存的數據,這裏用int 類型。
node
類構造爲:
class node {//結點 public int value; public node left; public node right; public node() { } public node(int value) { this.value=value; this.left=null; this.right=null; } public node(int value,node l,node r) { this.value=value; this.left=l; this.right=r; } }
既然節點構造好了,那麼就須要節點等其餘信息構形成樹,有了鏈表構造經驗,很容易得知一棵樹最主要的仍是root
根節點。
因此樹的構造爲:
public class BinarySortTree { node root;//根 public BinarySortTree() {root=null;} public void makeEmpty()//變空 {root=null;} public boolean isEmpty()//查看是否爲空 {return root==null;} //各類方法 }
既然已經構造好一棵樹,那麼就須要實現主要的方法,由於二叉排序樹中每一個節點都能看做一棵樹。因此咱們建立方法的是時候加上節點參數(方便一些遞歸調用)
findmin()找到最小節點:
由於全部節點的最小都是往左插入,因此只須要找到最左側的返回便可,具體實現可以使用遞歸也可非遞歸while循環。
findmax()找到最大節點:
由於全部節點大的都是往右面插入,因此只須要找到最右側的返回便可,實現方法與findmin()方法一致。
代碼使用遞歸函數
public node findmin(node t)//查找最小返回值是node,調用查看結果時須要.value { if(t==null) {return null;} else if(t.left==null) {return t;} else return(findmin(t.left)); } public node findmax(node t)//查找最大 { if(t==null) {return null;} else if(t.right==null) {return t;} else return(findmax(t.right)); }
這裏的意思是查找二叉查找樹中是否存在值爲x的節點。
在具體實現上,根據二叉排序樹左側更小,右側更大的性質進行往下查找,若是找到值爲x的節點則返回true,若是找不到就返回false,固然實現上能夠採用遞歸或者非遞歸,我這裏使用非遞歸的方式。
public boolean isContains(int x)//是否存在 { node current=root; if(root==null) {return false;} while(current.value!=x&¤t!=null) { if(x<current.value) {current=current.left;} if(x>current.value) {current=current.right;} if(current==null) {return false;}//在裏面判斷若是超直接返回 } //若是在這個位置判斷是否爲空會致使current.value不存在報錯 if(current.value==x) {return true;} return false; }
插入的思想和前面isContains(int x)
相似,找到本身的位置(空位置)插入。
可是具體實現上有須要注意的地方,咱們要到待插入位置上一層節點,你可能會疑問爲何不直接找到最後一個空,而後將current賦值過去current=new node(x)
,這樣的化current就至關於指向一個new node(x)節點,和原來樹就脫離關係(原樹至關於沒有任何操做),因此要提早經過父節點斷定是否爲空找到位置,找到合適位置經過父節點的left或者right節點指向新建立的節點才能完成插入的操做。
public node insert(int x)// 插入 t是root的引用 { node current = root; if (root == null) { root = new node(x); return root; } while (current != null) { if (x < current.value) { if (current.left == null) { return current.left = new node(x);} else current = current.left;} else if (x > current.value) { if (current.right == null) { return current.right = new node(x);} else current = current.right; } } return current;//其中用不到 }
好比說上面樹插入值爲51的節點。
刪除操做算是一個相對較難理解的操做了,由於待刪除的點可能在不一樣位置因此具體處理的方式也不一樣,若是是葉子便可可直接刪除,有一個孩子節點用子節點替換便可,有兩個子節點的就要先找到值距離待刪除節點最近的點(左子樹最大點或者右子樹最小點),將值替換掉而後遞歸操做在子樹中刪除已經替換的節點,固然沒具體分析能夠看下面:
刪除的節點沒有子孫:
這種狀況不須要考慮,直接刪除便可(節點=null
便可)(圖中紅色點均知足這種方式)。
一個子節點爲空:
此種狀況也很容易,直接將刪除點的子節點放到被刪除位置便可。
左右節點均不空
左右孩子節點都不爲空這種狀況是相對比較複雜的,由於不能直接用其中一個孩子節點替代當前節點(放不下,若是孩子節點也有兩個孩子那麼有一個節點沒法放,例如拿下面71節點替代)
若是拿19或者71節點填補。雖然能夠保證部分側大於小於該節點,可是會引發合併的混亂.好比你若用71替代23節點。那麼你須要考慮三個節點(19,50,75)
之間如何處理,還要考慮他們是否滿,是否有子女,這是個複雜的過程,不適合考慮。
因此,咱們要分析咱們要的這個點的屬性:可以保證該點在這個位置仍知足二叉搜索樹的性質(找到值最近的),那麼子樹中哪一個節點知足這樣的關係呢?
左子樹中最右側節點或者右子樹中最左側節點都知足,咱們能夠選一個節點將待刪除節點值替換掉(這裏替換成左子樹最右側節點)。
這個點替換以後該怎麼辦呢?很簡單啊,二叉樹用遞歸思路解決問題,再次調用刪除函數在左子樹中刪除替換的節點便可。
這裏演示是選取左子樹最大節點(最右側)替代,固然使用右子樹最小節點也能知足在這待刪除的大小關係,原理一致。整個刪除算法流程爲:
這部分操做的代碼爲:
public node remove(int x, node t)// 刪除節點 { if (t == null) { return null; } if (x < t.value) { t.left = remove(x, t.left); } else if (x > t.value) { t.right = remove(x, t.right); } else if (t.left != null && t.right != null)// 左右節點均不空 { t.value = findmin(t.right).value;// 找到右側最小值替代 t.right = remove(t.value, t.right); } else // 左右單空或者左右都空 { if (t.left == null && t.right == null) { t = null; } else if (t.right != null) { t = t.right; } else if (t.left != null) { t = t.left; } return t; } return t; }
這個完整代碼是筆者在大三時候寫的,可能有很多疏漏或者不規範的地方,僅供學習參考,若有疏漏錯誤還請指正。
二叉排序樹完整代碼爲:
package 二叉樹; import java.util.ArrayDeque; import java.util.Queue; import java.util.Stack; public class BinarySortTree { class node {// 結點 public int value; public node left; public node right; public node() { } public node(int value) { this.value = value; this.left = null; this.right = null; } public node(int value, node l, node r) { this.value = value; this.left = l; this.right = r; } } node root;// 根 public BinarySortTree() { root = null; } public void makeEmpty()// 變空 { root = null; } public boolean isEmpty()// 查看是否爲空 { return root == null; } public node findmin(node t)// 查找最小返回值是node,調用查看結果時須要.value { if (t == null) { return null; } else if (t.left == null) { return t; } else return (findmin(t.left)); } public node findmax(node t)// 查找最大 { if (t == null) { return null; } else if (t.right == null) { return t; } else return (findmax(t.right)); } public boolean isContains(int x)// 是否存在 { node current = root; if (root == null) { return false; } while (current.value != x && current != null) { if (x < current.value) { current = current.left; } if (x > current.value) { current = current.right; } if (current == null) { return false; } // 在裏面判斷若是超直接返回 } // 若是在這個位置判斷是否爲空會致使current.value不存在報錯 if (current.value == x) { return true; } return false; } public node insert(int x)// 插入 t是root的引用 { node current = root; if (root == null) { root = new node(x); return root; } while (current != null) { if (x < current.value) { if (current.left == null) { return current.left = new node(x);} else current = current.left;} else if (x > current.value) { if (current.right == null) { return current.right = new node(x);} else current = current.right; } } return current;//其中用不到 } public node remove(int x, node t)// 刪除節點 { if (t == null) { return null; } if (x < t.value) { t.left = remove(x, t.left); } else if (x > t.value) { t.right = remove(x, t.right); } else if (t.left != null && t.right != null)// 左右節點均不空 { t.value = findmin(t.right).value;// 找到右側最小值替代 t.right = remove(t.value, t.right); } else // 左右單空或者左右都空 { if (t.left == null && t.right == null) { t = null; } else if (t.right != null) { t = t.right; } else if (t.left != null) { t = t.left; } return t; } return t; } }
這裏咱們學習瞭解了樹、二叉樹、以及二叉搜素樹,對於二叉搜素樹插入查找比較容易理解,可是實現的時候要注意函數參數的引用等等。
偏有難度的是二叉樹的刪除,利用一個遞歸的思想,分類討論待刪除狀況,要找到特殊狀況和普通狀況,遞歸必定程度也是問題的轉化(轉成本身相同問題,做用域減少)須要思考。
下面還會介紹二叉樹的三序遍歷(遞歸和非遞歸)和層序遍歷。這些都是比較經典熱門的問題須要深刻了解。
若是看了本文以爲有收穫歡迎 點贊、收藏、分享一波,也歡迎加我v信好友(q1315426911)一塊兒學習交流,我也創了一個力扣打卡羣,裏面不少熱情的夥伴但願一塊兒進步!