关于递归:简单聊聊递归缓存分治回溯

一、初识递归递归函数 = 终止条件 + 递归关系终止条件: 当大问题被拆解成能轻松解决的小问题时,运行终止条件中的逻辑递归关系: 定义如何将大问题拆解为小问题 例子:小名跑步。例如:小名跑4公里,能够分为(跑1km+再跑3km)-> (跑1km+再跑2km)-> (跑1km+再跑1km)-> (跑齐全程)实现: public void running(int distance) {if (distance == 0) { // 终止条件System.out.println("小名跑完了全程!");return;} else {System.out.println("小名跑了1km");distance = distance - 1;System.out.println("还剩" + distance + "km");running(distance); // 递归调用}} @Testpublic void test1() {int distance = 4;System.out.println("跑步总程:" + distance + "km");running(distance);}输入: 跑步总程:4km小名跑了1km还剩3km小名跑了1km还剩2km小名跑了1km还剩1km小名跑了1km还剩0km小名跑完了全程! 正如: 二叉搜寻树中的搜寻树对象: public class TreeNode {int val;TreeNode left;TreeNode right; TreeNode() {} TreeNode(int val) {this.val = val;} TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;} ...

January 3, 2023 · 2 min · jiezi

关于递归:JS从扁平array转tree

有时咱们须要从扁平的数组构造(flat array),依据id,和pid构建出树形数组构造(tree array),这种情景经常出现在嵌套的目录构造,如下图: 或者企业的部门架构,也会呈现下面的相似构造。相似下面的情景,如果咱们有以下的数据:let entries = [{ "id": "12", "parentId": "0", "text": "Man", "level": "1", "children": null }, { "id": "7", "parentId": "12", "text": "Other", "level": "2", "children": null }, { "id": "8", "parentId": "7", "text": "Bird", "level": "3", "children": null }, { "id": "9", "parentId": "0", "text": "Woman", "level": "1", "children": null }, { "id": "11", "parentId": "9", "text": "Girl", "level": "2", "children": null }];咱们期待通过一个算法,构建出上面的构造: [ { "id": "12", "parentId": "0", "text": "Man", "level": "1", "children": [ { "id": "7", "parentId": "12", "text": "Other", "level": "2", "children": [ { "id": "8", "parentId": "7", "text": "Bird", "level": "3", "children": [] } ] } ] }, { "id": "9", "parentId": "0", "text": "Woman", "level": "1", "children": [ { "id": "11", "parentId": "9", "text": "Girl", "level": "2", "children": [] } ] }]兴许咱们能够想到,在遍历数组的时候应用递归。没错能够解决问题,然而这种形式并不高效。 ...

June 19, 2022 · 1 min · jiezi

关于递归:Golang力扣LeetBook初级算法反转字符串

题目:编写一个函数,其作用是将输出的字符串反转过去。输出字符串以字符数组 s 的模式给出。不要给另外的数组调配额定的空间,你必须原地批改输出数组、应用 O(1) 的额定空间解决这一问题。 链接: 力扣LeetBook—高级算法—数组—反转字符串. 示例 1: 输出:s = ["h","e","l","l","o"]输入:["o","l","l","e","h"]示例 2: 输出:s = ["H","a","n","n","a","h"]输入:["h","a","n","n","a","H"]标签:递归、双指针、字符串 解题思路:对称替换即 次要Go代码如下: func reverseString(s []byte) { n := len(s) mid := n / 2 for i := 0; i < mid; i++ { s[i], s[n-i-1] = s[n-i-1], s[i] }}提交截图:

January 15, 2022 · 1 min · jiezi

关于递归:递归教程一份可疑的暧昧名单

最近解决了一次有限分类的数据,次要是用到了递归函数,当然laravel有更优雅的关联查问方法,不过因为数据须要有很多其余二次批改,于是采纳了原生的递归解决,同时对于递归的原理,采纳一个形象的比喻来阐明:(外围思路:递归有去有回)官网阐明递归函数即自调用函数,也就是函数在函数体外部间接或间接地本人调用本人。须要留神的是应用递归函数时通常会在函数体中附加一个判断条件,以判断是否须要继续执行递归调用,当条件满足时会终止函数的递归调用。 背景阐明忽然的一天,现任女友发现你还和以前的一些女性朋友保持联系,并且找到了一份证据摆到你的背后,让你解释一下你跟他们之间的关系,相干证据如下: <?php//出处:https://www.dongyao.ren/$arr = [ [ 'id' =>1, 'pid'=>0, 'name' => '前女友' ], [ 'id' =>2, 'pid'=>1, 'name' =>'二毛' ], [ 'id' =>3, 'pid'=>0, 'name' =>'可疑人员' ], [ 'id' =>4, 'pid'=>2, 'name' =>'小红' ], [ 'id' =>5, 'pid'=>2, 'name' =>'小绿' ], [ 'id' =>6, 'pid'=>3, 'name' =>'共事' ], [ 'id' =>7, 'pid'=>1, 'name' =>'大毛' ], [ 'id' =>8, 'pid'=>3, 'name' =>'同学' ]];相干证人现任女友严格发话了,你本人交代结果可能会好一点,要不然她就要去问上面这个证人敌人了: <?php//出处:https://www.dongyao.ren/getMenu($menus_main,$parent_id=0,$sub='children',$level=1){ $data = array(); foreach($menus_main as $key=>$val){ if($val['parent_id']==$parent_id){ $val['level']=$level; $val[$sub]=$this->getMenu($menus_main,$val['id'],$sub,$level+1); $data[] = $val; } } return $data; }被动交代以下是这些名单的关系图谱,目前曾经一头冷汗了 ...

December 22, 2021 · 2 min · jiezi

关于递归:算法递归问题的思路

确定何时完结、何时持续确定递归链条的连接点所谓递归,无非就是传递+回归。传递必然有连接点,回归必然有完结点。确定这两点,递归问题即可解决。

August 24, 2021 · 1 min · jiezi

关于递归:前端面试每日-31-第570天

明天的知识点 (2020.11.06) —— 第570天 (我也要出题)[html] HTML5语义化更好的标签有哪些?[css] 如果设置一个元素的字体为:font-size:18,没有写单位px,那么会默认有px的单位吗?[js] 你感觉递归好写吗?[软技能] 须要从前端上传一个大文件(如500M)到服务器,你是如何思考的?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

November 6, 2020 · 1 min · jiezi

关于递归:递归实现汉诺塔问题

一.起源: 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。有人造了三根柱子,在一根柱子上从下往上依照大小程序摞着64片黄金圆盘。让上司把圆盘从上面开始按大小程序从新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能挪动一个圆盘。 二.形象为数学问题: 如下图所示,从左到右有A、B、C三根柱子,其中A柱子下面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子下来,期间只有一个准则:一次只能移到一个盘子且大盘子不能在小盘子下面,求挪动的步骤和挪动的次数 解:(1)n == 1       第1次  1号盘  A---->C       sum = 1 次        (2)  n == 2        第1次  1号盘  A---->B        第2次  2号盘  A---->C        第3次  1号盘  B---->C        sum = 3 次 (3)n == 3 第1次  1号盘  A---->C 第2次  2号盘  A---->B 第3次  1号盘  C---->B 第4次  3号盘  A---->C 第5次  1号盘  B---->A 第6次  2号盘  B---->C 第7次  1号盘  A---->C        sum = 7 次 ...

November 2, 2020 · 2 min · jiezi

乐字节Java方法调用重载递归

一、概述方法是指人们在实践过程中为达到一定目的和效果所采取的办法、手段和解决方案。 所谓方法,就是解决一类问题的代码的有序组合,是一个功能模块。编程语言中的方法是组合在一起来执行操作语句的集合。例如,System.out.println 方法,该系统实际上是为了在控制台上输出的消息执行多条语句。可以将方法理解为“CD机”即放入不同的碟片出现不同的歌曲;或“榨汁机”即放入不同的水果榨出不同的果汁。 方法就是 给能够解决问题的多行代码取了一个名字的功能块 ,方便我们多次使用。方法的作用: 1)、main方法过于臃肿 2)、重复执行的代码块 命名,方便重复使用 3)、方便自上而下分解问题 4)、方便维护代码 现在,我们将学习如何定义自己的方法有或没有返回值,使用即调用带或不带参数,使用相同的名称重载方法的方法中。 二、定义(method define)2.1 声明格式: 1)、访问修饰符:方法允许被访问的权限范围; 2)、返回值类型:如果方法不返回任何值,则指定为void;如果方法具有返回值, 则需要指定返回值的类型,并且在方法体中使用 return语句返回值; 3)、方法名:定义方法的名字,必须使用合法的标识符,见名知意。 4)、形参列表:参数可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开。 2.2 现有格式: 注意: 方法只能定义在类中;同时方法不能嵌套;方法编写位置与使用无关。 2.3 分类根据方法是否带参、是否有返回值,可以将方法分为: 2.4 void 关键字一个 void方法,它不返回任何值。 2.5 return关键字return 为 跳出方法 或 返回值。 注意:在一个作用域内 return 之后不能再存在代码 return语句: ①return语句可以出现在任何(有返回值和没有返回值)方法中 ②return语句 在没有返回值的方法中,用来提前结束方法 ③return语句 在有返回值的方法当中,有两个作用:提前结束方法,送出结果。 ④一个方法只能执行一条return语句 ⑤在一定会被执行的return语句后,写的语句为不可达语句,程序自动检测这种语句,永远不会被执行到,报错。 ⑥在循环中无条件的break后写语句,会出现不可达语句 ⑦在死循环(没有break来结束的死循环)后写语句,会出现不可达语句 2.6 思考角度编写一个方法时,请思考这四个方面: 1)、确定方法的功能 2)、确定方法的名称 3)、此方法能否独立运行,不能独立,需要外界数据参与运算,确定形参。 4)、此方法完成后,其结果是否直接影响调用处的后续操作,如果影响,确定返回类型,不影响则为 void 2.7 签名(signature)方法的签名,确保在一个类中的唯一性。方法的签名只看 方法名和形参 ( 类型 个数 和顺序) ,与修饰符 返回类型 和形参名无关。 ...

