不得不知道的实用数据结构算法(一)
不得不知道的实用数据结构算法(一)
新坑说开就开,就是这么随(不)心(知)所(死)欲(活),这个系列实际上是针对数据结构中的一些低时间复杂度的常用模板or思考方式的总结,ok,今天先进行的是线性表。
线性表
定义:
1 |
|
1 |
|
秉承着不给自己找麻烦,后边就不用动态分配空间的代码了,虽然在我初学的时候觉得动态分配真的很优雅(起码不怕爆了)
OK,然后迅速的过一下基本操作以及其操作的流程以及弊端(懒得写了):
删除
:删除一个元素,要将后边的元素都移过来,实际上也就是说复杂度是O(n)
遍历
:这不必多说,自然O(n)
随机存取
:这是顺序表的强项
插入
:和删除其实一致,但是要求后移后面的元素
然而很多算法题会颠覆你对基本操作的认识,也就是会让你优化一种插入操作or删除操作,使一个本来暴力解时间复杂度为O(n2)的算法优化为时间复杂度为O(n)的算法
我们先举个例子:
现在有一个顺序表,其中有元素n个,现在我希望删除其中的所有偶数元素
我们从直觉上很容易写出这样一个代码:
1 | void dele_even(SeqList *l) |
简单瞅一眼,很明显可以看出这是个O(n2)的时间复杂度的算法,但是有没有一种可能,这个算法可以被优化到O(n),这就是技巧一:有效的循环合并
技巧一:有效的循环合并
我们可以分析一下刚才删除的过程,其实有一些动作是多余的,我们其实没有必要去移动一个可能会被删除的元素:当我们找到一个偶数时,实际上后边的元素中依旧可能存在偶数,那么我们将这些偶数移动到前面就是一个多余的动作,因为以后依旧要被删除,其后边的元素仍旧面临的再一次循环的境地,因此我们是不是可以通过某种手段避免无效的遍历移动呢?
1 | void dele_even_good(SeqList *l) |
这里采用了一边删除一边寻找的方式,将两个循环合并到了一起,从而有效的降低了时间复杂度。这种思想很适合于使用在处理一些你想无脑调API的时候的优化。
技巧二:为什么不试试多次就地逆置呢
众所周知,就地逆置是处理一些奇奇怪怪需要一大堆循环的题目的利器,比如互换两块顺序表的位置:
有一个数组A[m+n],要求将这前m个元素和后n个元素前后互换那么就地逆置就很有用了,我们只需要整体一次,再局部两次:
(1,2,3,……,m,m+1,……,m+n)
第一次就地逆置:(m+n,……,m+1,m,……,3,2,1)
第二次局部逆置:(m+1,……,m+n,m,……,3,2,1)
第三次局部逆置:(m+1,……,m+n,1,2,3,……,m)
漂亮!
技巧三:空间换时间,随机存取之光
随机存取就是一个很爽的性质,这意味着只要我们找到了元素的index就可以迅速的锁定元素,比如现在需要给一个顺序剔除重复元素,那么我怎么知道元素重复了呢?我们可以再来一个顺序表通过哈希的方式得到,举个例子:
假如我们扫描到了一个元素3,那么我们就将hash[3]=1(即新创建的顺序表)的位置,以此类推,那么当我们每扫描到一个元素的时候,只需要其比较hash[data]是否值为1,如果为1那么就重复了,再去执行我们前面做好的删除算法即可。
总结
还有一些顺序表的算法题目,就比较没规律了,需要通过画图,手动运算一下,来找一下其中的规律,嗯,这些应付普通考试已经够了,剩下等我刷刷Leecode再行总结。