二叉树的基本概念
- 概念
- 二叉树是一种特殊的树。二叉树的每个结点最多有两个子结点,左边的叫左子结点,右边的叫右子结点
- 二叉树要么为空,要么由根结点、左子树、右子树组成,而左子树和右子树分别是一颗二叉树
- 特点
- 若一个父节点的下标是parent,它的左孩子节点下标是2 × parent + 1 , 右孩子节点下标是2 × parent + 2
- 若一个左孩子节点的下标是leftChild,它的父节点下标是(leftChild - 1)/ 2
二叉树的存储结构
- 顺序存储结构——数组
- 使用数组存储,按照某种顺序把二叉树的节点放到数组中对应的位置上
- 如果某一个节点的左子结点或右子结点空缺,则数组的相应位置也空出来
- 为什么这样设计呢???因为这样可以更方便地在数组中定位二叉树的子结点和父结点
- 链式存储结构——类
- 根据面向对象,将二叉树的结点抽象为一个类
- 类中包含当前结点的数据,以及对左子结点和右子结点的引用
/**
* 树的节点
*/
class TreeNode {
int data; //当前结点的数据
TreeNode leftChild; // 左子结点
TreeNode rightChild; // 右子结点
TreeNode(int data) {
this.data = data;
}
}
如何构建一颗二叉树?
- 构建二叉树的过程,实际上是将一组顺序的值 依次 转化为node结点的过程。
- 再简单点,就是把数组中的值,转换为TreeNode对象的过程
- 那么,如图所示的二叉树,按照一定的顺序(先序遍历)转换为数组
3 | 2 | 9 | null | null | 10 | null | null | 8 | null | 4 |
---|
- java构建二叉树
/**
* 创建二叉树:3,2,9,null,null,10,null,null,8,null,4
*/
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 preOrder(TreeNode node) {
if (node == null) {
return;
}
System.out.print(node.data + " ");
preOrder(node.leftChild);
preOrder(node.rightChild);
}
先序遍历-栈的实现思路:
- 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
- 如果栈不为空,将栈中结点出栈,并找到该结点的右子结点,以该结点为根结点继续循环
- 如果该结点没有右子结点,且栈不为空,则继续出栈,并找到该结点的右子结点继续循环
- 关键在于,在找左子结点的过程中,打印结点的值
/**
* 先序遍历-栈的实现方式
* @param node
*/
public static void preOrderStack(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
TreeNode tempNode = node;
while (tempNode != null || !stack.isEmpty()) {
// 左子树
while (tempNode != null) {
System.out.print(tempNode.data + " ");
stack.push(tempNode);
tempNode = tempNode.leftChild;
}
// 右子树
if (!stack.isEmpty()) {
tempNode = stack.pop();
tempNode = tempNode.rightChild;
}
}
System.out.println();
}
- 中序遍历 (左、根、右)
- 所谓的中序遍历就是先访问左节点,再访问根节点,最后访问右节点
/**
* 中序遍历 - 递归实现方式
* @param node
*/
public static void midOrder(TreeNode node) {
if (node == null) {
return;
}
midOrder(node.leftChild);
System.out.print(node.data + " ");
midOrder(node.rightChild);
}
中序遍历-栈的实现思路:
- 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
- 如果栈不为空,将栈中结点出栈,并找到该结点的右子结点,以该结点为根结点继续循环
- 如果该结点没有右子结点,且栈不为空,则继续出栈,并找到该结点的右子结点继续循环
- 关键在于,在找右子结点之前,打印结点的值
/**
* 中序遍历-栈的实现方式
* @param node
*/
public static void midOrderStack(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
TreeNode tempNode = node;
while (tempNode != null || !stack.isEmpty()) {
// 左节点
while (tempNode != null) {
stack.push(tempNode);
tempNode = tempNode.leftChild;
}
// 右节点
if (!stack.isEmpty()) {
tempNode = stack.pop();
System.out.print(tempNode.data + " ");
tempNode = tempNode.rightChild;
}
}
System.out.println();
}
- 后序遍历 (左、右、根)
- 所谓的后序遍历就是先访问左节点,再访问右节点,最后访问根节点
/**
* 后序遍历 - 递归实现方式
* @param node
*/
public static void lastOrder(TreeNode node) {
if (node == null) {
return;
}
lastOrder(node.leftChild);
lastOrder(node.rightChild);
System.out.print(node.data + " ");
}
后序遍历-栈的实现思路:
- 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
- 从栈顶开始遍历栈中结点,判断栈顶元素是否存在右子结点
- 如果存在并且没有被访问,则将右子结点入栈;否则,就访问栈顶元素
- 其关键就在于判断是否该结点的右子结点是否被访问过
/**
* 后序遍历-栈的实现方式
* @param node
*/
public static void lastOrderStack(TreeNode node) {
Stack<TreeNode> stack = new Stack<>();
TreeNode tempNode = node;
TreeNode preNode = null;
while (tempNode != null || !stack.isEmpty()) {
// 左节点
while (tempNode != null) {
stack.push(tempNode);
tempNode = tempNode.leftChild;
}
// 右节点
if (!stack.isEmpty()) {
// 取栈顶的元素
tempNode = stack.peek();
// 没有右子结点,或者右子结点已经被访问过
if (tempNode.rightChild == null || tempNode.rightChild == preNode) {
// 可以访问栈顶元素
tempNode = stack.pop();
System.out.print(tempNode.data + " ");
// 标记上一次访问的节点
preNode = tempNode;
tempNode = null;
}
// 存在没有被访问的右子结点
else {
tempNode = tempNode.rightChild;
}
}
}
}
广度优先遍历(层序遍历)
- 所谓的层序遍历就是从根结点从上往下逐层遍历,在同一层,按左到右的顺序逐个访问
实现思路
- 利用队列先进先出的特征,先让根结点入队
- 遍历队列,并打印出根结点的值
- 如果根结点存在左右子结点,将根结点的左右子结点依次按照从左到右的顺序入队
- 根据队列先进先出的特征,会一层一层出队并打印结点的值
/**
* 层序遍历 - 队列实现方式
* @param node
*/
public static void layerOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root); //入队
while (!queue.isEmpty()) {
TreeNode node = queue.poll(); //出队
System.out.print(node.data + " ");
if (node.leftChild != null) {
queue.offer(node.leftChild); //左子节点入队
}
if (node.rightChild != null) {
queue.offer(node.rightChild);//右子节点入队
}
}
}
运行代码
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>(Arrays.asList(3, 2, 9, null, null, 10, null, null, 8, null, 4));
TreeNode node = createBinaryTree(list);
System.out.print("先序遍历-递归:");
preOrder(node);
System.out.println();
System.out.print("中序遍历-递归:");
midOrder(node);
System.out.println();
System.out.print("后序遍历-递归:");
lastOrder(node);
System.out.println();
System.out.print("先序遍历-栈:");
preOrderStack(node);
System.out.print("中序遍历-栈:");
midOrderStack(node);
System.out.print("后序遍历-栈:");
lastOrderStack(node);
System.out.println();
System.out.print("层序遍历:");
layerOrder(node);
}
}