学算法先学数据结构?是否是无稽之谈?

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学算法先学数据结构?是否是无稽之谈?相关的知识,希望对你有一定的参考价值。

前言

  「 数据结构 」「 算法 」 是密不可分的,两者往往是「 相辅相成 」的存在,所以,在学习 「 数据结构 」 的过程中,不免会遇到各种「 算法 」
  到底是先学 数据结构 ,还是先学 算法,我认为不必纠结这个问题,一定是一起学的。
  数据结构 常用的操作一般为:「 增 」「 删 」「 改 」「 查 」。基本上所有的数据结构都是围绕这几个操作进行展开的。
  那么这篇文章,作者将主要来聊聊:

「 算法和数据结构 」

10分钟过一遍算法学习路线 | 面试 | 蓝桥杯 | ACM

完整版视频地址

专栏定位适宜人群
「 光天化日学C语言 」「 入门 」没有任何语言基础
「 LeetCode零基础指南 」「 初级 」零基础快速上手力扣
「 C语言入门100例 」「 中级 」零基础持续C语言练习教程
「 算法零基础100讲 」「 高级 」零基础持续算法练习教程
「 画解数据结构 」「 高级 」「 推荐 」 数据结构动图教程
「 算法进阶50讲 」「 资深 」进阶持续算法练习教程
「 LeetCode算法题集汇总 」「 资深 」全面的力扣算法题练习集锦
「 夜深人静写算法 」「 资级 」竞赛高端算法集锦

  在学习数据结构的过程中,如果你能够自己把图画出来,并且能够描述整个 「 增 」「 删 」「 改 」「 查 」 的过程,那么说明你已经真正理解了数据结构的真谛,来看下下面几张图:




文章目录

一、算法和数据结构的重要性

1、为什么要学习算法

  如果你只是想学会写代码,或许 「 算法与数据结构 」 并不是那么重要,但是,想要进一步发展自己的事业,「 算法与数据结构 」 是必不可少的。
  现在一些主流的大厂,诸如:字节、网易、腾讯、阿里、美团、京东、滴滴 等等,在面时都会让候选人写一道 「 算法题 」 ,如果你敲不出来,可能你的 offer 年包就打了骨折,或者直接与 offer 失之交臂,都是有可能的。
  当然,它不能完全代表你的编码能力(因为有些算法确实是很巧妙,加上紧张的面试氛围,想不出来其实也是正常的),但是你能确保面试官是这么想的吗?我们要做的是十足的准备,既然决定出来,offer 当然是越高越好,毕竟大家都要养家糊口,房价又这么贵,如果能够在算法这一块取得先机,也不失为一个捷径。
  所以,你问我算法和数据结构有什么用?我可以很明确的说,和你的年薪息息相关。当然,面试中 「算法与数据结构」 知识的考察只是面试内容的一部分。其它还有很多面试要考察的内容,当然不是本文主要核心内容,这里就不做展开了。

2、如何有效的学习

  这篇文章中,我会着重讲解一些常见的 「 算法和数据结构 」 的设计思想,并且配上动图。主要针对面试中常见的问题和新手朋友们比较难理解的点进行解析。当然,后面也会给出面向算法竞赛的提纲,如果有兴趣深入学习的欢迎在评论区留言,一起成长交流。
  零基础学算法的最好方法,莫过于 「 刷题 」 了。任何事情都是需要 「 坚持 」 的,刷题也一样,没有刷够足够的题,就很难做出系统性的总结。所以上大学的时候,我花了三年的时间来刷题, 工作以后还是会抽点时间出来刷题。
  当然,每天不需要花太多时间在这个上面,把这个事情做成一个 「 规划 」 ,按照长期去推进。反正也没有 KPI 压力,就当成是工作之余的一种消遣,还能够提升思维能力。所谓: 「 十年磨一剑,今朝把示君 」

