剑指offer中树的总结(基于层次遍历)
说明:本人全都手撸过以下代码,并调试过的,也在XXX网上,经过 测试,有些是从网上抄袭的。我不是计算机科班 出身,学习起来可能稍有些吃力,但是就是这么一道道 题目刷起来,慢慢就会对解决某类型题目有点思路。最初接触的时候确实是无从下手,不知道怎么回事儿,简单的 递归能看明白,直到现在还是有些复杂的递归程序理解起来还是有些吃力,但是个人觉得,当理解不了,别人的程序的时候,就一行一行的走断点,多走几遍就会理解,当再遇到一个比当前更深入的程序时,就会觉得当前的这段小程序 很简单。所以,我把有些类似的题目总结起来,这样能更好的掌握。
第一类型的题目:树的层次遍历及拓展应用。
基础题:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
// 所有结点的list
ArrayList<Integer> allNodeList = new ArrayList<Integer>();
LinkedList<TreeNode> allNodeTmp = new LinkedList<TreeNode>();
// 为空判断,并处理
if (root==null) {
return allNodeList;
}
allNodeTmp.add(root);
while (!allNodeTmp.isEmpty()) {
TreeNode currentNode = allNodeTmp.remove();
allNodeList.add(currentNode.val);
if (currentNode.left!=null) {
allNodeTmp.add(currentNode.left);
}
if (currentNode.right!=null) {
allNodeTmp.add(currentNode.right);
}
}
return allNodeList;
}
这是本类型中的基础题,以下几个题目都是在此基础上进行扩展的。
所以,将这道基础题思路是:
1、定义一个返回值ArrayList<Integer>,用来存储最终的返回结果。
2、定义一个LinkedList<Integer>用来临时存储每个结点,类似用C实现时的循环队列功能。
3、这里是add再remove,而且此时的LinkedList的remove方法,恰好是remove了最先进入list中的数据,先进先出的特点。
扩展题一:
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
示例程序:
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
/**
* 定义三个变量:ArrayList<ArrayList<Integer>> result 用来存储所有的行
* 2、定义每行的临时存放的ArrayList<Integer>
* 3、定义一个LinkedList存放遍历的结点。此处不能使用ArrayList 因为ArrayList的remove()方法需要参数,没有默认的remove()
*/
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> currentRowlist = new ArrayList<Integer>();
LinkedList<TreeNode> nodeListTmp = new LinkedList<TreeNode>();
/**
* 判断根结点是否为null
*/
if(pRoot == null)
return result;
// 添加根结点
nodeListTmp.add(pRoot);
// 定义两个变量
int currentRowNumSizeFlag = 1, nextRowNodeSize = 0;
// 判断是否还有结点
while(!nodeListTmp.isEmpty()) {
// 如果有结点,取出该结点,
TreeNode t = nodeListTmp.remove();
// 改行的node值减1
currentRowNumSizeFlag--;
// 将该结点的值添加到每行list中
currentRowlist.add(t.val);
// 判断是否有左右孩子,如果有则添加到所有结点的list中
if(t.left != null) {
nodeListTmp.add(t.left);
// 改行结点数+1
nextRowNodeSize++;
}
if(t.right != null) {
nodeListTmp.add(t.right);
nextRowNodeSize++;
}
// 如果对改行的所有结点遍历结束,完成了添加到每行结点数,则输出每行结点数,添加到每行的所有行的list中
if(currentRowNumSizeFlag == 0) {
result.add(new ArrayList<Integer>(currentRowlist));
// 清空临时存放每行结点list
currentRowlist.clear();
// 将下一层的所有结点数赋值给当前行node个数
currentRowNumSizeFlag = nextRowNodeSize;
// 下一层结点数重新赋值为0
nextRowNodeSize = 0;
}
}
return result;
}
相比基础程序添加了两个控制变量currentRowNodeSizeFlag、nextRowNodeSize,一个临时行list存储当前行的currentRowNodeList.
扩展题二:
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
示例程序:
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
// 最终的返回结果
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
if (pRoot==null) {
return result;
}
// 当前访问的行
ArrayList<Integer> currentRowList = new ArrayList<Integer>();
// 所有结点 注意此处应当是TreeNode 不是Integer类型的变量
// LinkedList<Integer> allNodeTmp = new LinkedList<Integer>();
LinkedList<TreeNode> allNodeTmp = new LinkedList<TreeNode>();
// 定义 当前行中的结点,下一行的结点
int currentRowNodeSize =1;
int nextRowNodeSize = 0;
allNodeTmp.add(pRoot);
boolean leftFirst = true;
while(!allNodeTmp.isEmpty()){
// 取出当前行
TreeNode currentNode = allNodeTmp.remove();
// 将当前的val添加currentRowList中
currentRowList.add(currentNode.val);
// 当前行的size-1
currentRowNodeSize--;
// 如果当前结点的左右孩子不为null,(添加到currentRowNodeList中,此处是错误的) 添加到allNodeTmp中,
// 下行的nodesize++
if (currentNode.left!=null) {
allNodeTmp.add(currentNode.left);
nextRowNodeSize++;
}
if (currentNode.right!=null) {
allNodeTmp.add(currentNode.right);
nextRowNodeSize++;
}
// 如果当前行中所有结点遍历完,则打印当前行
if (currentRowNodeSize==0) {
if (!leftFirst) {
ArrayList<Integer> revserList = new ArrayList<Integer>();
for (int i = currentRowList.size()-1; i >=0 ; i--) {
revserList.add(currentRowList.get(i));
}
result.add(new ArrayList<Integer>(revserList));
}else {
result.add(new ArrayList<Integer>(currentRowList));
}
// 此处一定要清空currentRowList
currentRowList.clear();
currentRowNodeSize=nextRowNodeSize;
nextRowNodeSize = 0;
leftFirst = !leftFirst?true:false;
}
}
return result;
}
在上面三道题的基础上再来看: 求二叉树定的宽度,就很简单了。
以上的这道题,没有用递归实现,之后会将递归单独总结。