最近看了一下大學的數據結構,??學到了以前沒學到的東西看到了二叉樹那一塊,感覺二叉樹是個很重要的東西,就看了一下底層是怎么實現的,雖然能看懂書上用c語言寫的偽代碼,但是不能運行,身為一個Java程序員,既然別人能用其他的語言能實現,自己也去試著寫了一下。
首先給出二叉樹的節點代碼
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;
}
}
然后模擬一個棧的代碼,會在后面非遞歸的時候用到
由于只是去遍歷所有節點,沒有考慮性能上的問題,只是讓大家了解思路,底層用ArrayList去實現棧功能
/*
模擬棧的功能
*/
public class Stack<E> {
public List<E> mBinTrees = new ArrayList<>();
/**
* 把節點放入棧
*
* @param binTree
*/
public void push(E binTree) {
mBinTrees.add(binTree);
}
/**
* 從棧頂pop出一個節點并得到這個節點
*
* @return
*/
public E pop() {
if (mBinTrees != null) {
return mBinTrees.remove(mBinTrees.size() - 1);
}
return null;
}
/**
* 判斷棧里是否還有數據
*
* @return
*/
public boolean isEmpty() {
return mBinTrees.size() == 0;
}
/**
* 查看當前棧頂的元素
*
* @return
*/
public E top() {
return mBinTrees.get(mBinTrees.size() - 1);
}
}
(1)先序遍歷
先看下先序遍歷的流程圖,接下來的代碼也會根據此圖是遍歷
先序遍歷顧名思義,就是先會遍歷根節點->左孩子->右孩子。遍歷是從根節點開始,遇到每個節點時,其遍歷過程為:
- 訪問根節點;
- 先序遍歷其左子樹;
- 先序遍歷其右子樹;
先序遍歷遞歸實現代碼:
/**
* 前序遍歷 遞歸
*/
public static void preOrderTraversal(BinTree binTree) {
if (binTree != null) {
System.out.print(binTree.name);
preOrderTraversal(binTree.left);
preOrderTraversal(binTree.right);
}
}
先序遍歷非遞歸實現代碼
/**
* 前序遍歷 非遞歸
* 輸出結果 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;
}
}
}
輸出結果
ABDFECGHI
(2)中序遍歷
中序遍歷是指對樹中任一節點的訪問是在遍歷完其左子樹后進行的,訪問此結點后再對其右子樹遍歷。遍歷從根節點開始,遇到每個結點時,其遍歷過程為:
- 中序遍歷其左子樹;
- 訪問根節點;
- 中序遍歷其右子樹
中序遍歷遞歸實現代碼:
/**
* 中序遍歷 遞歸
* DBEFAGHCI
*/
public static void inOrderTraversal(BinTree binTree) {
if (binTree != null) {
inOrderTraversal(binTree.left);
System.out.print(binTree.name);
inOrderTraversal(binTree.right);
}
}
中序遍歷非遞歸實現代碼:
/**
* 中序遍歷 非遞歸
* 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;
}
}
}
過程
在沿左子樹深入時,進入一個結點就將其壓人堆棧。若是先序遍歷,則在入棧之前訪問之;
當沿左分支深人不下去時,則返回,即從堆棧中彈出前面壓人的結點;若為中序遍歷,則此時訪問
該結點,然后從該結點的右子樹繼續深入;若為后序遍歷,則將此結點二次入棧,然后從該結點的
右子樹繼續深入,與前面類同,仍為進入一個結點入棧一個結點,深入不下去再返回,直到第二次
從棧里彈出該結點,才訪問之。
因此,按照上述描述的過程,使用堆棧可以直接實現相應的非遞歸算法。先序和中序算法相
對間果些,而后序遍歷因為需要兩次將一個結點人棧,情況稍復雜些。下面只以中序遍所頭份
介紹二叉樹遍歷的非遞歸算法。
在按中序遍歷二叉樹時,遇到一個結點,就把它入棧,并去遍歷它的左子樹,當左子樹遍歷結束后,從棧頂彈出這個結點并訪問它,然后按其右指針再去中序遍歷該結點的右子樹。
(3)后序遍歷
后序遍歷是指對樹中任一節點的訪問是在遍歷完其左、右子樹后進行的。遍歷從根節點開始,遇到每個結點時,其遍歷過程為:
- 后序遍歷其左子樹;
- 后序遍歷其右子樹
- 訪問根節點;
后序遍歷遞歸實現代碼:
/**
* 后續遍歷 遞歸
* DEFBHGICA
*
* @param tree
*/
public static void postOrderTraversal(BinTree tree) {
if (tree != null) {
postOrderTraversal(tree.left);
postOrderTraversal(tree.right);
System.out.print(tree.name);
}
}
后序遍歷非遞歸實現代碼:
有兩種思路:
第一種思路:對于任一結點P,將其入棧,然后沿其左子樹一直往下搜索,直到搜索到沒有左孩子的結點,此時該結點出現在棧頂,但是此時不能將其出棧并訪問,因此其右孩子還為被訪問。所以接下來按照相同的規則對其右子樹進行相同的處理,當訪問完其右孩子時,該結點又出現在棧頂,此時可以將其出棧并訪問。這樣就保證了正確的訪問順序。可以看出,在這個過程中,每個結點都兩次出現在棧頂,只有在第二次出現在棧頂時,才能訪問它。因此需要多設置一個變量標識該結點是否是第一次出現在棧頂。
/**
* 后續遍歷 非遞歸
* 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);
}
}
}
}
第二種思路:對于任一節點,將其左子樹入棧,直到左子樹為空,查看當前棧頂得元素是否有右子樹或者當前右子樹不等于前一訪問節點,如有,重復之前將其左子樹入棧,沒有,訪問當前節點,并把此節點出棧,并把當前節點作為訪問過的節點。
/**
* 后續遍歷 非遞歸
* 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中有現成的隊列,所以就選擇了鏈表式的隊列。
非遞歸代碼
/**
* 層序遍歷
*
* @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);
}
}
}
先寫到這吧,等學到了再更新。
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;
}