目录
因为这一系列博客是基于咱们的课程来写的,所以咱之后的博客都会更加强调同课程的适应性。一些课程中不作考察的知识我们就略过。
一、树的基本概念
1.1树的定义
树是什么?
是一种抽象的数据结构,用来模拟具有树状结构性质的数据集合。其具有以下特点:
1)每个节点有零个或多个子节点;
·2)没有父节点的节点称为根节点;
3)每一个非根节点有且只有一个父节点;
4)除了根节点外,每个子节点可以分为多个不相交的子树;
二、二叉树的基本概念
2.1二叉树的定义与性质
二叉树是一种树状数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。
2.2二叉树的存储结构
顺序存储:将数据结构存储在固定的数组中,虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。
链式存储:既然顺序存储适用性不强,我们就要考虑链式存储结构。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。
left | data | right |
三、二叉树的链式实现
3.1二叉树结点类
public class TreeNode<T> {
T data;
TreeNode<T> left;
TreeNode<T> right;
public TreeNode() {
right = left = null;
}
public TreeNode(T data) {
this.data = data;
right = left = null;
this.isThreadedLeft = this.isThreadedRight = false;
}
public TreeNode(T data, TreeNode<T> left, TreeNode<T> right) {
this.data = data;
this.left = left;
this.right = right;
this.isThreadedLeft = this.isThreadedRight = false;
}
}
3.2二叉树类的构造
作为链式存储的二叉树类,我们只需要在类中定义一个根结点。作为这个二叉树最为核心的root结点,我们肯定不希望用户能随意的在任何其他地方访问更改根节点,为了保证代码的健壮性和安全性,我们用private修饰root。并提供更具指向性的getter and setter。
public class BinaryTree<T> {
private TreeNode<T> root;
public BinaryTree() {
}
public BinaryTree(TreeNode<T> root) {
this.root = root;
}
public TreeNode<T> getRoot() {
return root;
}
public void setRoot(TreeNode<T> root) {
this.root = root;
}
}
四、二叉树的遍历
为了真正理解二叉树的遍历算法,我们最好要先掌握二叉树遍历的思维是什么。
首先声明的是,二叉树具有递归性质。一个二叉树的所有子树都构成一个二叉树,即它的所有节点都连接着两个子二叉树,而同时呢,在我们三种便利前中后续便利中,我们也是通过递归的方式来进行便利。
4.0一句话总结:
在系统的整那么些弯弯绕绕之前,其实这些遍历算法说白了,特别特别的简单。
前中后序遍历一句话就是每当访问到节点时永远按照对应顺序访问根节点,根节点的左子数,根节点的右子数的顺序,也就是根左右,左根右,左右根。
比如说前序便利,我们在每个节点上,永远根左右的顺序执行根(访问结点信息),左(向左子树递归),右(向右子树递归)。
那对于广度优先遍历和深度优先遍历,两者的代码说实话其实完全没有什么区别,无非就是一个用队列实现,一个用栈实现。
栈先进后出,入栈的时候,永远先右边入栈,再左边入栈,最后永远是左边先取出,右边再取出;队列先进先出,我们到每个结点都把孩子入队,然后再取出队首元素,就永远会是先访问上一层的结点再访问下一层的结点。
4.1前序遍历(same as深度优先遍历)--NLR
前序遍历的实现方式分两种,分别为递归法和循环法。递归法就是我们在实验课上提到的前序遍历,非递归法就是深度优先遍历(tips:DFS作为非递归方式的遍历方法,它在底层实现上比递归法具有更强大的适应性,我们也能在图论及一些其他非二叉树数据结构中见到该算法)。
前序遍历,即在进入每一次遍历方法中,首先访问当前节点,然后访问当前节点的左子树节点,再访问当前节点的右子树节点。
如果你能理解好前面这么一个递归的思维,很好,算法的核心逻辑就是对每个递归到的节点上都进行根左右的访问次序。
(执行步骤的次序很重要!!!)
4.1.1循环实现
/**
* 1.先序遍历-Pre Order(NLR)
* depth first
*/
void preOrder(TreeNode<T> BiTNode) {
if (BiTNode != null) {
visit(BiTNode);
preOrder(BiTNode.left);
preOrder(BiTNode.right);
}
}
4.1.2循环实现(又名深度优先遍历)
虽然我不知道为什么要把这俩分开来叫。
深度优先遍历从树的根节点开始,沿着一条路径一直深入直到不能再深入为止,然后回溯到前一个节点,再探索其他分支。具体步骤如下:
- 将根节点入栈。
- 循环直到栈为空:
a. 弹出栈顶节点,访问它。
b. 将该节点的未访问邻居节点按照逆序(或者任意顺序)入栈。
c. 标记当前节点为已访问。
/**
* 1.先序遍历-Pre Order(NLR) by Iterative method
* depth first
* Using stack for helper.We want a traversal order of 'NLR',
* we first pop and visit node and then push the node right child then left child,
* so that at next turn we can pop previous left child node and visit it.
*/
void DFS(TreeNode<T> BiTNode) {
LinkedStack<TreeNode<T>> stackHelper = new LinkedStack<>();
TreeNode<T> node = BiTNode;
stackHelper.push(node); //stackHelper
while (!stackHelper.isEmpty()) { //till stack empty
node = stackHelper.pop(); //pop always left first unless no left
if (node != null) {
stackHelper.push(node.right); //first right push
stackHelper.push(node.left); //last left push
System.out.print(node.data + " "); //visit
}
}
}
4.2中序遍历--LNR
中序遍历,即在进入每一次遍历方法中,首先访问当前节点的左子树节点,然后访问当前节点,再访问当前节点的右子树节点。
当我们理解了遍历的思维逻辑之后,中序遍历和后序遍历就会像马奇诺防线一样直接被我们轻松绕开拿下。
/**
* 1.中序遍历-In Order(LNR)
*/
void inOrder(TreeNode<T> BiTNode) {
if (BiTNode != null) {
inOrder(BiTNode.left); //visit left subTree
System.out.print(BiTNode.data + " "); //visit node
inOrder(BiTNode.right); //visit right subTree
}
}
4.3后序遍历--LRN
后序遍历,即在进入每一次遍历方法中,首先访问当前节点的左子树节点,然后访问当前节点的右子树节点,再访问当前节点。
/**
* 1.后序遍历-Post Order(LRN)
*/
void postOrder(TreeNode<T> BiTNode) {
if (BiTNode != null) {
postOrder(BiTNode.left); //visit left subTree
postOrder(BiTNode.right); //visit right subTree
System.out.print(BiTNode.data + " "); //visit node
}
}
4.4层序遍历(same as 广度优先遍历)
广度优先搜索是一种层次遍历的算法,它从树的根节点开始逐层遍历。具体步骤如下:
- 将根节点放入队列。
- 从队列中取出一个节点,访问它。
- 将该节点的所有未访问邻居节点放入队列。
- 标记当前节点为已访问。
- 重复步骤2-4,直到队列为空。
/**
* Breadth First Search
* 层次遍历-Level Order
* Using queue for helper.We want a traversal order of 'NLR',
* As the principal of queue is "FIFO",we can always dequeue
* the elements with a same order when we euqueue.
* then we can always visit elem in previous ladder first then visit next ladder.
*/
void BFS(TreeNode<T> BiTNode) {
Queue<TreeNode<T>> queueHelper = new LinkedList<>();
queueHelper.add(BiTNode);
TreeNode<T> node;
while (!queueHelper.isEmpty()) {
//node dequeue
node = queueHelper.poll();
System.out.print(node.data + " ");//specific visit operation
//node`s children enqueue
if (node.left != null) {
queueHelper.add(node.left);
}
if (node.right != null) {
queueHelper.add(node.right);
}
}
}