本題的核心旨在增強對二叉樹的理解以及對深度優先搜索DFS以及廣度優先搜索BFS的理解,理解本篇結合已經發過的LeetCode進階226-翻轉二叉樹(華爲面試題)將會對二叉樹數據結構有初步的理解。樹結構在實際開發平常中也常常被用到,關於樹結構的進階能夠關注後續推文。java
老規則,爲方便驗證,找一下leetcode的原題不難發現對應着leetcode上的第103題——Binary Tree Zigzag Level Order Traversal (蛇形遍歷二叉樹)。node
Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).面試
For example: Given binary tree [3,9,20,null,null,15,7],算法
return its zigzag level order traversal as:數據結構
[ [3],編碼
[20,9],spa
[15,7] ]code
給定一個二叉樹,返回其節點值的鋸齒形層次遍歷。(即先從左往右,再從右往左進行下一層遍歷,以此類推,層與層之間交替進行)。cdn
例如: 給定二叉樹 [3,9,20,null,null,15,7],blog
返回鋸齒形層次遍歷以下:
[ [3],
[20,9],
[15,7] ]
根據題意,因爲題目要求蛇形遍歷,不能依次按照順序將遍歷到的點數據存入列表中。而是要知足:一、奇數層的節點數據按照從左到右的順序存入列表;二、偶數層的節點數據按照從右到左的順序存入列表。而凡是涉及到二叉樹遍歷相關,可分兩種思想實現:使用深度優先搜索DFS;使用廣度優先搜索BFS。
相同的DFS思想又能夠分爲遞歸和非遞歸的思路
遞歸遍歷二叉樹的最簡單理解方法:將二叉樹當成「三個節點「,遍歷完當前(根)節點以後,依次遍歷左子節點(左子樹)、右子節點(右子樹)。而在遍歷拿到節點數據時,將節點數據存入對應層的列表中,存儲數據根據奇偶層會有不一樣的順序。
zigzagLevelOrder方法:
1、建立結果嵌套列表,列表中每一個元素是一個列表用於存儲二叉樹每一層的節點數據;
2、聲明int型二叉樹的層數;
3、調用dfs搜索,傳入根節點、存儲列表、當前層數;
4、返回結果數據列表;
dfs方法:
1、節點空判斷,空節點遍歷結束返回;
2、判斷數據列表是否有存儲當前層的數據,若未存儲則先建立當前層的存儲列表;
3、根據層數獲取當前層數據列表;
4、若當前層爲奇數層,則將當前節點數據按照順序存入當前層列表的最後;
5、若當前層爲偶數層,則將當前節點數據存入當前層列表的第一位;
6、遞歸遍歷左子樹;
7、遞歸遍歷右子樹;
複製代碼
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int level = 0;
dfs(root, result, level);
return result;
}
public void dfs(TreeNode node, List<List<Integer>> list, int level) {
if (node == null) {
return;
}
if (list.size() <= level) {
List<Integer> newLevel = new ArrayList<Integer>();
list.add(newLevel);
}
List<Integer> curList = list.get(level);
if ((level & 1) == 0) {
curList.add(node.val);
} else {
curList.add(0, node.val);
}
dfs(node.left, list, level + 1);
dfs(node.right, list, level + 1);
}
複製代碼
非遞歸本質藉助存儲數據結構將待遍歷子節點暫時存入,然後依次取出循環遍歷,本質思路和遞歸同樣。
LevelNode類:
用於存儲節點信息以及節點當前所在二叉樹層數
zigzagLevelOrder方法:
1、建立結果嵌套列表,列表中每一個元素是一個列表用於存儲二叉樹每一層的節點數據;
2、建立用於存儲節點信息的棧,初始存儲根節點信息;
3、while循環直到棧中節點所有遍歷;
3.1、節點出棧,並根據LevelNode獲取當前節點層數;
3.2、若當前層爲奇數層,則將當前節點數據按照順序存入當前層列表的最後;
3.3、若當前層爲偶數層,則將當前節點數據存入當前層列表的第一位;
3.4、判斷當前節點的左子節點是否爲空,不爲空則入棧;
3.5、判斷當前節點的右子節點是否爲空,不爲空則入棧;
4、返回結果數據列表;
複製代碼
static class LevelNode {
int level;
TreeNode node;
public LevelNode(int l, TreeNode tn) {
level = l;
node = tn;
}
}
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
Stack<LevelNode> stack = new Stack<LevelNode>();
if (root != null) {
stack.push(new LevelNode(0, root));
}
while (!stack.isEmpty()) {
LevelNode lNode = stack.pop();
TreeNode node = lNode.node;
int level = lNode.level;
if (result.size() <= level) {
List<Integer> newLevel = new ArrayList<Integer>();
result.add(newLevel);
}
List<Integer> curList = result.get(level);
if ((level & 1) == 0) {
curList.add(0, node.val);
} else {
curList.add(node.val);
}
if (node.left != null) {
stack.push(new LevelNode(level + 1, node.left));
}
if (node.right != null) {
stack.push(new LevelNode(level + 1, node.right));
}
}
return result;
}
複製代碼
廣度優先搜索只有非遞歸實現,同深度優先的非遞歸實現同樣,須要藉助額外的數據結構對節點進行存儲,循環遍歷的形式。
廣度優先搜索通常藉助隊列數據機構進行存儲和遍歷。因爲搜索「廣」,分層次遍歷,所以使用隊列。
zigzagLevelOrder方法:
1、建立結果嵌套列表,列表中每一個元素是一個列表用於存儲二叉樹每一層的節點數據;
2、根節點判空;
3、建立用於存儲節點信息的隊列,初始存儲根節點;
4、聲明標記位,標記當前層數是否爲奇數層,初始第一層爲奇數故賦爲true;
5、while循環進行分層遍歷,直到隊列爲空;
5.1、建立當前層的列表;
5.2、根據隊列獲取當前層的節點個數;
5.3、循環遍歷當前層的全部節點
5.3.一、節點出隊列;
5.3.二、根據奇偶層標記,將節點數據存入當前層列表,奇數層順序,偶數層倒序;
5.3.三、判斷當前節點的左子節點是否爲空,不爲空則入隊列;
5.3.四、判斷當前節點的右子節點是否爲空,不爲空則入隊列;
5.4、將當前層數據列表計入結果列表中;
5.5、更新奇偶層標記;
4、返回結果數據列表;
複製代碼
public static List<List<Integer>> zigzagLevelOrder(TreeNode node) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if (node == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(node);
boolean flag = true;
while (!queue.isEmpty()) {
List<Integer> list = new ArrayList<Integer>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
if (flag) {
list.add(cur.val);
} else {
list.add(0, cur.val);
}
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
result.add(list);
flag = !flag;
}
return result;
}
複製代碼
關於遞歸與非遞歸,從本題結果上看遞歸在時間複雜度上更勝一籌,而使用了額外數據結構的非遞歸循環實現缺根遞歸實現相差無幾。咱們彷佛很難找到具體規律,而在實際開發中,若是再遇到一個業務算法既可使用遞歸又可使用非遞歸實現的時候該如何選擇呢?這即是本文的彩蛋。