数据结构与算法的学习——思维的学习与高屋建瓴

  • 一、算法思维的练习
  • 二、练习过程
    • 1、是什么(WHat)
    • 2、怎么用(How)
  • 三、进阶思考
    • 1、栈的思考
    • 2、树的问题
    • 3、关于递归
      • (1)排列组合问题:对[A, B ,C, D]进行全排列并输出
      • (2)归并排序问题
      • (3)再看快排问题
      • (4)flatten问题
      • (5)其他类似问题。
  • 四、总结
  • 参考

由于从没有系统的学习过编程,也没有完整的上过计算机课程,现在工作经常感觉特别困难,所以决心从头开始练习,从数据结构与算法开始慢慢啃,然后把自己的心得记录下,总结学习才能使人进步。

一、算法思维的练习

学算法到底学什么,我一开始是疑惑的,因为没有概念。当学习了才知道,首先得了解计算机是怎么工作的,学习数据结构;然后学习逻辑思维——就是算法,站在不同角度看一致的问题,同时对问题进行优化。计算机是处理数据的工具,不断的做重复工作,那必然有一定的结构,其中最最基本的结构就是数组和链表。或者说其实,数据结构的存储方式只有两种:数组(顺序存储)和链表(链式存储)。【1】

那常见的树、图、队列那些呢?这就得用上计算机递归的思维,所有这些复杂的上层结构是由基础结构组成的,由其封装或者特殊的操作而得来的结构。比如树,就可以用数组实现,也可以用链表实现,只是在逻辑上是看成一棵树;堆,本质是一个完全二叉树,存储方式却是数组,只是按照堆的计算方式处理;队列是一种先进先出的结构,也可以用数组或者链表实现,在应用上可以用来实现二叉树的层序遍历,层序遍历也即广度优先搜索,那就就将树和队列结合起来了,但在最底层仍然是数组或者链表的操作。

所以其实,数据结构的本质就是:定义一种性质,然后维护一种性质。某一种性质可以解决一些问题,然后再结合另一种结构又会衍生出新的高级结构。所以最本质的就是数据的基础存储方式:数组和链表。

数组是什么?一块连续的存储区域,每一个位置都知道值,也知道总大小,一次性分配足够,如果要更大,那只能把数据复制过去重新分配。类似你的抽屉,放好东西了,你知道什么东西都在哪,但是如果再需要放更多东西(一个抽屉能放下),你只能申请一个新的更大的抽屉,然后把所有东西移过去,重新放好。

那链表呢?链表是只知道头结点的值,剩下是靠指针指向下一个元素的位置,然后依次串起来。存储空间是不连续的,所以你不知道链表有多大,每一次你要看中间某个值的时候,只能从头结点一个一个访问。但如果你要在中间插入或删除一个值,直接动它的前后指针指向就可以了,跟头结点或者后续的节点没有关系。类似什么呢?想了很久,觉得像自行车的链条,或者双节棍(李连杰在冒险王电影里的九节鞭)?有了第一个,后面是依次串起来的,然后中间随意插和删,所以也就不存在固定大小的问题。但是因为要存储指向元素的指针,所以消耗更多的存储空间。

回到数据结构的本质,如何维护一种性质呢?那就是对数据结构的操作了:增删查改。对任何数据结构最重要的操作就是增删查改,在计算机上数组和链表的操作各有优势:数组方便查找和改值,时间复杂度O(1),但增加或删除元素的复杂将为O(n);链表的删除或插入元素时间复杂度为O(1),但查找和修改值将变得困难,时间复杂度为O(n)。

二、练习过程

个人觉得主要是两个过程:“是什么(What)”、“怎么用(How)”这两个问题。

1、是什么(WHat)

是什么,那就是原理性的探讨,首先有个基础认识,哪一个数据结构是怎么设计的,然后了解其基本操作就可以。

比如队列,就是一种先进先出的结构,就存在入队、出队、判满、判空的操作,然后两段都支持入队和出队,那就是双端队列,如果在出队时遵循某一规则就是优先队列,堆就是优先队列的实现方式,堆又是完全二叉树,所以又有二叉树的性质,这样就把数组、链表、队列、堆、树的一些性质都串起来了,一是方便理解,融会贯通,二是也更加方便记忆,毕竟学习最终的目的是记忆。

2、怎么用(How)

怎么用,那就是如何利用性质解决问题的时候,这时候需要刷题以及总结,遇到问题分析问题,然后找到核心的解决办法,很多时候就是不断的练习,找到了一套固定模式和思维。

