算法学习总结

对算法的一些理解

算法是什么

算法就是在一些特定数据结构上的应用;巧用数据结构是关键

算法能给我们带来什么

思维方式的转变以及编程功底的加深。

算法所依赖的数据结构

算法所依赖的数据结构大体上分两类

  1. 线性数据结构
  2. 非线性数据结构

线性数据结构

数组

数组的随机访问特性,支持O(1)时间复杂度的查询操作,可以充分利用cpu缓存;以及合理使用System.arraycopy移动数组位置(重置数组开始索引),往往在应对一些题目时(比如差分)代码书写起来比较简单; 当然数组也是一些其他数据结构的载体,比如完全二叉树一般来说用数组存储比较合理,再比如并查集;数组的一些基本操作:增、删、改、查

链表

链表不受存储空间限制,支持O(1)时间复杂度的插入、删除操作,操作链表时要先加保护节点(哨兵节点),减少特判;链表的基本操作:插入节点、删除节点、查询节点(删除节点和修改节点值的操作是基于查询的);链表细分又可以分为
* 单链表
* 循环链表
* 双向链表
* 双向循环列表

栈满足先进后出,凡是最近相关的可以用栈来处理,比如解决逆波兰式(后缀表达式)求值问题,验证括号是否有效,以及前缀最小值问题,浏览器前进后退问题等等,通常栈可以用队列来实现

队列

队列满足先进先出(FIFO)

集合

集合分为有序集合,无序集合,有序集合比如java中的TreeSet,无序集合比如java中的HashSet

非线形数据结构

树形数据结构

抽象来讲树分为多叉树和二叉树; 满足某些特性的树又称为完全二叉树、满二叉树;针对二叉树满足某些特性又可称为二叉搜索树;二叉搜索树的递归定义
满足 (1)节点的左子树只包含小于当前节点的数
(2)节点的右子树只包含大于当前节点的数
(3)所有左右子树自身必须也是二叉搜索树
树的存储方式:可以使用数组也可以使用链表

**更加具体更加贴合实践场景的一些树形数据结构**
(1)平衡二叉树(avl):

	平衡因子(Balance Factory):一个结点的左子树的高度 减去 它的右子树的高度
	Avl规定任意结点的平衡因子的绝对值都不超过1
	Avl 一般会存储关键码,左孩子,右孩子,父亲 以及树的高度
	超过平衡因子需要旋转,旋转分四个场景
		场景1:LL — 左左子树  : 右旋
		场景2:RR — 右右子树:左旋
		场景3:LR — 左右子树:先左旋,再右旋
		场景4:RL— 右左子树,先右旋,再左旋
(2)红黑树:

	是一种近似平衡的二叉搜索树
	从根到叶子的最长路径<=2 * 最短路径(简记:高度差在2倍以内)
	规则
		每个节点要么是红色,要么是黑色
		跟节点是黑色
		最底层视作有一层空叶结点,是黑色的
		不能有两个相邻的红色节点
		从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
		规则被打破时 通过变色或者旋转操作来修正
		TreeSet TreeMap
AVL和红黑树的比较

	红黑树插入删除更快(旋转少,可通过变色替代旋转)
	红黑树更省空间(只用存颜色 :: 颜色 vs 平衡因子)
	avl树查询更快 (红黑树不如avl树平衡)
(3)并查集

	用于处理不相交集合(disjoint sets)的合并和查询问题,即处理分组问题,维护无序二元关系
	使用一维数组存储 fa[j] = i ,j 的 father 是i ,代表元素j 处于 元素i 的集合
	涉及到的操作:
	MakeSet(s) 建立一个新的并查集,初始化包含s个集合,每个集合只有一个元素
	UnionSet(x,y)把元素x和元素y所在的集合合并,如果x和y所在的集合不相交,则无需合并
	Find(x) 找到x所在的集合代表
	并查集查找过程中可以使用路径压缩优化查询性能 具体实现 fa[x] = find(fa[x])
(4)Trie树(字典树)

	字典树是一种由“结点”和“带有字符的边”构成的树形结构
	应用场景:统计和排序大量的字符串(不仅限于字符串)
	核心思想:空间换时间
	优点:最大限度的减少无谓的字符串比较,查询效率比哈希表高
(5)堆

	堆是一棵二叉树,并且满足堆性质
	大根堆:根比孩子大 (左右孩子无需对比)
	小根堆:根小于孩子  
	二叉堆是堆的一种简易实现;本质上是一棵满足堆性质的完全二叉树;一般使用一个一维数组存储
	关于堆和树的异同点:二叉树是一个基本的数据结构,堆是一个特殊的二叉树
(6)树状数组
	
	是一种维护数组前缀和、区间和的数据结构;
	思想和跳表类似
	跳表 :添加索引,高效维护链表
	树状数组:添加索引,高效维护数组
	树状数组的索引数据数量和节点编号的lowbit值相关,lowbit 值为多少,则索引多少个原始节点
	性质:
	(1)除树根外,每个内部节点 c[x] 的父亲是c[x + lowbit(x)]
	(2)每个内部节点c[x]保存以它为根的子树中所有叶节点的和
(7)线段树

	线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计。
	1. 线段树的每个节点都代表一个闭区间
	2. 线段树具有唯一的根结点,代表的区间是整个统计的范围,如[1,N]
	3. 线段树的每个叶子节点都代表一个长度为1的元区间[X,X]
	4. 对于每个内部节点[l,r],它的左子节点是[l,mid],右子节点是[mid+1,r],其中mid =(l + r) / 2 (向下取整)
	实际使用线段树处理问题时,递归建树,回溯统计; 对于支持线段树的区间修改,可以采用懒惰修改,bug下传的方式;
	线段树的使用场景:求最值,求和,线段覆盖

