數(shù)據(jù)結(jié)構(gòu)-平衡二叉樹的前序遍歷、中序遍歷、后續(xù)遍歷的Java 實現(xiàn)

最近看了一下大學(xué)的數(shù)據(jù)結(jié)構(gòu),??學(xué)到了以前沒學(xué)到的東西看到了二叉樹那一塊,感覺二叉樹是個很重要的東西,就看了一下底層是怎么實現(xiàn)的,雖然能看懂書上用c語言寫的偽代碼,但是不能運行,身為一個Java程序員,既然別人能用其他的語言能實現(xiàn),自己也去試著寫了一下。

首先給出二叉樹的節(jié)點代碼

public class BinTree {

  public BinTree(String name) {
    this.name = name;
  }

  public BinTree(int name) {
    this.name = name + "";
  }

  BinTree left;
  BinTree right;
  public String name;



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

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

  public void setName(String name) {
    this.name = name;
  }
}

然后模擬一個棧的代碼,會在后面非遞歸的時候用到

由于只是去遍歷所有節(jié)點,沒有考慮性能上的問題,只是讓大家了解思路,底層用ArrayList去實現(xiàn)棧功能

/*
模擬棧的功能
*/
public class Stack<E> {
  public List<E> mBinTrees = new ArrayList<>();

  /**
   * 把節(jié)點放入棧
   * 
   * @param binTree
   */
  public void push(E binTree) {
    mBinTrees.add(binTree);
  }

  /**
   * 從棧頂pop出一個節(jié)點并得到這個節(jié)點
   * 
   * @return
   */
  public E pop() {
    if (mBinTrees != null) {

      return mBinTrees.remove(mBinTrees.size() - 1);
    }

    return null;
  }

  /**
   * 判斷棧里是否還有數(shù)據(jù)
   * 
   * @return
   */
  public boolean isEmpty() {
    return mBinTrees.size() == 0;
  }

  /**
   * 查看當(dāng)前棧頂?shù)脑?   * 
   * @return
   */
  public E top() {
    return mBinTrees.get(mBinTrees.size() - 1);
  }
}

(1)先序遍歷

先看下先序遍歷的流程圖,接下來的代碼也會根據(jù)此圖是遍歷


先序遍歷.JPG

先序遍歷顧名思義,就是先會遍歷根節(jié)點->左孩子->右孩子。遍歷是從根節(jié)點開始,遇到每個節(jié)點時,其遍歷過程為:

  • 訪問根節(jié)點;
  • 先序遍歷其左子樹;
  • 先序遍歷其右子樹;

先序遍歷遞歸實現(xiàn)代碼:

/**
   * 前序遍歷 遞歸
   */
  public static void preOrderTraversal(BinTree binTree) {
    if (binTree != null) {
      System.out.print(binTree.name);
      preOrderTraversal(binTree.left);
      preOrderTraversal(binTree.right);
    }
  }

先序遍歷非遞歸實現(xiàn)代碼

/**
   * 前序遍歷 非遞歸
   * 輸出結(jié)果 ABDFECGHI
   */
  public static void preOrderTraversalNoDIGUI(BinTree binTree) {
    Stack<BinTree> stack = new Stack();
    BinTree t = binTree;
    while (t != null || !stack.isEmpty()) {
      while (t != null) {
        System.out.print(t.name);
        stack.push(t);
        t = t.left;
      }
      if (!stack.isEmpty()) {
        t = stack.pop();
        t = t.right;
      }
    }
  }

輸出結(jié)果

ABDFECGHI

(2)中序遍歷

中序遍歷.JPG

中序遍歷是指對樹中任一節(jié)點的訪問是在遍歷完其左子樹后進行的,訪問此結(jié)點后再對其右子樹遍歷。遍歷從根節(jié)點開始,遇到每個結(jié)點時,其遍歷過程為:

  • 中序遍歷其左子樹;
  • 訪問根節(jié)點;
  • 中序遍歷其右子樹

中序遍歷遞歸實現(xiàn)代碼:

  /**
   * 中序遍歷 遞歸
   * DBEFAGHCI
   */
  public static void inOrderTraversal(BinTree binTree) {
    if (binTree != null) {
      inOrderTraversal(binTree.left);
      System.out.print(binTree.name);
      inOrderTraversal(binTree.right);
    }
  }

中序遍歷非遞歸實現(xiàn)代碼:

  /**
   * 中序遍歷 非遞歸
   * DBEFAGHCI
   */
  public static void inOrderTraversalNODIGUI(BinTree binTree) {
    Stack<BinTree> stack = new Stack();
    BinTree t = binTree;
    while (t != null || !stack.isEmpty()) {
      while (t != null) {
        stack.push(t);
        t = t.left;
      }
      if (!stack.isEmpty()) {
        t = stack.pop();
        System.out.println(t.name);
        t = t.right;
      }
    }
  }

過程

