1. 程式人生 > >java實現二叉樹的Node節點定義,並手撕8種遍歷

java實現二叉樹的Node節點定義,並手撕8種遍歷

最近準備秋招面試,發現二叉樹這種可以無限擴充套件知識點來虐別人的資料結構,很受面試官的青睞。

如果掌握的不好,會直接死在一面哦。

怕嗎?當你原理、思想,內部結構通通明白,分分鐘手撕程式碼的程度,還怕嗎?

本篇文章就從用java的思想和程式從最基本的怎麼將一個int型的陣列變成Node樹狀結構說起,再到遞迴前序遍歷,遞迴中序遍歷,遞迴後序遍歷,非遞迴前序遍歷,非遞迴前序遍歷,非遞迴前序遍歷,到最後的廣度優先遍歷和深度優先遍歷

你想要的都在這裡,是不是很貼心! 【麼麼】

來咯!

一、Node節點的java實現

首先在可以看到打上Node這個字串,就可以看到只能的IDEA系統提供的好多提示:
在這裡插入圖片描述

點進去看,卻不是可以直接構成二叉樹的Node,不是我們需要的東西。這裡舉個例子來看
org.w3c.dom
這裡面的Node是一個介面,是解析XML時的文件樹。在官方文件裡面看出:該 Node 介面是整個文件物件模型的主要資料型別。它表示該文件樹中的單個節點。當實現 Node 介面的所有物件公開處理子節點的方法時,不是實現 Node 介面的所有物件都有子節點。

所以我們需要自定義一個Node類

package com.sort.text;

public class Node {
	private int value;        //節點的值
	private Node node;        //此節點,資料型別為Node
	private Node left;        //此節點的左子節點,資料型別為Node
	private Node right;       //此節點的右子節點,資料型別為Node
	
	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}

	public Node getNode() {
		return node;
	}

	public void setNode(Node node) {
		this.node = node;
	}

	public Node getLeft() {
		return left;
	}

	public void setLeft(Node left) {
		this.left = left;
	}

	public Node getRight() {
		return right;
	}

	public void setRight(Node right) {
		this.right = right;
	}

	public Node(int value) {
		this.value=value;
		this.left=null;
		this.right=null;
	}
	public String toString() {         //自定義的toString方法,為了方便之後的輸出
		return this.value+" ";
	}
}

定義好了之後就可以開始直接使用了,相信大家都可以秒看懂。

二、陣列昇華二叉樹

一般拿到的資料是一個int型的陣列,那怎麼將這個陣列變成我們可以直接操作的樹結構呢?

1、陣列元素變Node型別節點
2、給N/2-1個節點設定子節點
3、給最後一個節點設定子節點【有可能只有左節點】

那現在就直接上程式碼

public  static void create(int[] datas,List<Node> list) {

		//將數組裡面的東西變成節點的形式
		for(int i=0;i<datas.length;i++) {
			Node node=new Node(datas[i]);
			list.add(node);
		}
		
		//節點關聯成樹
		for(int index=0;index<list.size()/2-1;index++) {
			list.get(index).setLeft(list.get(index*2+1));
                          //編號為n的節點他的左子節點編號為2*n 右子節點編號為2*n+1 但是因為list從0開始編號,所以還要+1
			list.get(index).setRight(list.get(index*2+2));  //與上同理
		}
		
		//單獨處理最後一個父節點 ,list.size()/2-1進行設定,避免單孩子情況
		int index=list.size()/2-1;
		list.get(index).setLeft(list.get(index*2+1));
		if(list.size()%2==1)     
		         //如果有奇數個節點,最後一個父節點才有右子節點
			list.get(index).setRight(list.get(index*2+2));
			
	}

很細緻的加上了很多的註釋啊,所以保證一看就懂。
開始大招前的攢金幣過程正式結束
現在開始放大招

三、遞迴前序遍歷

具體的原理沒有什麼好講的,知道順序即可

先序遍歷過程:
(1)訪問根節點;
(2)採用先序遞迴遍歷左子樹;
(3)採用先序遞迴遍歷右子樹;

這裡用圖來說明
在這裡插入圖片描述
先序遍歷結果:A BDFE CGHI

還是看程式碼吧

 public void preTraversal(Node node){
       if (node == null) //很重要,必須加上 當遇到葉子節點用來停止向下遍歷
             return; 
         System.out.print(node.getValue()+" ");
        preTraversal(node.getLeft());
        preTraversal(node.getRight());
 }

看,說了很簡單吧!

四、遞迴中序遍歷

中序遍歷:
(1)採用中序遍歷左子樹;
(2)訪問根節點;
(3)採用中序遍歷右子樹


中序遍歷結果:DBEF A GHCI

有請程式碼:

public void    MidTraversal(Node node){
      if (node == null)
          return; 
     MidTraversa(node.getLeft());
     System.out.print(node.getValue()+" ");
     MidTraversa(node.getRight());
 }

五、遞迴後序遍歷

