数据结构那点事—遍历二叉树
程序=数据结构+算法,研究新技术,中间件,框架的同时,最为基础的东西一定不能落下,灵活运用各种数据结构,根据业务需求可以定制出合适的数据结构,是一个必须掌握的技能
数据结构常见的运算有 1)创建运算.2)清楚运算.3)插入运算.4)删除运算.5)搜索运算.6)更新运算.7)访问运算.8)遍历运算
这里就从二叉树的三种遍历入手,手撕数据结构
所谓遍历,就是按照某种次序,系统地访问数据结构中的元素,是的每个元素恰好被访问一次
简易的树的代码示例
class Node {
char val;
Node left;
Node right;
public Node(char val) {
this.val = val;
}
}
public class Visit {
static Node root;
static {
Node root = new Node('A');
Node b = new Node('B');
Node c = new Node('C');
Node d = new Node('D');
Node e = new Node('E');
Node f = new Node('F');
Node g = new Node('G');
Node h = new Node('H');
Node j = new Node('J');
Node k = new Node('K');
root.left = d;
root.right = b;
d.left = e;
e.left = h;
e.right = f;
f.left = j;
f.right = g;
b.right = c;
c.left = k;
}
}
前序遍历
若二叉树为空,则空操作;
否则1)访问根结点;2)先序遍历左子树;3)先序遍历右子树
有了指导思想,采用递归的方式可以轻松写出代码
递归实现
static void preOrder(Node root) {
if (root != null) {
// 遍历根结点
System.out.println(root.val);
// 先序遍历左子树
preOrder(root.left);
// 先序遍历右子树
preOrder(root.right);
}
}
非递归实现
static void preOrder(Node root){
Deque<Node> stack = new LinkedList();
if(root != null)stack.push(root);
while(!stack.isEmpty()){
Node node = stack.pop();
System.out.println(node.val);
if(node.right != null)stack.push(node.left);
if(node.left != null)stack.push(node.right);
}
}
几乎所有的递归都可以转换成循环(特例后面补上),因为栈和递归都有回溯的特性,在非递归实现中,按照先序遍历的次序,是先根结点,然后左子树,再右子树,所以先讲右结点压栈,然后左结点,这样下次循环就会将栈顶的左结点弹出
中序遍历
若二叉树为空,则空操作;
否则1)中序遍历左子树;2)访问根结点;3)中序遍历右子树
递归实现
static void inOrder(Node root) {
if (root != null) {
// 中序遍历左子树
inOrder(root.left);
// 遍历根结点
System.out.println(root.val);
// 中序遍历右子树
inOrder(root.right);
}
}
非递归实现
递归实现有些太简单了,有手就会写,下面给出中序遍历的非递归实现,思路是这样的,先循环遍历左结点,将这些左结点压栈,一条路走到黑,因为中序遍历是左 -根-右的顺序,弹出栈顶的左结点(栈顶的结点已经不存在左孩子结点了),检查其是否有右孩子结点,如果有,将其压栈,如此循环便可
static void inOrderWithLoop(Node root) {
Deque<Node> stack = new LinkedList<>();
Node node = root;
while (!stack.isEmpty() || node != null) {
// 循环遍历左结点,一条路走到黑
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
System.out.println(node.val);
node = node.right;
}
}
后序遍历
若二叉树为空,则空操作;
否则1)后序遍历左子树;2)后序遍历右子树;3)访问根结点
递归实现
static void postOrder(Node root) {
if (root != null) {
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val);
}
}
非递归实现
递归实现简单易懂,但是由于性能问题,递归次数过多函数调用栈会越深,容易出现内存不够用的问题,因此在实际生产中,能转换成循环的尽量转换成循环
static void postVisitWithLoop(Node root) {
Deque<Node> stack = new LinkedList<>();
stack.push(root);
Deque<Node> s = new LinkedList<>();
while (!stack.isEmpty()) {
Node node = stack.pop();
s.push(node);
if (node.left != null) stack.push(node.left);
if (node.right != null) stack.push(node.right);
}
while(!s.isEmpty()){
System.out.println(s.pop().val);
}
}
后续遍历的非递归实现:准备两个栈,姑且叫做栈a和栈b,首先根结点压入到栈a中,然后将其左右结点分别压入栈a,他们入栈b的顺序为先根结点,然后右孩子结点(其实是右子树),左孩子结点;这样栈b的出栈顺序就是左子树,右子树,根结点
小结
没发现特别好的技巧,基础的知识改死记硬背就死记硬背吧