首先,任何数据结构其基本操作都是增删查改,实现起来也就是遍历+访问。那怎么实现呢?又分线性和非线性。线性就是循环,以for、while为代表,非线性就是递归为代表。比如求数组中的第k大元素问题,那就是堆的问题,堆就是有这个性质:堆顶元素具有最大或最小,然后来不断弹出找到第k大元素,从而解决这类问题。还有二叉树的遍历问题,是前序、中序、后续、层序遍历中的哪种,然后想到递归问题,那就是设计递归函数的意义,需要定义哪些参数,还有边界停止条件,就可以直接得出结果了。千万不能试图跳进递归,因为一旦跳进去几乎就出不来了,人脑计算是非常有限的。。

最后其实也就是不断总结知识点,以及常用的解题思路,再加上不断练习,一定会有豁然开朗的那一天。

三、进阶思考

学习的时候经常是不断的堆砌知识点,无法做到统一,甚至举一反三。在经过一段时间的学习之后,总感觉有些东西是相通的。

1、栈的思考

栈是一种先进后出的结构,类似弹夹,或者羽毛球筒,只能在一边进和出。那么在处理问题的时候,比如元素进栈,那就+1;出栈,就-1。就非常适合处理符号匹配,校验语法,实现计算器这些功能。反过来+1代表进栈,-1代表出栈,一直加或者一直减,就拿符号处理举例:(3+(8-(2-(5-1*2))+4/2)),那么此时处理问题时就处理最里层括号的内容:(5-1*2),也就是栈的最顶层,然后再依次弹出,依次实现次里层直至结束。

再体会这个过程的时候,是否有感觉到:这其实是处理内容的包含关系?也即事件的包含关系,先解决子问题,再解决外面的大问题,再往外思考,函数的调用关系、递归其实就是此原理,那么递归和栈的关系基本就可以平齐了,也就是一类问题了。

2、树的问题

树的问题最重要一环是二叉树的问题,二叉树无非就是遍历的问题,遍历最重要的就是递归,那递归其实就是明确函数的意义,是否能拆解成子问题。就是当前的root应该做什么,根据函数定义递归调用子节点,做相同的任务。所以关键就是明确每个节点要做的事情。剩下的就是先处理左还是右或者根节点,确定遍历顺序,然后找到边界停止条件,实现过程。当然树的问题还有很复杂的,后续再继续更新。

3、关于递归

我们在学习算法的时候基本是这四个思路:暴力穷举,分而治之,逐步迭代,随机化。递归就是暴力穷举往分而治之的实现,就是找到子问题,然后自己调用自己完成某一个总问题。我们来看几个问题。

(1)排列组合问题:对[A, B ,C, D]进行全排列并输出

当我们对一个字符串或者数字列表完成全排列时,我们试试递归的思路。如果划开A,知道BCD的全排列,然后把A依次插入进去就行。要知道BCD的全排列,就得知道CD的全排列,把B依次插入进去。这样就转化成了子问题,且每个子问题的实现方式是一样的,那么就可以用递归实现。转换成代码如下。

def permutation(elements):if len(elements) == 0:  # 停止条件return [[]]head = elements[0]remain_parts = permutation(elements[1:])    # 拆分成子问题,不需要知道细节return [r[:i] + [head] + r[i:] for i in range(len(elements)) for r in remain_parts]
>> permutation(['A', 'B', 'C'])
[['A', 'B', 'C'], ['A', 'C', 'B'], ['B', 'A', 'C'], ['C', 'A', 'B'], ['B', 'C', 'A'], ['C', 'B', 'A']]

(2)归并排序问题

归并排序(merge sort)也是利用归并的思想不断分解成子问题递归进行求解,最终达到排序的目的。也就是把要排序的序列一分为二,然后再对两个子序列用同样的方法排序,直到两个数字有明显的顺序为止,再拼接起来即是归并的思想。示意图参考如下【2】。

代码如下:

def merge_sort(elements):if len(elements) == 1:return elementsmid = len(elements) // 2left, right = merge_sort(elements[:mid]), merge_sort(elements[mid:])sorted_result = []while left and right:left_head = left[0]right_head = right[0]if left_head < right_head:sorted_result.append(left_head)left.pop(0)else:sorted_result.append(right_head)right.pop(0)sorted_result += (left or right)return sorted_result
>> elements:  [-84, -10, -33, -19, 5, -21, 1, -73, -9, 92]
merge_sort: [-84, -73, -33, -21, -19, -10, -9, 1, 5, 92]

