数据结构与算法
2023-12-01 17:46:02 0 举报
AI智能生成
go版本的代码实现
作者其他创作
大纲/内容
排序
要点
时间复杂度
空间复杂度
稳定性
在一个待排序的数据中存在多个值相等的元素,经过稳定算法排序之后,相等元素之间的原有的先后顺序不变
原地性
不需要申请新的存储空间来存储待排序数据
原地排序空间复杂度不一定是 O(1)
空间复杂度为 O(1)的排序算法肯定是原地排序算法
稳定排序
冒泡排序
原理
每个元素和相邻的元素比较,不符合条件的交换位置
代码实现
算法分析
时间复杂度
最好情况:O(n),完全顺序
最坏情况:O(n^2),完全逆序
平均时间复杂度 O(n^2)
空间复杂度
O(1)<br>
原因是只是在本地进行交换,并没有行进多余的空间创建
是不是原地排序
是
是不是稳定排序
是
插入排序
原理
将数组中的数据分为:<b>已排序区间 </b>和 <b>未排序区间</b><br>
初始已排序区间只有一个元素,就是数组中的第一个元素
核心思想是:取未排序区间中的元素,<br>在已排序区间中找到合适的插入位置将其插入(遍历倒序已排序区间),<br>保证已排序区间数据一直有序
代码实现
算法分析
时间复杂度
最好情况:O(n),数据是顺序的
最坏情况:O(n^2),数据是逆序的
平均时间复杂度:O(n^2)
空间复杂度
O(1)
是否是原地排序
是
是否是稳定排序
是
选择排序
原理
讲数组划分为 <b>已排序区间</b> 和 <b>未排序区间<br></b>初始,已排序区间为空,未排序区间为整个数组<br>遍历未排序区间,找到最小的元素放入到已排序区间的末尾
代码实现
算法分析
时间复杂度
最好情况:O(n),数据是顺序的
最坏情况:O(n^2),数据是逆序的
平均时间复杂度O(n^2)
空间复杂度
O(1)
是否是原地排序
是
是否是稳定排序
否
它对比数据之后是有一个交换操作,比当前数小就会交换<br>[2,6,4,2,3,1] 第一次交换后[1,6,4,2,3,2]<br>原来的 2 挪到最后去了,并且它之前还有一个2,这个2会优先操作
归并排序
原理
把一个数组从中间分成两部分,分别对其进行排序,然后把两个已经排序好的数组合并
递归,问题拆分成最小子问题,这里的数组,最后就拆分成了一个元素一个数组
代码实现
算法分析
时间复杂度
O(nlogn)
空间复杂度
O(n)
是否是原地排序
否
因为有个临时数组 tmp
是否是稳定排序
是
快速排序
原理
随机取数组中一个值,以它为中点,然后把数组比分为两部分,一部分比它大,一部分比它小
一般这个中点是数组中最后一个值,把比中点小的放左边
然后一直去重复这个操作,到最后把中点放到最后一个比它小的值后面
代码实现
算法分析
时间复杂度
最好情况:O(nlogn) 分区每次都平均分成2份
最坏情况:O(n^2) 中点极端,一个数组非常大,一个数组非常小
如 1,3,5,6,8
越有序,排序越慢
平均情况下:O(nlogn)
空间复杂度
O(logn)
是否是原地排序
是
是否是稳定排序
否
6,8,7,6,2,9,4
二分查找
原理
条件:有序数组
在数组中找一个中点,然后和对比值比较
如果中点比对比值大,则在前半部分查找
如果中点比对比值小,则在后半部分查找
一直到找到和对比值相等的元素,如果最后没找到,返回-1
代码实现
查找区间永远是闭区间[low,high] or [p,r]
循环条件永远是:low<=high or p<=r
对于 low==high 必要时特殊处理,在 while 内部补充退出条件 ,这条适用于某些变种问题
返回值永远是mid,而不是 low 、high
low、high 的更新永远是 low=mid+1和high=mid-1
对于非确定性查找,使用前后探测法,来确定搜索区间
先处理命中情况,再处理在左右半部分查找的情况
算法分析
时间复杂度
最好情况:O(1)
最坏情况:O(logn)
平均情况下:O(logn)
空间复杂度
O(1)
常用场景
二分查找适合数据量适中的数组去查询
二分查找的变形
查找第一个值等于给定值的元素
查找最后一个值等于给定值的元素
查找第一个大于等于给定值的元素
查找最后一个小于等于给定值的元素
寻找旋转排序数组中的给定值
[7,9,10,11,14,1,2,3,5,6]
问题拆分成:顺序区间,循环顺序区间
顺序区间检查办法,通过中点mid 和 low 来检查是否在顺序区间<br>如上面的数组,mid 为4,元素是14,那么在[low,mid]是顺序区间<br>这样程序将会简化处理顺序区间和非顺序区间
然后做对应的处理
如果target 在 非顺序区间,那就在非顺序区间处理,target 最终是会落到顺序区间的
寻找旋转排序数组中的最小值
有重复数据的循环有序数组中查找给定值
查找峰值
思路:把数组分成山的两侧,一侧是左边,一侧是右边
然后命中目标的条件是 mid>mid-1 && mid > mid+1
在山的左侧找山顶时,mid>mid-1,low=mid+1
在山的右侧找山顶时,mid>mid+1,high=mid-1
二分答案
散列表/哈希表
原理
特殊存在
位图
原理
利用二进制位来表示,位图存的是范围,每次设置的都是位图的最大范围
分析
如有1千万的数据,数据是32位的整型数,存储需要大约40M(32位,1字节=8位,每个数据是4字节,1千万*4字节/1024/1024约等于40M)
使用位图,如果数字范围是在1到1亿之间,取最大值,1亿个二进制位,(1亿/8位/1024/1024约等于12M)
优点
大数据量节省内存(数据分布均匀)
无重复数据可以直接排序,时间复杂度为O(n)
缺点
如果数据的元素的范围特别大(数据分布不均匀),那么很消耗内存
元素不是整型的时候,不适用
比如1千万数据,数据为32位整数型,数字范围是1到10亿,那么取最大值,10个二进制位,(10亿/8位/1024/1024约等于120M)
代码实现
变量定义:num or value 是传参的值,index 是数组的下标,remainder 是余数, bit 是 bit 数组
index = num/8
remainder=num%8
增:bit[index] |= 1<< remainder
查:(bit[index] & (1<< remainder))!=0
删:bit[index] &^= 1<<remainder
布隆过滤器
基于位图实现,利用哈希冲突(设计多个哈希函数),减少存储空间带来的问题
原理
基于位图实现
实现多个hash函数,来分别计算元素应该存储在哪一位上
使用场景
不需要100%准确的,允许存在小概率误判的大规模判重场景
请求特别大的时候,查询数据库前,先查布隆过滤器,如果数据不存在,就不需要访问数据库,这样来减少数据库的操作
如 某个数据是否存在数据库中,uv去重,防止缓存穿透等
优点
快速判重、节省内存,对位图的优化,属于CPU密集型
缺点
会误判,不适合高精的判重场景
特点
判定存在,有可能不存在
例如:3123和1123 哈希值相同,位图中存储了 3123,没有存储1123,那查询1123也会返回true
判定不存在,那肯定不存在(主要使用这个特点)
例如查询3123 返回false,那就3123不存在
代码实现
树
概念
根节点
没有父节点的结点是根节点
父节点
比如根节点,它有左右两个子节点,相对于子节点来说,他是两个子节点的父节点
子节点
父节点下的节点
兄弟节点
右子节点和左子节点可以互相叫为兄弟节点
叶子节点、叶节点
把没有子节点的节点叫叶子节点
节点的高度
节点到叶子节点的最长路径(边数)
节点的深度
根节点到这个节点所经历的边的个数
节点的层数
节点的深度+1
树的高度
根节点的高度
分类
二叉树
有两个叉的树,也就是两个子节点
左子节点和右子节点
满二叉树
概念
除了叶子节点外,其他的所有节点都有左右两个子节点
完全二叉树
概念
和满二叉树稍微有些不同,只要是最后一排子节点的叶子节点是顺序缺失的就是完全二叉树(除了最后一排子节点外,其他的子节点都是满的)
各个操作的性能
完全二叉树>平衡二叉树>近似平衡二叉树
维护平衡的成本
完全二叉树>平衡二叉树>近似平衡二叉树
在工程中使用红黑树,这种近似平衡二叉树的原因是,能降低维护平衡的成本,并且操作性能也不差
二叉树
分类
二叉树
有两个叉的树,也就是两个子节点
左子节点和右子节点
满二叉树
概念
除了叶子节点外,其他的所有节点都有左右两个子节点
完全二叉树
概念
和满二叉树稍微有些不同,只要是最后一排子节点的叶子节点是顺序缺失的就是完全二叉树(除了最后一排子节点外,其他的子节点都是满的)
分辨小技巧
分辨是不是完全二叉树还有一个小技巧,把二叉树的节点落在数组中
如果数组中间没有空洞,那就是完全二叉树
如果数组中间有空洞,那就不是完全二叉树
如[1,2,3,4,空,6,7,8] 这是不完全二叉树
如[1,2,3,4,5,6,7,8,空,空,空] 这是完全二叉树,这里的空说明是创建数组的时候多创建了,无所谓,不要即可
二叉搜索树
BST
平衡二叉树\VAL
二叉树图
图
二叉树图
存储方式
基于指针存储
代码
基于数组存储
只适合于完全二叉树、堆、线段树
存储方法(根存储在下标为1的位置)
根节点存储在下标为1的位置
左子节点位置为 2*i
右子节点位置为 2*i+1
父节点位置 i/2
存储方法2(根存储在下标为0的位置)
左子节点位置为 2*i+1
右子节点位置为 2*i+2
父节点位置 (i-1)/2
遍历方法
前序遍历
先当前节点,然后左子节点,然后右子节点
中序遍历
先左子节点,然后当前节点,然后右子节点
后序遍历
先左子节点,然后右子节点,然后当前节点
这里的前序,中序,后序说的就是当前节点在遍历中的位置,前序就第一个是当前节点,中序就当前节点在中间,后序就是当前节点在最后
三种遍历方法图
算法分析
时间复杂度
最优时间复杂度
log2^n次方
最坏时间复杂度
O(n) 二叉树成了一条线的链表
二叉搜索树\查找树
原理
一种特殊的二叉树,支持快速查找、插入、删除数据
在二叉树中的任意一个节点
左子树中的每个节点的值,都小于父节点的值
右子树中的没个节点的值,都大于父节点的值
如果出现反着来的,那么上面的条件也反过开
查找操作
如果比根节点小,从左子树查找
如果比根节点大,从右子树查找
代码实现
插入操作
要插入的数据比当前节点的值小,且当前节点左子树为空,直接把数据插入到左子节点的位置
如果左子树不为空,继续递归左子树,直到找到左子树为空的位置
右子树一样
代码实现
删除操作
第一种情况:被删除的节点没有子节点
直接把父节点中指向要删除节点的指针指向null即可
第二种情况:被删除的节点只有一个子节点
把父节点中指向要删除节点的指针,重新指向要删除节点的子节点即可
第三种情况:被删除的节点有两个子节点
找到这个节点的右子树中的“最小节点”或者左子树中的“最大节点”,把它替换到要删除的节点上
然后再删除掉这个“最小节点”或者“最大节点”
如果用“最小节点”,那么它肯定没有左子节点,所以,可以应对第一种和第二种情况来处理
如果用“最大节点”,那么它肯定没有右子节点,所以,也可以应对第一种和第二种情况来处理
代码实现
复杂度分析
二叉树的查找、插入、删除性能,跟书的高度成正比
时间复杂度位于 log2^n ~ h 之间
二叉查找树在频繁的动态更新过程中,可能会出现树的高度远大于log2^n情况
从而各个操作效率下降,极端情况下,二叉树退化为链表,时间复杂度退化为O(n)
平衡二叉查找树/AVL树
原理
任意一个节点的左右子树的高度相差不能大于1
近似的树
红黑树
时间复杂度
log2^n
堆
定义和存储
一、堆必须是一个完全二叉树
二、堆中的每个父节点的值必须大于等于(或者小于等于)左右子树中每个节点的值
大顶堆:堆中每个节点的值都大于等于其左右子树中每一个节点的值
小顶堆:堆中每个节点的值都小于等于其左右子树中每一个节点的值
存储:堆事完全二叉树,适用于数组来存储
计算父节点和子节点位置方法
以根节点为下标1的数组
左子节点位置 2*i
右子节点位置 2*i+1
父节点位置 i/2
以根节点为下标0的数组
左子节点位置 2*i+1
右子节点位置 2*i+2
父节点位置 (i-1)/2<br>
操作
自上而下的操作
自下而上的操作
插入数据
操作方法:
先插入数据
然后自下而上的堆化<br>
对比插入值和父节点的大小,如果堆是大顶堆,哪个值大哪个就在父节点,小的成为子节点
如果是小顶堆,把小的那个放到父节点
时间复杂度
最好情况 O(1) 插入之后不需要堆化
最坏情况 O(h) h是堆的高度
取堆顶元素
删除堆顶元素
操作方法:
先把最后一个节点替换到堆顶,然后删除原有堆顶
然后自上而下的堆化
判断左右子节点和父节点,三个中哪个最大,把大的交换到父节点
时间复杂度
最优时间复杂度O(1)
最坏时间复杂度O(h)
更新元素值
操作方法
更新之后的值变小了,进行自上而下的堆化
更新之后的值变大了,进行自下而上的堆化
堆排序
原理
基于堆
先建堆,把数组中的元素原地组织成一个堆
然后再基于堆进行排序
建堆
第一种方法
从前往后处理每个元素,对每个元素执行自下而上的堆化
时间复杂度O(nlogn)
第二种方法
从后往前处理每个元素,对每个元素执行自上而下的堆化
时间复杂度O(n)
排序
将堆顶元素与最后一个元素交换,最大元素就放到了下标n的位置,堆大小 -1
交换之后的堆顶元素,自上而下堆化,重新构建成堆
一直重复这个过程,直到最后堆中只剩下一个元素,排序工作就完成了
算法分析
是否是原地排序
是
是否是稳定排序
否
因为他会把元素重复的哪来放在堆顶
时间复杂度
O(nlogn)
空间复杂度
O(1)
字符串匹配
匹配过程,模式串和主串的匹配过程,模式串在主串中不停地往后滑动
BF算法
暴力算法
分为主串和模式串(就是短的那个匹配串)
让模式串在主串中一个一个的去对比,直到全部匹配
代码实现
RK算法 掌握就好,不需要写
Rabin-Karp 算法
通过哈希算法对主串中的 n-m+1个子串分别求哈希值,然后逐个与模式串的哈希值比较
n代表主串的长度,m表示模式串的长度 n-m+1 是主串按照模式串的长度分割,主串的最后m个不能分割,视为1,所以主串能分割n-m+1个子串
哈希算法,普通的哈希算法太复杂,RK算法设计了一套利用当前子串来算出下一个子串的哈希算法
缺点:
模式串不能太长
RK的哈希算法思想:
比如主串,模式串是由26个字母组成
我们用26进制来做哈希算法
把子串的哈希值算出来,h[i]=s[i,i+m-1]哈希值(h是长度 n-m+1,i 是下标,i+m-1是第i个子串的尾部)
h[i+1]=s[i+1,i+m]的哈希值
比如 h[i] = "dfc",那么h[i]的哈希函数为 ('d'-'a')*26^2+('f'-'a')*26^1+('c'-'a')*26^0
h[i+1]="fca",那么h[i]的哈希函数为 ('f'-'a')*26^2+('c'-'a')*26^1+('a'-'a')*26^0
h[i]的哈希公式为 (26^(m-1))*(s[i]-'a')+(26^(m-2))*(s[i+1]-'a')+...++(26^0)*(s[i+m-1]-'a')) 这里的s代表着字符串
h[i+1]的哈希公式为 (26^(m-1))*(s[i+1]-'a')+...+(26^(1))*(s[i+m-1]-'a')+(26^(0))*(s[i+m]-'a')
所以h[i+1]的哈希公式为 (h[i] - (26^(m-1))*(s[i]-'a'))*26+(26^(0))*(s[i+m]-'a')
哈希函数可以根据 h[i]返回 h[i+1]的哈希值
BF和RK的做法
遇到不匹配的字符时,讲模式串往后滑动一位,然后模式串的第一位开始重新匹配
BM和KMP算法
复杂度
最好情况下时间复杂度O(n/m),n是主串的长度,m是模式串的长度
最坏情况下,O(n)
BM和KMP的做法
寻找某种规律,借助这种规律,当模式串和主串中的某个字符不匹配时,能够跳过一些肯定不会匹配的情况,将模式串往后多滑动几位
比如 主串 abcacabdc,模式串 abd,当匹配到abc的时候,主串中的c和模式串中的d不匹配,那么模式串就直接挪到主串的c后面一位开始匹配
单模式串匹配
在字符串 abcdefg 中查找字符串 abc
多模式串匹配
在字符串 abcdefghijk 中查找字符串 abc,def,ijk
可以用 Trie树 和 AC自动机,首选AC自动机
Trie树
适合前缀匹配
原理
Trie树是一颗多叉树,根节点不包含字符,其他每个节点表示字符串中的一个字符,从根节点到红色节点的一条路径表示字符串集合中的一个字符串
Trie树图
应用场景
字符串查找
完全匹配
代替方案:红黑树,散列表
多模式串匹配
优先使用AC自动机
前缀匹配
例如自动提示,搜索引擎、输入法、浏览器
回溯(重点)
是DFS和动态规划的基础
需要用到递归
解决穷举问题
核心思想
回溯的处理过程是一个穷举的过程。
枚举所有的解,找出其中满足期望的可行解
为了枚举所有可能的解,避免遗漏和重复,可以把问题求解的过程归纳为<b>多阶段决策模型</b>
每个阶段的决策会对应多个选择,从可选的选择列表中,任意选择一个,然后继续进行下一个阶段的决策
整个决策的过程,如果用图像形象话表示,就是一颗<b>决策树</b>
回溯穷举所有解来查找可行解的过程,就是<b>在决策树中进行遍历</b>的过程<br>
遍历过程中记录的<b>路径</b>就是解
回溯一般使<b>用递归来实现</b>,递归树和决策树完全一样<br>
递的过程进行函数调用,对应到递归树上为从一个节点进入它的子节点
归的过程进行函数调用返回,对应到递归树上是从子节点返回上一层节点
代码模板
源码
常见算法
全排列
思路:暴力遍历把元素拆分成树,类似于二叉树那样的遍历
源码
N皇后
思路:检查每一的每个位置能不能放皇后,直到找到能放皇后的位置为止
能放皇后的位置:在随意一个位置检查当前位置的整列,左上角对角线,右上角对角线,只要有任何一条线上有皇后,就不可以放皇后,否则就可以放
源码
0-1背包问题
思路:也是暴力遍历,只不过把是把总重量不超过规定重量的最大值即可
决策树就是装和不装两种关系
源码
数独问题
思路:和N皇后思路差不多,检查每一个要插入的位置的行,列,块(所在的小九宫格)是否有重复元素,没有就插入
不一样的地方,借助位图,来判断行,列,块里的位置有没有元素和查找重复元素
源码
子集问题
思路:把整个数组拆成决策树来遍历,那么就会出现两种情况,选与不选
源码
组合问题
和子集问题相思,不过是先把数转换成类似数组,然后进行决策树来遍历,选与不选
组合问题也是一种全排列问题
代码
图
应用:
微博、微信、Linkedln 等可以互加好友的
图的算法:图的搜索,最短路径、最小生成树、二分图等
什么是图
图是一种非线性表数据结构
图中的元素被称为“顶点”
图中的顶点可以和任意其它顶点建立连接关系,这种关系我们叫“边”
ABCDEF叫顶点,他们之间的线叫做边
无向图
无向图就是没有指向,他默认是双向的
有向图
定义
边带指向的图叫有向图
度
入度
有几条指向这个顶点的边就说明有几个入度
出度
这个顶点有几条边指向别的顶点就说明有几个出度
比如A有一个入度,2个出度
带权图
定义
每条边有一个“权重”
通过这个权重表示某种关系度,亲密度等
A,C 的边权重是5,这个图是个无向图
邻接矩阵存储方式
邻接矩阵
底层依赖一个二维数组
他把两个顶点组合成一个二维数组,比如1,3之间有边
无向图在二维数组中把[1,3]和[3,1]的位置上都标记为true
有向图(1出度,3入度)在二维数组中在[1,3]位置上为true,[3,1]位置上还是false
带权图 (这里用无向图 1,3的边上权重是5)在二维数组中 [1,3]和[3,1]的位置上都标记为5
源码
邻接矩阵
邻接表
底层是一个链表加数组构成
数组的下标表示图的顶点,数组的链表表示了该顶点的指向顶点,有可能是一个,有可能是多个
邻接表有邻接表和逆邻接表
社交使用场景
邻接表
用来表示你关注了谁
逆邻接表
用来表示谁关注了你
源码
邻接表:1关注了2,2关注了3,5,4,逆邻接表:1被4关注了,2被1,4关注了
邻接表的优化
基础邻接表
链表构成
不适合快速判断两个用户间是否关注与被关注
优化版
使用跳表
支持快速查找,插入,删除
时间复杂度 O(logn)
空间复杂度 O(n)
跳表有序,分页获取粉丝列表或关系列表非常高效
可以通过 <b>哈希算法 </b>等数据分片方式,将邻接表存储在不同的机器上
或者利用外部存储,持久化到存储关系数据库,在表上建立多个索引
用邻接表存储在不同服务器
存储图的方式
邻接表(内存中存储,可以使用邻接表)
关系型数据库(图需要持久化就需要关系型数据库)
图数据库(超大图,并且设计大量图计算,用专业的图数据库)
图上常用的算法
搜索 or 遍历
BFS
DFS
最短路径
Dijkstra
针对有权图的单源最短路径算法,并且要求没有负权边
Bellman-Ford
针对有权图的单源最短路径算法,允许存在负权边
Floyd
针对有权图的多源最短路径算法,允许存在负权边,但不允许负权环
A*算法
启发式搜索算法,求有权图的次优最短路线
最小生成树
Prim 算法
Kruskal 算法
最大流、二分匹配
Ford-Fulkerson
Edmonds-Karp
DFS&BFS(深度和广度优先算法)搜索/遍历<br>
BFS
Breadth-First Search 广度优先算法
树是图的一种特殊情况
二叉树的按层遍历,实际上就是广度优先搜索,先遍历与根节点近的,再逐层与根节点远的
图的广度优先搜索和树上的按层遍历很像,先查找离起始顶点最近的,然后是次近,依次往外搜索,直到找到终止顶点
思路:
二叉树广度优先遍历(层序遍历)使用的是队列,顶点的下一层是2*i和2*i+1
图的广度优先搜索也要用到队列
图的按层遍历,需要用一个 visited 数组,记录已经遍历过的顶点,这是为了防止图中存在环,出现循环遍历多次的情况
DFS
Depth-First-Search 深度优先算法
类似于树的递归,前中后序遍历
动态规划
解决穷举但是有重复子问题的
经典模型
背包问题(0-1、完全、多重、二维费用、分组、有依赖)
路径问题
打家劫舍&股票买卖
爬楼梯问题
匹配问题(LCS、编辑距离)
其它(LIS)
DP专题
适用问题
使用于可以用回溯解决,但是有重复字问题的
解题步骤
可用回溯解决
需要穷举搜索才能得到结果的问题
能用动归解决的问题都能用回溯解决
能用回溯解决的问题不一定能用动归解决
最值、可行、计数
构建多阶段决策模型
是否能将问题求解过程分为多个阶段
查看是否存在重复子问题
是否有多个路径到达同一状态
定义状态 (难点)
如何记录每一阶段的不重复状态
二维费用背包
int dp[n][w+1]记录每一阶段可达的所有状态
dp[i][j]表示第 i 个物品决策完之后,存在背包中物品重量为 j ,对应的最大物品价值
定义状态转移方程 (难点)
找到如何通过上一阶段的状态推导出下一阶段的状态
二维费用背包
确定第 i 阶段的(i,j)这个状态,如何通过上一阶段i-1的哪些状态转移过来(i,j)这个状态只有可能由(i-1,j)和(i-1,j-weight[i])转换过来
dp[i][j]=math.Max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
画状态转移表
辅助理解,验证正确性,确定状态转移的初始值
动态规划代码
0-1背包问题
问题:对于一组不同重量的物品,选择其中一些物品装入背包,在不超过背包最大重量限制的前提下,背包中可装物品总重量的最大值是多少?
1、可以用回溯解决, 穷举问题
2、构建多阶段决策模型:每一阶段决策一个物品是否放入背包
3、查看是否存在重复子问题:某一阶段背包中物品重量为cw,可以通过不同路径到达
4、定义状态:var dp[n][w+1] bool 记录每一阶段可达的所有状态<br>dp[i][j]=true 表示第i个物品决策完之后,存在背包中物品重量为j这种状态
5、定义状态转移方程:
确定第 i 阶段的(i,j)这个状态,如何通过上一个阶段 i-1 的哪些状态来转移过来
(i,j)这个状态只有可能由(i-1,j)和(i-1,j-weight[i])转移过来
dp[i][j]=dp[i-1][j]||dp[i-1][j-weight[i]]
6、画状态转移表:辅助理解,验证正确性,确定状态转移的初始值
7、写代码
二维费用背包问题
问题:对于一组不同重量、不同价值的物品,选择其中某些物品装入背包,不超过背包最大重量限制的前提下,背包中可装入物品的总价值最大是多少?
1、可以用回溯解决, 穷举问题
2、构建多阶段决策模型:每一阶段决策一个物品是否放入背包
3、查看是否存在重复子问题:某一阶段背包中物品重量为cw,可以通过不同路径到达
4、定义状态:var dp[n][w+1] bool 记录每一阶段可达的所有状态<br>dp[i][j]=true 表示第i个物品决策完之后,存在背包中物品重量为j这种状态
5、定义状态转移方程:
确定第 i 阶段的(i,j)这个状态,如何通过上一阶段i-1的哪些状态转移过来
(i,j)这个状态只有可能由(i-1,j)和(i-1,j-weight[i])来转移过来
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]+value[i]),weight 是物品重量数组,value 是物品价值数组
6、画状态转移表:辅助理解,验证正确性,确定状态转移的初始值
7、写代码
最值
问题:有N个物品,选择其中一些物品装入背包,在不超过背包最大重量限制的前提下,背包中可装物品总重量的最大值是多少?
状态:
var dp [n][w+1]bool 记录每阶段可达状态
dp[i][j]=true 表示第 i 个物品决策完之后背包重量为 j 这个状态可达
状态转移方程:
(i,j)这个状态只有可能(i-1,j)和(i-1,j-weight[i])两个状态转移过来的
dp[i][j]=dp[i-1][j] || dp[i-1][j-weight[i]]
最值
问题:有n个物品,选择其中一些物品装入背包,正好装满背包所需物品的最少个数?(如果装不满,返回-1)
状态:
var dp[n][w+1] int 记录每个阶段可达状态,就是记录每个阶段最少物品个数
dp[i][j] 表示第 i 个物品决策完,重量为 j ,对应的最少物品个数
状态转移方程:
(i,j) 这个状态只有可能从(i-1,j)和(i-1,j-weight[i]) 两个状态转移过来
dp[i][j]=min(dp[i-1][j],dp[i-1][j-weight[i]]+1)
可行
问题: 有n个物品,选择其中一些物品装入背包,能不能正好装满背包?
状态:
var dp [n][w+1]bool 记录每阶段可达状态
dp[i][j]=true 表示第 i 个物品决策完之后背包重量为 j 这个状态可达
状态转移方程:
(i,j) 这个状态只有可能从(i-1,j)和(i-1,j-weight[i]) 两个状态转移过来
dp[i][j]=dp[i-1][j] || dp[i-1][j-weight[i]]
计数
问题:有n个物品,选择其中一些物品装入背包,装满背包有多少种不同的装法?
状态:
var dp [n][w+1]int 记录每阶段可达重量对应的装法个数
dp[i][j]表示第 i 次决策完之后,背包重量为j,对应有几中装法
状态转移方程
(i,j) 这个状态只有可能从(i-1,j)和(i-1,j-weight[i]) 两个状态转移过来
dp[i][j]=dp[i-1][j]+dp[i-1][j-weight[i]]
空间优化
使用二维的滚动数组来替代dp数组
只使用一维数组来代替二维数组
数据结构
线性结构
数组
链表
栈
队列
非线性结构
树
图
堆
算法
排序算法
冒泡排序
选择排序
插入排序
快速排序
查找算法
顺序查找
二分查找
哈希查找
0 条评论
下一页