关于数据结构:图解数据结构开篇

3次阅读

共计 7369 个字符,预计需要花费 19 分钟才能阅读完成。

图解数据结构

  • 作者:天行

加入了 lucifer 的 91 天学算法流动,人不知; 鬼不觉中曾经一月无余。从自觉地做到有目标、有套路地去做。

在 lucifer 的 91 课程中,从根底到进阶到专题,在这个月中,经验了根底篇的洗礼,不论在做题思路,还是做题速度都有了很大的晋升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维形式的扭转,还是在工程上的利用。

对一个问题应用画图、举例、合成这 3 种办法将其化繁为简,造成清晰思路再入手写代码,一张好的图可能更好地帮忙去了解一个算法。因而本次分享如何应用画图同时联合经典的题目的办法去论述数据结构。

​<!– more –>

数据结构与算法有用么?

这里我摘录了一个知乎的高赞答复给大家做参考:

集体认为数据结构是编程最重要的基本功没有之一!学了程序表和链表,你就晓得,在查问操作更多的程序中,你应该用程序表;而批改操作更多的程序中,你要应用链表;而单向链表不不便怎么办,每次都从头到尾好麻烦啊,怎么办?你这时就会想到双向链表 or 循环链表。
学了栈之后,你就晓得,很多波及后入先出的问题,例如函数递归就是个栈模型、Android 的屏幕跳转就用到栈,很多相似的货色,你就会第一工夫想到:我会用这货色来去写算法实现这个性能。
学了队列之后,你就晓得,对于先入先出要排队的问题,你就要用到队列,例如多个网络下载工作,我该怎么去调度它们去取得网络资源呢?再例如操作系统的过程(or 线程)调度,我该怎么去分配资源(像 CPU)给多个工作呢?必定不能全副一起领有的,资源只有一个,那就要排队!那么怎么排队呢?用一般的队列?然而对于那些优先级高的线程怎么办?那也太共产主义了吧,这时,你就会想到了优先队列,优先队列怎么实现?用堆,而后你就有疑难了,堆是啥玩意?本人查吧,敲累了。
总之好好学数据结构就对了。我感觉数据结构就相当于:我塞牙了,那么就要用到牙签这“数据结构”,当然你用指甲也行,只不过“性能”没那么好;我要拧螺母,必定用扳手这个“数据结构”,当然你用钳子也行,只不过也没那么好用。学习数据结构,就是为了理解当前在 IT 行业里搬砖须要用到什么工具,这些工具有什么利弊,利用于什么场景。当前用的过程中,你会发现这些根底的“工具”也存在着一些缺点,你不满足于此工具,此时,你就开始本人在这些数据结构的根底上加以革新,这就叫做自定义数据结构。而且,你当前还会造出很多其余利用于理论场景的数据结构。。你用这些数据结构去造轮子,人不知; 鬼不觉,你成了又一个轮子哥。

既然这么有用,那咱们怎么学习呢?我的倡议是先把常见的数据结构学个大略,而后开始装置专题的模式冲破算法。这篇文章就是给大家疾速过一下一部分常见的数据结构。

从逻辑上分,数据结构分为线性和非线性两大类。

  • 线性数据结构包含数组、链表、栈、队列。
  • 非线性构造包含树、哈希表、堆、图。

而咱们罕用的数据结构次要是数组、链表、栈、树,这同时也是本文要讲的内容。

数据结构一览

数组

数组的定义为寄存在 间断内存空间 上的 雷同类型数据 的汇合。因为内存空间间断,所以能在 O(1)的工夫进行存取。

剑指 offer03. 数组中的反复的数字

题目形容:


在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范畴内。数组中某些数字是反复的,但不晓得有几个数字反复了,也不晓得每个数字反复了几次。请找出数组中任意一个反复的数字。

剖析:

反复象征至多呈现两次,那么找反复就变成了统计数字呈现的频率了。那如何统计数字频率呢?(不应用哈希表),咱们能够开拓一个长度为 n 的数组 count_nums,并且初始化为 0,遍历数组 nums,应用 nums[i]为 count_nums 赋值.

图解:

(留神:数组下标从 0 开始)

剑指 offer21. 调整数组程序使奇数位于偶数后面

题目形容:


输出一个整数数组,实现一个函数来调整该数组中数字的程序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半局部。

剖析:

依据题目要求,须要咱们调整数组中奇偶数的程序,那这样的话,咱们能够从数组的两端同时开始遍历,左边遇到奇数的时候停下,右边遇到偶数的时候停下,而后进行替换。

1122. 数组的绝对排序

题目形容:


给你两个数组,arr1 和  arr2,arr2  中的元素各不相同
arr2 中的每个元素都呈现在  arr1  中
对 arr1  中的元素进行排序,使 arr1 中项的绝对程序和  arr2  中的绝对程序雷同。未在  arr2  中呈现过的元素须要依照升序放在  arr1  的开端。示例