July 16, 2019 · 1 min · jiezi

动态规划n个台阶的走法

陌上人如玉公子世无双 前言n个台阶 一次只能走 一步或者两步,问有多少种走法 问题分析假设有n个台阶: 当 n=0,即没有台阶时 走法为0 当 n=1,台阶为1时 走法为1 当 n=2,台阶为2时 走法为2:一步一步 , 两步 当为n个时, 相当于在n-1这个台阶走一步或者在n-2这个台阶走两步 所以n个台阶 相当于 n-1个台阶的走法加上n-2个台阶的走法 递归实现: 递归函数: dp(n) = dp(n-1) + dp(n-2) 递归出口: n=0 return 0 n=1 return 1 n=2 return 2 如果有对递归思想不是很懂的可以查看我之前发过的递归文章链接描述,相信对你有很大帮助 代码实现/** n个台阶 一次只能走 一步l或者两步,问有多少步解法 当 n=0,即没有台阶时 走法为0 当 n=1,台阶为1时 走法为1 当 n=2,台阶为2时 走法为:一步一步 , 两步 当为n个时, 相当于在n-1这个台阶走一步或者在n-2这个台阶走两步 所以n个台阶 相当于 n-1个台阶的走法加上n-2个台阶的走法 递归实现: 递归函数: dp(n) = dp(n-1) + dp(n-2) 递归出口: n=0 return 0、n=1 return 1、n=2 return 2 @param n 个台阶 @return n个台阶走法 */ int dp(int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else if (n == 2) { return 2; } else { return dp(n-1) + dp(n-2); } }功能测试 int main(int argc, const char * argv[]) { @autoreleasepool { int sum; sum = dp(2); NSLog(@"2个台阶的走法:%d\n", sum); sum = dp(3); NSLog(@"3个台阶的走法:%d\n", sum); sum = dp(4); NSLog(@"4个台阶的走法:%d\n", sum); sum = dp(9); NSLog(@"9个台阶的走法:%d\n", sum); } return 0; } ...