(3)再看快排问题

快速排序的思想是从数列中挑出一个元素,作为"基准值";然后重新排序数列,所有元素比基准值小的摆放在基准值前面,所有元素比基准值大的摆在基准值的后面。在这步之后,该基准值就处于数列的中间位置。再递归地把小于基准值元素的子数列和大于基准值元素的子数列进行排序。我们在实现的时候甚至可以随机选择一个数,然后对左右两边的数进行排列,并递归的实现,之后将两边拼接起来。

# 快排
def quick_sort(elements):if not elements:return []pivot = random.choice(elements)return quick_sort([e for e in elements if e < pivot]) + [pivot] + quick_sort([e for e in elements if e > pivot])
>> elements:  [12, 82, 34, -43, 47, 30, 49, -31, -60, 13]
quick_sort: [-60, -43, -31, 12, 13, 30, 34, 47, 49, 82]

还有选择排序也是可以用递归的思想实现。

(4)flatten问题

在深度学习模型输出的时候经常遇到shape转换问题,在刷LeetCode题目的时候也有遇到去括号的问题,这时候就需要flatten,都是类似:把第一个元素拿开,不断解决剩下的元素集合,确定好边界条件作为出口。上代码。

def flatten(elements):if not elements:return []if not isinstance(elements, tuple):return [elements]else:return flatten(elements[0]) + flatten(elements[1:])
>> elements:((1, 2), (3, 8), ((5, 7, 9), (0, 3)), ((4, 10), 6))
flatten():  [1, 2, 3, 8, 5, 7, 9, 3, 4, 10, 6]

(5)其他类似问题。

比如图的遍历问题,给定一个图,即可拿开一个元素,然后依次遍历剩下所有的节点,也是递归问题。
比如两字符串的编辑距离问题:先比较最后一个字符,然后比较剩下的字符串,再依次往前比较,直至字符串比较完。

四、总结

再回到我们学算法的始末,就是学习思维,以及利用强大的计算能力可以解决一些重复的问题。暴力穷举,不行就分而治之或者逐步迭代,结合固定的设计模式以及数据结构,总会慢慢摸出自己的门道的。在学习的时候看了一些文章,都有一些固定套路,在刷LeetCode题目的时候可以用上,而且很多需要熟悉,就是看到题目就想到某一思路去解题,部分参考了相关博客(见参考),学会高屋建瓴,框架思维。引用到本文如下。

14种编程模式【3】:

 1.滑动窗口2.二指针或迭代器3.快速和慢速指针或迭代器4.合并区间5.循环排序6.原地反转链表7.树的广度优先搜索(BFS)8.树的深度优先搜索(DFS)9.大顶堆与小顶堆10.子集11.经过修改的二叉搜索12.前 K 个元素13.K 路合并14.拓扑排序

五大算法设计思想【4】:

 1. 分治法2. 动态规划3. 贪心法4. 回溯法5. 分支限界法

学数据结构离不开LeetCode,对LeetCode上的题目进行大致分类【5】:

大类 具体类型
数据结构类型 数组,栈,队列,堆(优先队列),集合和映射
字符串类型
指针结构:链表,树结构(递归,遍历方式,查找树,字典树),图(二分图,拓扑排序)
并查集
复合的数据结构
算法设计类型 指针类型(快慢指针,双指针/滑动窗口)
二分查找
排序算法
优先搜索(广度优先,深度优先/回溯法)
动态规划
递归或分治
贪心算法
数学问题 位运算
因数和倍数
质数
数字处理
随机与取样

当我们以后学习某些数据结构或者刷某些题的时候,我们把这三个表拿出来,是哪一种题型,然后需要什么样的思想,使用什么编程模式去解题,慢慢的熟悉了套路,自然框架也就出来了,万变不离其宗,抓住事务的本质,找对方法,肯定能取得长足的进步的。

参考

【1】: 《labuladong》公众号
【2】: 图解排序算法之归并排序
【3】: 《图解面试算法》
【4】: 五大算法设计思想
【5】: 高畅-LeetCode 101:和你一起你轻松刷题(C++)