输出:arr1 = [2,3,1,3,2,4,6,7,9,2], arr2 = [2,1,4,3,9,6]

输入:[2,2,2,1,4,3,3,9,6,7]

剖析:

察看输入,发现数字,因为 arr1 总是依据 arr2 中元素的绝对大小来排序,所以只相当于在 arr2 中进行填充,每个中央该填充多少呢?这个时候就须要去统计 arr1 中每个数字呈现的频率。

小结

在数组中,因为数组是一个有序的构造,这里的有序是指在地位上的有序,所以大多数只须要思考程序或者绝对程序即可。

链表

链表是一种线性数据结构,其中的每个元素实际上是一个独自的对象,每一个节点里存到下一个节点的指针(Pointer)。
就像咱们玩的寻宝游戏一样,当咱们找到一个宝箱的时候,外面还存在寻找下一个宝箱的藏宝图,顺次类推,每一个宝箱都是如此,始终到找到最终宝藏。

通过单链表,能够衍生出循环链表,双向链表等。

咱们来看下链表中比拟经典的几个题目。

面试题 02.02. 返回倒数第 k 个节点

题目形容:


实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。示例:输出:1->2->3->4->5 和 k = 2
输入:4

剖析:

想要找到倒数第 k 个节点,如果此时在数组中,那咱们只须要用最初一个数组的索引减去 k 就能找到这个值,然而链表是不能间接通过索引失去的。如果此时,咱们晓得最初一个节点的地位,而后往前找 k 个不就找到咱们须要的节点了吗?等价于咱们要找的节点和最初一个节点相隔 k 个地位。所以当有一个指针 front 登程 k 步后,咱们再登程,等 front 达到起点时,咱们刚好达到倒数第 k 个节点。

咱们把这种解法叫做双指针,或者快慢指针,或者前后指针,这种办法能够用于寻找链表两头节点,判断是链表中是否存在环(循环链表)并寻找环入口。

61. 旋转链表

题目形容:


给定一个链表,旋转链表,将链表每个节点向右挪动 k 个地位,其中 k 是非正数。示例:输出: 1->2->3->4->5->NULL, k = 2

输入: 4->5->1->2->3->NULL

剖析:

每个数字向后挪动 k 位,那么最初 k 位就须要挪动到后面,和找倒数第 k 位数字很类似,k 位前面的都移到结尾。惟一须要留神的中央就是,k 的值可能大于链表长度的 2 倍及以上,所以须要算出链表的长度,以保障尽快找到倒数 k 的地位。

解法 1

找到地位后,间接断开

解法 2

制作循环链表,而后再找倒数第 k 个数,而后断开循环链表

24. 两两替换链表中的节点

题目形容:


给定一个链表,两两替换其中相邻的节点,并返回替换后的链表。你不能只是单纯的扭转节点外部的值,而是须要理论的进行节点替换。示例:输出:head = [1,2,3,4]

输入:[2,1,4,3]

剖析:

原理很简略,两个指针,别离指向相邻的两个节点,而后再增加一个长期指针做换替换的中介 增加 dummy 节点,不必思考头节点的状况,更加不便。间接上图:

除了同时操作一个链表之外,有的题目也会给出两个或者更多的链表,如两数相加,如 leetcode 中 2. 两数相加、21. 合并两个有序链表、160. 相交链表

21. 相交节点

题目形容:


编写一个程序,找到两个单链表相交的起始节点。

如上面的两个链表

剖析:

咱们晓得,对于任意两个数 ab,肯定有 a+b-c=b+a-c,

基于 a+b-c=b+a-c,咱们能够设置两个指针,别离指向 A 和 B,以雷同的步长同时挪动,并在第一次达到尾节点的时候,指向另一个链表,如果存在相交节点,也就是说 c > 0,那么两个指针肯定会相遇,相遇处也就是相交节点。而当不存在时,即 c=0,那么两个指针最终都会指向空节点。

小结

链表中的操作无非就是两种,插入,删除。解题办法无非就是增加 dummy 节点(解决头节点的判断问题)、快慢指针(快慢不肯定是单次步长一样,应该了解为均匀步长,即应用了雷同的工夫,走的途程的长度来定义快慢)。

栈是一种 先进后出 (FILO, First In Last Out) 的数据结构
能够把栈了解为

<center>

</center>

没错,就是上图的罐装薯片,想要吃到最底下的那片,必须顺次吃完下面的。而在装薯片的时候,最底下的反而是最先装进去的。

在 leetcode 外面对于栈比拟经典的题目有:20. 无效的括号;150. 逆波兰表达式求值

20. 无效的括号

题目形容:


给定一个只包含 '(',')','{','}','[',']' 的字符串,判断字符串是否无效。示例:"{[()][]}()"

