二叉树实现
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
二叉树中的访问,访问其实是根据实际的需要来确定要做什么,比如对每个结点进行相关计算,输出打印等
二叉树的遍历分为两种:深度优先遍历和广度优先遍历;深度优先遍历又分为两种,先序,中序,后序等,深度优先遍历的辅助结构是栈。
无论是哪种遍历方法,考察节点的顺序都是一样的。
考察节点顺序:
二叉树的考察节点顺序是先进入根节点的左节点,之后若该节点的左节点存在则继续延申考察,知直到左节点不存在,则返回原节点,考察右节点。
上图的考察顺序为:1->2->4->4->6->7->7->7->6->8->8->8->6->4->->2->2->1->3->3->5->5->5->3->1
简单讲下,首先进入根节点元素1,考察其左节点2,考察左节点2的左节点4,左节点4的的左节点不存在,则返回至左节点4(这样节点4就出现了两次),考察其右节点6,继续考察右节点6的左节点7,左节点7的左节点不存在,返回至左点7,考察左节点7的右节点,右节点不存在,返回至左节点7,这样节点7就出现了三次,其他同理
可以看到每个节点都出现了三次,先序遍历就是选择每个节点第一次出现的位置,中序遍历选择每个节点第二次出现的位置,后序遍历选择每个节点第三次出现的位置
三种遍历方法(人工)得到的结果分别是:
先序:1 2 4 6 7 8 3 5
中序:4 7 6 8 2 1 3 5
后序:7 8 6 4 2 5 3 1
三种遍历方法的考查顺序一致,得到的结果却不一样,原因在于:
- 先序:考察到一个节点后,即刻输出该节点的值,并继续遍历其左右子树。(根左右)
- 中序:考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右)
- 后序:考察到一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值。(左右根)
可以简单理解为所谓前序、中序、后序是指"根"在其中的位置。
三种遍历方式之间的特点
- 前序遍历的第一个节点为根节点
- 中序遍历中,root在左右子树中间
- 后序遍历的最后一个节点为根节点
二叉树的先序遍历递归
若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
public static void preOrderRecur(Node head) {
//根->左->右
if (head == null) {
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
二叉树的中序遍历递归
若二叉树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点左子树,然后访问根结点,最后中序遍历右子树
public static void inOrderRecur(Node head) {
//左->根->右
if (head == null) {
return;
}
inOrderRecur(head.left);
System.out.print(head.value + " ");
inOrderRecur(head.right);
}
二叉树的后序遍历递归
若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。后序遍历的最后一个必然是根
public static void posOrderRecur(Node head) {
//左->右->根
if (head == null) {
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.value + " ");
}
二叉树的先序遍历非递归
根据前序遍历访问的顺序,优先访问根节点,然后再分别访问左孩子和右孩子,即对于任一结点,其可看做是根节点,可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问完它的左子树后,再访问它的右子树。
public static void preOrderUnRecur(Node head) {
System.out.print("pre-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
//压入根节点
stack.add(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.value + " ");
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
}
System.out.println();
}
二叉树的中序遍历非递归
二叉树的中序遍历是:左-根-右。非递归实现是一路将根节点的左子树压入栈直到不存在左节点为止,之后弹出栈,压入右子树。
中序遍历就是将左子树作为单元,一层左子树遍历完后,跳到右节点,再遍历该右节点的左子树
public static void inOrderUnRecur(Node head) {
System.out.print("in-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
System.out.print(head.value + " ");
head = head.right;
}
}
}
System.out.println();
}
二叉树的后序遍历非递归
二叉树的后序遍历为:左->右->根。后序遍历的非递归实现较为复杂,需要两个栈进行实现。
栈二入栈的顺序是右子树先入,弹栈的顺序便是左->右->根
public static void posOrderUnRecur1(Node head) {
System.out.print("pos-order: ");
if (head != null) {
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while (!s1.isEmpty()) {
head = s1.pop();
s2.push(head);
if (head.left != null) {
s1.push(head.left);
}
if (head.right != null) {
s1.push(head.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
打印一颗二叉树
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
打印出来的内容需要逆时针90度去看