最近在刷算法题的过程中发现以前学过的一些数据结构方面的知识有些遗忘了,就开始复习数据结构。
在计算机科学中,二叉树(binaryTree)是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
以下是关于二叉树的创建及其递归和非递归的三种遍历方式,在刷算法题的过程中发现二叉树可以运用在多种场景下,在运用得当的情况下可以极大提升程序的效率,但万丈高楼平地起,只有对二叉树基础定义、实现及遍历足够了解,才能将其运用得当。
- 二叉树的结点定义类
class Node {
Node leftChild;
Node rightChild;
int data;
Node(int data) {
leftChild = null;
rightChild = null;
this.data = data;
}
}
由于二叉树具有天然的递归结构,因此在二叉树的创建及遍历过程中使用递归是不错的方法。
- 二叉树的创建
// 创建二叉树
public void createBinaryTree() {
nodeList = new ArrayList<>();
// 将数组中的值依次转化为二叉树的结点
for(int i = 0; i < array.length; i++) {
nodeList.add(new Node(array[i]));
}
for(int i = 0; i < array.length/2 - 1; i++) {
nodeList.get(i).leftChild = nodeList.get(2 * i + 1);
nodeList.get(i).rightChild = nodeList.get(2 * i + 2);
}
int lastIndex = array.length/2 - 1;
nodeList.get(lastIndex).leftChild = nodeList.get(2 * lastIndex + 1);
// 判断是否有右节点
if(array.length % 2 == 1) {
nodeList.get(lastIndex).rightChild = nodeList.get(2 * lastIndex + 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 afterOrderTraverse(Node node) {
if(node == null) {
return;
}
afterOrderTraverse(node.leftChild);
afterOrderTraverse(node.rightChild);
System.out.print(node.data + " ");
}
非递归方式的先序、中序及后续遍历
// 非递归前序遍历
public static void preOrder(Node node) {
if(node == null) {
return ;
}
Node temp = node;
Stack<Node> s = new Stack<>();
// 根节点入栈
s.push(temp);
while(!s.isEmpty()) {
// 1. 访问根节点
Node pop = s.pop();
System.out.print(pop.data + " ");
// 2. 若根节点存在右孩子结点,将其入栈
if(pop.rightChild != null) {
s.push(pop.rightChild);
}
// 3. 若根节点存在左孩子结点,将其入栈
if(pop.leftChild != null) {
s.push(pop.leftChild);
}
}
}
// 非递归中序遍历
public static void inOrder(Node node) {
if(node == null) {
return ;
}
Node temp = node;
Stack<Node> s = new Stack<>();
while(temp != null || !s.isEmpty()) {
// 1. 将根节点入栈
// 2. 将所有左孩子入栈
while(temp != null) {
s.push(temp);
temp = temp.leftChild;
}
// 3. 访问栈顶元素
temp = s.pop();
System.out.print(temp.data + " ");
// 4. 若栈顶元素存在右孩子结点,则将右孩子赋值给temp,也就是将右孩子入栈
if(temp.rightChild != null) {
temp = temp.rightChild;
}else {
temp = null;
}
}
}
// 非递归后序遍历
public static void afterOrder(Node node) {
if(node == null) {
return ;
}
// 当前结点
Node temp = node;
// 上一次访问的结点;
Node prev = null;
Stack<Node> s = new Stack<>();
while(temp != null || !s.isEmpty()) {
// 1. 将根结点及其左孩子入栈
while(temp != null) {
s.push(temp);
temp = temp.leftChild;
}
if(!s.isEmpty()) {
// 2. 获取栈顶元素值
temp = s.peek();
// 3. 没有右孩子,或者右孩子已经被访问过
if(temp.rightChild == null || temp.rightChild == prev) {
temp = s.pop();
System.out.print(temp.data + " ");
// 标记上一次访问的结点
prev = temp;
temp = null;
}else {
// 存在没有被访问的右孩子
temp = temp.rightChild;
}
}
}
}