数据结构与算法的学习——思维的学习与高屋建瓴相关推荐

  1. 数据结构稀疏矩阵的加法十字链表_学习数据结构和算法的框架思维

    ----------- 通知:如果本站对你学习算法有帮助,请收藏网址,并推荐给你的朋友.由于 labuladong 的算法套路太火,很多人直接拿我的 GitHub 文章去开付费专栏,价格还不便宜.我这 ...

  2. labuladong的算法小抄_学习数据结构和算法的框架思维

    ----------- 通知:如果本站对你学习算法有帮助,请收藏网址,并推荐给你的朋友.由于 labuladong 的算法套路太火,很多人直接拿我的 GitHub 文章去开付费专栏,价格还不便宜.我这 ...

  3. 良心推荐两个学习数据结构和算法的利器,让学习像呼吸一样轻松

    数据结构和算法的重要性想必各位在江湖上早就有所耳闻.它对我们最直接的影响就是面试,一般来说,程序员一面都会涉及数据结构和算法知识,尤其是当前找工作比较难的情况下,各个公司会更加重视对候选人基本能力的考 ...

  4. 数据结构与算法心得笔记——零起点学习(一)

    从今天开始,我会跟大家分享一下本人学习数据结构和算法的心得体会,也算是今后的复习笔记,如有说错的地方还望各位大佬批评指正,我定虚心请教.同时也希望刚刚接触数据结构和算法的小白们能养成记笔记的习惯,等你 ...

  5. 数据结构和算法学多久_重新学习数据结构和算法

    数据结构和算法学多久 为什么? 我记得在我第一次参加计算机科学算法课程时 伊丽莎白市州立大学(ECSU)认为:"我得到了什么 我自己变成了?!". 材料令人生畏,而且(大部分时间) ...

  6. python数据结构与算法第六讲_Python 学习 -- 数据结构与算法 (六)

    栈 是一种 "操作受限"的线性表,只允许在一端插入和删除数据. 从功能是上来说,数组和链表确实可以替代栈,但是特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口 ...

  7. 什么是数据结构?什么是算法?怎么学习数据结构与算法?

    01 前言 学习算法,我们不需要死记硬背那些冗长复杂的背景知识.底层原理.指令语法--需要做的是领悟算法思想.理解算法对内存空间和性能的影响,以及开动脑筋去寻求解决问题的最佳方案.相比编程领域的其他技 ...

  8. 常年霸榜 Amazon 数据结构与算法领域 TOP3

    今天的主角是以下三本书中的第一本和第三本: 第一本是<程序员面试金典>,第二本是<算法导论>,第三本是<Python数据结构与算法分析>,中文版都已经由国内出版社引 ...

  9. java数据结构与算法之顺序表与链表深入分析

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结 ...

最新文章

  1. sentinel使用(结合OpenFeign)
  2. 好用!目前用下来最溜的MacOS微信多开工具!
  3. JVM中的STW和CMS
  4. cordova开发中,android端利用百度sdk定位。
  5. Excel 文件读取
  6. ubuntu中启动oracle数据库
  7. 不愧是Alibaba技术官,java数组实现单向链表
  8. 阿里P8架构师谈:分布式、集群、负载均衡、分布式数据一致性的区别与关联
  9. 自我投资,最好的方式就是写作
  10. LNMP一键自动安装脚本
  11. classdefnotfound本地不报错_四种解决Nginx出现403 forbidden 报错的方法
  12. 正好股票开户有色金属应声大涨
  13. 服务器上打开PHP文件却出现下载界面或者502
  14. 手机连接电脑linux系统怎么样,电脑(Linux/Windows)使用SSH远程登录安卓(Android)手机实现无线传输和管理文件(图文详解)...
  15. 视频直播本地测试服务器搭建
  16. 每天一个编程题·iOS开发算法提升计划(1)
  17. 在家看片利器,有Android App以及桌面应用(已开源)。
  18. 【读书笔记】《价值投资的秘密》
  19. 一个专家级软件架构师的自白书
  20. 什么是进程/线程/协程

热门文章

  1. 超详细的R语言热图之complexheatmap系列(1)
  2. 数据库之逻辑设计阶段(候选码、主码、外码、范式…)
  3. 分布式系统的冰与火与技术栈
  4. 东欧黑客入侵港股造市图利 半年涉款5300万
  5. Liferay 页面
  6. openjudge-noi-2.6-1775:采药
  7. Python中字符串的迷幻操作-----驻留机制的理解
  8. 南京邮电大学网络攻防平台WriteUP——WEB(上)
  9. 架构师小跟班:SSL证书免费申请及部署,解决页面样式错乱问题
  10. JavaScript 教程「6」:数组