Java實現二叉樹的建立和遍歷操作(有更新)
阿新 • • 發佈:2019-02-05
博主強烈建議跳過分割線前面的部分,直接看下文更新的那些即可。
最近在學習二叉樹的相關知識,一開始真的是毫無頭緒。本來學的是C++二叉樹,但苦於編譯器老是出故障,於是就轉用Java來實現二叉樹的操作。但是二者原理是一致的,而且實現的方式也是大同小異!
下面就讓我們來看看程式碼吧。
1、首先我們需要建立一個二叉樹的節點類,便於我們對樹的操作,當然了,你也可以在二叉樹類的內部將節點類宣告為內部類,但是這樣會降低操作的靈活性。我才用的是單獨建立一個BinaryTreeNode類,程式碼如下:
package MyBinaryTree;
public class BinaryTreeNode<T> {
T data;
BinaryTreeNode<T> leftChild;
BinaryTreeNode<T> rightChild;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
BinaryTreeNode() {
this.data = null;
this.leftChild = null;
this.rightChild = null;
}
BinaryTreeNode(T data) {
this.data = data;
this.leftChild = null ;
this.rightChild = null;
}
public BinaryTreeNode(T data, BinaryTreeNode<T> leftChild,
BinaryTreeNode<T> rightChild) {
super();
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public BinaryTreeNode<T> getLeftChild () {
return leftChild;
}
public void setLeftChild(BinaryTreeNode<T> leftChild) {
this.leftChild = leftChild;
}
public BinaryTreeNode<T> getRightChild() {
return rightChild;
}
public void setRightChild(BinaryTreeNode<T> rightChild) {
this.rightChild = rightChild;
}
public boolean isLeaf() {
if (this.leftChild == null && this.rightChild == null) {
return true;
}
return false;
}
}
//我才用的是泛型定義,在C++中我們可以使用模板來實現相同的處理
2、有了節點類,下面就是二叉樹類了,註釋什麼的在程式碼中已經非常詳細了:
package MyBinaryTree;
import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.LinkedBlockingQueue;
public class BinaryTree<T> {
private BinaryTreeNode<T> root;
public BinaryTree() {
BinaryTreeNode<T> node = new BinaryTreeNode<T>();
this.root = node;
}
public boolean isEmpty() {
if (root == null) {
return true;
}
return false;
}
public BinaryTreeNode<T> getRoot() {
return this.root;
}
public void CreateTree(BinaryTreeNode<T> node, T data) {
if (root == null) {
root = new BinaryTreeNode<T>();
} else {
if (Math.random() > 0.5) { //採用隨機方式建立二叉樹
if (node.leftChild == null) {
node.leftChild = new BinaryTreeNode<T>(data);
} else {
CreateTree(node.leftChild, data);
}
} else {
if (node.rightChild == null) {
node.rightChild = new BinaryTreeNode<T>(data);
} else {
CreateTree(node.rightChild, data);
}
}
}
}
/*
* 訪問當前節點
*/
public void Visit(BinaryTreeNode<T> current) {
if(current!=null&¤t.getData()!=null){
System.out.println(current.getData());
}else{
System.out.println("null");
}
}
/*
* 廣度優先遍歷二叉樹
*/
public void levelOrder(BinaryTreeNode<T> root) {
Queue<BinaryTreeNode<T>> queue = new LinkedBlockingQueue<BinaryTreeNode<T>>();
BinaryTreeNode<T> pointer = root;
/*
* 當前節點不為空時,放入隊首
*/
if (pointer != null) {
queue.add(pointer);
}
/*
* 佇列不為空時,先訪問中間節點,訪問完成後彈出隊首節點;然後是左節點,再是右節點;
*/
while (!queue.isEmpty()) {
pointer = queue.peek();
Visit(pointer);
queue.remove();
if (pointer.leftChild != null) {
queue.add(pointer.leftChild);
}
if (pointer.rightChild != null) {
queue.add(pointer.rightChild);
}
}
}
/*
* 遞迴方式的前序遍歷
*/
public void preOrder(BinaryTreeNode<T> root) {
Visit(root);
preOrder(root.leftChild);
preOrder(root.rightChild);
}
/*
* 非遞迴方式實現的前序遍歷
*/
public void NPreOrder(BinaryTreeNode<T> root){
Queue<BinaryTreeNode<T>> queue=new LinkedBlockingQueue<BinaryTreeNode<T>>();
BinaryTreeNode<T> pointer=root;
/*
* 當前節點不為空,就一直放入隊尾;當前節點為空時,訪問隊首元素,然後訪問做孩子節點;然後彈出,再對新的隊首元素進行判斷
*/
while(!queue.isEmpty()||pointer!=null){
if(pointer!=null){
Visit(pointer);
if(pointer.rightChild!=null){
queue.add(pointer.rightChild);
}
pointer=pointer.leftChild;
}else{
pointer=queue.peek();
queue.remove();
}
}
}
/*
* 採用遞迴方式實現的中序遍歷操作
*/
public void inOrder(BinaryTreeNode<T> root){
inOrder(root.leftChild);
Visit(root);
inOrder(root.rightChild);
}
/*
* 非遞迴方式實現的中序遍歷
*/
public void NInOrder(BinaryTreeNode<T> root){
Stack<BinaryTreeNode<T>> stack=new Stack<BinaryTreeNode<T>>();
BinaryTreeNode<T> pointer=root;
/*
* 當前節點不為空,就一直進棧;當前節點為空時,訪問棧頂元素,然後再訪問右孩子節點
*/
while(!stack.isEmpty()||pointer!=null){
if(pointer!=null){
stack.push(pointer);
pointer=pointer.leftChild;
}else{
pointer=stack.peek();
Visit(pointer);
pointer=pointer.rightChild;
stack.pop();
}
}
}
/*
* 遞迴方式實現的後序遍歷二叉樹
*/
public void postOrder(BinaryTreeNode<T> root){
postOrder(root.leftChild);
postOrder(root.rightChild);
Visit(root);
}
/*
* 非遞迴方式實現的後序遍歷二叉樹
*/
public void NPostOrder(BinaryTreeNode<T> root){
Stack<BinaryTreeNode<T>> stack=new Stack<BinaryTreeNode<T>>();//初始化棧,用於儲存帶訪問的節點
BinaryTreeNode<T> pointer=root; //儲存根節點
BinaryTreeNode<T> preNode=root; //儲存前一個被訪問的節點
while(!stack.isEmpty()||pointer!=null){
//若當前節點不空,就一直進棧,然後繼續向左走
while(pointer.leftChild!=null){
stack.push(pointer);
pointer=pointer.leftChild;
}
/*
* 當前節點為空時,分兩種情況:
* 1、當前節點移動到棧頂處,然後訪問棧頂元素的右節點
* 2、當前節點移動到棧頂,但是棧頂元素沒有右節點,這就需要彈出棧頂元素,再對此元素訪問;
* 然後再對新的棧頂元素進行判斷即可
*/
while(pointer!=null&&(pointer.rightChild==null)||(pointer.rightChild==preNode)){
Visit(pointer);
preNode=pointer;
if(stack.isEmpty()){
return;
}
pointer=stack.peek();
stack.pop();
}
stack.push(pointer);
pointer=pointer.rightChild;
}
}
}
3、然後是我的測試類,下面請看程式碼:
public static void main(String[] args) {
BinaryTree<Integer> tree = new BinaryTree<Integer>();
for (int i = 1; i < 10; i++) {
tree.CreateTree(tree.root, i);
}
System.out.println("-----------下面是廣度優先遍歷二叉樹--------------");
tree.levelOrder(tree.root);
System.out.println("-----------下面是非遞迴的前序遍歷方式-------------");
tree.NPreOrder(tree.root);
System.out.println("-----------下面是非遞迴的中序遍歷方式-------------");
tree.NInOrder(tree.root);
System.out.println("-----------下面是非遞迴的後序遍歷方式-------------");
tree.NPostOrder(tree.root);
}
4、接下來是測試的結果:
-----------下面是廣度優先遍歷二叉樹--------------
null
1
2
6
4
3
8
7
5
9
-----------下面是非遞迴的前序遍歷方式-------------
null
1
6
2
3
4
5
7
8
9
-----------下面是非遞迴的中序遍歷方式-------------
6
7
1
5
4
null
3
9
2
8
-----------下面是非遞迴的後序遍歷方式-------------
7
6
5
4
1
9
3
8
2
null
5、不足之處:
也許是測試的時候方式不對,因為使用遞迴方式對二叉樹進行遍歷的時候會報出NullPointerException的空指標錯誤。如果你知道原因在哪?不妨寫下你的評論。也好讓我加以改正。
6、總結:
在學習的過程中我意識到了一點,希望與君共勉!那就是埋頭敲程式碼是解決不了問題的。重要的是思路。沒有思路,一味的測試也是不可能成功的。在敲程式碼之前,我們一定要搞懂我們要做什麼,怎麼做,這樣才會事半功倍。希望能和大家共同學習,一起進步!
—————————時間的分割線——————————————————————–
2016年9月12日19:01:38
看到大二剛開始學資料結構的時候,寫的這篇文章,水平真的是不忍直視啊。不過話又說回來了,程式設計的提高不就是這樣一點一點積聚來的嘛。下面來寫點比較容易理解的思路清晰的二叉樹遍歷相關的操作。
/**
* @Date 2016年9月12日
*
* @author 郭 璞
*
*/
package tree;
import java.util.Stack;
/**
* @author 郭 璞 <br>
* 二叉樹的先序,中序,以及後序,遞迴以及非遞迴的實現
*
*/
public class FullScan {
public static void main(String[] args) {
Node head = createTree();
// recurseFront(head);
// recurseMid(head);
recurseEnd(head);
// front(head);
// mid(head);
endWith2Stack(head);
endWithOneStack(head);
}
/**
* 非遞迴實現的二叉樹後序遍歷<br>
* 藉助於一個棧進行實現
*
* @param head
*/
public static void endWithOneStack(Node head) {
System.out.println();
if (head == null) {
return;
} else {
Stack<Node> stack = new Stack<Node>();
stack.push(head);
// 該節點代表已經列印過的節點,待會會及時的進行更新
Node printedNode = null;
while (!stack.isEmpty()) {
// 獲取 棧頂的元素的值,而不是pop掉棧頂的值
head = stack.peek();
// 如果當前棧頂元素的左節點不為空,左右節點均未被列印過,說明該節點是全新的,所以壓入棧中
if (head.getLeft() != null && printedNode != head.getLeft() && printedNode != head.getRight()) {
stack.push(head.getLeft());
} else if (head.getRight() != null && printedNode != head.getRight()) {
// 第一層不滿足,則說明該節點的左子樹已經被列印過了。如果棧頂元素的右節點未被列印過,則將右節點壓入棧中
stack.push(head.getRight());
} else {
// 上面兩種情況均不滿足的時候則說明左右子樹均被列印過,此時只需要彈出棧頂元素,列印該值即可
System.out.println("當前值為:" + stack.pop().getValue());
// 記得實時的更新列印過的節點的值
printedNode = head;
}
}
}
}
/**
* 非遞迴實現的二叉樹的後序遍歷<br>
* 藉助於兩個棧來實現
*
* @param head
*/
public static void endWith2Stack(Node head) {
System.out.println();
if (head == null) {
return;
} else {
Stack<Node> stack1 = new Stack<Node>();
Stack<Node> stack2 = new Stack<Node>();
stack1.push(head);
// 對每一個頭結點進行判斷,先將頭結點放入棧2中,然後依次將該節點的子元素放入棧1.順序為left-->right。便是因為後序遍歷為“左右根”
while (!stack1.isEmpty()) {
head = stack1.pop();
stack2.push(head);
if (head.getLeft() != null) {
stack1.push(head.getLeft());
}
if (head.getRight() != null) {
stack1.push(head.getRight());
}
}
// 直接遍歷輸出棧2,即可實現後序遍歷的節點值的輸出
while (!stack2.isEmpty()) {
System.out.println("當前節點的值:" + stack2.pop().getValue());
}
}
}
/**
* 非遞迴實現的二叉樹的中序遍歷
*
* @param head
*/
public static void mid(Node head) {
System.out.println();
if (head == null) {
return;
} else {
Stack<Node> nodes = new Stack<Node>();
// 使用或的方式是因為 第一次的時候戰中元素為空,head的非null特性可以保證程式可以執行下去
while (!nodes.isEmpty() || head != null) {
// 當前節點元素值不為空,則放入棧中,否則先打印出當前節點的值,然後將頭結點變為當前節點的右子節點。
if (head != null) {
nodes.push(head);
head = head.getLeft();
} else {
Node temp = nodes.pop();
System.out.println("當前節點的值:" + temp.getValue());
head = temp.getRight();
}
}
}
}
/**
* 非遞迴實現的二叉樹的先序遍歷
*
* @param head
*/
public static void front(Node head) {
System.out.println();
// 如果頭結點為空,則沒有遍歷的必要性,直接返回即可
if (head == null) {
return;
} else {
// 初始化用於存放節點順序的棧結構
Stack<Node> nodes = new Stack<Node>();
// 先把head節點放入棧中,便於接下來的迴圈放入節點操作
nodes.add(head);
while (!nodes.isEmpty()) {
// 取出棧頂元素,判斷其是否有子節點
Node temp = nodes.pop();
System.out.println("當前節點的值:" + temp.getValue());
// 先放入右邊子節點的原因是先序遍歷的話輸出的時候左節點優先於右節點輸出,而棧的特性決定了要先放入右邊的節點
if (temp.getRight() != null) {
nodes.push(temp.getRight());
}
if (temp.getLeft() != null) {
nodes.push(temp.getLeft());
}
}
}
}
/**
* 遞迴實現的先序遍歷
*
* @param head
*/
public static void recurseFront(Node head) {
System.out.println();
if (head == null) {
return;
}
System.out.println("當前節點值:" + head.getValue());
recurseFront(head.left);
recurseFront(head.right);
}
/**
* 遞迴實現的中序遍歷
*
* @param head
*/
public static void recurseMid(Node head) {
System.out.println();
if (head == null)
return;
recurseMid(head.getLeft());
System.out.println("當前節點的值:" + head.getValue());
recurseMid(head.getRight());
}
/**
* 遞迴實現的後序遍歷遞迴實現
*
* @param head
*/
public static void recurseEnd(Node head) {
System.out.println();
if (head == null)
return;
recurseEnd(head.getLeft());
recurseEnd(head.getRight());
System.out.println("當前節點的值為:" + head.getValue());
}
public static Node createTree() {
// 初始化節點
Node head = new Node(1);
Node headLeft = new Node(2);
Node headRight = new Node(3);
Node headLeftLeft = new Node(4);
Node headLeftRigth = new Node(5);
Node headRightLeft = new Node(6);
// 為head節點 賦予左右值
head.setLeft(headLeft);
head.setRight(headRight);
headLeft.setLeft(headLeftLeft);
headLeft.setRight(headLeftRigth);
headRight.setLeft(headRightLeft);
// 返回樹根節點
return head;
}
}
class Node {
public int value;
public Node left;
public Node right;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
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() {
}
public Node(int value) {
this.value = value;
}
}