要想灵活使用数据结构,你需要先弄清楚数据在代码中被处理、加工的最小单位动作,也就是数据结构的基本操作,有了这些动作之后,你才可以基于此去选择更合适的数据结构。
从一个例题深度拆解代码的数据操作例题:在一个数组中找出出现次数最多的那个元素的数值。
例如,输入数组a=[1,2,3,4,5,5,6]中,只有5出现了两次,其余都是1次,显然5出现的次数最多,则输出5。
为了降低时间复杂度,我们引入了k-v的字典的数据结构。
那么问题来了,究竟是什么原因促使我们使用字典的数据结构呢?如果不使用字典,改为使用数组行不行呢?
为了回答这些问题,我们先看一下究竟此处代码需要对数据进行哪些操作。
这段代码处理数据的核心思路是:
根据原始数组计算每个元素出现的次数
根据第一步的结果,找到出现次数最多的元素
首先来分析第一步统计出现次数的处理。此时,你还不知道应该采用什么数据结构。
对于每一次的循环,你得到了输入数组中的某个元素a。接着,你需要判断这个元素在未知的数据结构中是否出现过:
如果出现了,就需要对出现的次数加1如果没有出现过,则把这个元素新增到未知数据结构中,并且把次数赋值为1这里的数据操作包括:
查找:看能否在数据结构中查找到这个元素,也就是判断元素是否出现过
新增:针对没有出现过的情况,新增这个元素
改动:针对出现过的情况,需要对这个元素出现的次数加1
接下来一起分析第二步,访问数据结构中的每个元素,找到次数最多的元素。
这里涉及的数据操作很简单,只有查找。因此,这段代码需要高频使用查找的功能。
此时,第一步的查找动作嵌套在for循环中,如果你的代码不能在O(1)的时间复杂度内完成,则代码整体的时间复杂度并没有下降。而能在O(1)的时间复杂度内完成查找动作的数据结构,只有字典类型。
这样,外层for循环是O(n)的时间复杂度,内部嵌套的查找操作是O(1)的时间复杂度,整体计算下来,就仍然是O(n)的时间复杂度。
字典的查找是通过键值对的匹配完成的,它可以在O(1)时间复杂度内,实现对数值条件查找。
现在,我们换个解决方案。假设采用两个数组,分别按照对应顺序记录元素及其对应的出现次数。数组对于元素的查找只能逐一访问,时间复杂度是O(n),也就是说,在O(n)复杂度的for循环中,又嵌套了O(n)复杂度的查找动作,所以时间复杂度是O(n2)。
因此,这里的数据结构,只能采用字典类型。
以不变应万变:掌握数据处理的基本操作不管是数组还是字典,都需要额外开辟空间,对数据进行存储。而且数据存储的数量,与输入的数据量一致。因此,消耗的空间复杂度相同,都是O(n)。
由前面的分析可见,同样采用复杂的数据结构消耗了O(n)的空间复杂度,其对时间复杂度降低的贡献有可能不一样。
因此,我们必须要设计合理的数据结构,以达到降低时间损耗的目的。
首先我们分析这段代码到底对数据先后进行了哪些操作
然后再根据分析出来的数据操作,找到合理的数据结构
经过对代码的拆解,你会发现,即便是很复杂的代码对数据的处理也只有这3个基本操作:增、删、查。
即使你遇到更复杂的问题,无非就是这些基本操作的叠加和组合。只要围绕这3个数据处理的操作进行分析,就能得出解决问题的最优方案。
如何进行分析呢?可以参考下面这些步骤:
这段代码对数据进行了哪些操作?
这些操作中,哪个操作最影响效率,对时间复杂度的损耗最大?
哪种数据结构最能帮助你提高数据操作的使用效率?
这3个步骤构成了设计合理数据结构的方法论。
推荐阅读:
搞定算法:如何理解复杂度
搞定算法:3步走降低复杂度!
算法这么清奇:如何从数学视角优化复杂度
预览时标签不可点收录于话题#个上一篇下一篇