July 10, 2019 · 1 min · jiezi

JavaScript-数据结构与算法之美-递归

1. 前言算法为王。排序算法博大精深,前辈们用了数年甚至一辈子的心血研究出来的算法,更值得我们学习与推敲。因为之后要讲有内容和算法,其代码的实现都要用到递归,所以,搞懂递归非常重要。 1. 定义方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。简单来说就是:自己调用自己。 现实例子:周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊 ?电影院里面太黑了,看不清,没法数,现在你怎么办 ? 于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。 基本上,所有的递归问题都可以用递推公式来表示,比如: f(n) = f(n-1) + 1; // 其中,f(1) = 1 f(n) 表示你想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1) = 1 表示第一排的人知道自己在第一排。 有了这个递推公式,我们就可以很轻松地将它改为递归代码,如下: function f(n) { if (n == 1) return 1; return f(n-1) + 1;}2. 为什么使用递归 ?递归的优缺点 ?优点:代码的表达力很强,写起来简洁。缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。3. 什么样的问题可以用递归解决呢 ?一个问题只要同时满足以下 3 个条件,就可以用递归来解决。 问题的解可以分解为几个子问题的解。何为子问题 ?就是数据规模更小的问题。比如,前面讲的电影院的例子,你要知道,自己在哪一排的问题,可以分解为前一排的人在哪一排这样一个子问题。 问题与子问题,除了数据规模不同,求解思路完全一样比如电影院那个例子,你求解自己在哪一排的思路,和前面一排人求解自己在哪一排的思路,是一模一样的。 存在递归终止条件比如电影院的例子,第一排的人不需要再继续询问任何人,就知道自己在哪一排,也就是 f(1) = 1,这就是递归的终止条件。 4. 递归常见问题及解决方案警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。5. 如何实现递归 ?1. 递归代码编写 写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 2. 递归代码理解 对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。 那该如何理解递归代码呢 ? 如果一个问题 A 可以分解为若干个子问题 B、C、D,你可以假设子问题 B、C、D 已经解决。而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。因此,理解递归代码,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。 ...

July 4, 2019 · 2 min · jiezi

C语言之通俗易懂的递归算法