3、坚持并且把它当成兴趣

  相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」
  如果你满足如下:
   ( 1 ) (1) (1) 有强烈欲望「 想要学好C语言 」的人
   ( 2 ) (2) (2) 有强烈欲望「 想要学好C++ 」的人
   ( 3 ) (3) (3) 有强烈欲望「 想要学好数据结构 」的人
   ( 4 ) (4) (4) 有强烈欲望「 想学好算法 」的人
   ( 5 ) (5) (5) 有强烈欲望「 想进大厂 」的人
  如果你满足以上任意一点,那么,我们就是志同道合的人啦!
  🔥联系作者,或者扫作者主页二维码加群,加入我们吧🔥

4、首先要有语言基础

  单纯学习语言未免太过枯燥乏味,所以建议一边学习一遍刷题,养成每天刷题的习惯,在刷题的过程中巩固语法,每过一个题相当于是一次正反馈,能够让你在刷题旅途中酣畅淋漓,从而更好的保证你一直坚持下去,在没有任何算法基础的情况下,可以按照我提供的专栏来刷题,这也是上上个视频提到的 九日集训 的完整教材,主要有以下几个内容:

  这个专栏主要讲解了一些 LeetCode 刷题时的一些难点和要点,主要分为以下几个章节,并且会持续补充一些方法论的文章。文章有试读,可以简单先看一看试读文章。

🍠《LeetCode零基础指南》🍠

导读
(第一讲)函数
(第二讲)循环
(第三讲)数组
(第四讲)指针
(第五讲)排序
(第六讲)贪心
(第七讲)矩阵
(第八讲)二级指针
(第九讲)简单递归

5、九日集训

  「 九日集训 」是博主推出的一个能够白嫖付费专栏「 LeetCode零基础指南 」的活动。通过 「 专栏中的联系方式 」 或者 「 本文末尾的联系方式 」 联系博主,进行报名即可参加。九日一个循环,第二期计划 「 2021.12.02 」 开启。

  玩法很简单,每天会开启一篇试读文章,要求有三点:
  1)阅读完文章后,课后习题 「 全部刷完 」(都能在文中找到解法,需要自己敲一遍代码);
  2)写 「 学习报告 」 并发布社区 九日集训(每日打卡) 频道
  3)在 「 打卡帖 」 提交 「 学习报告 」 链接;

  完成以上三点后方可晋级到下一天,所有坚持到 9天 的同学,会成为 「 英雄算法联盟合伙人 」 群成员,只限500个名额,优胜劣汰,和精英在一起,无论是沟通,学习,都能有更好的发展,你接触到的人脉也都是不一样的,等找工作的时候,我也会为大家打通 hr 和猎头,让你前程无忧~
  详细规则参见:九日集训规则详解
  目前第四轮「 九日集训 」已经进行到第四天,即将开启第五轮。

6、零基础如何学习算法

  数学是算法的基石,可以先从刷数学题开始。
  LeetCode上的题目相比ACM来说,数学题较少,所以对数学有恐惧的同学也不必担心,比较常见的数学题主要有:位运算,线性代数,计算几何,组合数学 ,数论,概率论。

板块题数
位运算30
线性代数20
计算几何5
组合数学5
数论5
概率论5

1)位运算

  位运算主要有:位与、位或、按位取反、异或、左移 和 右移。对应的文章可以看:

(第42讲) 位运算 (位与) 入门
(第44讲) 位运算 (位或) 入门
(第46讲) 位运算 (异或) 入门
(第48讲) 位运算 (左移) 入门
(第49讲) 位运算 (右移) 入门
(第50讲) 位运算 (取反) 入门

  位运算是计算机的精华所在,是必须掌握的内容。大概每个运算操作刷 三 到 五 题就基本有感觉了。

2)线性代数

  线性代数在刷题中,主要内容有 矩阵运算高斯消元。矩阵在程序中的抽象就是二维数组。如下:

(第七讲)矩阵

  高斯消元是求解多元一次方程组的,一般在竞赛中会遇到,面试一般不问,因为面试官可能也不会。

夜深人静写算法 (十六) 高斯消元

3)计算几何

  数论 是 ACM 中一个比较重要的内容,至少一旦出现,一定不会是一个水题,编码量较大,但是在 LeetCode 中题型较少,可以适当掌握一些基础内容即可。对应文章如下:

