最近要开始准备秋招了,作为非计算机出身,还有大量的坑需要填,这几天研究了算法入门书——《啊哈算法》,算是读过最通俗易懂的了,趁还有印象总结一下,以便未来参考。


一、排序

1 桶排序——用book(数组)标记每个数出现的次数

注意: 若0-1000排序,需要1001个桶;时间复杂度为O(m+n)

2 冒泡排序——每次比较相邻的两个元素,如果他们的顺序错误,则交换过来

注意: 若n个数排序,则n-1个数归位;时间复杂度为O(N平方),因为需要双重嵌套循环。

3 快速排序——每一轮基准数归位

注意: 基准数、哨兵、二分思想;最坏复杂度O(N平方),平均(NlogN)

4例子🌰:

买书:涉及到去重与排序

桶排序 或 先排序再去重 


二、栈、队列、链表

1 队列

删除一个数:head++

head == tail 空队列

出队操作,入队操作,先进先出

是广度优先算法,队列优化,Bellman-Ford最短路算法的核心数据结构

结构体包括:data+ head + tail

2 栈

出栈操作,入栈操作,后进先出

典型例子如回文数的判定

结构体包括:data + top

3 例子🌰——小猫钓鱼

注意:C语言数组的下标由0开始;可以用book来代替枚举

4 链表

指针与动态分配内存函数malloc:★★★★★

指针 int *p : 指针储存一个内存空间的地址,在这里是储存了一个int的地址

int a;  p = &a  &在这里是取值符 如 printf("%a",*p);

* :  在C语言中的三种用法——乘号;申明指针;简接运算符(作用是取得指针P所指向内存中的值)

动态内存申请:

malloc(4):申请4个字节的内存空间

sizeof(int):获取int类型所占用的字节数

如: int *p; p = (int*)malloc(sizeof(int));

malloc函数所返回的类型为viod*类型,表示指针类型未确定,在C/C++中viod*可以强制转换成其他类型

指针储存的是内存的首地址,而指针类型表明多少连续内存

在程序中使用malloc函数,需要stdlib.h头文件

链表:data + *next(后继指针)(struct node *next)

先建立一个头指针 head,方便以后从头遍历链表

->为结构体指针运算符,用于访问结构体内部成员

注意临时指针的用法

5 模拟链表

用数组实现,包括data+right(存储右边的元素),也可以实现双向链表与循环链表


三、枚举

1 奥数例子

基本思想:“有序的去尝试 每一种可能”

2 炸弹人💣例子

测出每一种可能;抽象成数据

3 火柴棍例子

注意考虑复杂度

4 数的全排列

引出搜索


四、搜索

1 深度优先搜索

book标记哪些牌被用了

C语言中的全局变量默认为0

关键:解决“当下如何做”,“下一步如何做”跟“当下如何做”是一样的

基本模型:

void dfs(int step)

{判断边界

        尝试每一种可能 for(i=1;i<n;i++)

                {

                  继续下一步 dfs(step+1);

                }

返回

用递归来实现

2 解救小哈🌰

即最短路径问题,深度优先搜索的应用

3 层层递进——广度优先搜索

“一层层”拓展的方法来找目标

可以用队列来模拟这一过程,处理完后出队

4 再解炸弹人💣

可用深度优先/广度优先来枚举

5 宝岛探险

着色法的应用

可用于求一个图中独立子图的个数

6 水管工游戏

用深度优先搜索,在x行y列,枚举每一种形式

注意对于越界的检查,终点的判断


五、图的遍历

时间戳的概念

1 深度优先遍历的基本思想

沿着某一个分支进行遍历到末端,然后回溯,再沿另一条进行同样的遍历,直到都被访问过

可以用二维数组储存数据——图的邻接矩阵存储法

无向图的概念

2 广度优先遍历

首先以一个未被访问过的顶点作为起始顶点,访问所有相邻顶点,然后对每一个相邻顶点,再访问没访问过的顶点,直到所有的顶点都被访问过,遍历结束

用深度优先与广度优先都可以得到这个图的生成树

3 城市地图——图的深度优先遍历

建立数组,用全局变量记录最短路径,用book记录哪些城市走过

有向图与无向图的区别

最短路径,广度优先搜索,floyd算法,B-F算法,Dijkstra算法等

4 最少转机的问题——图的广度优先遍历


六、最短路径

1 Floyd算法

多源最短路径问题

二维数组存储

a 可以n*n遍深度/广度优先搜素

b 动态规划思想

注意无穷数的设置

注意不能有负权回路

2 Dijkstra算法 ——通过边实现松弛

单源最短路径问题

通过边来松弛一号顶点到其余各顶点的路程

基本思想:每次找到离源点最近的一个顶点,然后以其为中心进行拓展,最终得到源点到其他所有点的最短路程

邻接表的概念

贪心算法

3 B-F算法-可以解决负权边问题

可以解决带有负权边的图

n-1次松弛操作

4 B-F算法的队列优化

利用队列,记录未松弛完毕的顶点

可以用一个Book来记录顶点是否在队列中

SPFA快速算法也是基于此

5 最短路径算法的对比分析

            空间复杂度 时间复杂度 适用情况 权

Floyd   O(n*n)    O(N*N*N)   稠密图,和顶点关系密切  可以解决负权

Dijkstra  O(M)    O((M+N)logN)  稠密图,和顶点关系密切 不能解决负权 

B-F算法  O(M)   O(MN)      稀疏图,和边关系密切  可以负权

队列优化B-F算法  O(M)   最坏O(MN)     稀疏图,和边关系密切  可以负权


七、神奇的树

1 树就是不包括回路的连通无向图

根(祖先)结点 叶结点 父节点 子节点

2 二叉树 

每个节点最多两个儿子

满二叉树与完全二叉树(满二叉树是一种完美的完全二叉树)

只需用一个一维数组就可以存储完全二叉树

编号:从上到下,从左到右

3 堆 ——神奇的优先队列

所有父节点都比子节点要小的完全二叉树——最小堆

所有父节点都比子节点要大的完全二叉树——最大堆

特性:当新增一个数被放到堆顶的时候,如果此时不符合最小堆的特征,则需要将这个数向下调整,直到找到合适的位置位置,使其重新符合最小堆的特性 O(logN)

创建堆:将N个结点自顶向下,从左到右的形式从1到n编码,这样就可以转换成一颗完全二叉树,紧接着最后一个非叶结点开始到根节点,逐个扫描所有的节点,根据需要将当前节点向下调整一直到以当前结点为根节点的子树符合堆的特征

时间复杂度为O(n),堆排序的时间复杂度 O(NlogN)

将这样支持插入元素和寻找最大/小值元素的数据结构称为优先队列

堆可以优化,用Dij算法,复杂度为O(M+N)(logN)

还可以求数列中第K大的数,只需要建立大小为K的最小堆,时间复杂度为O(NlogK)

4 并查

“擒贼先擒王”“靠左原则”

树的其他用法:红黑树,线段树,字典树,二叉搜索树等等


八、更多精彩算法

1 镖局问题➹

用最小的边将图联通,也就是求最小生成树:Kruskal算法——按边的权值从小到大排序,每次从剩余的边中选取权值较小且边的两个顶点不在同一个集合的边,直到加入了n-1条边为止

可以用并查集

O(MlogM + MlogN)  -> O(MlogM) (M>>N)

Prim算法(DJP)构造树-加入-记录树到各个结点距离-加入更新

适用范围: K:稀疏图 P:稠密图 堆优化P:稀疏图

2 图的割点,割边,二分图的最大分配等

————————————————————————————————————————————————————

一个一个字,终于写完了,去练习代码实现了🐔..