创建二叉树的方式,目前掌握的三种:
1、从先序
2、从层序
3、从先序+中序【LeetCode,剑指Offer07】
一、 思路
1、从先序创建二叉树
/**
* 从先序建立二叉树,0表示停止延伸。停止延伸时,叶子节点后面要有2个0才算停止延伸。
* 例1:
* 1
* 2 3
* 4 5
* 先序为,1 2 4 0 0 0 3 5 0 0 0
*
* 例2:
* 1
* 2 3
* 4 5
* 先序为,1 2 0 4 0 0 3 5 0 0 0
*
* 中序为,0 2 4 1 5 3 0 ?这个可以搞定吗?感觉搞不定,你上来就空,让人怎么接?
*
* 后序为,0 4 2 5 0 3 1 ?这个呢?
*
*/
2、从层序创建二叉树
/**
* 从层序创建二叉树,用的是下标。【不知道有没有递归的方法???】
*
*
* 从层序创建二叉树,0表示为空,以#结束。与上面的停止延伸还有所不同,叶子节点不用输入2个0。
*
* 例子:
* 1
* 2 3
* 4 5
* 层序为,1 2 3 4 0 5 0
*
*
* 问题:
* 1、传入的root没有用到,而是新建的root,返回的也是新建的root。如果必须要用到传入root的话,0要单独处理!
* 2、log是以e为底的对数,如果以2为底,得除以log(2)。应该是(Math.log(n + 1) / Math.log(2)),而非 Math.log(n + 1)。
* 3、求2的幂次方,应该用Math.pow(2, i),不能用2^i。2^0是2,因为^是异或符号,不是多少次方!!!
*
*
* 【不行了,脑子不够用了,撤吧,明天再搞!!!-1:53】
* 全部搞定了,开干,2021-8-1 22:01:09
* 终于OK啦!2021-8-2 03:13:01
*
*/
3、从先序+中序 创建二叉树
之前做的,忘了。【有空再做一遍,字节面试中问过】
只记得:
前提:无重复节点。
1、先序的第一个是root。
2、在中序中查到root位置。中序的root左边是左子树,中序的root右边是右子树。
3、 先序的第二个开始是左子树,长度由2可知。剩下的就是右子树。
4、3中,先序左子树第一个节点即左子树的根就是root的左孩子,右子树的第一个节点即右子树的根就是root的右孩子。
二、代码+输出【完整】
包括代码:
《二叉树父子关系+推导》和《二叉树四种遍历:前序、中序、后序、层序。方法:递归、迭代、Morris》
1、代码
package 数据结构.树; import java.util.*; import java.util.stream.Collectors; public class TestTree { public static void main(String[] args) { // * 例1: // * 1 // * 2 3 // * 4 5 // * 先序为,1 2 4 0 0 0 3 5 0 0 0 // 层序为,1 2 3 4 0 5 0 // * // * 例2: // * 1 // * 2 3 // * 4 5 // * 先序为,1 2 0 4 0 0 3 5 0 0 0 // 层序为,1 2 3 0 4 5 0 //1 2 4 0 0 0 3 5 0 0 0 # //1 2 0 4 0 0 3 5 0 0 0 # //控制台输入Queue: // Scanner in = new Scanner(System.in); // Queue<Integer> queue = new LinkedList<>(); // while (!in.hasNext("#")) { // queue.offer(in.nextInt()); // } BTNode root = new BTNode(); // 创建二叉树 // 方法1:从先序中创建 // 直接int数组转为Queue: // int[] arr = {1, 2, 4, 0, 0, 0, 3, 5, 0, 0, 0}; //【例1】 // int[] arr = {1, 2, 0, 4, 0, 0, 3, 5, 0, 0, 0}; //【例2】 // Queue<Integer> queue = Arrays.stream(arr).boxed(). // collect(Collectors.toCollection(LinkedList::new)); // root = createFromPre(root, queue); //从先序中创建 // 方法2:从层序中创建 int[] arr = {1, 2, 3, 4, 0, 5, 0}; //【例1】 // int[] arr = {1, 2, 3, 0, 4, 5, 0}; //【例2】 root = createFromLevel(arr); //从层序中创建 System.out.println("三种层序遍历:"); System.out.println(levelOrderRecursion(new ArrayList<>(), root)); System.out.println(levelOrderIteration(new ArrayList<>(), root)); System.out.println(eachLevelOrderIteration(new ArrayList<>(), root)); System.out.println("三种先序遍历:"); // System.out.println(preOrderTraversal(root)); System.out.println(preOrderRecursion(new ArrayList<>(), root)); System.out.println(preOrderIteration(new ArrayList<>(), root)); System.out.println(preOrderMorris(new ArrayList<>(), root)); System.out.println("三种中序遍历:"); System.out.println(inOrderRecursion(new ArrayList<>(), root)); System.out.println(inOrderIteration(new ArrayList<>(), root)); System.out.println(inOrderMorris(new ArrayList<>(), root)); System.out.println("三种后序遍历:"); System.out.println(postOrderRecursion(new ArrayList<>(), root)); System.out.println(postOrderIteration(new ArrayList<>(), root)); System.out.println(postOrderMorris(new ArrayList<>(), root)); } /** * 从先序建立二叉树,0表示停止延伸。停止延伸时,叶子节点后面要有2个0才算停止延伸。 * 例1: * 1 * 2 3 * 4 5 * 先序为,1 2 4 0 0 0 3 5 0 0 0 * * 例2: * 1 * 2 3 * 4 5 * 先序为,1 2 0 4 0 0 3 5 0 0 0 * * 中序为,0 2 4 1 5 3 0 ?这个可以搞定吗?感觉搞不定,你上来就空,让人怎么接? * * 后序为,0 4 2 5 0 3 1 ?这个呢? * */ public static BTNode createFromPre(BTNode curr, Queue<Integer> queue) { //这个i有问题啊,根本没法当指针用!你需要回传i(return 2个值,要么用list,要么用对象), // 或者干脆用queue来控制!! int curr_val = queue.poll(); if (curr_val == 0) { return null; } curr.val = curr_val; curr.lc = new BTNode(); curr.rc = new BTNode(); curr.lc = createFromPre(curr.lc, queue); curr.rc = createFromPre(curr.rc, queue); return curr; } /** * 从层序创建二叉树,用的是下标。【不知道有没有递归的方法???】 * * * 从层序创建二叉树,0表示为空,以#结束。与上面的停止延伸还有所不同,叶子节点不用输入2个0。 * * 例子: * 1 * 2 3 * 4 5 * 层序为,1 2 3 4 0 5 0 * * * 问题: * 1、传入的root没有用到,而是新建的root,返回的也是新建的root。如果必须要用到传入root的话,0要单独处理! * 2、log是以e为底的对数,如果以2为底,得除以log(2)。应该是(Math.log(n + 1) / Math.log(2)),而非 Math.log(n + 1)。 * 3、求2的幂次方,应该用Math.pow(2, i),不能用2^i。2^0是2,因为^是异或符号,不是多少次方!!! * * * 【不行了,脑子不够用了,撤吧,明天再搞!!!-1:53】 * 全部搞定了,开干,2021-8-1 22:01:09 * 终于OK啦!2021-8-2 03:13:01 * */ public static BTNode createFromLevel(int[] arr) { int n = arr.length; int h = (int) ((Math.log(n + 1) / Math.log(2)) - 1); //log是以e为底的对数,如果以2为底,得除以log(2) List<BTNode> bt = new ArrayList<>(); for (int i = 0; i <= h; i++) { //层编号i,从0开始,最大为h。 int num = (int) Math.pow(2, i); // 这一层的个数【按满二叉树】。 不能用2^i,2^0是2。因为^是异或符号,不是多少次方!!! int start = num - 1; // 从0开始。 int end = start + num - 1; // 2*num-2 //先把数都存进去 for (int j = start; j <= end; j++) { BTNode tmp = new BTNode(arr[j]); bt.add(tmp); } } //再连接父子节点 BTNode root = connectChilds(bt); return root; } /** * 把List中的所有节点连接为一棵树。 * List[0]为root节点。 * 返回root。 * * 具体实现: * (1)为当前节点i连接左右孩子。i是编号,从0开始。 * (2)i的左孩子编号为1+2i,右孩子编号为2+2i。 * //连接父子,两种办法: // 1.计算父亲的下标 // 大前提:编号从0开始! // 由2-结论,可以推出,编号j的父亲编号为(j-1)/2。 // 由2-副结论,可以推出,第j个结点的父亲是第j/2个结点。 // // 2.计算孩子下标 // 大前提:编号从0开始! // 结论:编号j的左孩子编号为1+2j。 // 副结论:第j个结点的左孩子是第2j个结点。 // // [推导过程] // // 当前j,求第一个孩子编号: // (1)第一个孩子编号=1+j+后面弟弟数+前面哥哥的孩子个数 (2)二叉树,前面哥哥的孩子个数=哥哥数*2 // 当前在这一层中排第几个至关重要: // (1)第k+1个,前面k个哥哥,后面num-k-1个弟弟。(2)k = j-前i层个数 = j-(num-1) // 第一个孩子编号 = 1 + j + (num-k-1) + k*2 = 1 + j + { num - [ j-(num-1) ] -1 } + [ j-(num-1) ] * 2 // = 1 + j + { num - [ j-num+1 ] -1 } + 2j-2num+2 // = 1 + j + { num - j + num - 1 -1 } + 2j-2num+2 // = 1 + j + { 2num - j - 2 } + 2j-2num+2 // = 1 + 2j // 结论:编号j的左孩子编号为1+2j。【这里的j是编号,是第j+1个结点!】 // // 副结论:第j个结点的左孩子是第2j个结点。 // 第j+1个结点的左孩子是第(2j+2)个结点。即第j个结点的左孩子是第2j个结点! // // 我也太棒了吧,我自己推导出来的!!!赞赞赞!!! * * @param list * @return */ private static BTNode connectChilds(List<BTNode> list) { for (int i = 0; (2 + 2 * i) < list.size(); i++) { BTNode cur = list.get(i); cur.lc = list.get(1 + 2 * i); cur.rc = list.get(2 + 2 * i); } return list.get(0); } /** * 层序,递归,按照每一层输出 * * 由于递归方法levelOrderRecursion_Height()只能输出第height层。 * 所以需要在外层加个循环,来遍历所有的层。 * * height树高,需要调用height()方法求。 * * @param list * @param root * @return */ private static List<Integer> levelOrderRecursion(List list, BTNode root) { int height = height(root); for (int i = 1; i <= height; i++) { List<Integer> tmp = new LinkedList(); list.add(levelOrderRecursion_Height(tmp, root, i)); } return list; } /** * 层序,递归【输出第height层】 * * 思路:加个参数,树高。 * * 这个只能打印第“height”层的节点。所以需要在外层加个循环,来遍历所有的层。 * * @param list * @param root * @return */ private static List<Integer> levelOrderRecursion_Height(List<Integer> list, BTNode root, int height) { if (height == 1) { if (root != null) { // System.out.println(root.val); list.add(root.val); } else { // System.out.println("空"); list.add(null); } return list; } list = levelOrderRecursion_Height(list, root.lc, height - 1); list = levelOrderRecursion_Height(list, root.rc, height - 1); return list; } /** * 求树高,递归 * * @param root * @return */ private static int height(BTNode root) { if (root == null) { return 0; } return Math.max(height(root.lc), height(root.rc)) + 1; } /** * 层序=广度优先,迭代 * 使用队列 * * 【应该没写完吧?】【OK了,之前出错是存入左右孩子时没有判空!现在没问题。2021-8-1 17:26:54】 * * @param list * @param root * @return */ public static List<Integer> levelOrderIteration(List<Integer> list, BTNode root) { BTNode curr = root; Queue<BTNode> queue = new LinkedList(); queue.offer(curr); while (!queue.isEmpty()) { curr = queue.poll(); list.add(curr.val); // System.out.print(curr.val + " "); if (curr.lc != null) { queue.offer(curr.lc); } if (curr.rc != null) { queue.offer(curr.rc); } } return list; } /** * 层序,迭代,按照每一层输出 * 使用两个队列,交替执行【用李喜旺牛腩饭中的萝卜想通的】 * * 具体实现:不按照“交替”,而是按照“临时-赋值”。 * 1、如果按照交替,两个while代码除了1和2交换,完全冗余。有划线,强迫症受不了。 * 2、按照临时-赋值,每次都是先把queue1一个个往外出,孩子全都临时存在queue2,最后queue1空之后,把queue2全都放回queue1,queue2清空。 * * 完美! 2021-8-1 17:10:46 * * @param list * @param root * @return */ public static List<Integer> eachLevelOrderIteration(List list, BTNode root) { BTNode curr = root; Queue<BTNode> queue1 = new LinkedList(); Queue<BTNode> queue2 = new LinkedList(); queue1.offer(curr); while (!queue1.isEmpty() || !queue2.isEmpty()) { LinkedList<Integer> tmpList1 = new LinkedList<>(); while (!queue1.isEmpty()) { curr = queue1.poll(); // System.out.print(curr.val + ","); tmpList1.add(curr.val); if (curr.lc != null) { queue2.offer(curr.lc); } if (curr.rc != null) { queue2.offer(curr.rc); } } // System.out.println(); list.add(tmpList1); queue1.addAll(queue2); queue2.clear(); } return list; } /** * 后序,递归 * @param list * @param root * @return */ private static List<Integer> postOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } postOrderRecursion(list, root.lc); postOrderRecursion(list, root.rc); list.add(root.val); return list; } /** * 后序,迭代 * * 与先序迭代的区别: * 1、输出时,新的要插到前面。这是为了保证后序,最后输出根。 * 2、左右子树入栈顺序,是先左后右。因为插到前面顺序正好与先序反着,先序是先右后左。 * * * @param list * @param root * @return */ private static List<Integer> postOrderIteration(List<Integer> list, BTNode root) { if (root == null) { return list; } Stack<BTNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { BTNode cur = stack.pop(); list.add(0, cur.val); if (cur.lc != null) { stack.push(cur.lc); } if (cur.rc != null) { stack.push(cur.rc); } } return list; } /** * 后序,Morris * * 先序DLR逆序输出?不行!问题在输出的是RLD,不是LRD!! * * 他人思路:【太妙了】 * 1、dump.lc = root: * cur从虚拟节点dump开始,而不是从root开始。。 * 这是为了把最后一个节点输出,否则会剩下最右节点无法输出,绝妙。 * (不加dump最右节点无法输出,加上dump,dump无法输出,相当于往后赶了一步) * * 2、输出:大顺序小逆序,从cur_lc到pre的逆序 * 在删除索引后输出从cur_lc到pre整个路径的逆序,其他地方均不输出。 * index保证,小序列内部逆序,但整体还是顺序往大序列后添加的! * * * 理解两方面:【不要妄想一口吃个胖子,先1后2,不急着先2】 * 1、知道他说的是啥:理解他如何实现,能走通数据结构。【主要看图,走数据结构】 * 2、知道他为什么这么说:理解他设计的针对点,妙在哪里,怎么想出来的? * 怎么想出来的? * (1)dump。是发现最后一个节点无法输出,再往下走一步就可以输出了! * (2)逆序输出cur_lc到pre。【这个还不知道怎么想的?】 * (3)index。这个是发现应该大顺序,小逆序,如果不用index,就全是逆序,也不行。 * * @param list * @param root * @return */ private static List<Integer> postOrderMorris(List<Integer> list, BTNode root) { BTNode dump = new BTNode(); dump.lc = root; BTNode cur = dump; BTNode pre = null; while (cur != null) { if (cur.lc == null) { cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) { pre = pre.rc; } if (pre.rc == null) { pre.rc = cur; cur = cur.lc; } else { int index = list.size(); //这个index可以保证,每次添加的小序列内部是逆序,但是大序列中还是往整个list后添加的! BTNode cur_lc = cur.lc; while (cur_lc != pre) { list.add(index, cur_lc.val); //这个之前错弄成了cur.val cur_lc = cur_lc.rc; } list.add(index, pre.val); //终止条件之后,再加进去一个pre。 pre.rc = null; cur = cur.rc; } } } return list; } /** * 中序,递归 * * @param list * @param root * @return */ private static List<Integer> inOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } inOrderRecursion(list, root.lc); list.add(root.val); inOrderRecursion(list, root.rc); return list; } /** * 中序,迭代 * * 1、左链入栈:先把左子树全部入栈,直到最左边的叶子。 * 2、栈不空时,弹出一个节点,输出val。 * 3、无论栈空不空,每次都要对右孩子继续做“左链入栈”。 * 4、循环退出条件,stack空且当前节点为空。 * * @param list * @param root * @return */ private static List<Integer> inOrderIteration(List<Integer> list, BTNode root) { Stack<BTNode> stack = new Stack(); while (!stack.isEmpty() || root != null) { while (root != null) { stack.push(root); root = root.lc; } if (!stack.isEmpty()) { root = stack.pop(); list.add(root.val); } //其实这个地方,有点像Morris的方法,只不过Morris用了叶子的指针存,而不是stack。 // 一旦弹出一个节点,对右孩子继续做“左链入栈” root = root.rc; } return list; } /** * 中序,Morris * 其实也是迭代,不用栈的特殊迭代。 * * 把二叉树拉直成链表。【也不是】 * 把rc当成链表的next指针。 * * 2、我的理解:Morris是用时间换空间。其实还不如用stack呢!【错了】 * * 一直往左走,每次左不空就先标索引(左子树最右节点为pre),往左走。cur=cur.lc。 * 直到左为空,就把cur输出, * 如果cur不是叶子,就是往右子树走。cur=cur.rc。 * 如果cur是叶子,就是根据索引返回父节点。仍然是cur=cur.rc。 * * 返回父节点后,再查一次pre,发现pre.rc=cur,则输出当前父亲(cur),并且把索引删掉。 * 然后就遍历右子树啦。cur=cur.rc。 * * 2个地方输出:输出后都是往右走,因为输出的是中。 * 左空,输出cur,往右走/返回父亲,都是cur=cur.rc。 * 左不空,且pre.rc==cur,输出cur,往右走cur=cur.rc。 * * 往左走: * 只要左不空就标索引pre.rc=cur,标完索引往左走cur=cur.lc。 * * * 3、空间和时间复杂度: * 空间复杂度为O(1):不用栈而是借助叶子的右指针不费空间,只用到了一个pre用O(1)。 * 时间复杂度是O(n): * 每次查询所谓“前驱节点”其实就是找到左子树的最右节点,每个节点都要查两遍左子树,一遍是加索引,一遍是删索引。 * 看似涉及树高度,是O(nlgn),实际上是每条边最多走两遍,是O(2n)也就是O(n)。 * * * @param list * @param root * @return */ private static List<Integer> inOrderMorris(List<Integer> list, BTNode root) { BTNode cur = root; BTNode pre = null; while (cur != null) { if (cur.lc == null) { list.add(cur.val); cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) {//找到最右节点,即为pre。 pre = pre.rc; } if (pre.rc == null) { pre.rc = cur; cur = cur.lc; } else { // pre.rc == cur pre.rc = null; list.add(cur.val); //与先序唯一的不同之处,是在删索引的时候输出!!! cur = cur.rc; } } } return list; } /** * 这个纯粹是为了迎合Offer07的参数列表要求 * @param root * @return */ public static List<Integer> preOrderTraversal(BTNode root) { List<Integer> list = new ArrayList(); list = preOrderRecursion(list, root); return list; } /** * 先序,递归 * * @param list * @param root * @return */ public static List<Integer> preOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } list.add(root.val); list = preOrderRecursion(list, root.lc); list = preOrderRecursion(list, root.rc); return list; } /** * 先序,迭代 * * 竟然用栈来实现,而且是先右后左!我惊呆了!!! * 我一直以为是队列。。。。 * * @param list * @param root * @return */ public static List<Integer> preOrderIteration(List<Integer> list, BTNode root) { if (root == null) { return list; } Stack<BTNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { BTNode curr = stack.pop(); list.add(curr.val); if (curr.rc != null) { stack.push(curr.rc); } if (curr.lc != null) { stack.push(curr.lc); } } return list; } /** * Morris的先序方法,也是迭代。 * * 与中序唯一的不同之处,是在加索引前输出!!! * * @param list * @param root * @return */ public static List<Integer> preOrderMorris(List<Integer> list, BTNode root) { BTNode cur = root; BTNode pre = null; while (cur != null) { if (cur.lc == null) { list.add(cur.val); cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) { pre = pre.rc; } if (pre.rc == null) { list.add(cur.val); //与中序唯一的不同之处,是在加索引前输出!!! pre.rc = cur; cur = cur.lc; } else { //pre.rc == cur pre.rc = null; cur = cur.rc; } } } return list; } } class BTNode { int val; BTNode lc; BTNode rc; public BTNode() { } public BTNode(int val) { this.val = val; this.lc = null; this.rc = null; } } /** * 不限子节点个数的一般树,用左孩子和右兄弟 */ class GeneralTreeNode { int val; GeneralTreeNode left_child; GeneralTreeNode right_sibling; }
2、输出结果:
三种层序遍历: [[1], [2, 3], [4, 0, 5, 0]] [1, 2, 3, 4, 0, 5, 0] [[1], [2, 3], [4, 0, 5, 0]] 三种先序遍历: [1, 2, 4, 0, 3, 5, 0] [1, 2, 4, 0, 3, 5, 0] [1, 2, 4, 0, 3, 5, 0] 三种中序遍历: [4, 2, 0, 1, 5, 3, 0] [4, 2, 0, 1, 5, 3, 0] [4, 2, 0, 1, 5, 3, 0] 三种后序遍历: [4, 0, 2, 5, 0, 3, 1] [4, 0, 2, 5, 0, 3, 1] [4, 0, 2, 5, 0, 3, 1]
问题:
1、从层序创建二叉树,目前是用下标。有没有递归/迭代的方法?
2、层序,有没有Morris方法?
3、可以从中序创建二叉树吗?后序呢?
4、从先序+中序 创建二叉树?
作者:西伯尔
出处:http://www.cnblogs.com/sybil-hxl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。