二叉樹的構建和遍歷
阿新 • • 發佈:2018-12-20
(1)下面二叉樹的構建是通過一個數組來構造二叉樹
- 將陣列元素轉換成n個Node節點(n為陣列大小,這裡是9)。
- 用LinkedList資料結構儲存成n個Node節點。
- 先處理前個父節點,因為陣列是從下標0開始的,所以這裡的3個父節點,即是下標為0、1、2的節點,然後處理最後1個父節點即下標為3的節點,因為最後一個父節點可能沒有右孩子,當n為奇數時才有右孩子,為偶數時只有左孩子。
- 父節點索引與左右孩子索引的關係:左孩子索引=;右孩子索引=,這裡如果按和來表示的話,由於下標是從0開始的,這樣下標為0的父節點的子節點就是0和1,很明顯是有問題的,所以才要分別加1。
(2)遍歷 遍歷是對樹的一種最基本的運算,就是按一定的規則和順序走遍二叉樹的所有結點,使每一個結點都被訪問一次,而且只被訪問一次。由於二叉樹是非線性結構,因此,樹的遍歷實質上是將二叉樹的各個結點轉換成為一個線性序列來表示。 設L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹, 則對一棵二叉樹的遍歷有三種情況:DLR(稱為先根次序遍歷),LDR(稱為中根次序遍歷),LRD (稱為後根次序遍歷)。即遍歷的次序是根據父節點的位置而定的。
例子1:
import java.util.LinkedList;
import java.util.List;
/**
* 功能:把一個數組的值存入二叉樹中,然後進行3種方式的遍歷
*/
public class TestBinaryTree {
public static void main(String[] args) {
TestBinaryTree binTree = new TestBinaryTree();
binTree.createBinTree();
Node root = nodeList. get(0); // nodeList中第0個索引處的值即為根節點
System.out.println("先序遍歷:");
preOrderTraverse(root);
System.out.println();
System.out.println("中序遍歷:");
inOrderTraverse(root);
System.out.println();
System.out.println("後序遍歷:");
postOrderTraverse(root);
}
private int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
private static List<Node> nodeList = null;
// 內部靜態類:節點類
private static class Node {
Node leftChild;
Node rightChild;
int data;
Node(int newData) {
leftChild = null;
rightChild = null;
data = newData;
}
}
public void createBinTree() {
nodeList = new LinkedList<Node> ();
// 將一個數組的值依次轉換為Node節點
for (int nodeIndex = 0; nodeIndex < array.length; nodeIndex++) {
nodeList.add(new Node(array[nodeIndex]));
}
// 對前lastParentIndex-1個父節點按照父節點與孩子節點的數字關係建立二叉樹
for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {
nodeList.get(parentIndex).leftChild = nodeList.get(parentIndex * 2 + 1); // 左孩子
nodeList.get(parentIndex).rightChild = nodeList.get(parentIndex * 2 + 2); // 右孩子
}
// 最後一個父節點:因為最後一個父節點可能沒有右孩子,所以單獨拿出來處理
int lastParentIndex = array.length / 2 - 1;
nodeList.get(lastParentIndex).leftChild = nodeList.get(lastParentIndex * 2 + 1); // 左孩子
if (array.length % 2 == 1) {
nodeList.get(lastParentIndex).rightChild = nodeList.get(lastParentIndex * 2 + 2); // 右孩子,如果陣列的長度為奇數才建立右孩子
}
}
// 先序遍歷(傳入的是根節點)
public static void preOrderTraverse(Node node) {
if (node == null)
return;
System.out.print(node.data + " ");
preOrderTraverse(node.leftChild);
preOrderTraverse(node.rightChild);
}
// 中序遍歷 ((傳入的是根節點))
public static void inOrderTraverse(Node node) {
if (node == null)
return;
inOrderTraverse(node.leftChild);
System.out.print(node.data + " ");
inOrderTraverse(node.rightChild);
}
// 後序遍歷((傳入的是根節點))
public static void postOrderTraverse(Node node) {
if (node == null)
return;
postOrderTraverse(node.leftChild);
postOrderTraverse(node.rightChild);
System.out.print(node.data + " ");
}
}
執行:
先序遍歷:
1 2 4 8 9 5 3 6 7
中序遍歷:
8 4 9 2 5 1 6 3 7
後序遍歷:
8 9 4 5 2 6 7 3 1
例子2:
下面這個例子對二叉樹的學習更全面
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
public class TestBinaryTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
TestBinaryTree tree = new TestBinaryTree();
int[] datas = new int[]{1,2,3,4,5,6,7,8,9};
List<Node> nodelist = new LinkedList<>();
tree.creatBinaryTree(datas, nodelist);
Node root = nodelist.get(0); // nodelist中的0索引儲存的是根節點
//System.out.println("遞迴先序遍歷:");
//tree.preOrderTraversal(root);
//System.out.println();
System.out.println("非遞迴先序遍歷:");
tree.preOrderTraversalbyLoop(root);
System.out.println();
System.out.println("遞迴中序遍歷:");
tree.inOrderTraversal(root);
System.out.println();
System.out.println("非遞迴中序遍歷:");
tree.inOrderTraversalbyLoop(root);
System.out.println();
System.out.println("遞迴後序遍歷:");
tree.postOrderTraversal(root);
System.out.println();
System.out.println("非遞迴後序遍歷:");
tree.postOrderTraversalbyLoop(root);
System.out.println();
System.out.println("廣度優先遍歷:");
tree.bfs(root);
System.out.println();
System.out.println("深度優先遍歷:");
List<List<Integer>> rst = new ArrayList<>();
List<Integer> list = new ArrayList<>();
tree.dfs(root,rst,list);
System.out.println(rst);
}
/**
*
* @param datas 實現二叉樹各節點值的陣列
* @param nodelist 二叉樹list
*/
private void creatBinaryTree(int[] datas,List<Node> nodelist){
//將陣列變成node節點
for(int nodeindex=0;nodeindex<datas.length;nodeindex++){
Node node = new Node(datas[nodeindex]);
nodelist.add(node);
}
//給所有父節點設定子節點
for(int index=0;index<nodelist.size()/2-1;index++){
//編號為n的節點他的左子節點編號為2*n 右子節點編號為2*n+1 但是因為list從0開始編號,所以還要+1
//這裡父節點有1(2,3),2(4,5),3(6,7),4(8,9) 但是最後一個父節點有可能沒有右子節點 需要單獨處理
nodelist.get(index).setLeft(nodelist.get(index*2+1));
nodelist.get(index).setRight(nodelist.get(index*2+2));
}
//單獨處理最後一個父節點 因為它有可能沒有右子節點
int index = nodelist.size()/2-1;
nodelist.get(index).setLeft(nodelist.get(index*2+1)); //先設定左子節點
if(nodelist.size() % 2 == 1){ //如果有奇數個節點,最後一個父節點才有右子節點
nodelist.get(index).setRight(nodelist.get(index*2+2));
}
}
/**
* 遍歷當前節點的值
* @param nodelist
* @param node
*/
public void checkCurrentNode(Node node){
System.out.print(node.getVar()+" ");
}
/**
* 非遞迴前序遍歷
* @param node
*/
public void preOrderTraversalbyLoop(Node node){
Stack<Node> stack = new Stack(); // 棧:FILO
Node p = node;
while(p!=null || !stack.isEmpty()){
while(p!=null){ //當p不為空時,就讀取p的值,並不斷更新p為其左子節點,即不斷讀取左子節點
checkCurrentNode(p); //列印輸出p節點的值
stack.push(p); //將p入棧
p = p.getLeft(); // 更新p的左子節點
}
if(!stack.isEmpty()){
p = stack.pop();
p = p.getRight();
}
}
}
/**
* 非遞迴中序遍歷
* @param node
*/
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();
checkCurrentNode(p);
p = p.getRight();
}
}
}
/**
* 非遞迴後序遍歷
* @param node
*/
public void postOrderTraversalbyLoop(Node node){
Stack<Node> stack = new Stack<>();
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(); // peek()檢視棧頂的物件而不移除它。
if(temp == null||temp == prev){
p = stack.pop();
checkCurrentNode(p);
prev = p;
p = null;
}else{
p = temp;
}
}
}
}
/**
* 先序遍歷二叉樹
* @param root 二叉樹根節點
*/
public void preOrderTraversal(Node node){
if (node == null) //很重要,必須加上 當遇到葉子節點用來停止向下遍歷
return;
checkCurrentNode(node);
preOrderTraversal(node.getLeft());
preOrderTraversal(node.getRight());
}
/**
* 中序遍歷二叉樹
* @param root 根節點
*/
public void inOrderTraversal(Node node){
if (node == null) //很重要,必須加上
return;
inOrderTraversal(node.getLeft());
checkCurrentNode(node);
inOrderTraversal(node.getRight());
}
/**
* 後序遍歷二叉樹
* @param root 根節點
*/
public void postOrderTraversal(Node node){
if (node == null) //很重要,必須加上
return;
postOrderTraversal(node.getLeft());
postOrderTraversal(node.getRight());
checkCurrentNode(node);
}
/**
* 廣度優先遍歷(從上到下遍歷二叉樹)
* @param root
*/
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);
}
}
}
/**
* 深度優先遍歷
* @param node
* @param rst
* @param list
*/
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);
/* 這