| 0   | 1   | 2   | 3   | 4   | 5   | 6   | 7   | 8   | 9   |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| {| [   | (   |)   | ]   | [|]   | }   | (|)   |

剖析:

  • 一个无效的括号为,左边必须和右边对应,且存在至多一对无效括号的索引为[i, i+1]。那么,咱们只有是括号右边局部,就入栈,左边局部,就和栈顶元素比拟。

图解:

150. 逆波兰表达式求值

题目形容:


依据 逆波兰表示法,求表达式的值。无效的运算符包含 +, -, \*, /。每个运算对象能够是整数,也能够是另一个逆波兰表达式。示例:["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+"]

剖析:

  • 依据运算法令,咱们能够晓得,一个运算有 num1,operation,num2 三局部组成。在一个逆波兰表达式中,运算符后面两个 num 就是这个运算的组成。
  • 咱们要做事件就是,找到一个运算符的时候,同时找到他后面的两个数,而栈的现金先去个性满足这个需要,应用栈来解决。

227. 根本计算器 II

题目形容:


实现一个根本的计算器来计算一个简略的字符串表达式的值。字符串表达式仅蕴含非负整数,+,-,\*,/ 四种运算符和空格。整数除法仅保留整数局部。示例:3+5\*2/2-3

剖析:

  • 与逆波兰表达式不同的中央是,这里运算符两边是操作数。然而,这又有什么问题呢?万变不离外围,咱们只须要在找到运算符的同时,失去运算符两边的操作数。问题来了,还须要思考运算符的优先级,想到的一个办法就是,只进行乘除法运算,最初进行加法运算,不进行减法运算(减去一个数 ⟺ 加上这个数的正数)

  • 如果可能把这个字符串表达式类似地转换位逆波兰表达式,那就能间接套用逆波兰表达式的代码了,回顾一下,逆波兰表达式是,每次有操作符的时候,就从栈顶进去两个元素。能够通过应用两个栈来实现,一个栈用来存储操作数,一个栈用来存储操作符。如果比栈顶的操作符符优先级低或者雷同,那么就从操作符栈取出栈顶运算符号

496. 下一个更大元素 I

题目形容:


给定两个 没有反复元素的数组  nums1 和  nums2,其中 nums1  是  nums2  的子集。找到  nums1  中每个元素在  nums2  中的下一个比其大的值。nums1  中数字  x  的下一个更大元素是指  x  在  nums2  中对应地位的左边的第一个比  x  大的元素。如果不存在,对应地位输入 -1。示例:输出: nums1 = [4,1,2], nums2 = [1,3,4,2].

输入: [-1,3,-1]

剖析:

题目要求咱们找出 nums1 中每个元素在 nums2 中的下一个比这个元素大的值,又提到 nums1 是 nums2 的一个子集,咱们无妨找出 nums2 中每个元素的下一个比他大的值,而后映射到 nums1 中。

那如何找出 nums2 中每个元素的下一个比他大的值呢?对于以后元素,若下一个元素比他大,则找到了,否则的话,就把这个元素增加到栈中,始终到找到一个元素比栈顶元素大,这时候把栈外面所有小于这个元素的元素都出栈,听起来很绕,不妨,看图 —->

最初栈中仍然有数据存在,这是为什么呢?因为这些元素前面找不到比他更大的值了。察看示例数据,4 前面没有比他更大的值了,5 和 1 也是。咱们还能察看到栈中元素是从大到小的,能够称这个栈为 == 枯燥递加栈 ==(如 1019. 寻找链表中的下一个更大节点,503. 下一个更大元素 II、402. 移掉 k 位数字,39. 每日温度,在 1673. 找出最具备竞争力的子序列中,其实只须要构建一个枯燥递增栈,而后截取前 k 个。)。

回到题目,须要找到 nums1 中元素在 nums2 中的下一个比其大的值,只须要在方才保留的信息中进行查找,找不到的则不存在,能够应用哈比表保留每个数对应的下一个较大值。

小结

栈因为其随时能够出栈和进栈,产生十分多的组合,带来了十分多的变动,所以读懂题目十分重要,而后抉择办法,正所谓题目是无限的,办法是无限的。所以紧跟 lucifer 大佬学习套路,是一条值得保持的路线,毕竟自古深情留不住,唯有套路得人心,这里举荐 lucifer 大佬的《枯燥栈模板带你秒杀八道题》,带你乱杀。

树虽相比于链表来说,至多有两个节点(n 个节点就叫 n 叉树),然而树是一个形象的概念,能够了解为一个不停做抉择的过程,给定一个起始条件,会产生多种后果,而这些后果又成为新的条件,以此类推,直到不再有新的条件。在树种,起始条件就是根节点,不再产生新的条件的就是叶子节点。在树种,应用较多的是二叉树。一颗二叉树不论有多大,咱们都能够把他拆分为五种模式,