夜深人静写算法 (四)- 计算几何入门
夜夜深人静写算法(十二)- 凸包

4)数论

  数论 是 ACM 中一个比较重要的内容,但是在 LeetCode 中题型较少,可以适当掌握一些基础内容即可。对应文章如下:

夜深人静写算法 (三) 初等数论入门

5)组合数学 和 概率论

  组合数学 和 概率论,在 LeetCode 中题目较少,有兴趣可以刷一刷,没有兴趣就不要去刷了,毕竟兴趣才是最好的老师。对应的文章如下:

(第4讲) 组合数
(第30讲) 概率问题

7、零基础如何学习数据结构

  学习数据结构之前,选择一款相对来说心仪的教程是必不可少的,我这里准备了一个用动画来解释数据结构的教程,在我这也有,就是:

🌳《画解数据结构》🌳

  这是我目前来说,写的最用心的一个教程,里面汇集了大量的动图,目前更新已经过半,好评如潮。
  当然,一边学习,一边做一些练习题是必不可少的,接下来就是推荐一个我自己整理的题集了,这个题集汇集了大量的算法。可以帮你在前行的路上扫平不少障碍。
🌌《算法入门指引》🌌

  在看上述题目时,如果遇到难以解决的问题,可以参考如下解题报告专栏:
🌌《算法解题报告》🌌

8、数据结构和算法是相辅相成的

  如果你在刷题的过程中,已经爱上了算法,那么恭喜你,你将会无法自拔,一直刷题一直爽,在遇到一些高端的算法时,也不要惊慌,这里推荐一个竞赛选手金典图文教程,如下:

💜夜深人静写算法💜

二、数据结构是根基

  学习算法,数据结构是根基,没有一些数据结构做支撑,这个算法都没有落脚点,任何一个简单的算法都是需要数据结构来支撑的,比如最简单的算法,

1、数组

内存结构:内存空间连续
实现难度:简单
下标访问:支持
分类:静态数组、动态数组
插入时间复杂度 O ( n ) O(n) O(n)
查找时间复杂度 O ( n ) O(n) O(n)
删除时间复杂度 O ( n ) O(n) O(n)

一、概念

1、顺序存储

  顺序存储结构,是指用一段地址连续的存储单元依次存储线性表的数据元素。

2、存储方式

  在编程语言中,用一维数组来实现顺序存储结构,在C语言中,把第一个数据元素存储到下标为 0 的位置中,把第 2 个数据元素存储到下标为 1 的位置中,以此类推。

3、长度和容量

  数组的长度指的是数组当前有多少个元素,数组的容量指的是数组最大能够存放多少个元素。如果数组元素大于最大能存储的范围,在程序上是不允许的,可能会产生意想不到的问题,实现上是需要规避的。

  如上图所示,数组的长度为 5,即红色部分;容量为 8,即红色 加 蓝色部分。

4、数据结构定义

#define MAXN 1024
#define DataType int        // (1)

struct SeqList 
    DataType data[MAXN];    // (2)
    int length;             // (3)
; 
  • ( 1 ) (1) (1) 数组类型为DataType,定义为int
  • ( 2 ) (2) (2) SeqList定义的就是一个最多存放MAXN个元素的数组,MAXN代表数组容量;
  • ( 3 ) (3) (3) length代表数组长度,即当前的元素个数。

二、常用接口实现

1、索引

  索引 就是通过 数组下标 寻找 数组元素 的过程。C语言实现如下:

DataType SeqListIndex(struct SeqList *sq, int i) 
    return sq->data[i];          // (1)

  • ( 1 ) (1) (1) 调用方需要注意 i i i 的取值必须为非负整数,且小于数组最大长度。否则有可能导致异常,引发崩溃。
  • 索引的算法时间复杂度为 O ( 1 ) O(1) O(1)

