这个是因该很早之前就要学习的东西,一直拖今天,惭愧…
本文是 labuladong的算法笔记
的读书笔记
Data Structures 一、数据结构的存储方式 本质上讲,数据结构的存储方式只有两种:数组和链表
根本结构:
数组
链表
存储方式:
顺序存储
链式存储
实现队列和栈:
处理扩容缩容问题
需要更多的内存空间存储结点指针
图:
邻接矩阵
邻接表
散列表(通过散列函数把键映射到一个大数组里):
线性探查法就需要数组特性,以便连续寻址,不需要指针的存储空间,但操作稍微复杂些
对于解决散列冲突的方法,拉链法需要链表特性,操作简单,但需要额外的空间存储指针
Redis:
Redis底层的存储方式直晒都提供了两种
来根据存储数据的实际情况是用合适的存储方式
优点和缺点:
数组 由于是紧凑连续存储,可以随机访问,通过索引快速找到对应元素,而且相对节约存储空间。但正因为连续存储,内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O(N)
;而且你如果想在数组中间进行插入和删除,每次必须搬移后面的所有数据以保持连续,时间复杂度 O(N)
。
链表 因为元素不连续,而是靠指针指向下一个元素的位置,所以不存在数组的扩容问题;如果知道某一元素的前驱和后驱,操作指针即可删除该元素或者插入新元素,时间复杂度 O(1)
。但是正因为存储空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问;而且由于每个元素必须存储指向前后元素位置的指针,会消耗相对更多的储存空间。
二、数据结构的基本操作 数据结构种类很多,但它们存在的目的都是在不同的应用场景,尽可能高效地增删查改 。
如何遍历 + 访问?我们仍然从最高层来看,各种数据结构的遍历 + 访问无非两种形式:线性的和非线性的。
基本的链表遍历框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ListNode { int val; ListNode next; } void traverse (ListNode head) { for (ListNode p = head; p != null ; p = p.next) { } } void traverse (ListNode head) { traverse(head.next) }
二叉树遍历框架,典型的非线性递归遍历结构:
1 2 3 4 5 6 7 8 9 10 class TreeNode { int val; TreeNode left, right; } void traverse (TreeNode root) { traverse(root.left) traverse(root.right) }
二叉树框架可以扩展为 N 叉树的遍历框架:
1 2 3 4 5 6 7 8 9 10 class TreeNode { int val; TreeNode[] children; } void traverse (TreeNode root) { for (TreeNode child : root.children) traverse(child); }
因为图就是好几 N 叉棵树的结合体,环状图用个布尔数组 visited 做标记就行了。
三、算法刷题指南 首先要明确的是,数据结构是工具,算法是通过合适的工具解决特定问题的方法 。也就是说,学习算法之前,最起码得了解那些常用的数据结构,了解它们的特性和缺陷。
先刷二叉树,原因:
因为二叉树是最容易培养框架思维的,而且大部分算法技巧,本质上都是树的遍历问题 。
二叉树的解题框架 1 2 3 4 5 6 7 void traverse (TreeNode root) { traverse(root.left) traverse(root.right) }
比如:
LeetCode 124 题,难度 Hard,让你求二叉树中最大路径和,主要代码如下:
1 2 3 4 5 6 7 8 int ans = INT_MIN;int oneSideMax (TreeNode* root) { if (root == nullptr) return 0 ; int left = max(0 , oneSideMax(root->left)); int right = max(0 , oneSideMax(root->right)); ans = max(ans, left + right + root->val); return max(left, right) + root->val; }
LeetCode 105 题,难度 Medium,让你根据前序遍历和中序遍历的结果还原一棵二叉树,很经典的问题吧,主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TreeNode buildTree (int [] preorder, int preStart, int preEnd, int [] inorder, int inStart, int inEnd, Map<Integer, Integer> inMap) { if (preStart > preEnd || inStart > inEnd) return null ; TreeNode root = new TreeNode(preorder[preStart]); int inRoot = inMap.get(root.val); int numsLeft = inRoot - inStart; root.left = buildTree(preorder, preStart + 1 , preStart + numsLeft, inorder, inStart, inRoot - 1 , inMap); root.right = buildTree(preorder, preStart + numsLeft + 1 , preEnd, inorder, inRoot + 1 , inEnd, inMap); return root; }
不要看这个函数的参数很多,只是为了控制数组索引而已,本质上该算法也就是一个前序遍历。
LeetCode 99 题,难度 Hard,恢复一棵 BST,主要代码如下:
1 2 3 4 5 6 7 8 9 10 void traverse (TreeNode* node) { if (!node) return ; traverse(node->left); if (node->val < prev->val) { s = (s == NULL) ? prev : s; t = node; } prev = node; traverse(node->right); }
这不就是个中序遍历嘛,对于一棵 BST 中序遍历意味着什么,应该不需要解释了吧。
你看,Hard 难度的题目不过如此,而且还这么有规律可循,只要把框架写出来,然后往相应的位置加东西就行了,这不就是思路吗。
对于一个理解二叉树的人来说,刷一道二叉树的题目花不了多长时间。那么如果你对刷题无从下手或者有畏惧心理,不妨从二叉树下手,前 10 道也许有点难受;结合框架再做 20 道,也许你就有点自己的理解了;刷完整个专题,再去做什么回溯动规分治专题,你就会发现只要涉及递归的问题,都是树的问题 。
上面的总结是该UP主给出的解题思路,说实话,看懂是不可能看懂的,先继续往后面刷。
继续往后面copy没啥意思,反正就是突出这个框架的重要性。
这就是框架的力量,能够保证你在快睡着的时候,依然能写出正确的程序;就算你啥都不会,都能比别人高一个级别。
四、总结几句 数据结构的基本存储方式就是链式和顺序两种,基本操作就是增删查改,遍历方式无非迭代和递归。
刷算法题建议从「树」分类开始刷,结合框架思维,把这几十道题刷完,对于树结构的理解应该就到位了。这时候去看回溯、动规、分治等算法专题,对思路的理解可能会更加深刻一些。
后面在本章中还介绍了一些针对细节的解题框架,我决定谨遵作者的嘱托先刷树。
五、时间复杂度和空间复杂度 这里仅仅介绍时间复杂度和空间复杂度的入门部分,后续如果要用到详细的部分,我会再补充。
1. 说明
2.常见的时间复杂度量级
我们一般用“大O符号表示法”来表示时间复杂度:T(n) = O(f(n)) n是影响复杂度变化的因子,f(n)是复杂度具体的算法。
常数阶O(1)
线性阶O(n)
对数阶O(logN)
线性对数阶O(nlogN)
平方阶O(n²)
立方阶O(n³)
K次方阶O(n^k)
指数阶(2^n)
T(n) = O((f(n))
T(n) 表示代码执行的时间;n 表示数据规模的大小;f(n) 表示每行代码执行的次数总和。因为这是一个公式,所以用 f(n) 来表示。公式中的 O,表示代码的执行时间 T(n) 与 f(n) 表达式成正比。
当 n 很大时,你可以把它想象成 10000、100000。而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了,如果用大 O 表示法表示刚讲的那两段代码的时间复杂度,就可以表示为,推导过程:
1 2 T(n) = O(2n+2) -> T(n) = O(n) T(n) = O(2n2+2n+3) -> T(n) = O(n2)
常数阶O(1) 1 2 3 int a = 1 ;int b = 2 ;int c = 3 ;
我们假定每执行一行代码所需要消耗的时间为1个时间单位,那么以上3行代码就消耗了3个时间单位。那是不是这段代码的时间复杂度表示为O(n)呢 ?
其实不是的,*因为大O符号表示法并不是用于来真实代表算法的执行时间的, 它是用来表示代码执行时间的增长变化趋势的。*** *上面的算法并没有随着某个 变量*的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
线性阶O(n) 1 2 3 4 for (i = 1 ; i <= n; i++) { j = i; j++; }
第1行会执行1次,第2行和第3行会分别执行n次,总的执行时间也就是 2n + 1 次,那它的时间复杂度表示是 O(2n + 1) 吗? No ! 还是那句话:“**大O符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的** ”。 所以它的时间复杂度其实是O(n);
对数阶O(logN) 1 2 3 4 平方阶O(n²)int i = 1 ; while (i < n) { i = i * 2 ; }
可以看到每次循环的时候 i 都会乘2,那么总共循环的次数就是log2n,因此这个代码的时间复杂度为O(logn)。 这儿有个问题,为什么明明应该是O(log2n),却要写成O(logn)呢? 其实这里的底数对于研究程序运行效率不重要,写代码时要考虑的是数据规模n对程序运行效率的影响,常数部分则忽略,同样的,如果不同时间复杂度的倍数关系为常数,那也可以近似认为两者为同一量级的时间复杂度。
线性对数阶O(nlogN) 1 2 3 4 5 6 for (m = 1 ; m < n; m++) { i = 1 ; while (i < n) { i = i * 2 ; } }
线性对数阶w的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)。
平方阶O(n²) 1 2 3 4 5 6 for (x = 1 ; i <= n; x++){ for (i = 1 ; i <= n; i++) { j = i; j++; } }
把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
立方阶O(n³)、K次方阶O(n^k) 参考上面的O(n²) 去理解就好了,O(n³)相当于三层n循环,其它的类似。
O(m+n) 、O(m*n) 1 2 3 4 5 6 7 8 9 10 11 12 13 function cal (m, n) { var sum_1 = 0 ; var i = 1 ; for (; i < m; ++i) { sum_1 = sum_1 + i; } var sum_2 = 0 ; var j = 1 ; for (; j < n; ++j) { sum_2 = sum_2 + j; } return sum_1 + sum_2; }
从代码中可以看出,m 和 n 是表示两个数据规模。我们无法事先评估 m 和 n 谁的量级大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,上面代码的时间复杂度就是 O(m+n)。
针对这种情况,原来的加法法则就不正确了,我们需要将加法规则改为:T1(m) + T2(n) = O(f(m) + g(n))。但是乘法法则继续有效:T1(m)*T2(n) = O(f(m) * f(n))。
数据结构操作的复杂性
数据结构
连接
查找
插入
删除
数组
1
n
n
n
栈
n
n
1
1
队列
n
n
1
1
链表
n
n
1
1
哈希表
-
n
n
n
二分查找树
n
n
n
n
B树
log(n)
log(n)
log(n)
log(n)
红黑树
log(n)
log(n)
log(n)
log(n)
AVL树
log(n)
log(n)
log(n)
log(n)
数组排序算法的复杂性
名称
最优
平均
最坏
内存
稳定
冒泡排序
n
n^2
n^2
1
Yes
插入排序
n
n^2
n^2
1
Yes
选择排序
n^2
n^2
n^2
1
No
堆排序
n log(n)
n log(n)
n log(n)
1
No
归并排序
n log(n)
n log(n)
n log(n)
n
Yes
快速排序
n log(n)
n log(n)
n^2
log(n)
No
希尔排序
n log(n)
取决于差距序列
n (log(n))^2
1
No
时间复杂度分析 三个使用分析方法:
只关注循环执行次数最多的的一段代码
大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以,我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。
1 2 3 4 5 6 7 8 1 function cal(n) { 2 var sum = 0; 3 var i = 1; 4 for (; i <= n; ++i) { 5 sum = sum + i; 6 } 7 return sum; 8 }
2.3行代码都是常量级别的执行时间,与n的大小无关,所以对于复杂度并没有影响。循环执行次数最多的是滴4、5行代码,所以这块代码要重点分析。那两行代码执行了n次,所以总的时间复杂度就是O(n)
加法法则:总复杂度等于量级最大的那段代码的复杂度
综合这三段代码的时间复杂度(分别是O(1), O(n), O(n2)),我们取其中最大的量级。所以,整段代码的时间复杂度就为 O(n2)。也就是说:总的时间复杂度就等于量级最大的那段代码的时间复杂度。那我们将这个规律抽象成公式就是:
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积,类似嵌套循环的,都是用乘法来处理
大O
大O描述的是算法的运行时间和输入数据之间的关系
以下是一些最常用的 大O标记法 列表以及它们与不同大小输入数据的性能比较。
大O标记法
计算10个元素
计算100个元素
计算1000个元素
O(1)
1
1
1
O(log N)
3
6
9
O(N)
10
100
1000
O(N log N)
30
600
9000
O(N^2)
100
10000
1000000
O(2^N)
1024
1.26e+29
1.07e+301
O(N!)
3628800
9.3e+157
4.02e+2567
3.常见空间复杂度计算 空间复杂度 O(1) 如果算法执行所需要的临时空间不随着某个变量n的大小而变化 ,即此算法空间复杂度为一个常量 ,可表示为 O(1)。
1 2 3 4 5 int i = 1 ;int j = 2 ;++i; j++; int m = i + j;
代码中的 i、j、m 所分配的空间都不随着处理数据量变化 ,因此它的空间复杂度 S(n) = O(1)。
空间复杂度 O(n) 1 2 3 4 5 int [] m = new int [n]for (i = 1 ; i <= n; ++i) { j = i; j++; }
空间复杂度分析
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。
类比一下,空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。
最好最坏情况时间复杂度 最简单的例子,如果我们在某个数组中查找某个元素,这个元素可能在各个位置,然后我们一旦找到了这个元素立即跳出循环,这段代码的时间复杂度还是 O(n) 吗?
我们需要引入三个概念:最好情况时间复杂度、最坏情况时间复杂度和平均情况时间复杂度。
总之分析下来,最好情况时间复杂度没有啥意义,最坏情况时间复杂度和平均情况时间貌似区别不大?后续有新的理解会更新上来。
均摊时间复杂度
大部分情况下,我们并不需要区分最好、最坏、平均三种复杂度。平均复杂度只在某些特殊情况下才会用到,而均摊时间复杂度应用的场景比它更加特殊、更加有限。找出所有的输入情况及相应的发生概率,然后再计算加权平均值。
对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。
Ex. 某段代码实现了一个往数组中插入数据的功能。当数组满了之后,也就是代码中的 count == array.length 时,我们用 for 循环遍历数组求和,并清空数组,将求和之后的 sum 值放到数组的第一个位置,然后再将新的数据插入。但如果数组一开始就有空闲空间,则直接将数据插入数组。
那这段代码的时间复杂度是多少呢?你可以先用我们刚讲到的三种时间复杂度的分析方法来分析一下。
最理想的情况下,数组中有空闲空间,我们只需要将数据插入到数组下标为 count 的位置就可以了,所以最好情况时间复杂度为 O(1)。最坏的情况下,数组中没有空闲空间了,我们需要先做一次数组的遍历求和,然后再将数据插入,所以最坏情况时间复杂度为 O(n)。
那平均时间复杂度是多少呢?答案是 O(1)。我们还是可以通过前面讲的概率论的方法来分析。
假设数组的长度是 n,根据数据插入的位置的不同,我们可以分为 n 种情况,每种情况的时间复杂度是 O(1)。除此之外,还有一种“额外”的情况,就是在数组没有空闲空间时插入一个数据,这个时候的时间复杂度是 O(n)。而且,这 n+1 种情况发生的概率一样,都是 1/(n+1)。所以,根据加权平均的计算方法,我们求得的平均时间复杂度就是:
O(1)
至此为止,前面的最好、最坏、平均时间复杂度的计算,理解起来应该都没有问题。但是这个例子里的平均复杂度分析其实并不需要这么复杂,不需要引入概率论的知识。这是为什么呢?我们先来对比一下这个 insert() 的例子和前面那个 find() 的例子,你就会发现这两者有很大差别。
首先,find() 函数在极端情况下,复杂度才为 O(1)。但 insert() 在大部分情况下,时间复杂度都为 O(1)。只有个别情况下,复杂度才比较高,为 O(n)。这是 insert()第一个区别于 find() 的地方。
我们再来看第二个不同的地方。对于 insert() 函数来说,O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。
所以,针对这样一种特殊场景的复杂度分析,我们并不需要像之前讲平均复杂度分析方法那样,找出所有的输入情况及相应的发生概率,然后再计算加权平均值。
针对这种特殊的场景,我们引入了一种更加简单的分析方法:摊还分析法,通过摊还分析得到的时间复杂度我们起了一个名字,叫均摊时间复杂度。
那究竟如何使用摊还分析法来分析算法的均摊时间复杂度呢?
我们还是继续看在数组中插入数据的这个例子。每一次 O(n) 的插入操作,都会跟着 n-1 次 O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1)。这就是均摊分析的大致思路。
均摊时间复杂度我没有去深刻理解,没必要看这个冷门知识点看睡着了。
对数据规模有一个概念和分析 想要在1s内解决问题:
O(n2)的算法可以处理大约10^4级别的数据
O(n)的算法可以处理大约10^8级别的数据
O(nlogn)的算法可以处理大约10^7级别的数据
保险起见,在实际处理过程中最好降一个级
4.递归算法复杂度分析 鉴于我对递归的使用并不熟练,这个坑放在这,日后来填。
5.避免复杂度的震荡 常见场景:
java数组动态扩容
假设我们现在有一个数组,这个数组的容量为n,并且现在也装满了元素,那么现在我们再调用一下addLast操作,显然在添加一个新的元素的时候会需要扩容(扩容会耗费O(N)的时间),之后我们马上进行removeLast操作(根据我们之前的逻辑,在上一个操作里通过扩容,容量变为了2n,在我们删除1个元素之后,元素又变为了n = 2n/2,根据我们代码中的逻辑,会触发缩容的操作,同样耗费了O(n)的时间);那么我们如果再addLast、removeLast…等相继依次操作。
对于addLast和removeLast来说,都是每隔n次操作都会触发resize,而不会每次都触发 但是现在我们制造了一种情景:同时看addLast和removeLast的时候,每一次都会耗费O(n)的复杂度,那么这就是复杂度的震荡
resize的复杂度分析——出现复杂度震荡的原因及解决方案
removeLast时resize过于着急(采用了Eager的策略: 一旦我们的元素变为当前容积的1/2的时候,我们马上就把当前的容积也缩容为1/2) 解决方案: Lazy (在线段树中,也会用到类似的思路) 当元素变为当前容积的1/2时,不着急把当前容积缩容,而是等等;如果后面一直有删除操作的话,当删除元素到整个数组容积的1/4时,那么这样看来我们的数组确实用不了这么大的容积,此时我们再来进行缩容,缩容整个数组的1/2(这样,即便我们要添加元素,也不需要马上触发扩容操作)
当 size == capacity / 4时,才将capacity减半!
这里只谈了如何避免,却没有说为什么要避免。
6. 复杂度分析的4个概念 一、复杂度分析的4个概念
1.最坏情况时间复杂度:代码在最理想情况下执行的时间复杂度。
2.最好情况时间复杂度:代码在最坏情况下执行的时间复杂度。
3.平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示。
4.均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。
二、为什么要引入这4个概念?
1.同一段代码在不同情况下时间复杂度会出现量级差异,为了更全面,更准确的描述代码的时间复杂度,所以引入这4个概念。
2.代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下,是不需要区别分析它们的。
三、如何分析平均、均摊时间复杂度?
1.平均时间复杂度
代码在不同情况下复杂度出现量级差别,则用代码所有可能情况下执行次数的加权平均值表示。
2.均摊时间复杂度
两个条件满足时使用:1)代码在绝大多数情况下是低级别复杂度,只有极少数情况是高级别复杂度;2)低级别和高级别复杂度出现具有时序规律。均摊结果一般都等于低级别复杂度。
7.常用排序算法
排序晚点再来看
《算法第四版》是肯定要看的,到时候再来完备整个复杂度体系。
六、实操 在经过了上面得简单梳理之后,没啥好说得,开始自己的实际操作。
链表 链表(Linked List)数据结构概览 链表(Linked List)是线性表的一种(线性表包含顺序表与链表),通过指针将一系列位于不连续的内存空间中的元素连接起来,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理,但也失去了快速随机存取的优点,同时增大了内存开销(存储指针域)。
链表数据结构由一连串节点(Node)组成,每个节点包含数据域(Data Fields)和一或两个用来指向上一个/或下一个节点位置的指针域(Pointer Fields)。链表可以方便地插入或移除表中任意位置的节点,但是随机存取效率不高。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
链表(Linked List)数据结构操作接口 我们将要使用 Java 语言手写一枚链表数据结构。首先,明确链表数据结构所具有的操作接口方法。
接口方法
解释说明
void addFirst(E element)
向链表头部添加一个新的元素。
void addLast(E element)
向链表尾部添加一个新的元素。
E removeFirst()
移除链表头部第一个元素。
E removeLast()
移除链表尾部最后一个元素。
E getFirst()
返回链表头部第一个元素。
E getLast()
返回链表尾部最后一个元素。
boolean contains(E element)
检查链表中是否包含指定元素。
E insert(int index, E element)
向链表指定索引位置插入新元素。
E get(int index)
返回链表中指定索引的元素。
E set(int index, E element)
为链表中指定索引的元素设新值。
E remove(int index)
移除链表中指定索引的元素。
boolean remove(E element)
移除链表中指定的元素。
int indexOf(E element)
返回指定元素所在链表的索引,元素不存在则返回-1
,若存在多个相同元素,则返回第一次出现的索引下标。
int size()
返回链表存储元素数量。
boolean isEmpty()
检查链表是否为空。
void clear()
清空链表。
String toString()
返回链表的字符串形式。
单向链表(Single Linked List) 我们使用 Java 语言实现一枚简单的单向链表 。顾名思义,单向链表只能做单向遍历(头节点 -> 尾节点),因为单向链表的节点(Node)只包含数据域和一个指针域(指向下一个节点)。
我们来定义出单链表中节点(Node)的数据结构,使用泛型类提高节点存储数据的灵活性。节点数据结构包含构造方法 ,两个私有变量:数据域和指针域 ,及其对应的Getter/Setter
公开方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Node <E > { private E elem; private Node<E> next; public Node (E element, Node<E> next) { this .elem = element; this .next = next; } public Node () { this (null , null ); } public void setElem (E element) { this .elem = element; } public E getElem () { return this .elem; } public void setNext (Node<E> next) { this .next = next; } public Node<E> getNext () { return this .next; } }
考虑单链表数据结构:单链表包含一枚头节点(Head),头结点不存储数据,而是指向第一个实际存储数据的节点;尾节点可以被定义为指针域为null
的最后一枚节点。
这边先把单向链表的完整代码放出来,后面再放出讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 import java.util.NoSuchElementException;public class SingleLinkedList <E > { private Node<E> head; public SingleLinkedList () { this .head = new Node<E>(); } public void addFirst (E element) { Node<E> node = new Node<E>(element, null ); node.setNext(head.getNext()); head.setNext(node); } public void addLast (E element) { Node<E> node = new Node<E>(element, null ); Node<E> tail = head; while (tail.getNext() != null ) { tail = tail.getNext(); } tail.setNext(node); } public E getFirst () throws NoSuchElementException { if (head.getNext() == null ) { throw new NoSuchElementException(); } return head.getNext().getElem(); } public E getLast () throws NoSuchElementException { Node<E> tail = head; while (tail.getNext() != null ) { tail = tail.getNext(); } if (tail == head) { throw new NoSuchElementException(); } return tail.getElem(); } public int size () { int cnt = 0 ; for (Node<E> n = head; n.getNext() != null ; n = n.getNext()) { ++cnt; } return cnt; } public boolean isEmpty () { return head.getNext() == null ? true : false ; } public boolean _isEmpty () { return this .size() > 0 ? false : true ; } public E removeFirst () throws NoSuchElementException { if (this .isEmpty()) { throw new NoSuchElementException(); } Node<E> first = head.getNext(); head.setNext( first.getNext() ); return first.getElem(); } public E removeLast () throws NoSuchElementException { if (this .isEmpty()) { throw new NoSuchElementException(); } Node<E> prev = head; while (prev.getNext().getNext() != null ) { prev = prev.getNext(); } Node<E> last = prev.getNext(); prev.setNext(null ); return last.getElem(); } public boolean contains (E element) { for (Node<E> current = head.getNext(); current != null ; current = current.getNext()) { if (element.equals(current.getElem())) { return true ; } } return false ; } public int indexOf (E element) { int index = 0 ; for (Node<E> current = head.getNext(); current != null ; current = current.getNext()) { if (element.equals(current.getElem())) { return index; } ++index; } return -1 ; } public E get (int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } Node<E> n = head.getNext(); while (index > 0 ) { n = n.getNext(); --index; } return n.getElem(); } public E set (int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } Node<E> n = head.getNext(); while (index > 0 ) { n = n.getNext(); --index; } E oldValue = n.getElem(); n.setElem(element); return oldValue; } public E remove (int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } index -= 1 ; Node<E> prev = head; while (index >= 0 ) { prev = prev.getNext(); --index; } Node<E> current = prev.getNext(); Node<E> next = current.getNext(); prev.setNext(next); return current.getElem(); } public boolean remove (E element) { int index = indexOf(element); return index == -1 ? false : element.equals(remove(index)); } public E insert (int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index > size()) { throw new IndexOutOfBoundsException(); } index -= 1 ; Node<E> prev = head; while (index >= 0 ) { prev = prev.getNext(); --index; } Node<E> current = prev.getNext(); Node<E> node = new Node<E>(element, null ); node.setNext(current); prev.setNext(node); return current == null ? null : current.getElem(); } public void clear () { while (head != null ) { Node<E> n = head; head = head.getNext(); n.setElem(null ); n.setNext(null ); } head = new Node<E>(); } @Override public String toString () { StringBuilder sb = new StringBuilder(); sb.append('[' ); for (Node<E> current = head.getNext(); current != null ; current = current.getNext()) { sb.append(current.getElem().toString()); sb.append(", " ); } sb.append(']' ); return sb.toString(); } }
分析完了整个流程,先来推导一遍单向链表的结点Node
数据域
指针域
构造方法
无参构造方法
getter & setter
在双向链表的DLNode里面还多一个内容就是多了一个向前的指针域。
下面是单向链表的详细分析
我们使用 Java 语言实现一枚简单的单向链表 。顾名思义,单向链表只能做单向遍历(头节点 -> 尾节点),因为单向链表的节点(Node)只包含数据域和一个指针域(指向下一个节点)。
我们来定义出单链表中节点(Node)的数据结构,使用泛型类提高节点存储数据的灵活性。节点数据结构包含构造方法 ,两个私有变量:数据域和指针域 ,及其对应的Getter/Setter
公开方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Node<E> { /** * 数据域 */ private E elem; /** * 指针域 */ private Node<E> next; /** * 构造方法 */ public Node(E element, Node<E> next) { this.elem = element; this.next = next; } /** * 无参构造方法 */ public Node() { this(null, null); } /* Setter & Getter */ public void setElem(E element) { this.elem = element; } public E getElem() { return this.elem; } public void setNext(Node<E> next) { this.next = next; } public Node<E> getNext() { return this.next; } }
代码清单:单链表节点(Node)
我们考虑单链表数据结构:单链表包含一枚头节点(Head),头结点不存储数据,而是指向第一个实际存储数据的节点;尾节点可以被定义为指针域为null
的最后一枚节点。
单链表的构造方法即为单链表初始化:构造一枚数据域和指针域均为空的头节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SingleLinkedList<E> { /** * 链表头节点 */ private Node<E> head; /** * 构造方法:创建空链表 * @param void */ public SingleLinkedList() { this.head = new Node<E>(); } }
代码清单:单链表(Single Linked List)结构
单链表addFirst()
方法向链表头部添加一个新的元素,插入的新元素总是位于链表头部(头节点指向的节点),这种插入元素的方式称为头插法 ,通过以下3步,即可完成向链表头部插入元素。
根据新元素构建一枚新节点
将新节点指针域置为头节点指向的节点
头节点指向新节点
1 2 3 4 5 6 7 8 9 10 11 12 ... /** * 向链表头部添加一个新的元素(头插法)。 * @param element * @return void */ public void addFirst(E element) { Node<E> node = new Node<E>(element, null); node.setNext(head.getNext()); head.setNext(node); } ...
代码清单:单链表addFirst()
头插法
单链表addLast()
方法向链表尾部添加一个新的元素,插入的新元素总是位于链表尾部(指针域为空的尾节点),这种插入元素的方式称为尾插法 ,通过以下4步,即可完成向链表尾部插入元素。
根据新元素构建一枚新节点
将新节点指针域置空
遍历链表找到尾节点(指针域为空的节点)
尾节点指向新节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... /** * 向链表尾部添加一个新的元素(尾插法)。 * @param element * @return void */ public void addLast(E element) { Node<E> node = new Node<E>(element, null); Node<E> tail = head; while (tail.getNext() != null) { tail = tail.getNext(); } tail.setNext(node); } ...
代码清单:单链表addLast()
尾插法
理解了addFirst()
与addLast()
方法实现后,实现getFirst()
与getLast()
方法就非常简单了,返回头/尾节点数据域中存储的数据即可,但是需要考虑到链表为空 的情况:直接抛出NoSuchElementException
异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ... /** * 取得链表头部第一个元素,链表为空则抛出异常。 * @param void * @return First element of {@code LinkedList}. * @throws NoSuchElementException if this {@code LinkedList} is empty. */ public E getFirst() throws NoSuchElementException { if (head.getNext() == null) { throw new NoSuchElementException(); } return head.getNext().getElem(); } /** * 取得链表尾部最后一个元素,链表为空则抛出异常。 * @param void * @return Last element of {@code LinkedList}. * @throws NoSuchElementException if this {@code LinkedList} is empty. */ public E getLast() throws NoSuchElementException { Node<E> tail = head; while (tail.getNext() != null) { tail = tail.getNext(); } if (tail == head) { throw new NoSuchElementException(); } return tail.getElem(); } ...
代码清单:单链表getFirst()
与getLast()
方法实现
上述实现代码段其实已经涉及到了isEmpty()
与size()
接口方法,现在我们来实现这两个方法。
size()
:遍历链表元素并计数,计算链表存储元素数量。
isEmpty()
:判断链表是否为空,可以借用size()
方法(链表存储元素数量为0
则表示链表为空),也可以直接判断头结点指针域是否为空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ... /** * 计算链表存储元素数量。 * @param void * @return Size of elements in {@code LinkedList}. */ public int size() { int cnt = 0; for (Node<E> n = head; n.getNext() != null; n = n.getNext()) { ++cnt; } return cnt; } /** * 检查链表是否为空。 * @param void * @return Boolean {@code true} or {@code false}. */ public boolean isEmpty() { return head.getNext() == null ? true : false; } /** * 检查链表是否为空。 * @param void * @return Boolean {@code true} or {@code false}. */ public boolean _isEmpty() { return this.size() > 0 ? false : true; } ...
代码清单:单链表isEmpty()
与size()
方法实现
单链表removeFirst()
方法返回并移除链表第一个元素,通过以下步骤完成。
检查链表是否为空
获取链表首元素节点
头节点指向第二元素节点(首元素节点的下一个节点)
返回首元素节点数据域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... /** * 返回并移除链表头部第一个元素。 * @param void * @return First element of this {@code Linked List}. * @throws NoSuchElementException */ public E removeFirst() throws NoSuchElementException { if (this.isEmpty()) { throw new NoSuchElementException(); } Node<E> first = head.getNext(); head.setNext( first.getNext() ); return first.getElem(); } ...
代码清单:单链表removeFirst()
方法实现
单链表removeLast()
方法返回并移除链表最后一个元素,通过以下步骤完成。
检查链表是否为空
获取链表倒数第二元素节点(尾元素前一节点)
获取链表尾元素节点
将链表倒数第二元素节点指针域置空
返回尾元素节点数据域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... /** * 返回并移除链表尾部最后一个元素。 * @param void * @return Last element of this {@code Linked List}. * @throws NoSuchElementException */ public E removeLast() throws NoSuchElementException { if (this.isEmpty()) { throw new NoSuchElementException(); } Node<E> prev = head; while (prev.getNext().getNext() != null) { prev = prev.getNext(); } Node<E> last = prev.getNext(); prev.setNext(null); return last.getElem(); } ...
代码清单:单链表removeLast()
方法实现
我们来考虑单链表的contains(E e)
方法,检查链表中是否包含指定元素。我们使用equals()
比较方法判断两个元素是否相等,因此,存入链表的数据类型必须实现equals()
比较方法。
contains(E e)
方法具体实现为:遍历链表,比较每个元素,找到即返回true
,找不到则返回false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... /** * 检查链表中是否包含目标元素, * 元素相等使用 {@code o.equals(obj)} 判断。 * @param element * @return Boolean */ public boolean contains(E element) { for (Node<E> current = head.getNext(); current != null; current = current.getNext()) { if (element.equals(current.getElem())) { return true; } } return false; } ...
代码清单:单链表contains()
方法实现
链表使用链式存储结构,存储的数据在内存空间中不连续,不能做到像数组一般高效的直接随即访问。我们来实现indexOf(E e)
方法,返回指定元素所在链表的索引,元素不存在则返回-1
,若存在多个相同元素,则返回第一次出现的索引。注意,我们将链表索引下标从0
计起,与数组保持一致。indexOf()
方法实现与contains()
方法相似,加入一枚索引下标计数器即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... /** * 返回指定元素所在链表的索引。 * @param element * @return The index of element in {@code LinkedList}, * return {@code -1} if element does not found. */ public int indexOf(E element) { int index = 0; for (Node<E> current = head.getNext(); current != null; current = current.getNext()) { if (element.equals(current.getElem())) { return index; } ++index; } return -1; } ...
代码清单:单链表indexOf()
方法实现
我们使用get(int index)
方法获取链表中指定索引的元素,如果索引越界,抛出IndexOutOfBoundsException
异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... /** * 获取链表指定索引的元素。 * @param index * @return element * @throws IndexOutOfBoundsException */ public E get(int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } Node<E> n = head.getNext(); while (index > 0) { n = n.getNext(); --index; } return n.getElem(); } ...
代码清单:单链表get()
方法实现
我们使用set(int index, E element)
方法为链表中指定索引位置的元素设新值,如果索引越界,抛出IndexOutOfBoundsException
异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ... /** * 为链表指定索引位置的元素设新值。 * @param index * @param element * @return Previous element in the index. * @throws IndexOutOfBoundsException */ public E set(int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } Node<E> n = head.getNext(); while (index > 0) { n = n.getNext(); --index; } E oldValue = n.getElem(); n.setElem(element); return oldValue; } ...
代码清单:单链表set()
方法实现
我们使用remove(int index)
方法移除链表中指定索引下标位置的元素,具体步骤如下。如果索引下标越界,则抛出IndexOutOfBoundsException
异常。
找到链表中指定索引下标的待移除节点及其前驱、后继节点
将指定索引下标节点的前后节点使用指针连接起来
返回移除节点数据域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ... /** * 移除链表指定索引下标元素。 * @param index * @return Removed element * @throws IndexOutOfBoundsException */ public E remove(int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } index -= 1; Node<E> prev = head; while (index >= 0) { prev = prev.getNext(); --index; } Node<E> current = prev.getNext(); Node<E> next = current.getNext(); prev.setNext(next); return current.getElem(); } ...
代码清单:单链表remove(int index)
方法实现
移除元素remove()
方法还有另一种形式:boolean remove(E element)
,移除链表中的指定元素。我们可以使用indexOf(E element)
配合remove(int index)
实现,先获取指定元素在链表中的索引下标,再移除掉,操作成功返回true
,如果不存在目标元素,则返回false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 ... /** * 移除链表指定元素, * 操作成功返回{@code true},不存在目标元素则返回{@code false}。 * @param element * @return Boolean */ public boolean remove(E element) { int index = indexOf(element); return index == -1 ? false : element.equals(remove(index)); } ...
代码清单:单链表remove(E element)
方法实现
链表数据结构的优势在于其插入元素的开销比起数组要小很多,我们来实现链表插入元素insert()
方法,具体步骤如下所示。如果索引下标越界,则抛出IndexOutOfBoundsException
异常。
找到链表中指定索引下标节点(当前节点)及其前驱节点
创建一枚新节点
新节点指向当前节点
前驱节点指向新节点
返回当前节点数据域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ... /** * 向列表指定位置插入一个新的元素。 * @param index * @param element * @return Previous element * @throws IndexOutOfBoundsException */ public E insert(int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index > size()) { throw new IndexOutOfBoundsException(); } index -= 1; Node<E> prev = head; while (index >= 0) { prev = prev.getNext(); --index; } Node<E> current = prev.getNext(); Node<E> node = new Node<E>(element, null); node.setNext(current); prev.setNext(node); return current == null ? null : current.getElem(); } ...
代码清单:单链表insert()
方法实现
我们为链表提供一枚clear()
方法,用于清空链表元素。由于 Java 语言的自动垃圾回收机制,直接将头节点(Head)置空即可表示清空链表,不用担心内存泄露问题,但是为了帮助垃圾收集器更好地做内存回收工作,这里我们选择显式清空每一个节点 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... /** * 清空链表。 * @param void * @return void */ public void clear() { while (head != null) { Node<E> n = head; head = head.getNext(); n.setElem(null); n.setNext(null); } head = new Node<E>(); } ...
代码清单:单链表clear()
方法实现
最后,我们来覆写链表toString()
方法,更加方便地查看链表元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('['); for (Node<E> current = head.getNext(); current != null; current = current.getNext()) { sb.append(current.getElem().toString()); sb.append(", "); } sb.append(']'); return sb.toString(); } ...
代码清单:单链表toString()
方法实现
双向链表(Double Linked List) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 import java.lang.IndexOutOfBoundsException;import java.util.NoSuchElementException;public class DoubleLinkedList <E > { private DLNode<E> head; private DLNode<E> tail; public DoubleLinkedList () { this .head = new DLNode<E>(); this .tail = new DLNode<E>(); head.setNext(tail); tail.setPrev(head); } public E insert (int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index > size()) { throw new IndexOutOfBoundsException(); } DLNode<E> current = head; while (index >= 0 ) { current = current.getNext(); --index; } DLNode<E> node = new DLNode<E>(element, null , null ); node.setNext(current); node.setPrev(current.getPrev()); current.getPrev().setNext(node); current.setPrev(node);; return current.getNext() == null ? null : current.getElem(); } public boolean remove (E element) { int index = indexOf(element); return index == -1 ? false : element.equals(remove(index)); } public E remove (int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } DLNode<E> node = head.getNext(); while (index > 0 ) { node = node.getNext(); --index; } node.getPrev().setNext(node.getNext()); node.getNext().setPrev(node.getPrev()); node.setPrev(null ); node.setNext(null ); return node.getElem(); } public E set (int index, E element) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } DLNode<E> node = head.getNext(); while (index > 0 ) { node = node.getNext(); --index; } E oldElem = node.getElem(); node.setElem(element); return oldElem; } public E get (int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException(); } DLNode<E> node = head.getNext(); while (index > 0 ) { node = node.getNext(); --index; } return node.getElem(); } public int indexOf (E element) { int index = 0 ; for (DLNode<E> current = head.getNext(); current.getNext() != null ; current = current.getNext()) { if (element.equals(current.getElem())) { return index; } ++index; } return -1 ; } public boolean contains (E element) { for (DLNode<E> current = head.getNext(); current.getNext() != null ; current = current.getNext()) { if (element.equals(current.getElem())) { return true ; } } return false ; } public E removeLast () throws NoSuchElementException { if (isEmpty()) { throw new NoSuchElementException(); } DLNode<E> node = tail.getPrev(); node.getPrev().setNext(tail); tail.setPrev(node.getPrev()); node.setPrev(null ); node.setNext(null ); return node.getElem(); } public E removeFirst () throws NoSuchElementException { if (isEmpty()) { throw new NoSuchElementException(); } DLNode<E> node = head.getNext(); node.getNext().setPrev(head); head.setNext(node.getNext()); node.setPrev(null ); node.setNext(null ); return node.getElem(); } public void addFirst (E element) { DLNode<E> node = new DLNode<E>(element, null , null ); node.setPrev(head); node.setNext( head.getNext() ); head.setNext(node); head.getNext().setPrev(node); } public void addLast (E element) { DLNode<E> node = new DLNode<E>(element, null , null ); node.setPrev(tail.getPrev()); node.setNext(tail); tail.getPrev().setNext(node); tail.setPrev(node); } public E getFirst () throws NoSuchElementException { if (isEmpty()) { throw new NoSuchElementException(); } return head.getNext().getElem(); } public E getLast () throws NoSuchElementException { if (isEmpty()) { throw new NoSuchElementException(); } return tail.getPrev().getElem(); } public int size () { int cnt = 0 ; for (DLNode<E> n = head.getNext(); n.getNext() != null ; n = n.getNext()) { ++cnt; } return cnt; } public boolean isEmpty () { return this .size() > 0 ? false : true ; } public void clear () { while (head.getNext() != null ) { DLNode<E> current = head; head = head.getNext(); current.setElem(null ); current.setPrev(null ); current.setNext(null ); } head = new DLNode<E>(); tail = new DLNode<E>(); head.setNext(tail); tail.setPrev(head); } @Override public String toString () { StringBuilder sb = new StringBuilder(); sb.append('[' ); for (DLNode<E> current = head.getNext(); current.getNext() != null ; current = current.getNext()) { sb.append(current.getElem().toString()); sb.append(", " ); } sb.append(']' ); return sb.toString(); } }
找出两个链表的交点
Intersection of Two Linked Lists (Easy)
Leetcode / 力扣
例如以下示例中 A 和 B 两个链表相交于 c1:
1 2 3 4 5 A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3
但是不会出现以下相交的情况,因为每个节点只有一个 next 指针,也就只能有一个后继节点,而以下示例中节点 c 有两个后继节点。
1 2 3 4 5 A: a1 → a2 d1 → d2 ↘ ↗ c ↗ ↘ B: b1 → b2 → b3 e1 → e2
要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a
。
给出来的标准答案:
1 2 3 4 5 6 7 8 public ListNode getIntersectionNode (ListNode headA, ListNode headB) { ListNode l1 = headA, l2 = headB; while (l1 != l2) { l1 = (l1 == null ) ? headB : l1.next; l2 = (l2 == null ) ? headA : l2.next; } return l1; }
这个代码只能跑一跑基本类型的链表,真正要运行的话还有一些坑要踩
当Node泛型是String的时候
1 2 3 4 5 6 7 8 9 private static Node getIntersectionNode (Node headA, Node headB) { Node l1 = headA, l2 = headB; while (l1.getElem() != l2.getElem()) { l1 = (l1 == null ) ? headB : l1.getNext(); l2 = (l2 == null ) ? headA : l2.getNext(); } return l1; }
这样的代码是可以通过的,但是这是特例,因为
equals不Override的情况下总是调用==,==在基本数据类型里面是比较值,在别的类型里面是比较内存地址,复写的情况下会调用equals方法,String类型不是基本数据类型,但是使用String a=”a”的时候,默认使用的不是堆内存,使用的常量池,所以使用 == 会判定true,使用new的话会使用堆内存所以判定false
举例一个普通的pojo类,需要实现一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Person { private String name; public Person (String name) { this .name = name; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + '}' ; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Person person = (Person) o; return getName().equals(person.getName()); } @Override public int hashCode () { return getName().hashCode(); } }
然后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static Node getIntersectionNode (Node<Person> headA, Node<Person> headB) { Node<Person> l1 = headA, l2 = headB; Person NULL = new Person("NULL" ); Node<Person> l3 = new Node<Person>(NULL,headA); while (!(l1.getElem().equals(l2.getElem()))) { l1 = (l1 == l3) ? headB : l1.getNext(); if (l1 == null ) l1 = l3; l2 = (l2 == null ) ? headA : l2.getNext(); if (l2 == null ) l2 = l3; } return l1; }
其中引入了一个l3
,引入的原因是如果两个链表不一样长的时候会出现某个链表先遍历完毕,然后这个时候null.getElem()
就会报错。
这个问题有个变种,就是判断两个链表是否存在交点,直接判断LinkedListA
和LinkedListB
两个链表最后一个Node元素是否相等即可。
解到这里,我突然感觉我走偏了,我这里链表相同的条件出了点问题,可能要判断的其实是Node是否相同而已,直接判断地址即可。
也就是说第一段代码,没有问题。
我仅仅通过addLast
是没有办法创建出两个有用相同节点的链表的,如果要模拟那种链表的话,需要三个链表,把第一个链表的Last节点的指针指向第三个链表的头结点即可。造成这种乌龙的原因是因为对一些底层的概念忘得差不多了。
这题同时还有一个变种就是是否存在交点。
如果仅仅问是否存在交点的话,最简单的办法就是直接把两个链表的最后一个节点分别拿出来判断,如果两个节点相等(地址相等)的话,和存在交点是互为充要条件的。
其实解法很多,有一种取巧解法先把两个链表的size拿出来,因为是有公共部分的,公共部分的长度肯定相等,于是乎把长的链表多出来的那个部分忽略,假设长链表的size是m,短链表的size是n,m-n得到多出来的部分,长链表从m-n+1的位置开始,锻炼表从head位置开始,在同一个循环里面遍历,如果两者相等了,那么就说明有交点。
有一种经典的解法可以把这个问题转化一下,把链表alist
的尾节点挂到blist
的头节点上面,于是问题变成了探究blist
是否存在环,如果存在的话,就说明两个链表存在着交点。
这里前面两种都相对简单,仅仅探讨第三种情况。
使用两个指针从头节点开始遍历,一个速度是1,一个速度是2,如果链表存在环的,那么速度块的指针总是会给速度慢的套圈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static boolean judgeIfTheIntersectionExists (Node head) { Node slow = head; Node fast = head; while (fast!=null && fast.getNext() != null ){ slow = slow.getNext(); fast = fast.getNext().getNext(); if (slow == fast) return true ; } return false ; }
如此,即可。
反转链表 反转链表介绍两种办法,一种是递归,一种是头插入法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 import com.hugh.datastructure.LinkedListUtils;import com.hugh.datastructure.dllist.DLNode;import com.hugh.datastructure.dllist.MyDoubleLinkedList;import com.hugh.datastructure.linkedlist.MySingleLinkedList;import com.hugh.datastructure.linkedlist.Node;import com.hugh.datastructure.linkedlist.pojo.Person;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class ReverseLinkedList { private static Logger logger = LogManager.getLogger(ReverseLinkedList.class.getName()); public static void main (String[] args) { MySingleLinkedList list = LinkedListUtils.generateSingleLinkList(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ); System.out.println(list); Node node = reverseList(list.getFirst()); while (node != null ) { System.out.println(node.getElem()); node = node.getNext(); } } public static Node reverseList (Node head) { logger.info("step1:" + head + " " + head.getElem()); if (head == null || head.getNext() == null ) { return head; } Node next = head.getNext(); Node newhead = reverseList(next); logger.info("newhead:" + newhead + " " + newhead.getElem()); next.setNext(head); head.setNext(null ); return newhead; } public static Node reverseIterativeltly (Node head) { Node pre = null ; Node next = null ; while (head != null ) { next = head.getNext(); head.setNext(pre); pre = head; head = next; } return pre; } }
归并两个有序的链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import com.hugh.datastructure.LinkedListUtils;import com.hugh.datastructure.linkedlist.MySingleLinkedList;import com.hugh.datastructure.linkedlist.Node;import com.hugh.datastructure.linkedlist.SingleLinkedList;public class MergeTwoSortedLists { public static void main (String[] args) { MySingleLinkedList<Integer> listA = LinkedListUtils.generateSingleLinkList(2 , 4 , 6 , 7 , 10 , 13 , 15 , 17 , 20 ); MySingleLinkedList<Integer> listB = LinkedListUtils.generateSingleLinkList(1 , 3 , 5 , 7 , 100 ); Node node = mergeTwoLists(listA.getFirst(), listB.getFirst()); while (node != null ) { System.out.println(node.getElem()); node = node.getNext(); } } private static Node mergeTwoLists (Node<Integer> l1, Node<Integer> l2) { if (l1 == null ) return l2; if (l2 == null ) return l1; if (l1.getElem() <= l2.getElem()) { l1.setNext(mergeTwoLists(l1.getNext(),l2)); return l1; } else { l2.setNext(mergeTwoLists(l1,l2.getNext())); return l2; } } }
从有序链表中删除重复节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static Node<Integer> deleteDuplicatesThird (Node<Integer> head) { if (head == null || head.getNext() == null ) return head; head.setNext(deleteDuplicatesThird(head.getNext())); return (head.getElem() == head.getNext().getElem()) ? head.getNext() : head; }
删除链表的倒数第 n 个节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private static Node<Integer> removeNthNodeFromEndOfList (Node<Integer> head, int n) { Node<Integer> pre = new Node<>(); pre.setNext(head); Node<Integer> slow = pre; Node<Integer> fast = pre; while (n != 0 ){ fast = fast.getNext(); n--; } while (fast.getNext() != null ) { fast = fast.getNext(); slow = slow.getNext(); } slow.setNext(slow.getNext().getNext()); return pre.getNext(); }
交换链表中的相邻结点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 private static Node swapPairsIteration (Node head) { Node dummy = new Node(); dummy.setNext(head); Node pre = dummy; Node first = null ; Node second = null ; while (head != null && head.getNext() != null ) { first = head; second = head.getNext(); pre.setNext(second); first.setNext(second.getNext()); second.setNext(first); pre = first; head = first.getNext(); } return dummy.getNext(); } private static Node swapPairsRecursion (Node head) { if (head == null || head.getNext() == null ) { return head; } Node odd = head; Node even = head.getNext(); odd.setNext(swapPairsRecursion(even.getNext())); even.setNext(odd); return even; }
链表求和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.hugh.datastructure.linkedlist.leecode;import com.hugh.datastructure.LinkedListUtils;import com.hugh.datastructure.linkedlist.MySingleLinkedList;import com.hugh.datastructure.linkedlist.Node;import java.util.Stack;public class AddTwoNumbers { public static void main (String[] args) { MySingleLinkedList listA = LinkedListUtils.generateSingleLinkList(7 ,2 ,4 ,3 ); MySingleLinkedList listB = LinkedListUtils.generateSingleLinkList(5 ,4 ,6 ); Node node = addTwoNumbersEasy(listA.getFirst(), listB.getFirst()); LinkedListUtils.traverseLinkListFromFirst(node); } private static Node addTwoNumbersMedium (Node<Integer> headA, Node<Integer> headB) { Stack<Integer> s1 = new Stack<>(); Stack<Integer> s2 = new Stack<>(); while (headA != null ) { s1.push(headA.getElem()); headA = headA.getNext(); } while (headB != null ) { s2.push(headB.getElem()); headB = headB.getNext(); } Node res = null ; int c = 0 ; while (!s1.isEmpty() || !s2.isEmpty() || c > 0 ) { int sum = (s1.isEmpty() ? 0 : s1.pop()) + (s2.isEmpty() ? 0 : s2.pop()) + c; Node n = new Node(sum % 10 , null ); c = sum / 10 ; n.setNext(res); res = n; } return res; } private static Node addTwoNumbersEasy (Node<Integer> headA, Node<Integer> headB) { Node nh = new Node(0 , null ); int c = 0 ; Node index = nh; while (headA != null || headB != null || c > 0 ) { int sum = ((headA == null ) ? 0 : headA.getElem()) + ((headB == null ) ? 0 : headB.getElem()) + c; index.setNext(new Node(sum % 10 , null )); index = index.getNext(); c = sum / 10 ; if (headA != null ) headA = headA.getNext(); if (headB != null ) headB = headB.getNext(); } if (c > 0 ) index.setNext(new Node(c, null )); return nh.getNext(); } }
回文链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 package com.hugh.datastructure.linkedlist.leecode;import com.hugh.datastructure.LinkedListUtils;import com.hugh.datastructure.linkedlist.MySingleLinkedList;import com.hugh.datastructure.linkedlist.Node;import sun.awt.image.ImageWatched;import java.util.ArrayList;public class isPalindrome { public static void main (String[] args) { MySingleLinkedList list = LinkedListUtils.generateSingleLinkList(1 , 2 , 3 , 3 , 2 , 1 , 4 ); boolean flag = new isPalindrome().isPalindromeThird(list.getFirst()); System.out.println(flag); System.out.println(list); } private static boolean isPalindromeFirst (Node head) { ArrayList arr = new ArrayList<>(); while (head != null ) { arr.add(head.getElem()); head = head.getNext(); } int front = 0 ; int back = arr.size() - 1 ; while (front < back) { if (((int )arr.get(front)) != ((int )arr.get(back))) { return false ; } front ++; back --; } return true ; } private boolean isPalindromeSecond (Node head) { firstNode = head; return compare(head); } private Node firstNode; private boolean compare (Node<Integer> head) { if (head != null ) { if (!compare(head.getNext())) return false ; if (head.getElem() != firstNode.getElem()) return false ; firstNode = firstNode.getNext(); } return true ; } private boolean isPalindromeThird (Node<Integer> head) { if (head == null ) return true ; Node<Integer> firstHalfEnd = endOfFirstHalf(head); Node<Integer> secondHalfStart = reverseList_interpolation(firstHalfEnd.getNext()); LinkedListUtils.traverseLinkListFromFirst(head); Node<Integer> index1 = head; Node<Integer> index2 = secondHalfStart; boolean flag = true ; while (flag && index2 != null ) { if (index1.getElem() != index2.getElem()) { flag = false ; } index1 = index1.getNext(); index2 = index2.getNext(); } firstHalfEnd.setNext(reverseList_interpolation(secondHalfStart)); return flag; } private Node reverseList (Node head) { if (head == null || head.getNext() == null ) { return head; } Node next = head.getNext(); Node newhead = reverseList(next); next.setNext(head); head.setNext(null ); return newhead; } private Node reverseList_interpolation (Node head) { Node pre = null ; Node cur = head; while (cur != null ) { Node next = cur.getNext(); cur.setNext(pre); pre = cur; cur = next; } return pre; } private Node endOfFirstHalf (Node head) { Node slow = head; Node fast = head.getNext(); while (fast.getNext() != null && fast.getNext().getNext() != null ) { slow = slow.getNext(); fast = fast.getNext().getNext(); } return slow; } }
tips 之前的Code都是用我自己的用例写的,后面为了方便直接调试代码,我会使用LeeCode的用例。
分隔链表 给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 1:
输入: root = [1, 2, 3], k = 5 输出: [[1],[2],[3],[],[]] 解释: 输入输出各部分都应该是链表,而不是数组。 例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。 第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。 最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。 示例 2:
输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
root 的长度范围: [0, 1000]. 输入的每个节点的大小范围:[0, 999]. k 的取值范围: [1, 50].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 package com.hugh.datastructure.linkedlist.leecode.nativecode;import com.hugh.datastructure.linkedlist.leecode.realex.ListNode;import com.hugh.datastructure.linkedlist.leecode.realex.ListNodeUtils;public class IsPalindrome { public static void main (String[] args) { IsPalindrome isPalindrome = new IsPalindrome(); ListNode head = ListNodeUtils.generateLinkedList(1 ,2 ,3 ,4 ,5 ); boolean palindrom = isPalindrome.isPalindrome(head); System.out.println(palindrom); } public boolean isPalindrome (ListNode head) { if (head == null || head.next == null ) { return true ; } ListNode endOfHalf = findEndOfHalf(head); ListNode backHead = findBackHead(endOfHalf.next); ListNode index1 = head; ListNode index2 = backHead; Boolean flag = true ; while (flag == true && index2 != null ) { if (index1.val != index2.val) { flag = false ; } index1 = index1.next; index2 = index2.next; } findBackHead(backHead); return flag; } private ListNode findBackHead (ListNode head) { ListNode pre = null ; while (head != null ) { ListNode next = head.next; head.next = pre; pre = head; head = next; } return pre; } private ListNode findEndOfHalf (ListNode head) { ListNode fast = head.next; ListNode slow = head; while (fast != null && fast.next != null ) { fast = fast.next.next; slow = slow.next; } return slow; } }
奇偶链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package com.hugh.datastructure.linkedlist.leecode.nativecode;import com.hugh.datastructure.linkedlist.leecode.realex.ListNode;import com.hugh.datastructure.linkedlist.leecode.realex.ListNodeUtils;public class OddEvenLinkedList { public static void main (String[] args) { OddEvenLinkedList oddEvenLinkedList = new OddEvenLinkedList(); ListNode node = ListNodeUtils.generateLinkedList(1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ); ListNode listNode = oddEvenLinkedList.oddEvenList(node); ListNodeUtils.traverseLinkListFromFirst(listNode); } public ListNode oddEvenList (ListNode head) { if (head == null ) return null ; ListNode odd = head; ListNode even = head.next; ListNode evenHead = even; while (even != null && even.next != null ){ odd.next = even.next; odd = odd.next; even.next = odd.next; even = even.next; } odd.next = evenHead; return head; } }
注:后期用到的工具类,样例类,能直接在本地能够调试的LeeCode
的代码环境在github 可获取。
树 树 (tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点 通过连接它们的边 组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树有很多种,向上面的一个节点有多余两个的子节点的树,称为多路树,后面会讲解2-3-4树和外部存储都是多路树的例子。而每个节点最多只能有两个子节点的一种形式称为二叉树。
节点 :上图的圆圈,比如A,B,C等都是表示节点。节点一般代表一些实体,在java面向对象编程中,节点一般代表对象。
边 :连接节点的线称为边,边表示节点的关联关系。一般从一个节点到另一个节点的唯一方法 就是沿着一条顺着有边的道路前进。在Java当中通常表示引用。
根 :树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点
子节点 :一个节点含有的子树的根节点称为该节点的子节点
兄弟节点 :具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点
叶节点 :没有子节点的节点称为叶节点
子树 :每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
节点的层次 :从根开始定义,根为第一层,根的子节点为第二层,以此类推。
深度 :对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
高度 :对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0
关于二叉树不成熟的一些理解
二叉树,本质上,是对链表和数组的一个折中(不成熟的说法,有说法是区块链)
链表和数组都是纯粹的数据结构,而二叉树就已经是分类器。
比如,我有一个任务,需要输入 10万个数据(32位整数),然后有两个操作: 1.添加(删除)一个整数。 2.询问第x大的数据。
比如,给出 1, 8, 13, 10(等等一堆数据)……. 然后询问第3大的数据, 然后插入 18 然后询问第4大的数据 再插入 9 再询问第2大的数据
不停的重复1,2 重复10万次。。
用有序链表,不行,查找(包括1需要找到对应位置,以及2查找)成本大O(N),但具体这个插入操作成本小O(1)。 用有序数组,查找(2的查找)成本小O(1)。但1的插入操作成本很大O(N)。
所以,我们折中使用排序二叉树(二叉树仅仅作为排序二叉树的基础),查找(包括1需要找到对应位置,以及2查找)成本挺小O(logN)。具体这个插入操作成本也挺小O(logN)。
具体的应用就是由排序二叉树(由于普通排序二叉树可能会有不平衡的情况)引申出来的红黑树(linux中ext3文件系统管理),avl树“windows对进程地址空间的管理”。
二叉树工具类 前序、中序、后续遍历三种遍历方式都是DFS(深度优先遍历的三种方式) 广度优先遍历是另外一种遍历方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hugh.datastructure.binarytree.utils;public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 package com.hugh.datastructure.binarytree.utils;import java.util.LinkedList;import java.util.Queue;public class TreeUtils { public static void main (String[] args) { TreeNode treeNode = generateTreeFromArray(3 ,9 ,20 ,null ,null ,15 ,7 ); System.out.println(treeNode.val); preOrderTraverse(treeNode); } public static TreeNode generateTreeFromArray (Integer... nums) { if (nums.length == 0 ) { return null ; } TreeNode head = new TreeNode(nums[0 ]); LinkedList<TreeNode> subTree = new LinkedList<>(); subTree.push(head); for (int i = 1 ; i < nums.length; i++) { if (!subTree.isEmpty()) { TreeNode cur = subTree.pop(); if (nums[i] != null ) { cur.left = new TreeNode(nums[i]); subTree.add(cur.left); } i++; if (i >= nums.length) { break ; } if (nums[i] != null ) { cur.right = new TreeNode(nums[i]); subTree.add(cur.right); } } else { break ; } } return head; } public static void preOrderTraverse (TreeNode root) { if (root != null ) { System.out.print(root.val+" " ); preOrderTraverse(root.left); preOrderTraverse(root.right); } } public void inOrderTraverse (TreeNode root) { if (root != null ) { inOrderTraverse(root.left); System.out.print(root.val+" " ); inOrderTraverse(root.right); } } public void postOrderTraverse (TreeNode root) { if (root != null ) { postOrderTraverse(root.left); postOrderTraverse(root.right); System.out.print(root.val+" " ); } } public void levelTraverse (TreeNode root) { if (root == null ) { return ; } LinkedList<TreeNode> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { TreeNode node = queue.poll(); System.out.print(node.val+" " ); if (node.left != null ) { queue.offer(node.left); } if (node.right != null ) { queue.offer(node.right); } } } public void depthOrderTraverse (TreeNode root) { if (root == null ) { return ; } LinkedList<TreeNode> stack = new LinkedList<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); System.out.print(node.val+" " ); if (node.right != null ) { stack.push(node.right); } if (node.left != null ) { stack.push(node.left); } } } public void BroadFirstSearch (TreeNode nodeHead) { if (nodeHead==null ) { return ; } Queue<TreeNode> myQueue=new LinkedList<>(); myQueue.add(nodeHead); while (!myQueue.isEmpty()) { TreeNode node=myQueue.poll(); System.out.print(node.val+" " ); if (null !=node.left) { myQueue.add(node.left); } if (null !=node.right) { myQueue.add(node.right); } } } }
树题通用模板 1 2 3 4 5 6 7 void traverse (TreeNode root) { traverse(root.left) traverse(root.right) }
套用模板:求二叉树中最大路径和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class BinaryTreeMaximumPathSum { public static void main (String[] args) { TreeNode root = TreeUtils.generateTreeFromArray(-10 ,9 ,20 ,null ,null ,15 ,7 ); BinaryTreeMaximumPathSum binaryTreeMaximumPathSum = new BinaryTreeMaximumPathSum(); int i = binaryTreeMaximumPathSum.maxPathSum(root); System.out.println(i); } public int maxPathSum (TreeNode root) { maxGain(root); return maxSum; } int maxSum = Integer.MIN_VALUE; public int maxGain (TreeNode node) { if (node == null ) { return 0 ; } int leftGain = Math.max(maxGain(node.left), 0 ); int rightGain = Math.max(maxGain(node.right), 0 ); int priceNewpath = node.val + leftGain + rightGain; maxSum = Math.max(maxSum, priceNewpath); return node.val + Math.max(leftGain, rightGain); } }
树的高度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class MaximumDepthOfBinaryTree { public static void main (String[] args) { TreeNode node = TreeUtils.generateTreeFromArray(3 , 9 , 20 , null , null , 15 , 7 ); int i = new MaximumDepthOfBinaryTree().maxDepth(node); System.out.println(i); } public int maxDepth (TreeNode root) { return (root == null ) ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 ; } }
判断是否是平衡二叉树 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class BalancedBinaryTree { public static void main (String[] args) { TreeNode root = TreeUtils.generateTreeFromArray(1 , 2 , 2 , 3 , null , null , 3 , 4 , null , null , 4 ); TreeUtils.preOrderTraverse(root); BalancedBinaryTree balancedBinaryTree = new BalancedBinaryTree(); boolean balanced = balancedBinaryTree.isBalanced(root); System.out.println(balanced); } private boolean flag = true ; public boolean isBalanced (TreeNode root) { getMaxDepth(root); return flag; } public int getMaxDepth (TreeNode node) { if (node == null ) return 0 ; int left = getMaxDepth(node.left); int right = getMaxDepth(node.right); if (Math.abs(left - right) > 1 ) flag = false ; return 1 + Math.max(left, right); } }
求二叉树的直径 基本套路和求二叉树的最大路径和相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class DiameterOfBinaryTree { public static void main (String[] args) { } private int diameter = 0 ; public int diameterOfBinaryTree (TreeNode root) { getDiameter(root); return diameter; } public int getDiameter (TreeNode node) { if (node == null ) { return 0 ; } int left = getDiameter(node.left); int right = getDiameter(node.right); diameter = Math.max(diameter, (left + right)); return 1 + Math.max(left, right); } }
翻转树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class InvertBinaryTree { public static void main (String[] args) { TreeNode root = TreeUtils.generateTreeFromArray(1 , 2 , 3 , 4 , 5 , 6 , 7 ); InvertBinaryTree InvertBinaryTree = new InvertBinaryTree(); TreeNode treeNode = InvertBinaryTree.invertTree(root); TreeUtils.preOrderTraverse(treeNode); } public TreeNode invertTree (TreeNode root) { if (root == null ) return root; TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); root.left = right; root.right = left; return root; } public TreeNode inverTree_improve (TreeNode root) { if (root == null ) return root; TreeNode left = root.left; root.left = inverTree_improve(root.right); root.right = inverTree_improve(left); return root; } }
合并二叉树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class MergeTwoBinaryTrees { public static void main (String[] args) { TreeNode t1 = TreeUtils.generateTreeFromArray(1 , 3 , 2 , 5 ); TreeNode t2 = TreeUtils.generateTreeFromArray(2 , 1 , 3 , null , 4 , null , 7 ); TreeNode root = new MergeTwoBinaryTrees().mergeTrees(t1, t2); TreeUtils.preOrderTraverse(root); } public TreeNode mergeTrees (TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null ) return null ; if (t1 == null ) return t2; if (t2 == null ) return t1; t1.val += t2.val; t1.left = mergeTrees(t1.left, t2.left); t1.right = mergeTrees(t1.right, t2.right); return t1; } }
判断路径和是否等于一个数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.ArrayList;public class PathSum { public static void main (String[] args) { } public boolean hasPathSum (TreeNode root, int sum) { if (root == null ) return false ; if (root.left == null && root.right == null && root.val == sum) return true ; return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); } }
PathSumIII 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class PathSumIII { public static void main (String[] args) { } public int pathSum (TreeNode root, int sum) { if (root == null ) return 0 ; int ret = 0 ; ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); return ret; } private int pathSumStartWithRoot (TreeNode root, int sum) { if (root == null ) return 0 ; int ret = 0 ; if (sum == root.val) ret++; ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val); return ret; } }
SubtreeOfAnotherTree 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class SubtreeOfAnotherTree { public static void main (String[] args) { } public boolean isSubtree (TreeNode s, TreeNode t) { if (s == null ) return false ; return isSameTree(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); } public boolean isSameTree (TreeNode p, TreeNode q) { if (p == null && q == null ) { return true ; } else if (p == null || q == null ) { return false ; } else if (p.val != q.val) { return false ; } else { return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); } } }
SymmetricTree 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.LinkedList;import java.util.Queue;public class SymmetricTree { public static void main (String[] args) { } public boolean isSymmetric (TreeNode root) { if (root == null ) return true ; return isSymmetric(root.left, root.right); } private boolean isSymmetric (TreeNode p, TreeNode q) { if (p == null && q == null ) return true ; if (p == null || q == null ) return false ; if (p.val != q.val) return false ; return isSymmetric(p.left, q.right) && isSymmetric(p.right, q.left); } public boolean isSymmetricIter (TreeNode root) { return check(root, root); } public boolean check (TreeNode u, TreeNode v) { Queue<TreeNode> q = new LinkedList<TreeNode>(); q.offer(u); q.offer(v); while (!q.isEmpty()) { u = q.poll(); v = q.poll(); if (u == null && v == null ) { continue ; } if ((u == null || v == null ) || (u.val != v.val)) { return false ; } q.offer(u.left); q.offer(v.right); q.offer(u.right); q.offer(v.left); } return true ; } }
SumOfLeftLeaves 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import static com.hugh.datastructure.binarytree.utils.TreeUtils.generateTreeFromArray;public class SumOfLeftLeaves { public static void main (String[] args) { TreeNode root = generateTreeFromArray(3 ,9 ,20 ,null ,null ,15 ,7 ); int i = new SumOfLeftLeaves().sumOfLeftLeavesNiubi(root); System.out.println(i); } public int sumOfLeftLeaves (TreeNode root) { if (root == null ) { return 0 ; } if (root.left != null && root.left.left == null && root.left.right == null ) { sum += root.left.val; } sumOfLeftLeaves(root.left); sumOfLeftLeaves(root.right); return sum; } private int sum; public int sumOfLeftLeavesNiubi (TreeNode root) { if (root == null ) return 0 ; if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); } private boolean isLeaf (TreeNode root) { if (root == null ) { return false ; } return root.left == null && root.right == null ; } }
LongestUnivaluePath 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class LongestUnivaluePath { public static void main (String[] args) { } public int longestUnivaluePath (TreeNode root) { dfs(root); return path; } private int path = 0 ; private int dfs (TreeNode root) { if (root == null ) return 0 ; int left = dfs(root.left); int right = dfs(root.right); int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0 ; int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0 ; path = Math.max(path, rightPath+leftPath); return Math.max(leftPath, rightPath); } }
HouseRobberIII 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class HouseRobberIII { public static void main (String[] args) { } public int rob (TreeNode root) { if (root == null ) return 0 ; int singleSum = root.val; if (root.left != null ) singleSum += rob(root.left.left) + rob(root.left.right); if (root.right!= null ) singleSum += rob(root.right.left) + rob(root.right.right); int doubleSum = rob(root.left) + rob(root.right); return Math.max(doubleSum, singleSum); } }
SecondMinimumNodeInABinaryTree 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class SecondMinimumNodeInABinaryTree { public static void main (String[] args) { } public int findSecondMinimumValue (TreeNode root) { if (root == null ) return -1 ; if (root.left == null && root.right == null ) return -1 ; int leftValue = root.left.val; int rightValue = root.right.val; if (leftValue == root.val) leftValue = findSecondMinimumValue(root.left); if (rightValue == root.val) rightValue = findSecondMinimumValue(root.right); if (leftValue != -1 && rightValue != -1 ) return Math.min(leftValue, rightValue); if (leftValue == -1 ) return rightValue; return leftValue; } }
一棵树每层节点的平均数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue;public class AverageOfLevelsInBinaryTree { public static void main (String[] args) { TreeNode root = TreeUtils.generateTreeFromArray(1 , 2 , 3 , 4 , 5 , 6 , 7 ); } public List<Double> averageOfLevels (TreeNode root) { ArrayList<Double> ret = new ArrayList<>(); if (root == null ) { return ret; } Queue<TreeNode> myQueue = new LinkedList<>(); myQueue.add(root); while (!myQueue.isEmpty()) { int cnt = myQueue.size(); double sum = 0 ; for (int i = 0 ; i < cnt; i++) { TreeNode poll = myQueue.poll(); sum += poll.val; if (poll.left != null ) { myQueue.add(poll.left); } if (poll.right != null ) { myQueue.add(poll.right); } } ret.add(sum / cnt); } return ret; } public static void broadFirstSearch (TreeNode root) { if (root == null ) { return ; } Queue<TreeNode> myQueue = new LinkedList<>(); myQueue.add(root); while (!myQueue.isEmpty()) { TreeNode poll = myQueue.poll(); System.out.print(poll.val + " " ); if (poll.left != null ) { myQueue.add(poll.left); } if (poll.right != null ) { myQueue.add(poll.right); } } } }
得到左下角的节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.LinkedList;import java.util.Queue;public class FindBottomLeftTreeValue { public static void main (String[] args) { } public int findBottomLeftValue (TreeNode root) { Queue<TreeNode> myQueue = new LinkedList<>(); myQueue.add(root); while (!myQueue.isEmpty()) { root = myQueue.poll(); if (root.right != null ) { myQueue.add(root.right); } if (root.left != null ) { myQueue.add(root.left); } } return root.val; } }
非递归遍历二叉树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.*;public class BinaryTreeOrderTraversal { public static void main (String[] args) { } public List<Integer> preorderTraversal (TreeNode root) { ArrayList<Integer> ret = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode pop = stack.pop(); if (pop == null ) continue ; ret.add(pop.val); stack.push(pop.right); stack.push(pop.left); } return ret; } public List<Integer> inorderTraversal (TreeNode root) { List<Integer> ret = new ArrayList<>(); if (root == null ) return ret; Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { while (cur != null ) { stack.push(cur); cur = cur.left; } TreeNode node = stack.pop(); ret.add(node.val); cur = node.right; } return ret; } public List<Integer> postorderTraversal (TreeNode root) { ArrayList<Integer> ret = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode pop = stack.pop(); if (pop == null ) continue ; ret.add(pop.val); stack.push(pop.left); stack.push(pop.right); } Collections.reverse(ret); return ret; } }
BST 二叉查找树(BST) 根节点大于等于左子树的所有节点,小于等于右子树的所有节点。
二叉查找树使用中序遍历是有序的。
修剪二叉查找树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class TrimBST { public static void main (String[] args) { } public TreeNode trimBST (TreeNode root, int L, int R) { if (root == null ) return null ; if (root.val > R) return trimBST(root.left, L, R); if (root.val < L) return trimBST(root.right, L, R); root.left = trimBST(root.left,L,R); root.right = trimBST(root.right,L,R); return root; } }
寻找二叉查找树的第 k 个元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class KthSmallestElementInABST { public static void main (String[] args) { } public int kthSmallest (TreeNode root, int k) { inOrder(root, k); return back; } private int count = 0 ; private int back; private void inOrder (TreeNode root, int k) { if (root == null ) return ; inOrder(root.left, k); count++; if (count == k) { back = root.val; return ; } inOrder(root.right, k); } public int kthSmallestNiu (TreeNode root, int k) { int leftCnt = count(root.left); if (leftCnt == k-1 ) return root.val; if (leftCnt > k-1 ) return kthSmallestNiu(root.left, k); return kthSmallestNiu(root.right, k - leftCnt - 1 ); } private int count (TreeNode root) { if (root == null ) return 0 ; return 1 + count(root.left) + count(root.right); } }
把二叉查找树每个节点的值都加上比它大的节点的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.binarytree.utils.TreeUtils;public class ConvertBSTToGreaterTree { public static void main (String[] args) { TreeNode treeNode = TreeUtils.generateTreeFromArray(5 , 2 , 13 ); ConvertBSTToGreaterTree ct = new ConvertBSTToGreaterTree(); TreeNode root = ct.convertBST(treeNode); } public TreeNode convertBST (TreeNode root) { traver(root); return root; } private int sum = 0 ; private void traver (TreeNode root) { if (root == null ) return ; traver(root.right); sum += root.val; root.val = sum; traver(root.left); } }
二叉搜索树最近公共先祖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class LowestCommonAncestor { public static void main (String[] args) { } public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left,p,q); if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right,p,q); return root; } }
二叉树最近公共先祖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class LowestCommonAncestor Ⅱ { public static void main (String[] args) { } public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); return left == null ? right : right == null ? left : root; } }
将有序数组转换为二叉搜索树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class ConvertSortedArrayToBinarySearchTree { public static void main (String[] args) { } public TreeNode sortedArrayToBST (int [] nums) { return toBST(nums, 0 , nums.length - 1 ); } private TreeNode toBST (int [] nums, int sIdx, int eIdex) { if (sIdx > eIdex) return null ; int mIdex = (sIdx + eIdex) / 2 ; TreeNode root = new TreeNode(nums[mIdex]); root.left = toBST(nums, sIdx, mIdex - 1 ); root.right = toBST(nums, mIdex + 1 , eIdex); return root; } }
有序链表转换二叉搜索树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import com.hugh.datastructure.linkedlist.leecode.standardutils.ListNode;public class ConvertSortedListToBinarySearchTree { public static void main (String[] args) { } public TreeNode sortedListToBST (ListNode head) { if (head==null ) return null ; if (head.next==null ) return new TreeNode(head.val); ListNode slow = head, fast = head, pre = null ; while (fast != null && fast.next != null ) { pre = slow; slow = slow.next; fast = fast.next.next; } pre.next = null ; TreeNode node = new TreeNode(slow.val); node.left = sortedListToBST(head); node.right = sortedListToBST(slow.next); return node; } }
在BST中寻找两节点差绝对值的最小值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;public class MinimumAbsoluteDifferenceInBST { public static void main (String[] args) { } public int getMinimumDifference (TreeNode root) { find(root); return min; } private void find (TreeNode root) { if (root==null ) return ; find(root.left); if (pre != null ) { min = Math.min(min,root.val - pre.val); } pre = root; find(root.right); } int min = Integer.MAX_VALUE; TreeNode pre = null ; }
在二叉查找树中寻找两个节点,使它们的和为一个给定值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.HashSet;public class TwoSumIV { public static void main (String[] args) { } public boolean findTarget (TreeNode root, int k) { HashSet<Integer> set = new HashSet<>(); return find(root, k, set); } private boolean find (TreeNode root, int k, HashSet<Integer> set) { if (root==null ) return false ; if (set.contains(k - root.val)) return true ; set.add(root.val); return find(root.left, k, set) || find(root.right, k, set); } }
寻找二叉查找树中出现次数最多的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package com.hugh.datastructure.binarytree.leecode;import com.hugh.datastructure.binarytree.utils.TreeNode;import java.util.ArrayList;public class FindModeInBinarySearchTree { public static void main (String[] args) { } public int [] findMode(TreeNode root) { final ArrayList<Integer> maxCntNums = new ArrayList<>(); inOrder(root,maxCntNums); int [] ret = new int [maxCntNums.size()]; int idx = 0 ; for (int num : maxCntNums) { ret[idx++] = num; } return ret; } private void inOrder (TreeNode node, ArrayList<Integer> nums) { if (node == null ) return ; inOrder(node.left,nums); if (preNode != null ) { if (preNode.val == node.val) curCnt++; else curCnt = 1 ; } if (curCnt > maxCnt) { maxCnt = curCnt; nums.clear(); nums.add(node.val); } else if (curCnt == maxCnt) { nums.add(node.val); } preNode = node; inOrder(node.right,nums); } private int curCnt = 1 ; private int maxCnt = 1 ; private TreeNode preNode = null ; }
Trie树,字典树、单词查找树或键树 哈希树的变种 典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 hash表,通过hash函数把所有的单词分别hash成key值,查询的时候直接通过hash函数即可,都知道hash表的效率是非常高的为O(1),直接说字典树的查询效率比hash高,难道有比O(1)还快的- -。 hash: 当然对于单词查询,如果我们hash函数选取的好,计算量少,且冲突少,那单词查询速度肯定是非常快的。那如果hash函数的计算量相对大呢,且冲突律高呢? 这些都是要考虑的因素。且hash表不支持动态查询,什么叫动态查询,当我们要查询单词apple时,hash表必须等待用户把单词apple输入完毕才能hash查询。 当你输入到appl时肯定不可能hash吧。 字典树(tries树): 对于单词查询这种,还是用字典树比较好,但也是有前提的,空间大小允许,字典树的空间相比较hash还是比较浪费的,毕竟hash可以用bit数组。 那么在空间要求不那么严格的情况下,字典树的效率不一定比hash若,它支持动态查询,比如apple,当用户输入到appl时,字典树此刻的查询位置可以就到达l这个位置,那么我在输入e时光查询e就可以了(更何况如果我们直接用字母的ASCII作下标肯定会更快)!字典树它并不用等待你完全输入完毕后才查询。 所以效率来讲我认为是相对的。
1 2 3 4 所以尽管哈希表可以在 O(1)O(1) 时间内寻找键值,却无法高效的完成以下操作: 找到具有同一前缀的全部键值。 按词典序枚举字符串的数据集。
本场景中,此Trie(读音try)是一个有根树,有以下特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package trie;public class TrieNode { TrieNode[] child; boolean isEnd; public TrieNode () { this .child = new TrieNode[26 ]; this .isEnd = false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package trie;public class Trie { TrieNode root; public Trie () { root = new TrieNode(); } public void insert (String word) { TrieNode p = root; for (int i = 0 ; i < word.length(); i ++) { char c = word.charAt(i); if (p.child[c - 'a' ] == null ) { p.child[c - 'a' ] = new TrieNode(); } p = p.child[c - 'a' ]; } p.isEnd = true ; } public boolean search (String word) { TrieNode p = root; for (int i = 0 ; i < word.length(); i ++) { char c = word.charAt(i); if (p.child[c - 'a' ] == null ) { return false ; } p = p.child[c - 'a' ]; } return p.isEnd; } public boolean startsWith (String prefix) { TrieNode p = root; for (int i = 0 ; i < prefix.length(); i ++) { char c = prefix.charAt(i); if (p.child[c - 'a' ] == null ) { return false ; } p = p.child[c - 'a' ]; } return true ; } }
实现一个 Trie,用来求前缀和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.hugh.datastructure.binarytree.leecode;public class MapSum { public static void main (String[] args) { } private class Node { Node[] child = new Node[26 ]; int value; } private Node root = new Node(); public MapSum () { } public void insert (String key, int val) { insert(key, root, val); } private void insert (String key, Node node, int val) { if (node == null ) return ; if (key.length() == 0 ) { node.value = val; return ; } int index = indexForChar(key.charAt(0 )); if (node.child[index] == null ) { node.child[index] = new Node(); } insert(key.substring(1 ), node.child[index], val); } public int sum (String prefix) { return sum(prefix, root); } private int sum (String prefix, Node node) { if (node == null ) return 0 ; if (prefix.length() != 0 ) { int index = indexForChar(prefix.charAt(0 )); return sum(prefix.substring(1 ), node.child[index]); } int sum = node.value; for (Node child : node.child) { sum += sum(prefix, child); } return sum; } private int indexForChar (char c) { return c - 'a' ; } }