森林

森林其实就是有多个根节点的树

	基环树:介于树和图之间的一种数据结构(向一棵树添加一条边形成一个环,叫基环树)
	图根据是否有方向大致分为两类:有向图、无向图
	根据是否联通分为:联通图、非联通图
	点的度数:一个点相连的边的数量
	出边数:出度
	入边数:入度
	如何存一张图(无向图看作是正反两条边的有向图去存储):
	(1)邻接矩阵
	(2)邻接表
	(3)出边数组 (java ArrayList<ArrayList<>>)
	
	图的遍历方式:
	(1)深度优先遍历(dfs)适合无向图找环(通过父节点去假环),当然无向图找环也可以使用并查集来处理(划分连通块)
	(2)广度优先遍历(bfs)适用场景拓扑排序(入度配合 bfs-有向图找环), 求最短距离 ,求层数
关注图的拓扑排序

解任何问题其实就是在由状态构成的图上做一些遍历,看怎么遍历比较高效
搜索就是采用直接遍历整个状态空间的方式寻找答案的一类算法(动归、图论、贪心都是基于搜索)

链表是特殊化的树,树是特殊化的图

哈希表

	基于基础数据衍生而来的数据结构
	hash函数选取很重要(数组就是一个简单的哈希表,数组的hash函数直接映射为数组下标)
	保证在碰撞概率相对较低的前提下,尽量设计比较简单的hash函数(性能考虑)。
	hash碰撞之后的解决方式主要有如下几种(包含但不限于)
	(1)开散列(拉链法):哈希冲突之后拉一个列表出来,查询hash值相同的key时直接在链表上查询即可;链表也可以进一步优化,比如优化成红黑树,提升查询性能
	(2)开放寻址法

其他。。。

一些常见算法介绍

快慢指针

作用于数组或者链表的数据结构上,常用来解决环路问题
定义两个指针,一快一慢遍历数据结构

前缀和

包含一维数组前缀和,二维数组前缀和
使用场景是子段查询

差分

和前缀后是互逆运算
使用场景涉及修改

双指针扫描

比较简单

单调栈

基于栈的数据结构,维护栈内数据单调递增/递减来处理一些实际问题;比如柱形图中的最大矩形

滑动窗口

基于队列这一数据结构,使队列内数据具有单调的特性(单调队列),实现滑动窗口算法,用来解决类似区间最值问题
滑动窗口的大小可以不固定,只要上下界同时满足单调性质就可以;用单调队列维护max

LRU算法

常见的页面置换算法,使用哈希表加双向链表实现的一种支持O(1) 查询 O(1)修改的数据结构,比较简单,不再赘述

分治算法

一般用递归实现,实现过程中要注意聚焦第一段,第一个子问题
划分子问题标准:第一个子问题,作为不可分割的整体
注意:递归和分治不是一个维度的东西,递归是一种实现形式, 比如用递归实现分治算法。

记忆化搜索

本质上还是搜索,只不过在搜索的基础上做了优化,比如剪枝; 实践:dfs + 记忆化搜索求最长路径

二分

更加一般化的二分要关注:条件满足,限定范围,开始截止,是否加一
从二分扩展三分

二分答案

把最优化问题转换为可行性问题,一般地二分配合贪心实现

排序

核心关注:归并排序(稳定),快排(不稳定),堆排序 时间复杂度均为 O(n logn)
插入排序,冒泡排序,选择排序 时间复杂度均为 O(n*n)
计数排序,从数值范围入手解决问题
希尔排序
关于快排
(1)快排轴元素选不好会退化成 O(n^2),解决思路 随机选取轴元素
(2)快排可以原地,归并不可以

贪心

一般地,先想分治,搜索,动归; 后想贪心,贪心往往用来和其他算法配合起来解决一些复杂问题,贪心题目是需要被证明的; 证明贪心的思路
(1)决策包容性
(2)决策范围扩展
(3)邻项交换(顺序类题目,即按照什么顺序做最好)

动态规划

注意切题方式,可以先人工模拟,核心是找出状态转移方程;简单来讲,关注题目中变化的信息,将每个变化的信息当作一维放入动规数组;找到状态之间的推导关系,从而确定最优子结构,从最优子结构出发找到状态转移方程(可以使用列表法来推导状态转移方程),最后确定边界和目标,使用递推实现(实现过程中可以从i -1推导i,同样 也可以同i推导i+1)。
具体编码时要注意:先 for 状态(阶段) 再 for决策
动规有几种类型:
(1)线形动规 : 常规简单动规
(2)区间动规 : 迭代时 要先处理阶段
(3)树形动规 : 状态的第一维放树的节点

图论相关算法

求最短路径

Bellman-Ford

算法基于动规和迭代思想,n个点,m条边,时间复杂度O(nm)
不考虑负环

Dijkstra

基于贪心,用堆来维护最小边,每次取最小边进行扩展; 先算小的再算大的
不考虑负边,非负保证取出一个点进行计算时,该点就是目前路径最小的点,前面的点均已算完

Floyd

用来求图中每一对点之间的最短路径 ,本质是动规,时间复杂度 O(nnn)
具体决策中使用中继点

求最小生成树

最小生成树:边权之和最小,把任何一个生成森林扩展为生成树,一定使用了图中剩余边中权值最小的
最小生成树性质:任意一颗最小生成树一定包含无向图中权值最小的边

Kruskal

使用并查集维护无向图的最小生成树

整个过程中的收获

关于如何高效的练习算法

多做题,多思考,前期注重分类刷题,熟练掌握三刷五步法;写算法时要注意以下几点

  • 主体思路要和细节分开
  • 遇事不决先枚举
  • 巧用方向数组
  • 要深入理解记忆算法模型,从而可以解决一类题目
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值