数据算法教程-学习数据结构和算法
数据结构与算法完形
在计算机科学领域,DSA一词代表数据结构和算法。
什么是数据结构与算法
数据结构和算法指的是研究组织和存储数据的方法,以及设计用于解决问题的程序(算法),这些程序和算法都是在这些数据结构上运行的。
怎样学习数据结构与算法
首先,也是最重要的一点,就是把整个程序分成小块,按顺序完成。从零开始学习DSA的完整过程可分为5个部分:
- 至少学习一门编程语言(由您自己选择)
- 学习数据结构
- 学习算法
- 了解时间和空间的复杂性
- 做DSA练习题
希望你已经学会了自己选择的编程语言,让我们继续下一步,在本DSA教程中学习DSA。在学习数据结构和算法的路线图中,最重要、最令人期待的阶段来了–开始学习DSA的阶段。DSA主题包括两个部分:
- 数据结构
- 算法
虽然他们是两件不同的事情,但却高度相关,要想最有效地学习它们,走正确的道路非常重要。如果您对先学哪个感到困惑,我们建议您阅读我们对该主题的详细分析:我应该先学什么-数据结构还是算法?
在这里,我们按照学习数据结构的流程,然后学习该数据结构所用的最相关、最重要的算法。
学习数据结构
数据结构是帮助在计算机内存中有效组织和存储数据的重要组件。它们提供了一种有效管理和操作数据的方法,使访问、插入和删除操作更加快捷。常见的数据结果包括数组、链表、栈、队列、树和图,根据当前问题的要求,各自发挥特定的作用。理解数据结构是设计高效算法和优化软件性能的基础。
要学习的常用数据结构:
- 数组
数组是一种线性数据结构,用于存储相同数据类型的元素集合。元素被连续分配到内存中,允许恒定时间访问。每个元素都有一个唯一的索引号。 - 字符串
字符串是一串字符,通常用来表示文本。它被视为一种数据类型,允许在计算机程序中操作和处理文本数据。 - 链表
链表是一种线性数据结构,它将数据存储在节点中,节点之间通过指针相连。与数组不同,链接列表不存储在连续的内存位置中。 - 矩阵
矩阵是按行和列排列的二维元素数组。它以矩阵网格表示,每个元素都位于行和列的交叉点上。 - 栈
栈是一种线性数据结构,按照特定的顺序执行操作。顺序可以是后进先出(LIFO)或先进后出(FILO)。后进先出(LIFO)是指最后插入的元素最先出来,先进后出(FILO)是指最先插入的元素最后出来。 - 队列
队列数据结构是计算机科学中的一个基本概念,用于按特定顺序存储和管理数据。它遵循“先进先出(FIFO)”原则,即第一个添加到队列中的元素就是第一个被移除的元素。 - 堆
堆是一种完整的二叉树数据结构,它满足堆属性:对于每个节点,其子节点的值都小于或等于自身的值。堆通常用于实现优先级队列,其中最小(或最大)的元素总是位于树的根部。 - 哈希表
哈希是一种使用称为哈希函数的数学公式,从大小可变的输入中生成固定大小输出(哈希值)的技术。哈希用于确定数据结构中存储项目的项目或位置,从而实现高效检索和插入。 - 树
树是一种非线性分层数据结构,由通过边连接的节点组成,顶层节点称为根节点,节点上有子节点。
10.图
图是一种非线性数据结构,由一组有限的顶点(或节点)和连接一对节点的边组成。
- 图形的遍历
- 广度优先搜索:逐级访问节点。
- 深度优先搜索:递归访问节点,每次探索一个分支。
- 图形的应用
- 社交网络
- 地图和导航
- 日程安排
- 数据挖掘
学习算法
1. 搜索算法
搜索算法用于在更大的数据集中查找特定数据。它有助于发现数据中是否存在目标值。搜索算法有多种类型,每种算法都有自己的方法和效率。
- 最常见的搜索算法:
- 线性搜索:从一端到另一端的迭代搜索。
- 二分查找:对排序数组进行分治搜索,每次迭代将搜索空间减半。
- 三元搜索:对排序数组进行分治搜索,每次迭代将搜索空间分为三部分。
- 其它搜索算法:
- 跳跃搜索
- 插值搜索
- 指数搜索
2. 排序算法
排序算法用于按特定顺序排列列表中的元素,如按数字或字母顺序排列。它以系统的方式组织项目,使搜索和访问特定元素变得更加容易。
排序算法种类繁多,一些广泛使用的算法如下:
排序算法 | 描述 |
---|---|
冒泡排序 | 迭代比较相邻的元素,如果他们的顺序不一致,就将他们对调。每次比较时,最大的元素会“冒泡”到列表末尾。 |
选择排序 | 查找列表未排序部分的最小元素,并将其与第一个元素交换。重复此过程,直到整个列表排序完毕。 |
插入排序 | 通过将每个未排序的元素插入到排序部分的正确位置,逐个建立排序列表。 |
快速排序 | 一种分治算法,它选择一个中轴元素,根据中轴将列表分成两个子列表,并递归地对子列表应用相同的过程。 |
归并排序 | 另一种分治算法,它递归地将列表分成更小的的子列表,对它们进行排序,然后再将它们合并在一起,得到排序后的列表。 |
3. 分治算法
分治算法采用递归策略解决问题,将问题划分为更小的子问题,解决这些子问题,并将解决方案合并以获得最终解决方案。
步骤:
- 分割:将问题划分为更小的、独立的子问题。
- 解决:递归解决每个子问题。
- 组合:合并子问题的解,得到最终解。
样例:
- 合并排序:将数组分为两半,对每一半递归排序,然后合并已排序的两半。
- 快速排序:选择一个基准元素,根据基准将数组划分为两个子数组,并递归地对每个子数组排序。
- 二分查找:反复将搜索空间一分为二,直到找到目标元素或搜索空间耗尽。
4. 贪婪算法
顾名思义,这种算法一次一个片段地建立解决方案,并选择能带来最明显、最直接利益的下一个片段,也就是说,哪个是当时最优的选择。因此,选择局部最优解也能得到全局解的问题最适合用贪婪法来解决。贪婪算法的一些重要问题包括:
算法 | 描述 |
---|---|
分数可纳包 | 求在允许零散放入物品的情况下,可放入背包的物品的最大总价值。 |
戴克斯特拉算法 | 查找加权图中从源顶点到所有其它顶点的最短路径。 |
克鲁斯卡尔算法 | 查找加权图的最小生成树。 |
赫夫曼编码 | 为一组符号创建最佳前缀码,最大限度地减少编码总长度。 |
5. 递归算法
递归是一种编程技术,在这种技术中,函数在其自身定义的范围内调用自身。它通常用于解决可分解为同一问题的更小实例的问题 。例如汉诺塔、阶乘计算和斐波那契数列等。
步骤:
- **基本情况:**定义一个停止递归调用并提供解决方案的条件。
- **递归情况:**确定将问题分解成更小实例的步骤,并进行递归调用。
- **返回:**从递归调用中返回解法,并将它们结合起来解决原始问题。
递归算法之所以成为最常用的算法之一,是因为它构成了许多其它算法的基础,例如树遍历算法、图遍历算法、分而治之算法和回溯算法。
6. 回溯算法
如前所述,回溯算法源于递归算法,在递归解失败时可以选择回溯,也就是说,如果一个解失败了,程序会回溯到它失败的那一刻,并建立在另一个解的基础上。因此,基本上它会尝试所有可能的解决方案,并找到正确的解决方案。
在继续前进之前,您必须解决的一些重要和最常见的回溯算法问题有:
问题 | 描述 |
---|---|
骑士的巡演问题 | 找出骑士在棋盘上的走棋顺序,使其恰好走过每个方格一次。 |
迷宫中的老鼠 | 在以墙壁为障碍物的迷宫中寻找从起点位置到出口的路径。 |
N皇后问题 | 在NxN的棋盘上摆放N个皇后,确保没有两个皇后互相攻击。 |
子集和问题 | 确定给定集合中是否存在具有给定和的子集。 |
数独 | 解一道9x9的方格谜题,谜题中的数字从1到9,每一行、每一列和每一个3x3的子方格都包含所有的数字,且不重复。 |
7. 动态编程
动态编程是数学和计算机科学中使用的一种方法,通过将复杂问题分解为较简单的子问题来解决这些问题。通过对每个子问题只求解一次并存储结果,它避免了冗余计算,从而为各种问题提供了更高效的解决方案。
关键概念
- 最佳子结构:一个问题的最优解可以由其子问题的最优解构建而成。
- 重叠子问题:子问题经常在大问题中重复出现,导致冗余计算。
- 备忘录/制表:存储子问题的解决方案,避免重新计算。
在继续学习之前,您必须解决的一些重要和最常见的动态编程算法问题有:
问题 | 描述 |
---|---|
斐波那契数列 | 利用动态编程生成斐波那契数字,避免冗余计算。 |
最长共同序列 | 找出两个序列共同的最长子序列。 |
最长递增序列 | 查找元素按递增顺序排列的给定序列中最长的子序列。 |
0/1包问题 | 确定在不超过规定重量限制的情况下,选择具有给定重量和价值的物品所能获得的最大值。 |
棒材切割问题 | 将给定长度的棒材切割成若干块,并按照给定的价格出售,从而实现利润最大化。 |
硬币找零问题 | 确定使用一组给定面额的硬币找零的方法数量。 |
编辑距离 | 找出将一个字符串转换成另一个字符串所需要的最少操作数(插入、删除、替换)。 |
子集和问题 | 确定给定集合中是否存在具有给定和的子集。 |
最长的回文子序列 | 找出给定序列中最长的回文子序列。 |
最大子阵列总和 | 查找给定一维数组中总和最大的连续子数组。 |
等分子集和 | 确定是否有可能将给定集合划分为两个总和相等的子集。 |
最小成本路径 | 寻找从给定网格左上角到右下角的最小成本路径。 |
最大产品子数组 | 查找给定一维数组中乘积最大的连续子数组。 |
8. 图形算法
数据结构和算法中的图形算法是一套用于解决与图相关问题的技术和方法,图是节点和边的集合。这些算法旨在对图形执行各种操作,如搜索、遍历、查找最短路径和确定连通性。它们对于解决网络路由、社交网络分析和资源分配等各种现实问题至关重要。
主题 | 主题描述 | 算法 | 算法描述 |
---|---|---|---|
图形遍历 | |||
最小生成树 | 最小生成树是连接图中所有节点而不形成循环的最小树,通过添加尽可能短的边来实现。 | 克鲁斯卡尔算法 ------------- 普里姆算法 | 它能为连通的加权图找到最小生成树。它添加不形成循环的最小权重边。 ------------------------------------------- 它也能为连通的加权树找到最小生成树。它添加了连接两棵树的权重最小的边。 |
拓扑排序 | 拓扑排序是有向无环图(DAG)中顶点的线性排序,对于从顶点u到顶点v的每条有向边,在排序中u都排在v之前 | 卡恩算法 ------------- 基于DFS的算法 | 卡恩算法可找到有向无环图(DAG)的拓扑排序 ------------------------------------------- 基于DFS的算法使用深度优先搜索在有向无环图中执行拓扑排序。 |
最短路径 | 图中的最短路径是指图中两个顶点之间的路径,与相同两个顶点之间的所有其他路径相比,该路径沿边的权重之和最小。 | 弗洛伊德-沃肖尔算法 贝尔曼福特算法 约翰逊算法 | 在O(V^3)时间内找出所有节点对之间的最短路径。 以O(V * E)的时间从单个源头查找最短路径。 在O(V^2logV + V * E)时间内找到所有顶点对之间的最短路径。 |
强连接组件 | 有向图的强连接组件(SCC)是一个最大的顶点集合,该集合中的每个顶点都有一条路径通向集合中的其他顶点。 | 科萨拉朱算法 塔扬算法 | Kosaraju算法是一种两步算法,可以高效地找到有向图的强连接成分(SSC) 塔扬算法是另一种在有向图中寻找SCC的高效算法 |
9. 模式搜索
模式搜索是DSA的一项基本技术,用于在给定文本中查找特定模式出现的次数。下面是一些标准的模式搜索算法:
算法 | 描述 | 时间复杂度 |
---|---|---|
蛮力 | 它将模式与文本的每个子字符串进行比较 | O(mn) |
克努特-莫里斯-普拉特 | 它使用预先计算的故障函数来跳过不必要的比较 | O(m + n) |
博耶-摩尔 | 它从右到左比较模式,跳过最后一个不匹配的字符 | O(mn) |
拉宾-卡普 | 它使用哈希算法快速检查潜在的匹配项 | O(m + n) |
相关主题
- 模式搜索教程
- 模式搜索练习题
10. 数学算法
数学算法是一类解决数学概念相关问题的算法。它们被广泛用于各个领域,包括计算机制图、数值分析、优化和密码学。
算法 | 描述 |
---|---|
GCD和LCM | 求两个数的最大公约数和最小公倍数 |
质因式分解 | 将一个数字分解成质因数 |
斐波那契数字 | 生成斐波那契数列,其中每个数字都是前两个数字之和 |
加泰罗尼亚语数字 | 计算带有给定括号对数的有效表达式的数量 |
模块化算术 | 根据给定值对数字进行算术运算 |
欧拉梯度函数 | 数出小于给定数的正整数中与概数相对质数的个数 |
nCr计算 | 计算二项式系数,它表示从n个元素集合中选择r个元素的方法数 |
质数和质数检验 | 确定给定的数字是否是质数,并高效地找到质数 |
筛分算法 | 找出给定极限以内的所有质数 |
相关主题
- 数学算法练习题
11. 几何算法
几何算法是一类解决几何相关问题的算法。几何算法对于解决计算机科学中的各种问题至关重要,例如:
算法 | 描述 |
---|---|
凸面船体 | 查找包含点集的最小凸多边形 |
最接近的一对点 | 找出集合中最接近的两个点 |
线性交叉 | 确定两条线是否相交,如果相交,则找出交点 |
多边形中的点 | 确定给定点是在多边形内部还是在外部 |
相关主题
- 几何算法练习题
12. 比特算法
比特算法是对数字的各个比特进行运算的算法。这些算法操纵数字的二进制表示,以执行位操作、位逻辑运算(AND、OR、XOR)、可变位移设置或清除数字中的特定位等任务。比特算常用于需要高效处理单个比特的底层编程、密码学和优化任务中。
|主题| 描述 |
|–|–|
| 位移 | 将比特向左或向右移动指定的位数 |
| 左移(<<) | 向左移动比特,实际上是将数字乘以2 |
| 右移(>>) | 向右移动比特,实际上是将数字除以2 |
| 提取位 | 使用掩码从整数中提取特定位 |
| 设置位 | 使用掩码将整数中的特定位设置为1 |
| 清除位 | 使用掩码将整数中的特定位设置位0 |
| 切换位 | 使用异或(^)来切换整数中的特定位 |
| 计数设定位 | 计算整数中设置位(1)的数量 |
相关主题
- 比特算法教程
- 比特算法练习题
13. 随机算法
随机算法是利用随机性解决问题的算法。它们利用随机输入来实现目标,往往会带来更简单或更高效的解决方案。
随机算法的类型
- 拉斯维加斯:结果总是正确的,但运行时间是随机的。
- 蒙特卡洛:可能产生错误结果的概率较小,但运行时间通常较快。
使用随机算法的重要算法
算法 | 描述 |
---|---|
快速排序 | 平均时间复杂度为O(n log n)的随机排序算法。 |
跳过列表 | 一种提供快速搜索和插入操作的随机数据结构。 |
布鲁姆过滤器 | 用于高效集合成员测试的概率数据结构。 |
14. 分支与边界算法
分支与边界算法是一种用于组合优化问题的方法,用于系统地搜索最佳解决方案。它的工作原理是将问题划分为更小的子问题或分支,然后根据最优解的界限消除某些分支。这一过程一直持续到找到最佳解决方案或探索完所有分支为止。
分支与边界算法的标准问题:
独特问题 | 描述 |
---|---|
使用分支和边界的0/1可纳包 | 解决0/1可纳包的分支和约束方法的实施细节 |
使用最小成本分支和边界的0/1可纳包 | 利用最小成本分支和边界技术解决0/1可纳包问题 |
使用分支和边界法解决八数码问题 | 应用分支和约束来解决八数码问题–一种流行的滑动拼图游戏 |
使用分支与边界法解决N个皇后问题 | 利用分支和约束找到N个皇后问题的解决方案,这是一个经典的国际象棋问题 |
学习复杂度
数据结构与算法中,主要目标是切实有效地解决问题。要确定程序的效率,我们看两种类型的复杂度:
- 时间复杂度:这将告诉我们代码运行所需的时间。
- 空间复杂度:这将告诉我们代码使用了多少内存。
渐进符号
为了比较算法的效率,我们使用了渐进符号,这是一种数学工具,可以根据输入大小估算时间,而无需运行代码。它侧重于程序中基本操作的数量。
符号 | 描述 |
---|---|
大O | 描述最坏情况,提供算法的时间上限 |
欧米伽(Ω) | 描述最佳情况,提供算法的时间下限 |
西塔(Θ) | 表示算法的平均复杂度 |
最常用的代码分析符号是大O符号,就输入大小提供运行时间或内存使用上限。
相关主题
- 通过简单例子了解时间复杂度
- 不同数据结构的时间复杂度
- 如何分析循环以进行算法复杂度分析
- 时间复杂度分析练习题
练习题小抄
从以下文章中整理出的问题列表:
- 桑迪普-贾因的DSA路线图
- SDE SHEET - 准备SDE的完整指南
- GeeksforGeeks总表 - 所有小抄列表