2、查找

  查找 就是通过 数组元素 寻找 数组下标 的过程,是索引的逆过程。
  对于有序数组,可以采用 二分 进行查找,时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n);对于无序数组,只能通过遍历比较,由于元素可能不在数组中,可能遍历全表,所以查找的最坏时间复杂度为 O ( n ) O(n) O(n)
  简单介绍一个线性查找的例子,实现如下:

DataType SeqListFind(struct SeqList *sq, DataType dt) 
    int i;
    for(i = 0; i < sq->length; ++i)  // (1)
        if(sq->data[i] == dt) 
            return i;                 // (2)
            
    
    return -1;                        // (3)

  • ( 1 ) (1) (1) 遍历数组元素;
  • ( 2 ) (2) (2) 对数组元素 和 传入的数据进行判等,一旦发现相等就返回对应数据的下标;
  • ( 3 ) (3) (3) 当数组遍历完还是找不到,说明这个数据肯定是不存在的,直接返回 − 1 -1 1

3、获取长度

  获取 数组的长度 指的是查询当前有多少元素。可以直接用结构体的内部变量。C语言代码实现如下:

DataType SeqListGetLength(struct SeqList *sq) 
    return sq->length; 

4、插入

  插入接口定义为:在数组的第 k k k 个元素前插入一个数 v v v。由于数组是连续存储的,那么从 k k k 个元素往后的元素都必须往后移动一位,当 k = 0 k=0 k=0 时,所有元素都必须移动,所以最坏时间复杂度为 O ( n ) O(n) O(n)。C语言代码实现如下:

int SeqListInsert(struct SeqList *sq, int k, DataType v) 
    int i;
    if(sq->length == MAXN) 
        return 0;                        // (1) 
     
    for(i = sq->length; i > k; --i) 
        sq->data[i] = sq->data[i-1];     // (2) 
    
    sq->data[k] = v;                     // (3) 
    sq->length ++;                       // (4) 
    return 1;                            // (5) 

  • ( 1 ) (1) (1) 当元素个数已满时,返回 0 0 0 代表插入失败;
  • ( 2 ) (2) (2) 从第 k k k 个数开始,每个数往后移动一个位置,注意必须逆序;
  • ( 3 ) (3) (3) 将第 k k k 个数变成 v v v
  • ( 4 ) (4) (4) 插入了一个数,数组长度加一;
  • ( 5 ) (5) (5) 返回 1 1 1 代表插入成功;

5、删除

  插入接口定义为:将数组的第 k k k 个元素删除。由于数组是连续存储的,那么第 k k k 个元素删除,往后的元素势必要往前移动一位,当 k = 0 k=0 k=0 时,所有元素都必须移动,所以最坏时间复杂度为 O ( n ) O(n) O(n)。C语言代码实现如下:

int SeqListDelete(struct SeqList *sq, int k) 
    int i;
    if(sq->length == 0) 
        return 0;                        // (1) 
     
    for(i = k; i < sq->length - 1; ++i) 
        sq->data[i] = sq->data[i+1];     // (2) 
     
    sq->length --;                       // (3) 
    return 1;                            // (4)  

  • ( 1 ) (1) (1) 返回0代表删除失败;
  • ( 2 ) (2) (2) 从前往后;
  • ( 3 ) (3) (3) 数组长度减一;
  • ( 4 ) (4) (4) 返回1代表删除成功;
  • 想要了解更多数组相关内容,可以参考:《画解数据结构》(1 - 1)- 数组

2、链表

内存结构:内存空间连续不连续,看具体实现
实现难度:一般
下标访问:不支持
分类:单向链表、双向链表、循环链表、DancingLinks
插入时间复杂度 O ( 1 ) O(1) O(1)
查找时间复杂度 O ( n ) O(n) O(n)
删除时间复杂度 O ( 1 ) O(1) O(1)

一、概念

  • 对于顺序存储的结构,如数组,最大的缺点就是:插入删除 的时候需要移动大量的元素。所以,基于前人的智慧,他们发明了链表。

1、链表定义

  链表 是由一个个 结点 组成,每个 结点 之间通过 链接关系 串联起来,每个 结点 都有一个 后继节点,最后一个 结点后继结点空结点。如下图所示: