从宏观的角度来看,二叉树的遍历归结为两大类:
- 深度优先遍历(前序遍历、中序遍历、后序遍历)
- 广度优先遍历(层序遍历)
二叉树的实现和前序遍历的非递归实现:
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;
public class myBinaryTree {
/**
* 构建二叉树
* @param inputList 输入序列
* @return
*/
public static TreeNode createBinaryTree (LinkedList<Integer> inputList) {
TreeNode node = null;
if (inputList == null || inputList.isEmpty()) {
return null;
}
Integer data = inputList.removeFirst();
if (data != null) {
node = new TreeNode(data);
node.leftChild = createBinaryTree(inputList);
node.rightChild = createBinaryTree(inputList);
}
return node;
}
/**
* 二叉树递归前序遍历
* @param node 二叉树节点
*/
public static void preOrderTraveral(TreeNode node) {
if (node == null) {
return;
}
System.out.println(node.data);
preOrderTraveral(node.leftChild);
preOrderTraveral(node.rightChild);
}
/**
* 二叉树非递归前序遍历
* @param root 二叉树节点
*/
public static void preOrderTraveralWithStack(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode treeNode = root;
while (treeNode != null || !stack.isEmpty()) {
// 迭代访问节点的左子树,并入栈
while (treeNode != null) {
System.out.println(treeNode.data);
stack.push(treeNode);
treeNode = treeNode.leftChild;
}
// 如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
if (!stack.isEmpty()) {
treeNode = stack.pop();
treeNode = treeNode.rightChild;
}
}
}
/**
* 二叉树中序遍历
* @param node 二叉树节点
*/
public static void inOrderTraveral(TreeNode node) {
if (node == null) {
return;
}
preOrderTraveral(node.leftChild);
System.out.println(node.data);
preOrderTraveral(node.rightChild);
}
/**
* 二叉树后序遍历
* @param node 二叉树节点
*/
public static void postOrderTraveral(TreeNode node) {
if (node == null) {
return;
}
preOrderTraveral(node.leftChild);
preOrderTraveral(node.rightChild);
System.out.println(node.data);
}
private static class TreeNode {
int data;
TreeNode leftChild;
TreeNode rightChild;
TreeNode (int data) {
this.data = data;
}
}
public static void main(String[] args) {
LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null}));
TreeNode treeNode = createBinaryTree(inputList);
System.out.println("前序遍历");
preOrderTraveral(treeNode);
System.out.println("非递归前序遍历");
preOrderTraveralWithStack(treeNode);
System.out.println("中序遍历");
inOrderTraveral(treeNode);
System.out.println("后序遍历");
postOrderTraveral(treeNode);
}
}
递归实现就没啥好说的了,就是一层一层地递归,将整个大问题,分解为类似的小问题。前序遍历的实现方式就是一直找左子树,边找边打印节点,找到了最左边的叶子节点,再以此为基础,前->左->右地打印。完了,再向上一级节点进行。
非递归的前序遍历,还是有些难以理解的。不过主要想法也是一样的,当要辅助栈,先一直左走一边压栈,当左子节点的左子节点为null时,先把目前的root节点置为栈顶节点,并将此元素弹出。此时再将root的右子节点置为root。这样有一个好处,就是如果其他都不考虑的话,避免已经遍历节点的重复打印,重复入栈,最后引起死循环。
层序遍历
需要借助队列来辅助工作。
/**
* 二叉树层序遍历
* @param root 二叉树根节点
*/
public static void levelOrderTraveral(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.println(node.data);
if (node.leftChild != null) {
queue.offer(node.leftChild);
}
if (node.rightChild != null) {
queue.offer(node.rightChild);
}
}
}
二叉堆
二叉堆本质上是一种完全二叉树,存储方式为顺序存储(即二叉堆的所有节点都存储在数组中)。它分为两个类型:
- 最大堆:任何一个父节点的值,都大于或等于它左、右孩子节点的值。
- 最小堆:任何一个父节点的值,都小于或等于它左、右孩子节点的值。
- 二叉树的根节点叫作堆顶。
- 最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素。
二叉堆有如下几种操作:
- 插入节点:插入节点时,插入位置为完全二叉树的最后一个位置;如果不符合二叉堆的性质,节点上浮。
- 删除节点:删除处于堆顶的节点 -> 把堆 的最后一个节点临时补到堆顶 -> 进行比较调整,节点下沉。
- 构建二叉堆:就是把一个无序的完全二叉树调整为二叉树,本质就是让所有非叶子节点依次“下沉”。
二叉堆的存储方式不是链式存储,而是顺序存储。即,二叉树的所有节点都存储在数组中。
在数组中,依靠数组下标来定位父节点的左孩子和右孩子。
假设父节点的下标是parent,则它的左孩子下标是2*parent+1;右孩子下标是2*parent+2。
以最小堆为例,代码实现:
import java.util.Arrays;
public class Test {
/**
* “上浮”调整
* @param array 待调整的堆
*/
public static void upAdjust(int[] array) {
int childIndex = array.length - 1;
int parentIndex = (childIndex - 1) / 2;
// temp保存插入的叶子节点,用于最后的赋值
int temp = array[childIndex];
while (childIndex > 0 && temp < array[parentIndex]) {
// 无需真正交换,单向赋值即可
array[childIndex] = array[parentIndex];
childIndex = parentIndex;
parentIndex = (parentIndex - 1) / 2;
}
array[childIndex] = temp;
}
/**
* “下沉”操作
* @param array 待调整的堆
* @param parentIndex 要“下沉”的父节点
* @param length 堆的有效大小
*/
public static void downAdjust(int[] array, int parentIndex, int length) {
// temp保存父节点值,用于最后的赋值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
// 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
childIndex++;
}
// 如果父节点小于任何一个孩子的值,则直接跳出
if (temp <= array[childIndex]) {
break;
}
// 无需真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
public static void buildHeap (int[] array) {
// 从最后一个非叶子节点开始,依次做“下沉调整
for (int i = (array.length - 2) / 2; i >= 0; i--) {
downAdjust(array, i, array.length);
}
}
public static void main(String[] args) {
int[] array = new int[] {1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
upAdjust(array);
System.out.println(Arrays.toString(array));
array = new int[] {7, 3, 10, 5, 2, 8, 9, 6};
buildHeap(array);
System.out.println(Arrays.toString(array));
}
}
输出:
[0, 1, 2, 6, 3, 7, 8, 9, 10, 5]
[2, 3, 8, 5, 7, 10, 9, 6]
二叉堆的用处:堆排序和优先队列的基础
优先队列不再遵循先入先出的原则,而是分为两种情况:
- 最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
- 最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队
优先队列实现:
import java.util.Arrays;
public class PriorityQueue {
private int[] array;
private int size;
public PriorityQueue() {
// 队列长度为32
array = new int[32];
}
/**
* 入队
* @param key 入队元素
*/
public void enQueue(int key) {
// 对列超出范围,扩容
if (size >= array.length) {
resize();
}
array[size++] = key;
upAdjust();
}
/**
* 出队
* @return
* @throws Exception
*/
public int deQueue() throws Exception {
if (size <= 0) {
throw new Exception("the queue is empty");
}
// 获取堆顶元素
int head = array[0];
// 将最后一个元素移动到堆顶
array[0] = array[--size];
downAdjust();
return head;
}
/**
* “上浮”调整
*/
public void upAdjust() {
int childIndex =size - 1;
int parentIndex = (childIndex - 1) / 2;
// temp保存插入的叶子节点,用于最后的赋值
int temp = array[childIndex];
while (childIndex > 0 && temp < array[parentIndex]) {
// 无需真正交换,单向赋值即可
array[childIndex] = array[parentIndex];
childIndex = parentIndex;
parentIndex = (parentIndex - 1) / 2;
}
array[childIndex] = temp;
}
/**
* “下沉”调整
*/
public void downAdjust() {
// temp保存父节点值,用于最后的赋值
int parentIndex = 0;
int temp = array[parentIndex];
int childIndex = 1;
while (childIndex < size) {
// 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if (childIndex + 1 < size && array[childIndex + 1] < array[childIndex]) {
childIndex++;
}
// 如果父节点小于任何一个孩子的值,则直接跳出
if (temp <= array[childIndex]) {
break;
}
// 无需真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
/**
* 对列扩容
*/
private void resize() {
// 队列容量翻倍
int newSize = this.size * 2;
this.array = Arrays.copyOf(this.array, newSize);
}
public static void main(String[] args) throws Exception {
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.enQueue(3);
priorityQueue.enQueue(5);
priorityQueue.enQueue(10);
priorityQueue.enQueue(2);
priorityQueue.enQueue(7);
System.out.println("出队元素: " + priorityQueue.deQueue());
System.out.println("出队元素: " + priorityQueue.deQueue());
}
}