摘要记得大学接触的第一门课程就是C语言,里面让我印象深刻之一就是递归,受大学老师讲递归的启发我尝试着用最通俗、最易懂的方式讲解递归。递归其实真的不难。觉得递归很难的朋友,可以试试看一下,相信如果你能认真的看完这篇文章,或许会有很大的收获 递归产生条件递归两个必要条件: **1.递归函数** **2.递归出口**下面的练习会让你清晰的发现,递归其实就是要找 递归函数和递归出口 这两步 练习 假设有个数列 1 3 5 7 9 11 .... 找到第n项的值逻辑分析: 递归函数:f(n) = f(n-1)+2;递归出口:f(1) = 1; 代码实现 /** 假设有个数列 1 3 5 7 9 11 .... 递归函数:f(n) = f(n-1)+2; 递归出口: f(1) = 1; @param n 求n项的值 @return 返回第n项的值 */ int find(int n) { if (n == 1) {// 递归出口 return 1; } else {//递归函数 return find(n-1)+2; } }Fibonacci 斐波那契数列逻辑分析: ...

June 28, 2019 · 2 min · jiezi

使用递归进行数组的每一项的组合结合

需求:数组中的每一项都要混合 例如(这里的数组长度不固定,子集的children也是不固定的): arr = [ { name: "颜色", children: [ { text: "红色" }, { text: "蓝色" } ] }, { name: "型号", children: [ { text: "A级" }, { text: "B级" }, { text: "C级" } ] }, { name: "尺寸", children: [ { text: "L" }, { text: "XL" }, { text: "XXL" } ] } 需要的输出的结果: [ ["红色", "A级", "L"] ["红色", "A级", "XL"] ["红色", "A级", "XXL"] ["红色", "B级", "L"] ["红色", "B级", "XL"] ["红色", "B级", "XXL"] ["红色", "C级", "L"] ["红色", "C级", "XL"] ["红色", "C级", "XXL"] ["蓝色", "A级", "L"] ["蓝色", "A级", "XL"] ["蓝色", "A级", "XXL"] ["蓝色", "B级", "L"] ["蓝色", "B级", "XL"] ["蓝色", "B级", "XXL"] ["蓝色", "C级", "L"] ["蓝色", "C级", "XL"] ["蓝色", "C级", "XXL"] ...

June 18, 2019 · 2 min · jiezi

从经典问题学递归3X4的方格-从左上角A走到右下角B-只能向右向下走-一共有多少种走法

题目:3X4的方格 从左上角A走到右下角B 只能向右向下走 一共有多少种走法?图形:题目转化为图形之后就是,从(1, 1)方格走到(3, 4)方格有多少种方案(每次走一格)?分析:1、根据题目我们知道只能往右走或者向下走,那么从(2, 4)格子走到(3, 4)格子只有一种方案,从(3, 3)格子走到(3, 4)格子也只有一种方案。2、以此类推,到某个格子A的走法 = A上面的格子走法 + A左边的格子走法;3、如果碰到第一行或者第一列的格子,那么走法只有一种4、如果碰到第一个格子,我们认为不需要走,即走法为0总结:1、这种把一个复杂的问题分解成若干个有相同规律的子问题的方法,我们称之为递归。2、递归由递归条件和递归出口组成,其中递归出口非常重要。3、分析中的第2点我们称之为递归条件。4、分析中的点3、4点我们称之为递归出口(返回明确的值)。代码:// N X M的方格 从左上角A(1, 1)走到右下角B(N, M) 只能向右向下走 一共有多少种走法?function calc(x, y){ // 坐标(1, 1) 递归出口 if(x == 1 && y == 1) return 0; // 坐标(x, 1) (1, y) 递归出口 if(x == 1 || y == 1) return 1; // 递归条件 if(x > 1 && y > 1) { return calc(x-1, y) + calc(x, y-1); } // 不符合条件,直接返回0 return 0;}calc(3, 4); // 10问题:根据上面的分析,我们知道在递归的过程中,会有很多重复的格子,非常浪费性能,当计算的数字越大,递归的性能也会越低,怎么提高递归的性能呢?下次我们再介绍(剪枝)。

June 3, 2019 · 1 min · jiezi

【算法】递归应用_常见算法的递归实现

前面学习了递归,趁热打铁就把常见的一些算法用递归实现了一遍,找找递归的感觉。斐波那契数列用递归函数定义如下:(1) n = 0时,f(n) = 0(2) n = 1时,f(n) = 1(3) n > 1时,f(n) = f(n-1) + f(n-2)–Haskellfibonacci :: (Num a, Eq a) => a -> afibonacci 0 = 0fibonacci 1 = 1fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)这代码几乎就是上面的递归定义原封不动的“照搬”。当然这个版本性能并不是太高,优化的空间很大,这里不讨论。插入排序大部分人在打牌的时候无意中就使用了这个算法。左手拿着排序好的排【一开始没有牌,当然满足条件】,假设按升序排列,每次右手摸完牌都会插入到左手适当的位置来保持左手中的牌是有序的。人的话,可能并没有意识到,实际上你在插入牌的时候,是通过将右手的牌或者从头开始或从尾开始依次跟左手的牌对比,然后找到合适的位置。时间复杂度为O(n2)。我们可以递归定义如下:(1)如果列表内没有元素,我们就返回空列表(2)如果列表不为空,对列表元素的排序,可以认为就是将列表首元素插入到已经排好序的其他元素中,这些元素的排序通过插入排序实现。代码如下:–HaskellinsertionSort :: Ord a => [a] -> [a]insertionSort [] = []insertionSort (x:xs) = insert x $ insertionSort xsinsert :: Ord a => a -> [a] -> [a]insert x y = let (p,q) = span (<x) y in p ++ [x] ++ q冒泡排序这个算法也较为直观,假设升序排列,第一次遍历会将最大的元素移动到列表的右侧,怎么移动的呢?只要从左到右,依次比较当前位置的元素和右侧的元素,交换顺序不对的两个元素的位置。通过多次遍历,最终得到所有元素有序的列表。时间复杂度为O(n2)。–HaskellbubbleSort :: Ord a => [a] -> [a]bubbleSort [] = []bubbleSort x = bubbleSort initx ++ [lastx] where x’ = swap’ x initx = init x’ lastx = last x’swap’ :: Ord a => [a] -> [a]swap’ [] = []swap’ [x] = [x]swap’ (x1:x2:xs) | x1 > x2 = x2 : swap’ (x1 : xs) | otherwise = x1 : swap’ (x2 : xs)归并排序这个算法的效率较高,时间复杂度为O(nlogn),采用了分治思想,即:将元素分为两部分,每一部分分别采用归并排序算法排序,然后将这两部分已排序的部分合并为整体有序。–HaskellmergeSort :: Ord a => [a] -> [a]mergeSort [] = []mergeSort [x] = [x]mergeSort xs = merge (mergeSort x1) (mergeSort x2) where (x1, x2) = splitAt half xs half = (length xs) div 2merge :: Ord a => [a] -> [a] -> [a]merge [] ys = ysmerge xs [] = xsmerge x1@(x:xs) y1@(y:ys) | x > y = y : merge x1 ys | otherwise = x : merge xs y1二分算法在前面实现了一版,如下–Haskellimport qualified Data.Vector as VbinarySearch :: (Ord a)=> V.Vector a -> Int -> Int -> a -> Maybe IntbinarySearch vec low high e | low > high = Nothing | vec V.! mid < e = binarySearch vec (mid+1) high e | vec V.! mid > e = binarySearch vec low (mid-1) e | otherwise = Just mid where mid = low + ((high-low) div 2)但只能返回一个元素,如果我们想返回所有与e相等的元素位置该怎么办呢?一种非递归的方案可能是:使用上面得到的位置,然后使用循环向前和向后搜索,从而得到与之相等的所有元素的索引。下面是递归的版本,与循环版本对应,只不过在找到相等元素的时候继续往下递归即可。–Haskellimport qualified Data.Vector as VbinarySearch’ :: (Ord a) => V.Vector a -> Int -> Int -> a -> [Int]binarySearch’ vec low high e | low > high = [] | vec V.! mid < e = binarySearch’ vec (mid + 1) high e | vec V.! mid > e = binarySearch’ vec low (mid - 1) e | otherwise = binarySearch’ vec low (mid - 1) e ++ [mid] ++ (binarySearch’ vec (mid + 1) high e) where mid = low + ((high - low) div 2)下一章介绍的快速排序也是递归的范本,下一篇文章介绍。微信公众号 ...

March 26, 2019 · 2 min · jiezi

浅谈尾递归

要说尾递归先理解尾调用尾调用定义来自尾调用维基百科 在计算机学里,尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果代码形式上表现为一个函数执行的最后是调用另一个函数//仅举例,没有使用特定语言的语法function f(x) { a(x); b(x); return g(x); //函数执行的最后调用另一个函数}核心理解就是看一个函数在调用另一个函数得时候,本身是否可以被“释放”判断:下列那种调用时尾调用// 情况一function f(x){ int a = g(x); return a;}// 情况二function f(x){ return 3+g(x);}// 情况三function f(x){ if (x > 0) { return g(x); } return r(x);}答案情况一是调用函数g(x)之后,还有别的操作,所以不属于尾调用,即使语义一样,因为要得到a得结果,需要等待g(x)函数,所以f(x)无法释放。情况二在调用后也有别的操作,所以不属于尾调用,同理f(x)也是无法释放,即使写在同一行。情况三中,不管x取什么值,最后一步操作都是函数调用,所以属于尾调用。尾调用有什么好处先看普通调用的过程//用如下三个函数举例function f(x){ res = g(x); return res+1;}function g(x){ res = r(x); return res + 1;}function r(x){ res = x + 1; return res + 1;}文字描述函数调用调用f(x),在内存形成一个调用记录,又称调用帧(call frame),保存调用位置和内部变量等信息。函数f(x)内调用函数g(x),那么在f(x)的调用帧上方会形成一个g(x)的调用帧函数g(x)内部还调用函数r(x),所以在g(x)的调用帧上方会形成一个r(x)的调用帧函数r(x)调用结束,将结果返回给g(x),同时函数r(x)结束并“消失”同理,g(x)调用结束并“消失”最后到f(x),结束并消失(图中没有体现)上述调用过程中,所有的调用帧会在一个调用栈(call stack)中上述过程维基百科中的描述 在程序运行时,计算机会为应用程序分配一定的内存空间;应用程序则会自行分配所获得的内存空间,其中一部分被用于记录程序中正在调用的各个函数的运行情况,这就是函数的调用栈。常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。上述调用过程中有什么风险如下图,当函数的调用层数非常多时,调用栈会消耗不少内存,甚至会撑爆内存空间(栈溢出),造成程序严重卡顿或意外崩溃。尾调用解决上述风险//这是一个尾调用function f() { m = 1; n = 2; return g(m + n);}f();// 等同于function f() { return g(3);}f();// 等同于g(3);上述代码,我们可以看到,我们调用g之后,和f就没有任何关系了,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录,尾调用的意义如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧为一,这将大大节省内存什么是尾递归尾递归 = 尾调用 + 递归递归:函数调用自身,称为递归尾调用:函数最后是调用另一个函数所以,尾递归可以总结为:一个函数在其内部最后一步调用其自身#用python举例def tailrecsum(x, running_total=0): if x == 0: return running_total else: #tailrecsum函数得最后一步是调用另一个函数,其中这个“另一个函数”是其自身 return tailrecsum(x - 1, running_total + x) 参考尾调用尾调用优化Tail Calls, Default Arguments, and Excessive Recycling in ES-6什么是尾调用漫谈递归:从斐波那契开始了解尾递归 ...

February 14, 2019 · 1 min · jiezi

【剑指offer】8.斐波那契数列

题目题目描述大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。基本思路这道题在剑指offer中实际是当作递归的反例来说的。递归的本质是吧一个问题分解成两个或者多个小问题,如果多个小问题存在互相重叠的情况,那么就存在重复计算。f(n) = f(n-1) + f(n-2) 这种拆分使用递归是典型的存在重叠的情况,所以会造成非常多的重复计算。另外,每一次函数调用爱内存中都需要分配空间,每个进程的栈的容量是有限的,递归层次过多,就会造成栈溢出。递归是从最大数开始,不断拆解成小的数计算,如果不去考虑递归,我们只需要从小数开始算起,从底层不断往上累加就可以了,其实思路也很简单。代码function Fibonacci(n){ if(n<=1){ return n; } let i = 1; let pre = 0; let current = 1; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}扩展1.跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。找规律:跳三级台阶等于跳两级台阶的跳法+跳一级台阶的跳法。跳四级台阶等于跳三级台阶的跳法+跳二级台阶的跳法。明显也符合斐波那契数列的规律function jumpFloor(n){ if(n<=2){ return n; } let i = 2; let pre = 1; let current = 2; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}3.矩形覆盖我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?跟上面跳台阶那个题很像。假设有8个块第1块竖着放,后面还剩7块,共f(7)种方法。第1块横着放,后面还剩6块,共f(6)种方法。即f(8)=f(6)+f(7) f(n)=f(n-1)+f(n-2)function rectCover(n){ if(n<=2){ return n; } let i = 2; let pre = 1; let current = 2; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}3.变态跳台阶一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。每个台阶都可以选择跳或者不跳,最后一个台阶必跳。每个台阶有两种选择,n个台阶有2的n次方种选择。所以一共有2的n-1次跳法。使用位运算function jumpFloorII(number){ return 1<<(–number);} ...

January 19, 2019 · 1 min · jiezi

leetcode讲解--872. Leaf-Similar Trees

题目Consider all the leaves of a binary tree. From left to right order, the values of those leaves form a leaf value sequence.For example, in the given tree above, the leaf value sequence is (6, 7, 4, 9, 8).Two binary trees are considered leaf-similar if their leaf value sequence is the same.Return true if and only if the two given trees with head nodes root1 and root2 are leaf-similar.Note:Both of the given trees will have between 1 and 100 nodes.讲解这道题的思路很简单,先扫描出两个树的叶子结果集,然后比较就行了。考察点是树的遍历。递归的时候首先要判断结点是否为空。Java代码/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { public boolean leafSimilar(TreeNode root1, TreeNode root2) { List<Integer> leaves1 = new ArrayList<>(); List<Integer> leaves2 = new ArrayList<>(); getLeaves(root1, leaves1); getLeaves(root2, leaves2); if(leaves1.size()!=leaves2.size()){ return false; }else{ for(int i=0;i<leaves1.size();i++){ if(leaves1.get(i)!=leaves2.get(i)){ return false; } } } return true; } private void getLeaves(TreeNode root, List<Integer> leaves){ if(root==null){ return; } if(root.left==null && root.right==null){ leaves.add(root.val); return; } getLeaves(root.left, leaves); getLeaves(root.right, leaves); }} ...

December 29, 2018 · 1 min · jiezi

leetcode讲解--559. Maximum Depth of N-ary Tree

题目Given a n-ary tree, find its maximum depth.The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.For example, given a 3-ary tree:We should return its max depth, which is 3.Note:The depth of the tree is at most 1000.The total number of nodes is at most 5000.题目地址讲解这道题需要对每次层的深度做个记录,我直接使用结点的val属性来记录深度。另外就是给根节点深度置为1的时候有个技巧,设置一个一次性的flag。Java代码/// Definition for a Node.class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; }};/class Solution { private int result=0; private boolean flag = true; public int maxDepth(Node root) { if(root==null){ return result; } if(flag){ root.val=1; flag = false; } if(result<root.val){ result = root.val; } for(Node node:root.children){ node.val = root.val+1; maxDepth(node); } return result; } } ...

December 26, 2018 · 1 min · jiezi

leetcode讲解--590. N-ary Tree Postorder Traversal

题目Given an n-ary tree, return the postorder traversal of its nodes’ values.For example, given a 3-ary tree:Return its postorder traversal as: [5,6,3,2,4,1].Note:Recursive solution is trivial, could you do it iteratively?题目地址讲解这道题跟先序遍历差不多,调换一下添加结点的顺序而已。参考:leetcode讲解–589. N-ary Tree Preorder Traversal但这道题的迭代解法稍微有点麻烦,需要一个标记,初次访问一个结点的时候我们并不能立即将它的值加进结果里,而是要等它的所有孩子访问完毕再加。也就是说我们第一次访问它并不pop,第二次访问它才pop。Java代码递归解法:/// Definition for a Node.class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; }};/class Solution { private List<Integer> result = new ArrayList<>(); public List<Integer> postorder(Node root) { if(root==null){ return result; } for(Node node:root.children){ postorder(node); } result.add(root.val); return result; }}迭代解法:/// Definition for a Node.class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; }};/class Solution { private List<Integer> result = new ArrayList<>(); private Stack<Bundle> stack = new Stack<>(); private Stack<Bundle> stack_temp = new Stack(); public List<Integer> postorder(Node root) { if(root==null){ return result; } Bundle rootBundle = new Bundle(root); stack.push(rootBundle); while(!stack.empty()){ Bundle bundle = stack.peek(); if(bundle.flag){ result.add(stack.pop().node.val); continue; } for(Node node:bundle.node.children){ Bundle d = new Bundle(node); stack_temp.push(d); } while(!stack_temp.empty()){ stack.push(stack_temp.pop()); } bundle.flag = true; } return result; } class Bundle{ Node node; Boolean flag; public Bundle(Node node){ flag = false; this.node = node; } }} ...

December 26, 2018 · 1 min · jiezi

leetcode讲解--700. Search in a Binary Search Tree

题目Given the root node of a binary search tree (BST) and a value. You need to find the node in the BST that the node’s value equals the given value. Return the subtree rooted with that node. If such node doesn’t exist, you should return NULL.For example,Given the tree: 4 / \ 2 7 / \ 1 3And the value to search: 2You should return this subtree: 2 / \ 1 3In the example above, if we want to search the value 5, since there is no node with value 5, we should return NULL.Note that an empty tree is represented by NULL, therefore you would see the expected output (serialized tree format) as [], not null.题目地址讲解要注意一下的是,如果我们searchBST作为递归函数,我们就要返回一个TreeNode类型,虽然返回,但我们不一定会用到。如果我们找到了,就把treeNode标记为结果结点。Java代码/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { private TreeNode treeNode; public TreeNode searchBST(TreeNode root, int val) { if(root==null){ return treeNode; } if(root.val == val){ treeNode = root; } searchBST(root.left, val); searchBST(root.right, val); return treeNode; }} ...

December 26, 2018 · 1 min · jiezi

leetcode讲解--589. N-ary Tree Preorder Traversal

题目Given an n-ary tree, return the preorder traversal of its nodes’ values.For example, given a 3-ary tree:Return its preorder traversal as: [1,3,5,6,2,4].Note:Recursive solution is trivial, could you do it iteratively?题目地址讲解如果用递归来解题的话,还是非常简单的。如果要用迭代来解题,无非就是用栈来实现。要注意的一点是,需要一个额外的栈来把压栈的顺序倒一下序。Java代码递归代码:/// Definition for a Node.class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; }};/class Solution { private List<Integer> result = new ArrayList<>(); public List<Integer> preorder(Node root) { if(root==null){ return result; } result.add(root.val); for(Node node:root.children){ preorder(node); } return result; }}迭代代码:/// Definition for a Node.class Node { public int val; public List<Node> children; public Node() {} public Node(int _val,List<Node> _children) { val = _val; children = _children; }};/class Solution { private List<Integer> result = new ArrayList<>(); private Stack<Node> stack = new Stack<>(); private Stack<Node> stack_temp = new Stack(); public List<Integer> preorder(Node root) { if(root==null){ return result; } stack.push(root); while(!stack.empty()){ Node theNode = stack.pop(); result.add(theNode.val); for(Node node:theNode.children){ stack_temp.push(node); } while(!stack_temp.empty()){ stack.push(stack_temp.pop()); } } return result; }} ...

December 26, 2018 · 1 min · jiezi

leetcode讲解--951. Flip Equivalent Binary Trees

题目For a binary tree T, we can define a flip operation as follows: choose any node, and swap the left and right child subtrees.A binary tree X is flip equivalent to a binary tree Y if and only if we can make X equal to Y after some number of flip operations.Write a function that determines whether two binary trees are flip equivalent. The trees are given by root nodes root1 and root2.Example 1:Input: root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7]Output: trueExplanation: We flipped at nodes with values 1, 3, and 5.Note:Each tree will have at most 100 nodes.Each value in each tree will be a unique integer in the range [0, 99].题目地址讲解这道题考察的依旧是递归,其实跟树有关的大部分题都是递归。树对应多叉递归,栈对应线性递归。然后递归重要的就一个点:结束条件。Java代码/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { public boolean flipEquiv(TreeNode root1, TreeNode root2) { if(root1==null && root2==null){ return true; }else if((root1==null&&root2!=null) || (root1!=null&&root2==null) || (root1.val!=root2.val)){ return false; } return (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right)) || (flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left)); }} ...