在沿左子樹深入時,進入一個結(jié)點就將其壓人堆棧。若是先序遍歷,則在入棧之前訪問之;
當(dāng)沿左分支深人不下去時,則返回,即從堆棧中彈出前面壓人的結(jié)點;若為中序遍歷,則此時訪問
該結(jié)點,然后從該結(jié)點的右子樹繼續(xù)深入;若為后序遍歷,則將此結(jié)點二次入棧,然后從該結(jié)點的
右子樹繼續(xù)深入,與前面類同,仍為進入一個結(jié)點入棧一個結(jié)點,深入不下去再返回,直到第二次
從棧里彈出該結(jié)點,才訪問之。
因此,按照上述描述的過程,使用堆棧可以直接實現(xiàn)相應(yīng)的非遞歸算法。先序和中序算法相
對間果些,而后序遍歷因為需要兩次將一個結(jié)點人棧,情況稍復(fù)雜些。下面只以中序遍所頭份
介紹二叉樹遍歷的非遞歸算法。
在按中序遍歷二叉樹時,遇到一個結(jié)點,就把它入棧,并去遍歷它的左子樹,當(dāng)左子樹遍歷結(jié)束后,從棧頂彈出這個結(jié)點并訪問它,然后按其右指針再去中序遍歷該結(jié)點的右子樹。

(3)后序遍歷

后續(xù)遍歷.JPG

后序遍歷是指對樹中任一節(jié)點的訪問是在遍歷完其左、右子樹后進行的。遍歷從根節(jié)點開始,遇到每個結(jié)點時,其遍歷過程為:

  • 后序遍歷其左子樹;
  • 后序遍歷其右子樹
  • 訪問根節(jié)點;

后序遍歷遞歸實現(xiàn)代碼:

  /**
   * 后續(xù)遍歷 遞歸
   * DEFBHGICA
   * 
   * @param tree
   */
  public static void postOrderTraversal(BinTree tree) {
    if (tree != null) {
      postOrderTraversal(tree.left);
      postOrderTraversal(tree.right);
      System.out.print(tree.name);
    }
  }

后序遍歷非遞歸實現(xiàn)代碼:

有兩種思路:

第一種思路:對于任一結(jié)點P,將其入棧,然后沿其左子樹一直往下搜索,直到搜索到?jīng)]有左孩子的結(jié)點,此時該結(jié)點出現(xiàn)在棧頂,但是此時不能將其出棧并訪問,因此其右孩子還為被訪問。所以接下來按照相同的規(guī)則對其右子樹進行相同的處理,當(dāng)訪問完其右孩子時,該結(jié)點又出現(xiàn)在棧頂,此時可以將其出棧并訪問。這樣就保證了正確的訪問順序??梢钥闯?,在這個過程中,每個結(jié)點都兩次出現(xiàn)在棧頂,只有在第二次出現(xiàn)在棧頂時,才能訪問它。因此需要多設(shè)置一個變量標(biāo)識該結(jié)點是否是第一次出現(xiàn)在棧頂。

/**
   * 后續(xù)遍歷 非遞歸
   * DEFBHGICA
   *
   * @param root
   */
  public static void postOrderTraversalNODIGUI(BinTree root) {
    BinTree cur;
    BinTree pre = null;
    Stack<BinTree> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
      cur = stack.top();
      if ((cur.left == null && cur.right == null)
          || (pre != null && (pre == cur.left || pre == cur.right))) {
        System.out.println(cur.name);
        stack.pop();
        pre = cur;
      } else {
        if (cur.right != null) {
          stack.push(cur.right);
        }
        if (cur.left != null) {
          stack.push(cur.left);
        }
      }
    }
  }

第二種思路:對于任一節(jié)點,將其左子樹入棧,直到左子樹為空,查看當(dāng)前棧頂?shù)迷厥欠裼杏易訕浠蛘弋?dāng)前右子樹不等于前一訪問節(jié)點,如有,重復(fù)之前將其左子樹入棧,沒有,訪問當(dāng)前節(jié)點,并把此節(jié)點出棧,并把當(dāng)前節(jié)點作為訪問過的節(jié)點。

 /**
   * 后續(xù)遍歷 非遞歸
   * DEFBHGICA
   *
   * @param root
   */
  public static void postOrderTraversalNODIGUI2(BinTree root) {
    BinTree tree = root;
    BinTree cur = null;
    BinTree pre = null;
    Stack<BinTree> stack = new Stack<>();
    while (!stack.isEmpty() || tree != null) {
      while (tree != null) {
        stack.push(tree);
        tree = tree.left;
      }
      cur = stack.top();
      if (cur.right != null && (pre != null && cur.right != pre)) {
        tree = cur.right;
      } else {
        System.out.println(cur.name);
        stack.pop();
      }
      pre = cur;
    }
  }

(4)層序遍歷

層序遍歷用到了隊列,Java中有現(xiàn)成的隊列,所以就選擇了鏈表式的隊列。
非遞歸代碼

/**
   * 層序遍歷
   * 
   * @param boot
   */
  public static void LevelOrderTraversal(BinTree boot) {
    LinkedBlockingQueue<BinTree> queue = new LinkedBlockingQueue<>();
    BinTree tree;
    if (boot == null) return;
    queue.add(boot);
    while (!queue.isEmpty()) {
      tree = queue.remove();
      System.out.println(tree.name);
      if (tree.left != null) {
        queue.add(tree.left);
      }
      if (tree.right != null) {
        queue.add(tree.right);
      }
    }
  }
先寫到這吧,等學(xué)到了再更新。

2017.3.9

(5)求一個二叉樹的深度

/**
   * 樹的深度
   *用到了遞歸
   * @param bt
   */
  public static int postOrderGetHeight(BinTree bt) {
    int leftHeight;
    int rightHeight;
    int maxHeight;
    if (bt != null) {
      leftHeight = postOrderGetHeight(bt.left);
      rightHeight = postOrderGetHeight(bt.right);
      maxHeight = leftHeight > rightHeight ? leftHeight : rightHeight;
      return maxHeight + 1;
    }
    return 0;
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容