前面咱們學了不一樣的數據結構,今天學習的是一種特別的數據結構:樹
node
首先咱們思考一下:什麼是樹呢?爲何咱們須要學習這種數據結構呢?
segmentfault
咱們對比以前學習的數據結構,分析看看以前的數據結構有什麼特色又有什麼缺陷數組
1、數組存儲方式的分析數據結構
優勢:經過下標方式訪問元素
,速度快。對於有序數組
,可使用二分查找
提升檢索速度。ide
缺點:若是要操做具體插入值
,那麼會總體移動(按必定順序),效率較低post
假如我當前有數組arr {1,3,5,8,10}
,若此時插入數據:6
那麼能放的進去嗎?學習
其實是不能
的,由於數組是事先分配空間
的,指說原建立好空間長度就不能動態增加
,可是數組在動態添加數據
的時候,底層有一個動做:數組擴容
。測試
那麼是如何擴容的呢?建立新的數組,並將數據拷貝,以及插入數據後移
優化
那麼這時會有小夥伴提出:咱們使用集合ArrayList不是能夠動態增加嗎?
this
其實咱們觀察集合ArrayList,發現也維護了數組擴容,只是策略不一樣。
那麼咱們一塊兒看看ArrayList 源碼
private static final Object [] DEFAULTCAPACITY_ EMPTY_ ELEMENTDATA = {}; //ArrayList的構造器 public ArrayList() { this.elementData = DEFAULTCAPACITY_ EMPTY_ ELEMENTDATA ; }
咱們發現ArrayList無參構造器,上來時將空數值給到elementData數組。
那麼elementData是什麼?
transient 0bject [] elementData;
實際上是一個對象Object數組
,也就是說ArrayList維護的0bject [] elementData數組
在ArrayList容量不夠的時候,有方法grow()按照不一樣的策略進行擴容,但仍然是一個數組擴容
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity - oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copy0f(elementData, newCapacity); }
Object類型的數組elementData
.無參構造器
,則初始elementData容量爲0 (jdk7是10)
指定容量capacity的構造器
,則初始elementData 容量爲capacity
.添加元素
時: 先判斷是否須要擴容
,若是須要擴容
,則調用grow方法,不然直接添加元素到合適位置
無參構造器
,若是第一次添加
,須要擴容
的話,則擴容elementData爲10
,若是須要再次擴容的話
,則擴容elementData爲1.5倍
。指定容量capacity的構造器
,若是還須要擴容,則直接擴容elementData爲1.5倍。咱們發現ArrayList爲了解決擴容,按照一種策略進行的,仍是會總體移動的,效率比較低,因而咱們看看鏈式存儲方式能不能更好解決問題
2、鏈式存儲方式的分析
優勢:在必定程度上對數組存儲方式有優化
(好比:插入數值
節點,只須要將插入節點,連接到鏈表中
便可,刪除效率也很好)。
缺點:在進行檢索時,效率仍然較低(好比:檢索某個值
,須要從頭節點開始
遍歷)
咱們發現數組與鏈式都有各自的優勢與缺點,那麼接下來介紹新的數據結構:樹
有什麼不一樣呢?
在樹的家族中,有一種高頻使用
的一種樹結構
:二叉樹
。
在二叉樹
中,每一個節點最多有兩個分支
,即每一個節點最多有兩個節點,分別稱爲左節點與右節點
。
在二叉樹中,還有兩個特殊的類型:滿二叉樹與徹底二叉樹
。
滿二叉樹:除了葉子節點外,全部節點都有兩個節點
。
徹底二叉樹:除了最後一層之外,其餘層節點個數都達到最大,而且最後一層的葉子節點向左排列
這和存儲二叉樹的兩種存儲方法:鏈式存儲法與數組順序法
。有關了
基於指針的鏈式存儲法
,也就是像鏈表
同樣,每一個節點有三個字段
。一個存儲數據,兩外兩個分別存儲指向左右節點的指針
,以下圖所示
基於數組的順序存儲法
,就是按照規律把節點存放在數組裏
,以下圖所示。爲了方便按照規律計算,把起始數據放在下標爲一的位置上
如果非徹底二叉樹則會浪費大量的數組存儲空間,以下圖所示
以二叉樹
爲例介紹樹的操做
一對一
"的關係,即前面的數據只跟下面的一個數據產生鏈接關係
。如鏈表、棧、隊列等一對多
"的關係,即前面的父節點跟若干個子節點產生了鏈接關係
與以前的數據結構相比,遍歷一個樹
。
有很是經典的三種方法,分別是前序遍歷、中序遍歷、後序遍歷
示例一:使用前序、中序、後序對下面二叉樹進行遍歷
咱們根據圖片定義英雄節點HeroNode 信息
//建立英雄節點HeroNode class HeroNode { private int no; //英雄節點編號 private String name; //英雄節點名稱 private HeroNode left; //默認null 左節點 private HeroNode right; //默認null 右節點 public HeroNode(int no, String name) { this.no = no; this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public HeroNode getLeft() { return left; } public void setLeft(HeroNode left) { this.left = left; } public HeroNode getRight() { return right; } public void setRight(HeroNode right) { this.right = right; } @Override public String toString() { return "HeroNode [no =" + no +", name =" + name +"]"; } /** * 前序遍歷方式 * 前序是先輸出父節點,再遍歷左子樹和右子樹 */ public void preOrder(){ //先輸出父節點 System.out.println(this); //遞歸向左節點進行前序遍歷 if(this.left!=null){ this.left.preOrder(); } //遞歸向右節點進行前序遍歷 if(this.right!=null){ this.right.preOrder(); } } /** * 中序遍歷方式 * 中序是先遍歷左子樹,再輸出父節點,再遍歷右子樹 */ public void infixOrder(){ //遞歸向左節點進行前序遍歷 if(this.left!=null){ this.left.infixOrder(); } //先輸出父節點 System.out.println(this); //遞歸向右節點進行前序遍歷 if(this.right!=null){ this.right.infixOrder(); } } /** * 後序遍歷方式 * 後序是先遍歷左子樹,再遍歷右子樹,最後輸出父節點 */ public void postOrder(){ //遞歸向左節點進行前序遍歷 if(this.left!=null){ this.left.postOrder(); } //遞歸向右節點進行前序遍歷 if(this.right!=null){ this.right.postOrder(); } //先輸出父節點 System.out.println(this); } }
咱們建立一顆二叉樹 BinaryTree 信息
//定義二叉樹 class BinaryTree{ private HeroNode root; //根節點 public void setRoot(HeroNode root) { this.root = root; } //root根節點前序遍歷的方法 public void preOrder(){ if(this.root!=null){ this.root.preOrder(); }else{ System.out.println("二叉樹爲空,沒法遍歷"); } } //root根節點中序遍歷的方法 public void infixOrder(){ if(this.root!=null){ this.root.infixOrder(); }else{ System.out.println("二叉樹爲空,沒法遍歷"); } } //root根節點後序遍歷的方法 public void postOrder(){ if(this.root!=null){ this.root.postOrder(); }else{ System.out.println("二叉樹爲空,沒法遍歷"); } } }
如今讓咱們如圖所示建立二叉樹與英雄節點來測試遍歷看看
public class BinaryTreeDemo { public static void main(String [] agrs){ //建立二叉樹 BinaryTree tree =new BinaryTree(); //建立英雄節點 HeroNode root = new HeroNode(1,"松江"); HeroNode node2 =new HeroNode(2,"吳用"); HeroNode node3 =new HeroNode(3,"盧俊"); HeroNode node4 =new HeroNode(4,"林沖"); //手動建立二叉樹依賴 root.setLeft(node2); root.setRight(node3); node3.setRight(node4); //將root 節點給到二叉樹 tree.setRoot(root); } }
咱們測試前序遍歷
,看看是否結果是[宋江-->吳用-->盧俊-->林沖]
//前序遍歷 tree.preOrder(); HeroNode [no =1, name =松江] HeroNode [no =2, name =吳用] HeroNode [no =3, name =盧俊] HeroNode [no =4, name =林沖]
咱們測試中序遍歷
,看看是否結果是[吳用-->松江-->盧俊-->林沖]
//中序遍歷 tree.infixOrder(); HeroNode [no =2, name =吳用] HeroNode [no =1, name =松江] HeroNode [no =3, name =盧俊] HeroNode [no =4, name =林沖]
咱們測試後序遍歷
,看看是否結果是[吳用-->林沖-->盧俊-->松江]
//後序遍歷 tree.postOrder(); HeroNode [no =2, name =吳用] HeroNode [no =4, name =林沖] HeroNode [no =3, name =盧俊] HeroNode [no =1, name =松江]
1.上圖的3號節點"盧俊",添加左節點[5,關勝]
2.使用前序、中序、後序 寫出各自輸出順序是啥?
示例二:使用前序、中序、後序不一樣遍歷對下面二叉樹進行查找
接下來咱們在分別在HeroNode、BinaryTree添加代碼
//添加前序、中序、後序查找代碼 class HeroNode { //省略以前序、中序、後序遍歷代碼 /** * @param no 查找no * @return 若是找到返回node,沒有返回null */ public HeroNode preOrderSearch(int no){ System.out.println("進入前序遍歷查找~~~~"); //比較當前節點看看是否是 if(this.no == no){ return this; } //1.判斷當前節點的左節點是否爲空,不爲空遞歸前序查找,找到符合要求的節點則返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.preOrderSearch(no); } //說明左節點找到了,相等 if(resnode != null){ return resnode; } //不相等則,判斷當前節點的右節點是否爲空,不爲空遞歸前序查找 if(this.right != null){ resnode = this.right.preOrderSearch(no); } return resnode; } /** * @param no 查找no * @return 若是找到返回node,沒有返回null */ public HeroNode infixOrderSearch(int no){ //1.判斷當前節點的左節點是否爲空,不爲空遞歸中序查找,找到符合要求的節點則返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.infixOrderSearch(no); } //說明符合要求的節點找到了,相等 if(resnode != null){ return resnode; } System.out.println("進入中序遍歷查找~~~~"); //沒有則和當前節點進行比較,知足則返回 if(this.no == no){ return this; } //不相等則,判斷當前節點的右節點是否爲空,不爲空遞歸前序查找 if(this.right != null){ resnode = this.right.infixOrderSearch(no); } return resnode; } /** * @param no 查找no * @return 若是找到返回node,沒有返回null */ public HeroNode postOrderSearch(int no){ //1.判斷當前節點的左節點是否爲空,不爲空遞歸後序查找,找到符合要求的節點則返回 HeroNode resnode = null; if(this.left!=null){ resnode = this.left.postOrderSearch(no); } //說明符合要求的節點找到了,相等 if(resnode != null){ return resnode; } //不相等則,判斷當前節點的右節點是否爲空,不爲空右遞歸後序查找 if(this.right != null){ resnode = this.right.postOrderSearch(no); } //說明符合要求的節點找到了,相等 if(resnode != null){ return resnode; } System.out.println("進入後序遍歷查找~~~~"); //沒有則和當前節點進行比較,知足則返回 if(this.no == no){ return this; } return resnode; } }
//添加前序、中序、後序查找代碼 class BinaryTree{ //省略以前序、中序、後序遍歷代碼 //root節點前序查找方法 public HeroNode preOrderSearch(int no){ if(this.root != null){ return this.root.preOrderSearch(no); }else{ return null; } } //root節點中序查找方法 public HeroNode infixOrderSearch(int no){ if(this.root != null){ return this.root.infixOrderSearch(no); }else{ return null; } } //root節點後序查找方法 public HeroNode postOrderSearch(int no){ if(this.root != null){ return this.root.postOrderSearch(no); }else{ return null; } } }
[舒適提示]:小夥伴必定要添加好關勝英雄數據
並關聯起樹的關係
哦
//建立英雄節點 HeroNode node5 =new HeroNode(5,"關勝"); //手動關聯二叉樹依賴關係 node3.setLeft(node5);
使用前序遍歷查找
測試[編號:5 關勝]
看看,看看是否找到
並是否四次找到
System.out.println("==========================使用前序遍歷查找方式"); HeroNode resNode = tree.preOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息爲no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("沒有找到no = %d的英雄",5); } 運行輸出結果以下: ==========================使用前序遍歷查找方式 進入前序遍歷查找~~~~ 進入前序遍歷查找~~~~ 進入前序遍歷查找~~~~ 進入前序遍歷查找~~~~ 找到了,信息爲no=5 name=關勝
使用中序遍歷查找
測試[編號:5 關勝]
看看,看看是否找到
並是否三次找到
System.out.println("==========================使用中序遍歷查找方式~~~"); HeroNode resNode = tree.infixOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息爲no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("沒有找到no = %d的英雄",5); } 運行輸出結果以下: ==========================使用中序遍歷查找方式~~~ 進入中序遍歷查找~~~~ 進入中序遍歷查找~~~~ 進入中序遍歷查找~~~~ 找到了,信息爲no=5 name=關勝
使用後序遍歷查找
測試[編號:5 關勝]
看看,看看是否找到
並是否二次找到
System.out.println("==========================使用後序遍歷查找方式~~~"); HeroNode resNode = tree.postOrderSearch(5); if (resNode != null) { System. out.printf("找到了,信息爲no=%d name=%s", resNode.getNo(), resNode. getName()); } else { System.out. printf("沒有找到no = %d的英雄",5); } 運行輸出結果以下: ==========================使用後序遍歷查找方式~~~ 進入後序遍歷查找~~~~ 進入後序遍歷查找~~~~ 找到了,信息爲no=5 name=關勝
由於目前的二叉樹:暫時是沒有規則的
,後邊深刻時再解決怎麼把左節點或者右節點提高上去的問題。
示例三:
刪除
的節點是葉子節
點,則刪除該節點
刪除
的節點是非葉子節
點,則刪除該子樹
.刪除
葉子節點五號
和子樹三號
.接下來咱們在分別在HeroNode、BinaryTree添加代碼
//添加刪除節點代碼 class HeroNode { //省略以前序、中序、後序遍歷代碼 //省略以前序、中序、後序查找代碼代碼 //遞歸刪除結點 //1.若是刪除的節點是葉子節點,則刪除該節點 //2.若是刪除的節點是非葉子節點,則刪除該子樹 public void delHerNode(int no){ //思路 // 由於咱們的二叉樹是鏈表單向的,因此刪除目標節點時不能直接判斷是否刪除該節點。 // 1.若是當前節點左子節點不爲空,而且左子節點是須要刪除的節點就將this.left = null,並結束返回 if(this.left != null && this.left.no == no){ this.left = null; return; } // 2.若是當前節點右子節點不爲空,而且右子節點是須要刪除的節點就將this.right = null,並結束返回 if(this.right != null && this.right.no == no){ this.right = null; return; } // 3.若是當前節點沒有刪除的節點,則判斷左節點是否爲空,進行左遞歸繼續刪除 if(this.left != null){ this.left.delHerNode(no); } // 4.若是左節點左遞歸沒有刪除的節點,則判斷右節點是否爲空,進行右遞歸繼續刪除 if(this.right != null){ this.right.delHerNode(no); } } }
//添加刪除節點代碼 class BinaryTree{ //省略以前序、中序、後序遍歷代碼 //省略以前序、中序、後序查找代碼代碼 public void delHerNode(int no){ //若是樹自己爲空,只有一個root節點則等價於二叉樹置空 if(root !=null){ //若是隻有一個root結點,這裏當即判斷root是否是就是要刪除結點 if(root.getNo() == no){ root = null; }else{ root.delHerNode(no); } }else{ System.out.println("空樹!不能刪除"); } } }
使用刪除節點
測試[編號:5 關勝]
看看,看看是否成功
System.out.println("==============前序遍歷顯示刪除前數據"); tree.preOrder(); System.out.println("==========================================刪除葉子節點五號:關勝"); tree.delHerNode(5); System.out.println("==============前序遍歷顯示刪除後數據"); tree.preOrder(); 運行結果以下: ==============前序遍歷顯示刪除前數據 HeroNode [no =1, name =松江] HeroNode [no =2, name =吳用] HeroNode [no =3, name =盧俊義] HeroNode [no =5, name =關勝] HeroNode [no =4, name =林沖] ==========================================刪除葉子節點五號:關勝 ==============前序遍歷顯示刪除後數據 HeroNode [no =1, name =松江] HeroNode [no =2, name =吳用] HeroNode [no =3, name =盧俊義] HeroNode [no =4, name =林沖]
執行刪除[葉子節點五號:關勝]
,看看是如何進行的吧!
當執行方法時,先判斷當前root 是否爲空
,緊接着判斷當前root 節點no 是否等於須要刪除的節點no
,不知足條件進入delNode方法
delNode方法裏,判斷當前節點宋江左節點
是不是[五號:關勝]
,但左節點當前爲[二號:吳用]
,不知足因而接着判斷右節點
但右節點當前爲[三號:盧俊義]
,不知足條件判斷
因而判斷當前左節點是否爲空
,不爲空則進行左遞歸查詢再次進入delNode方法,那麼當前節點爲[二號:吳用]
delNode方法裏,判斷當前節點吳用左節點
是不是[五號:關勝]
,但當前吳用節點左節點爲空因而接着判斷右節點
但右節點當前爲空,不知足條件判斷
因而判斷當前左節點是否爲空,不爲空則進行左遞歸查詢再次進入,可是很遺憾,吳用的左邊是null,則進行判斷右節點
可是吳用右邊也是null,則往回溯回到宋江的左遞歸查詢
接着判斷當前宋江右節點
是否爲空,不爲空則進行右遞歸查詢再次進入delNode方法,那麼當前節點爲[三號:盧俊義]
delNode方法裏,判斷當前節點吳用左節點是不是[五號:關勝]
,知足條件則盧俊義左節點更改成:null