December 25, 2018 · 1 min · jiezi

leetcode讲解--894. All Possible Full Binary Trees

题目A full binary tree is a binary tree where each node has exactly 0 or 2 children.Return a list of all possible full binary trees with N nodes. Each element of the answer is the root node of one possible tree.Each node of each tree in the answer must have node.val = 0.You may return the final list of trees in any order.Example 1:Input: 7Output: [[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]Explanation:Note:1 <= N <= 20题目地址讲解这道题太适合用来训练递归了。与以前做过的题不同,这里的返回值是一个list。而且还需要对树比较了解。最好是先画出N=1到5的时候,树的结构,然后会发现一些规律。注意下i+=2,步长是2。Java代码/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { public List<TreeNode> allPossibleFBT(int N) { List<TreeNode> result = new ArrayList<>(); if(N%2==0){ return result; } if(N==1){ result.add(new TreeNode(0)); return result; } for(int i=1;i<N;i+=2){ for(TreeNode l:allPossibleFBT(i)){ for(TreeNode r:allPossibleFBT(N-1-i)){ TreeNode root = new TreeNode(0); root.left = l; root.right = r; result.add(root); } } } return result; }} ...

December 25, 2018 · 1 min · jiezi

红黑树插入操作的java实现

前言网上有非常多的关于红黑树理论的描述,本文的重点将不在于此,但是会在文中给出优秀文章的链接。对红黑树不了解的建议先阅读文章再看实现。本红黑树实现不支持多线程环境。因为删除操作灰常复杂,所以后续更新。源码在文末可以查看。参考链接https://www.geeksforgeeks.org…https://www.geeksforgeeks.org...http://www.codebytes.in/2014/…https://blog.csdn.net/eson_15…数据结构定义的红黑树的节点如下:private static class Node<T>{ static final int RED = 0; static final int BLACK = 1; T value; int color = RED; Node<T> leftChild; Node<T> rightChild; Node<T> parent; Node(T value) { this.value = value; } boolean isRoot() { return this.parent == null; } boolean isLeaf() { return this.leftChild == null && this.rightChild == null; } boolean isLeftChild() { return this.parent != null && this.parent.leftChild == this; } boolean isRightChild() { return this.parent != null && this.parent.rightChild == this; } Node<T> getUncle() { if(this.parent == null || this.parent.parent == null) return null; if(this.parent.isLeftChild()) { return this.parent.parent.rightChild; } else { return this.parent.parent.leftChild; } } Node<T> getSibling() { if(this.parent == null) return null; return this.parent.leftChild == this ? this.parent.rightChild : this.parent.leftChild; } boolean isRed() { return this.color == RED; } boolean isBlack() { return this.color == BLACK; } }该Node作为RedBlackTree的一个内部类存在。除了和普通的TreeNode相同给的左子节点和右子节点的引用,还额外引用了父节点,方便我们回溯。除此以外还封装了一些方法,比如获得自己的祖父节点,叔叔节点以及兄弟节点等。旋转操作因为额外持有了父节点,所以在执行旋转操作的时候需要额外注意空指针以及不恰当的赋值带来的循环引用。左旋和右旋的代码如下: private void rotateLeft(Node<T> node) { if(node == null || node.rightChild == null) return; Node<T> parent = node.parent; Node<T> rightChild = node.rightChild; if(rightChild.leftChild != null) { node.rightChild = rightChild.leftChild; node.rightChild.parent = node; } else { node.rightChild = null; } rightChild.leftChild = node; node.parent = rightChild; if(parent != null) { rightChild.parent = parent; if(node == parent.leftChild) { parent.leftChild = rightChild; } else { parent.rightChild = rightChild; } } else { rightChild.parent = null; root = rightChild; } } private void rotateRight(Node<T> node) { if(node == null || node.leftChild == null) return; Node<T> parent = node.parent; Node<T> leftChild = node.leftChild; if(leftChild.rightChild != null) { node.leftChild = leftChild.rightChild; node.leftChild.parent = node; } else { node.leftChild = null; } leftChild.rightChild = node; node.parent = leftChild; if(parent != null) { leftChild.parent = parent; if(node == parent.leftChild) { parent.leftChild = leftChild; } else { parent.rightChild = leftChild; } } else { leftChild.parent = null; root = leftChild; } }插入我们知道,在红黑树中插入一个节点相当于在一个二叉搜索树中插入一个节点。因此该节点一定是作为叶节点而插入的。二者唯一的不同在于,默认插入的节点为红色,我们需要重新调整树结构从而确保红黑树重新达到平衡。 @Override public void insert(T t) { Node<T> newNode = new Node<T>(t); if(root == null) { root = newNode; root.color = Node.BLACK; size++; } else { Node<T> tmp = root; while(tmp != null) { if(tmp.value.equals(t)) { newNode = tmp; break; } else if(tmp.value.compareTo(t) < 0) { if(tmp.rightChild == null) { tmp.rightChild = newNode; newNode.parent = tmp; size++; break; } else { tmp = tmp.rightChild; } } else { if(tmp.leftChild == null) { tmp.leftChild = newNode; newNode.parent = tmp; size++; break; } else { tmp = tmp.leftChild; } } } } adjust(newNode); } private void adjust(Node<T> node) { if(node.isRoot()) { node.color = Node.BLACK; return; } else if(node.parent.isRed()) { //肯定存在祖父节点,因为根节点必须为黑色 Node<T> parent = node.parent; Node<T> grandParent = node.parent.parent; Node<T> uncle = node.getUncle(); if(uncle!=null && uncle.isRed()) { parent.color = Node.BLACK; uncle.color = Node.BLACK; grandParent.color = Node.RED; adjust(grandParent); } else { if(node.isLeftChild() && node.parent.isLeftChild()) { parent.color = Node.BLACK; grandParent.color = Node.RED; rotateRight(grandParent); } else if(node.isRightChild() && node.parent.isRightChild()) { parent.color = Node.BLACK; grandParent.color = Node.RED; rotateLeft(grandParent); } else if(node.isLeftChild() && node.parent.isRightChild()) { node.color = Node.BLACK; grandParent.color = Node.RED; rotateRight(parent); rotateLeft(grandParent); } else { node.color = Node.BLACK; grandParent.color = Node.RED; rotateLeft(parent); rotateRight(grandParent); } } } }删除待更新源码public interface RedBlackTreeADT<T extends Comparable<T>> { boolean contains(T t); void insert(T t); void delete(T t); int size(); }public class RedBlackTree<T extends Comparable<T>> implements RedBlackTreeADT<T>{ private int size; private Node<T> root; private static class Node<T>{ static final int RED = 0; static final int BLACK = 1; T value; int color = RED; Node<T> leftChild; Node<T> rightChild; Node<T> parent; Node(T value) { this.value = value; } boolean isRoot() { return this.parent == null; } boolean isLeaf() { return this.leftChild == null && this.rightChild == null; } boolean isLeftChild() { return this.parent != null && this.parent.leftChild == this; } boolean isRightChild() { return this.parent != null && this.parent.rightChild == this; } Node<T> getUncle() { if(this.parent == null || this.parent.parent == null) return null; if(this.parent.isLeftChild()) { return this.parent.parent.rightChild; } else { return this.parent.parent.leftChild; } } Node<T> getSibling() { if(this.parent == null) return null; return this.parent.leftChild == this ? this.parent.rightChild : this.parent.leftChild; } boolean isRed() { return this.color == RED; } boolean isBlack() { return this.color == BLACK; } } @Override public boolean contains(T t) { Node<T> tmp = root; while(tmp != null) { int cmp = tmp.value.compareTo(t); if(cmp == 0) { return true; } else if (cmp < 0) { tmp = tmp.rightChild; } else { tmp = tmp.leftChild; } } return false; } @Override public void insert(T t) { Node<T> newNode = new Node<T>(t); if(root == null) { root = newNode; root.color = Node.BLACK; size++; } else { Node<T> tmp = root; while(tmp != null) { if(tmp.value.equals(t)) { newNode = tmp; break; } else if(tmp.value.compareTo(t) < 0) { if(tmp.rightChild == null) { tmp.rightChild = newNode; newNode.parent = tmp; size++; break; } else { tmp = tmp.rightChild; } } else { if(tmp.leftChild == null) { tmp.leftChild = newNode; newNode.parent = tmp; size++; break; } else { tmp = tmp.leftChild; } } } } adjust(newNode); } private void adjust(Node<T> node) { if(node.isRoot()) { node.color = Node.BLACK; return; } else if(node.parent.isRed()) { //肯定存在祖父节点,因为根节点必须为黑色 Node<T> parent = node.parent; Node<T> grandParent = node.parent.parent; Node<T> uncle = node.getUncle(); if(uncle!=null && uncle.isRed()) { parent.color = Node.BLACK; uncle.color = Node.BLACK; grandParent.color = Node.RED; adjust(grandParent); } else { if(node.isLeftChild() && node.parent.isLeftChild()) { parent.color = Node.BLACK; grandParent.color = Node.RED; rotateRight(grandParent); } else if(node.isRightChild() && node.parent.isRightChild()) { parent.color = Node.BLACK; grandParent.color = Node.RED; rotateLeft(grandParent); } else if(node.isLeftChild() && node.parent.isRightChild()) { node.color = Node.BLACK; grandParent.color = Node.RED; rotateRight(parent); rotateLeft(grandParent); } else { node.color = Node.BLACK; grandParent.color = Node.RED; rotateLeft(parent); rotateRight(grandParent); } } } } private void rotateLeft(Node<T> node) { if(node == null || node.rightChild == null) return; Node<T> parent = node.parent; Node<T> rightChild = node.rightChild; if(rightChild.leftChild != null) { node.rightChild = rightChild.leftChild; node.rightChild.parent = node; } else { node.rightChild = null; } rightChild.leftChild = node; node.parent = rightChild; if(parent != null) { rightChild.parent = parent; if(node == parent.leftChild) { parent.leftChild = rightChild; } else { parent.rightChild = rightChild; } } else { rightChild.parent = null; root = rightChild; } } private void rotateRight(Node<T> node) { if(node == null || node.leftChild == null) return; Node<T> parent = node.parent; Node<T> leftChild = node.leftChild; if(leftChild.rightChild != null) { node.leftChild = leftChild.rightChild; node.leftChild.parent = node; } else { node.leftChild = null; } leftChild.rightChild = node; node.parent = leftChild; if(parent != null) { leftChild.parent = parent; if(node == parent.leftChild) { parent.leftChild = leftChild; } else { parent.rightChild = leftChild; } } else { leftChild.parent = null; root = leftChild; } } @Override public int size() { return size; } public void printTree() { this.printTree(this.root); } private void printTree(Node<T> root) { if(root == null) { System.out.print(“nil(BLACK)”); return; } printTree(root.leftChild); System.out.print(root.value + “(” + (root.color==Node.RED ? “RED” : “BLACK”) + “)”); printTree(root.rightChild); } @Override public void delete(T t) { // TODO Auto-generated method stub }}有任何问题,欢迎指出! ...

October 15, 2018 · 6 min · jiezi