大道至简-数据结构(开篇)

业界有三句流传很经典的话:

Program=data structure+algorithm (程序= 数据结构 + 算法)

Software=Program+Software Engineering ( 软件 = 程序 + 软件工程)

Software Company=software+Business Model (软件公司 = 软件 + 商业模式)

数据结构是程序的灵魂,也是支撑算法的基本骨架, 它能优化算法的运行效率,节约程序的内存使用,减少程序的运行时间等等,使很多不可能成为可能。

那么我们应该以什么样的方式去学习数据结构呢?以我个人的经验,我觉得要抓住以下三条主线:

①: 数组和链表 (即顺序存储和链式存储)

②: 时间复杂度(Time complexity)和空间复杂度(Space complexity)

③: CRUD(create增加、Read读取、Update修改、Delete删除)以及遍历方式

数组由于是紧凑连续存储,可以随机访问,通过索引快速找到对应元素,而且相对节约存储空间。但正因为连续存储,内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O ( N ) O(N) ;而且你如果想在数组中间进行插入和删除,每次必须搬移后面的所有数据以保持连续,时间复杂度 O ( N ) O(N)

链表因为元素不连续,而是靠指针指向下一个元素的位置,所以不存在数组的扩容问题;如果知道某一元素的前驱和后驱,操作指针即可删除该元素或者插入新元素,时间复杂度 O ( 1 ) O(1) 。但是正因为存储空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问;而且由于每个元素必须存储指向前后元素位置的指针,会消耗相对更多的储存空间。

为什么单单提出来数组和链表呢?不是还有栈,队列,树,图等等数据结构吗?

如果我们以「自底向上」的观点来看待数据结构,你会发现所有的数据结构都是基于上面两种结构,要么是基于数组,要么是基于链表,比如:

「图」这个数据结构,有邻接矩阵和邻接表两种的表示方法,邻接矩阵是基于二维数组的存储方式,而邻接表是基于链表的存储方式,再者,邻接矩阵虽然便于随机访问,但在「稀疏图」情况下,非常的浪费内存,而邻接表,虽然节约内存,但消耗了更多的时候对图的节点进行遍历。

再比如「散列表」这个数据结构,就是通过散列函数得到key,把元素映射到一个数组里。而解决散列冲突的方法,比如拉链法需要使用到链表,虽然很简单,但需要额外的空间来存储指针;而线性探查法就需要使用到数组,可以进行连续寻址,所以不需要指针的存储空间,但操作比拉链法要复杂。

这就是我最开始提出来的两条主线,存储结构和时空复杂度,邻接矩阵和邻接表,是一个非常典型的空间换时间和时间换空间的思想,一般情况很难做到两者兼顾,只能看情况来使用对应数据结构,比如在内存紧张的环境下,我们就应该使用邻接表,虽然会多耗费一些时间,但能保证内存安全的运行,而在内存比较富余的情况下,我们使用邻接矩阵加快程序的运行速度。

明白了上述的两条主线,其实你自己也能去创造各种各样的数据结构了,但不管你怎么创造,实际上还是离不开数组和链表这两种宏观意义上的原子结构。

而数据结构的基本操作就是围绕着上面两个基本原子结构来进行的,比如数组的插入和删除操作,时间复杂度就是 O ( n ) O(n) 的,而查找和更新的时间复杂度为 O ( 1 ) O(1) ,链表正好相反。

再者,数据结构的遍历无非就两种方式,顺序遍历和递归遍历(线性方式和非线性方式),数组使用的是顺序遍历,而链表兼顾顺序和递归遍历,对数据结构的CRUD实际上就是在遍历的基础上对存储的数据进行一系列的操作。

学习数据结构,一是学习其存储结构是如何定义的以及空间复杂度,二是学习其如何做CRUD,以及CRUD的时间复杂度。

总结:

  1. 学习数据结构抓住上述三条主线
  2. 学习数据结构可以使用自底向上的观点去看待它,任何数据结构都是基于数组或者链表这种宏观的原子结构来扩展的。
  3. 学习数据结构需要明白他们能使用的场合,结合实际,这样才能真正理解和运用。
发布了128 篇原创文章 · 获赞 20 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/u011544909/article/details/105444815
今日推荐