常见的数据结构与算法

文章目录

  • 前言
  • 一.常见的数据结构
    • 1.数组
    • 2.链表
    • 3.栈
    • 4.队列
    • 5.树
  • 二.排序
    • 1. 基本的排序算法
    • 2. 常考的排序算法
    • 3. 其他排序算法
  • 三.递归与回溯
    • 1.递归
    • 2.回溯
  • 四.深度与广度优先搜索
    • 1.深度优先搜索
    • 2.广度优先搜索
  • 五.动态规划
  • 六.二分搜索与贪婪
    • 1.二分搜索
    • 2.贪婪算法


前言

在这里简单介绍下常见的数据结构与算法,当然对于大多数人来说,数据结构与算法对于一名程序员来说是非常基本的基本功,刚开始在LeetCode官网上刷题也是一阵头疼,感觉无从下手,只有多刷多练,才能解决突破层层障碍,希望大家都能养成刷算法的好习惯。


一.常见的数据结构

1.数组

优点
构建非常简单
缺点
构建时必须分配一段连续的空间
查询某个元素是否存在时需要遍历整个数组,耗费 O(n) 的时间(其中,n 是元素的个数)
删除和添加某个元素时,同样需要耗费 O(n) 的时间
总结
当你在考虑是否应当采用数组去辅助你的算法时,请务必考虑它的优缺点。
力扣题型:给定两个字符串 s 和 t,编写一个函数来判断 t 是否是 s 的字母异位词。

2.链表

优点
链表能灵活地分配内存空间;
能在 O(1) 时间内删除或者添加元素,前提是该元素的前一个元素已知,当然也取决于是单链表还是双链表,在双链表中,如果已知该元素的后一个元素,同样可以在 O(1) 时间内删除或者添加该元素。
缺点
不像数组能通过下标迅速读取元素,每次都要从链表头开始一个一个读取;
查询第 k 个元素需要 O(k) 时间。
解题技巧
(1).利用快慢指针
经典题目:链表的翻转,寻找倒数第k个元素,寻找链表元素中间位置,判断链表是否有环
(2).构建一个虚假的链表头:一般用在要返回新的链表的题目中。

3.栈

特点
栈的最大特点就是后进先出(LIFO)。对于栈中的数据来说,所有操作都是在栈的顶部完成的,只可以查看栈顶部的元素,只能够向栈的顶部压⼊数据,也只能从栈的顶部弹出数据。
实现:
利用一个单链表来实现栈的数据结构。而且,因为我们都只针对栈顶元素进行操作,所以借用单链表的头就能让所有栈的操作在 O(1) 的时间内完成。
力扣题型
(1)给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
(2)根据每日气温列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

4.队列

特点:
和栈不同,队列的最大特点是先进先出(FIFO),就好像按顺序排队一样。对于队列的数据来说,我们只允许在队尾查看和添加数据,在队头查看和删除数据。
实现:
可以借助双链表来实现队列。双链表的头指针允许在队头查看和删除数据,而双链表的尾指针允许我们在队尾查看和添加数据。
双端队列:在队列的头尾两端能在O(1)的时间内进行数据的查看,添加和删除。
常用场景:实现一个长度动态变化的窗口或者连续区间

5.树

特点
树的结构十分直观,而树的很多概念定义都有一个相同的特点:递归,也就是说,一棵树要满足某种性质,往往要求每个节点都必须满足。例如,在定义一棵二叉搜索树时,每个节点也都必须是一棵二叉搜索树。
树的遍历:
(1)前序遍历(Preorder Traversal)
先访问根节点,然后访问左子树,最后访问右子树。在访问左、右子树的时候,同样,先访问子树的根节点,再访问子树根节点的左子树和右子树,这是一个不断递归的过程.
(2)中序遍历(Inorder Traversal)
先访问左子树,然后访问根节点,最后访问右子树,在访问左、右子树的时候,同样,先访问子树的左边,再访问子树的根节点,最后再访问子树的右边。
(3)后序遍历(Postorder Traversal)
先访问左子树,然后访问右子树,最后访问根节点。
力扣题型
(1).在一棵二叉树里,统计有多少棵子树,要求子树里面的元素拥有相同的数字。
(2)给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

二.排序

1. 基本的排序算法

冒泡排序(Bubble Sort)
基本思想:每一轮,从杂乱无章的数组头部开始,每两个元素比较大小并进行交换,直到这一轮当中最大或最小的元素被放置在数组的尾部,然后不断地重复这个过程,直到所有元素都排好位置。
插入排序(Insertion Sort)
基本思想:不断地将尚未排好序的数插入到已经排好序的部分

2. 常考的排序算法

归并排序(Merge Sort)
基本思想:核心是分治,就是把一个复杂的问题分成两个或多个相同或相似的子问题,然后把子问题分成更小的子问题,直到子问题可以简单的直接求解,最原问题的解就是子问题解的合并。归并排序将分治的思想体现得淋漓尽致。
一开始先把数组从中间划分成两个子数组,一直递归地把子数组划分成更小的子数组,直到子数组里面只有一个元素,才开始排序。
快速排序(Quick Sort)
快速排序也采用了分治的思想,把原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组。

拓扑排序(Topological Sort) :将图论里的顶点按照相连的性质进行排序(有向无环图),将问题用一个有向无环图(DAG, Directed Acyclic Graph)进行抽象表达,定义出哪些是图的顶点,顶点之间如何互相关联。可以利用广度优先搜索或深度优先搜索来进行拓扑排序