不论是在树上进行什么操作,都须要进行遍历,遍历的形式有两种:广度优先遍历(BFS)和深度优先遍历(DFS)。
简略来说,广度就是先找到有多少种可能,而后找出这些可能有多少种可能;而深度就是每次只依据一个条件来找,直到最终没有条件。
话不多说,上图。

如果是试错的话,广度是一次把所有的后果都试一试,深度则是一条路走到黑。

这里间接借用 lucifer 大佬的广度、深度优先遍历模板(套路)

function dfs(root) {
 if (满足特定条件){// 返回后果 or 退出搜寻空间}
 for (const child of root.children) {dfs(child)
 }
}

深度优先遍历依据逻辑解决(== 敲黑板,很重要 ==)的先后分为前序遍历、中序遍历、后序遍历

// 前序遍历
function dfs(root) {
 if (满足特定条件){// 返回后果 or 退出搜寻空间}
    // 次要逻辑
    dfs(root.left)
    dfs(root.right)
// 中序遍历
function dfs(root) {
 if (满足特定条件){// 返回后果 or 退出搜寻空间}
    dfs(root.left)
    // 次要逻辑
    dfs(root.right)
// 后序遍历
function dfs(root) {
 if (满足特定条件){// 返回后果 or 退出搜寻空间}
    dfs(root.left)
    dfs(root.right)
    // 次要逻辑
}
}

接下来就要实操了

199. 二叉树的右视图

题目形容:

给定一棵二叉树,设想本人站在它的右侧,依照从顶部到底部的程序,返回从右侧所能看到的节点值。

示例:

输出: [1,2,3,null,5,null,4]

输入: [1, 3, 4]

解释:

剖析:

此题即能够应用广度优先,也能够深度优先。
应用广度优先,只须要将每一层的节点用一个数组保留下来,而后输入最初一个
应用深度优先,这里我应用的是根右左的形式,这样能保障在每进入到一个新的层时,第一个拜访到的就是最左边的元素。

上图:

112. 门路总和

题目形容:

给定一个二叉树和一个指标和,判断该树中是否存在根节点到叶子节点的门路,这条门路上所有节点值相加等于指标和。阐明: 叶子节点是指没有子节点的节点。

示例:

剖析:

求一条门路(根节点到叶子节点),这不就一条路走到底吗,没什么好犹豫的,抉择深度优先遍历。因为须要取得门路上的和,咱们须要把每个节点的值(状态)传递给下一个节点。

在 113. 门路总和 II 中,和本题相似,只须要把节点退出到数组中传递给下一个节点;在 129. 求根到叶子节点数字之和,须要把以后值 *10 传递给下一个节点。

662. 二叉树最大宽度

题目形容:

给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)构造雷同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的 null 节点也计入长度)之间的长度。

示例:

剖析:

最大宽度,不就是找出哪一层最长吗?广度优先搜寻会更加不便,须要留神的是,非两端节点的 null 节点也要算到长度中,所以当初每一层存储的不仅仅是有值节点。

上图

513. 找树左下角的值

题目形容:

给定一个二叉树,在树的最初一行找到最右边的值。

示例:

<center>

</center>
剖析:
两个要害信息,一个最初一行,一个最右边。如同广度,深度都能够找到,在这里以深度进行阐明,最初一行就是 depth 最大的,所以在深度遍历的时候,每次给一层传递 depth 信息。

与此题相似的还有 111. 二叉树的最小深度,104. 二叉树的最大深度

感觉,就这?如同也没什么难的啊,学完 lucifer 的课程,我就是这么收缩。

小结

无非就是,深度遍历时,是否传递信息给下一层,给下一层传递什么信息;广度遍历时,是否保留每一层,是否保留空节点。

总结

本次给大家介绍了四种比拟常见的数据结构,别离是数组,链表,栈和树。这四种只有树是逻辑上的非线性数据结构,因为一个节点可能有多个孩子,而其余数据结构只有一个前驱和一个后继。

因为先进后出的个性,咱们能够用数组轻松地在 $O(1)$ 工夫复杂度模仿栈的操作。然而队列就没那么好命了,咱们必须应用链表来优化工夫复杂度。

链表的考题绝对比拟繁多,只有记住那几个点就好了。

树的题目比拟丰盛,和它的非线性数据结构有很大关系。因为其是非线性的,因而有了各种遍历形式,常见的是广度优先和深度优先,很多题目都是灵活运用这两种遍历形式问题就迎刃而解。

关注公众号力扣加加,学习算法不迷路。

参考:

  • 根底的数据结构(总览)
  • 简直刷完了力扣所有的链表题,我发现了这些货色
  • 简直刷完了力扣所有的树题,我发现了这些货色
  • 回炉重铸,91 天见证不一样的本人(第二期)
正文完
 0