用樹作為存儲數據的結構兼具像數組一樣查詢速度快和像鏈表一樣具有很快的插入和刪除數據項的優點
我們用圓點表示節點,連接圓的直線表示邊如下圖所示就表示了一顆樹,接下來我們討論的二叉樹即每個節點最多只有兩個子節點的樹稱作是二叉樹。除了二叉樹還有多路樹,比如2-3-4樹和外部存儲就屬於多路樹
二叉搜索樹:一個節點的左子節點關鍵字小於這個節點,右子節點關鍵字大於或等於這個父節點,
在java中我們要設計一個樹可以只用下面的代碼
class Node{ //樹節點類 class Person{ //封裝樹節點的數據 class Tree{ //相當於樹的根節點
Person person; int iData; private Node root;
Node leftChild; double dData; //包含一些操作樹的方法
Node rightChild; } }
}
二叉樹查找元素
下圖展示了在搜索二叉樹中查找關鍵值為57的節點的查找流程圖
由上圖我們可以寫出上面的們Tree類中的查找的方法
- public Node find ( int key ){
- Node current = root;
- while(current.iData != key){
- if(key < current.iData){
- current = current.leftChild;
- }else{
- current = current.rightChild;
- }
- if(current == null){ //說明此時沒有找到對應的節點
- return null;
- }
- }
- return current; //循環執行結束跳出循環,說明找到此關鍵字節點,返回節點
- }
這里我們可以總結出在樹中查找節點的效率,首先取決於關鍵字所在樹的層數,像我們圖中給出的樹最多5層,即查找最多進行5次比較,節點數最多為31准確來說所需要的時間復雜度為O(log2N)
二叉樹插入節點
接下來討論在樹中插入一個節點:插入的方法和查找很像,只不過插入在最后一步遇到null不是立即返回null,而需要在返回之前插入節點
- public void insert( int id,double dd){
- Node newNode = new Node; //創建新的節點
- newNode.iData = id;
- newNode.dData = dd;
- if(root == null){
- root = newNode;
- }else{
- Node current = root;
- Node parent; //引入parent是為了記錄新節點插入的位置,不然當找到插入的地方會去其插入的父節點的位置
- while(true){
- parent = current;
- if(id< current.iData){
- current = current.leftChild;
- if(current == null){ //說明此時沒有找到對應的節點,將新節點連入左子節點
- parrent.leftChild = newNode;
- return;
- }
- }else{
- current = current.rightChild;
- if(current == null){ //說明此時沒有找到對應的節點,將新節點連入右子節點
- parrent.rightChild= newNode;
- return;
- }
- }
- }
- }
- }
遍歷二叉樹
接下來我們介紹遍歷一個二叉樹:我們常用的遍歷樹的方法有三種:前序,中序,后序。二叉樹中常用的遍歷方式是中序遍歷,這里我們遍歷的方法只需要做三件事情,1.調用自身來遍歷節點的左子樹,2.訪問這個節點,3.調用自身來遍歷節點的右子樹,下面是java代碼
- public void inOrder(Node localRoot){ //使用遞歸的方式來遍歷一顆樹
- if(localRoot != null){
- inOrder(localRoot.leftChild); //遍歷左子樹
- System.out.print(localRoot.iData+" "); //訪問自身數據
- inOrder(localRoot.rightChild); //遍歷右子樹
- }
- }
前序遍歷步驟 :1.訪問這個節點,2.調用自身來遍歷節點的左子樹,3.調用自身來遍歷節點的右子樹
后序遍歷步驟 :1.調用自身來遍歷節點的左子樹,2.調用自身來遍歷節點的右子樹,3.訪問這個節點
查找二叉搜索樹中的最大最小值:最小值一直往樹的最左邊往下查找即可得到最小值,相反向右可得到最大值。
- public Node minimum(){ //查找二叉搜索樹中的最小值
- Node current,last;
- current = root;
- while(current != null){
- last = current;
- current = current.leftChild; //將這里換成Right即可查詢的到最大值
- }
- return last;
- }
刪除二叉樹中的節點
接下來介紹二叉樹如何刪除一個節點,要刪除二叉樹的節點我們需要考慮到三種刪除的狀態:
1.該節點是葉節點,是葉節點的情況刪除很簡單,只需要將父節點對應的子節點改為null即可,如圖
2.節點有一個子節點,刪除該節點,只需要將需要刪除的節點的唯一一個子節點連接到需要刪除的節點的父節點之上,如圖
3.有兩個子節點,若有兩個子節點,則需要找到該刪除節點的中序后繼節點來替換當前刪除的節點(找后繼節點的算法簡單來說就是先向該節點右邊找到子節點,然后一直往左邊找到最小值即可得到該節點的后繼節點)
接下來寫出完整的刪除節點的java代碼
- public boolean delete(int key){
- Node current = root;
- Node parent = root;
- boolean isLeftChild = true;
- while(current.iData != key){
- parent = current;
- if(key < current.iData){
- isLeftChild = true;
- current = current.leftChild;
- }else{
- isLeftChild = false;
- current = current.rightChild;
- }
- if(current == null){
- return; //沒有找到需要刪除的數據項,直接返回
- }
- }
- //1.如果刪除的節點是葉子結點,直接刪除
- if(current.leftChild == null && current.rightChild == null){
- if(current == root){
- root = null;
- }else if(isLeftChild){
- parent.leftChild = null;
- }else{
- parent.rightChild = null;
- }
- }else if(current.rightChild == null){ //刪除的節點只有左子節點
- if(current == root){
- root = current.leftChild;
- }else if(isLeftChild){
- parent.leftChild = current.leftChild;
- }else{
- parent.rightChild = current.leftChild ;
- }
- }else if(current.leftChild == null){ //刪除的節點只有右子節點
- if(current == root){
- root = current.leftChild;
- }else if(isLeftChild){
- parent.leftChild = current.rightChild ;
- }else{
- parent.rightChild = current.rightChild ;
- }
- }else{
- Node successor = getSuccessor(current); //查找得到刪除節點的后繼
- if(current == root){
- root = successor;
- }else if(isLeftChild){
- parent.leftChild = successor;
- }else{
- parent.rightChild = successor;
- }
- successor.leftChild = current.leftChild; //最后一步設置刪除替換后的左子節點
- }
- }
- //查找后繼節點的算法
- private Node getSuccessor(Node delNode){
- Node successorParent = delNode;
- Node successor = delNode;
- Node current = delNode.rightChild;
- While(current!=null){
- successorParent = successor;
- successor = current;
- current = current.leftChild;
- }
- if(successor != delNode.rightChild){
- successorParent.leftChild = successor.rightChild;//將后繼節點的父節點的左子節點值設置為后繼的右子節點
- successor.rightChild = delNode.rightChild;//將后繼節點的右子節點設置成當前刪除節點的右子節點(處理后)
- }
- return successor;
- }
二叉樹的效率
二叉樹的效率:二叉樹的效率取決於二叉樹的層數(層數即為操作時最多的比較次數),下表給出了一個二叉滿樹的節點數和層數的關系,我們可以設第一列節點數為N層數為L,那么
N = 2L-1 <=> L = (log2N+1)
這里我們能換算成大O表示的時間復雜度為O(logN)。同樣層數的不滿的樹的用時是要小於滿樹的時間的,樹對於常用的數據存儲操作有很高的效率,遍歷不如其他的操作快。