3. 其他排序算法

堆排序(Heap Sort)
桶排序(Bucket Sort)

三.递归与回溯

1.递归

算法思想:要懂得如何将一个问题的规模变小,再利用从小规模问题中得出的结果,结合当前的值或者情况,得出最终的结果。通俗来讲就是要把实现的递归函数,看成已经实现好的,直接利用解决子问题
代码实现
function fn(n){
第一步:判断输入或者状态是否非法
if(input/status is invalid){
return;
}
第二步:判断递归是否应当结束
if(match condition){
return some values;
}
第三步:缩小问题规模
result1 = fn(n1)‘

result2 =fn(n2)

第四步:整合结果
return combine(result1,result2)
时间复杂度:T(n)=a.T(n/b)+f(n) O(nlogba)
}
力扣题型:汉诺塔,找到所有长度为n的中心对称数,

2.回溯

算法思想:回溯实际上是一种试探算法,这种算法跟暴力搜索最大的不同在于,在回溯算法里,是一步一步地小心翼翼地进行向前试探,会对每一步探测到的情况进行评估,如果当前的情况已经无法满足要求,那么就没有必要继续进行下去,也就是说,它可以帮助我们避免走很多的弯路。
特点:当出现非法的情况时,算法可以回退到之前的情景,可以是返回一步,有时候甚至可以返回多步,然后再去尝试别的路径和办法。这也就意味着,想要采用回溯算法,就必须保证,每次都有多种尝试的可能。
代码模板
function fn(n) {
// 第一步:判断输入或者状态是否非法?
if (input/state is invalid) {
return;
}
// 第二步:判读递归是否应当结束?
if (match condition) {
return some value;
}
// 遍历所有可能出现的情况
for (all possible cases) {
// 第三步: 尝试下一步的可能性
solution.push(case)
// 递归
result = fn(m)
// 第四步:回溯到上一步
solution.pop(case)
}
}
力扣题型:
(1)定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
(2) 在一个 N×N 的国际象棋棋盘上放置 N 个皇后,每行一个并使她们不能互相攻击。给定一个整数 N,返回 N 皇后不同的的解决方案的数量。

四.深度与广度优先搜索

1.深度优先搜索

算法思想:
深度优先搜索,从起点出发,从规定的方向中选择其中一个不断地向前走,直到无法继续为止,然后尝试另外一种方向,直到最后走到终点。就像走迷宫一样,尽量往深处走。
DFS解决是连通性的问题,即给定一个起始点(或某种其实状态)和一个终点
(或某种最终状态),判断是否有一条路径能从起点连接到终点。
DFS只关心路径是否存在,而不关心长短。
解题思路:必须依赖栈(Stack),特点是后进先出(LIFO)
力扣题型:迷宫问题(递归实现非递归实现),寻找最短的路径。

2.广度优先搜索

算法思想:
广度优先搜索,一般用来解决最短路径的问题。和深度优先搜索不同,广度优先的搜索是从起始点出发,一层一层地进行,每层当中的点距离起始点的步数都是相同的,当找到了目的地之后就可以立即结束。
广度优先的搜索可以同时从起始点和终点开始进行,称之为双端 BFS。这种算法往往可以大大地提高搜索的效率。
解题思路:依赖队列(Queue),先进先出(FIFO)
力扣题型:迷宫中寻找最短的路径。

五.动态规划

基本思想:
将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
性质
(1)最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质
(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
(2)无后效性:即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。
(3)子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
常见题目:统计,最优解
动态规划最棘手的两个问题:
(1)应当采用什么样的数据结构来保存什么样的计算结果
(2)如何利用保存下来的计算结果推导出状态转移方程
力扣题型
(1)斐波那契数列
(2)给定一个无序的整数数组,找到其中最长子序列长度
(3)一个机器人位于一个 网格的左上角(起始点在下图中标记为“Start”)。机器人每次只能向下或向右移动一步。机器人试图到达网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径
(4)背包问题
面试题型:
(1)线性规划:各个问题的规模以线性的方式分布
(2)区间规划:各个子问题的规模由不同区间来定义,一般结果存在于二维数组中
(3)约束规划:题目会对输出结果的元素添加一定的限制或约束条件,

六.二分搜索与贪婪

1.二分搜索

基本思想:也称折半查找,是一种在有序数组中查找某一特定元素的搜索方法
运用前提:数据必须是排好序的
力扣题型:
(1)找确定的边界:在一个排好序的数组中找出某个数第一次出现和最后一次出现的下标位置。
(2)找模糊的边界:从数组 {-2, 0, 1, 4, 7, 9, 10} 中找到第一个大于 6 的数。
(3)旋转过的排序数组:给定一个经过旋转了的排序数组,判断一下某个数是否在里面。

2.贪婪算法

基本思想:是一种在每一步选中都采取在当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。
优点:对于一些问题,非常直观有效。
缺点:并不是所有问题都能用它去解决;得到的结果并一定不是正确的,因为这种算法容易过早地做出决定,从而没有办法达到最优解。
力扣题型
(1)0-1背包问题
(2)给定一系列会议的起始时间和结束时间,求最少需要多少个会议室就可以让这些会议顺利召开。

你可能感兴趣的:(算法,数据结构)