後序遍歷:
(1)採用後序遞迴遍歷左子樹;
(2)採用後序遞迴遍歷右子樹;
(3)訪問根節點;

在這裡插入圖片描述

後序遍歷的結果:DEFB HGIC A

程式碼:

 public void postTraversal(Node node){
         if (node == null) 
                 return; 
         postTraversal(node.getLeft());
        postTraversal(node.getRight());
        System.out.print(node.getValue()+" ");
 }

其實程式碼和思想一樣,只是輸出的位置和遞迴呼叫的位置不同而已。

個人覺得懂得非遞迴的原理和程式碼比懂遞迴更有意思,當你能手撕非遞迴二叉樹遍歷的時候,面試官問你原理,還能不知道嗎?

那接下來的三個模組就是非遞迴的三種遍歷

拭目以待

六、非遞迴前序遍歷

我這裡使用了棧這個資料結構,用來儲存不到遍歷過但是沒有遍歷完全的父節點
之後再進行回滾。

基本的原理就是當迴圈中的p不為空時,就讀取p的值,並不斷更新p為其左子節點,即不斷讀取左子節點,直到一個枝節到達最後的子節點,再繼續返回上一層進行取值

程式碼:

public void preOrderTraversalbyLoop(Node node){
          Stack<Node> stack = new Stack();
          Node p = node;
          while(p!=null || !stack.isEmpty()){
                 while(p!=null){ 
                 //當p不為空時,就讀取p的值,並不斷更新p為其左子節點,即不斷讀取左子節點
                   System.out.print(p.getValue()+" ");
                   stack.push(p); //將p入棧
                   p = p.getLeft(); 
               }
              if(!stack.isEmpty()){
               p = stack.pop();
               p = p.getRight();
             }
        }
 }

執行結果,很順利的得到想要的結果

在這裡插入圖片描述

七、非遞迴中序遍歷

同原理

就是當迴圈中的p不為空時,就讀取p的值,並不斷更新p為其左子節點,但是切記這個時候不能進行輸出,必須不斷讀取左子節點,直到一個枝節到達最後的子節點,然後每次從棧中拿出一個元素,就進行輸出,再繼續返回上一層進行取值

程式碼如下:

public void inOrderTraversalbyLoop(Node node){
         Stack<Node> stack = new Stack();
         Node p = node;
         while(p!=null || !stack.isEmpty()){
            while(p!=null){
               stack.push(p);
               p = p.getLeft();
          }
           if(!stack.isEmpty()){ 
                  p = stack.pop();
                     System.out.print(p.getValue()+" ");
                 p = p.getRight();
            }
      }
 }

八、非遞迴後序遍歷

後序遍歷相比前面的前序遍歷和中序遍歷在程式設計這裡會難一點,不過理解了思想,看程式碼還是沒有什麼問題的

 public void postOrderTraversalbyLoop(Node node){

          Stack<Node> stack = new Stack<Node>();
         Node p = node,    prev = node;
         while(p!=null || !stack.isEmpty()){
                while(p!=null){
                     stack.push(p);
                     p = p.getLeft();
              }
               if(!stack.isEmpty()){
                     Node temp = stack.peek().getRight();
                     //只是拿出來棧頂這個值,並沒有進行刪除
                     if(temp == null||temp == prev){
                       //節點沒有右子節點或者到達根節點【考慮到最後一種情況】
                        p = stack.pop();
                       System.out.print(p.getValue()+" ");
                       prev = p;
                       p = null;
                       }
                       else{
                                p = temp;
                                } 
                 }
       }
 }

最後就可以放大招了,來看看廣度優先遍歷和深度優先遍歷吧

九、廣度優先遍歷

 public void bfs(Node root){
  if(root == null) return;
  LinkedList<Node> queue = new LinkedList<Node>();
  queue.offer(root); //首先將根節點存入佇列
  //當佇列裡有值時,每次取出隊首的node列印,列印之後判斷node是否有子節點,若有,則將子節點加入佇列
  while(queue.size() > 0){ 
  Node node = queue.peek();
   queue.poll(); //取出隊首元素並列印
   System.out.print(node.var+" ");
   if(node.left != null){ //如果有左子節點,則將其存入佇列
    queue.offer(node.left);
   }
   if(node.right != null){ //如果有右子節點,則將其存入佇列
    queue.offer(node.right);
   }
  }
 }

九、深度優先遍歷

public void dfs(Node node,List<List<Integer>> rst,List<Integer> list){
 if(node == null) return;
 if(node.left == null && node.right == null){
  list.add(node.var);
  /* 這裡將list存入rst中時,不能直接將list存入,而是通過新建一個list來實現,
  * 因為如果直接用list的話,後面remove的時候也會將其最後一個存的節點刪掉
  * */
  rst.add(new ArrayList<>(list));
  list.remove(list.size()-1);
 }
 list.add(node.var);
 dfs(node.left,rst,list);
 dfs(node.right,rst,list);
 list.remove(list.size()-1);
 }

------------------------------------------------------未完待續哦-----------------------------