算法概述
- 算法
- 输入
- 输出
- 确定性
- 有限性
- 可行性
- 算法与程序的区别
- 算法的渐进复杂性
- 时间复杂性
- OOO 渐进上界
- Ω\OmegaΩ 渐进下界
- Θ\ThetaΘ 确界
- 空间复杂性
- 规模与复杂性
- lognlognlogn, nnn, nlognnlognnlogn, n2n^2n2, 2n2^n2n, n!n!n!
- 渐进复杂性的运算
- 对减法和除法不满足
- 时间复杂性
- 算法的实现
- 数据结构
- 数组与链表
- 栈与队列
- 集合
- 字典
- 图与树
- 数据结构
排序
- 冒泡排序
- 无序部分小的元素向前移动直到有序部分末尾
- 插入排序
- 无序部分第一个元素插入到有序部分的合适位置
- 选择排序
- 选择无序部分的最小元素放到有序部分的末尾
- 快速排序
- 找支点 + 划分
- 实现
def bubbleSort(arr): # 无需部分小的元素向前移动直到有序部分末尾 n = len(arr) for i in range(n): for j in range(n-1, i, -1): if arr[j] < arr[j-1]: arr[j], arr[j-1] = arr[j-1], arr[j]def insertSort(arr): # 无序部分第一个元素插入到有序部分的合适位置 n = len(arr) for i in range(n): k = i for j in range(i): if arr[j] >= array[i]: k = j break for j in range(i, k, -1): arr[j], arr[j-1] = arr[j-1], arr[j]def selectionSort(arr): # 选择无序部分的最小元素放到有序部分的末尾 n = len(arr) for i in range(n): k = i for j in range(i, n): if arr[j] < arr[k]: k = j arr[i], arr[k] = arr[k], arr[i]def quicksort(arr): n = len(arr) dfs(arr, 0, n - 1) def dfs(arr, left, right): if left >= right: return j = left - 1 for i in range(left, right): if arr[i] < arr[right]: arr[i], arr[j + 1] = arr[j + 1], arr[i] j = j + 1 arr[right], arr[j + 1] = arr[j + 1], arr[right] dfs(arr, left, j) dfs(arr, j + 2, right)
1.分治
- 思想
- 原问题分解为相似的子问题,先求解子问题,再将子问题组合为原问题
- 主定理
- T(n)=aT(n/b)+O(nk)T(n)=aT(n/b)+O(n^k)T(n)=aT(n/b)+O(nk)
- logb(a)>klog_b(a)>klogb(a)>k, O(nlogb(a))O(n^{log_b(a)})O(nlogb(a))
- logb(a)=klog_b(a)=klogb(a)=k, O(nlogb(a)log(n))O(n^{log_b(a)}log(n))O(nlogb(a)log(n))
- logb(a)<klog_b(a)<klogb(a)<k, O(nk)O(n^{k})O(nk)
- T(n)=aT(n/b)+O(nk)T(n)=aT(n/b)+O(n^k)T(n)=aT(n/b)+O(nk)
- 案例
- 大数相乘
- T(n)=3T(n/2)+O(n)T(n)=3T(n/2)+O(n)T(n)=3T(n/2)+O(n)
- 矩阵乘法
- T(n)=7T(n/2)+O(n2)T(n) = 7T(n/2)+O(n^2)T(n)=7T(n/2)+O(n2)
- 二分查找
- 针对已排序的数组
- T(n)=T(⌈n/2⌉)+O(1)T(n)=T(\lceil n/2 \rceil) + O(1)T(n)=T(⌈n/2⌉)+O(1)
- 归并排序
- 划分+排序+归并
- T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)
- 快速排序
- 找支点+划分+排序
- 最好情况:T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)
- 最坏情况:T(n)=T(0)+T(n−1)+O(n)T(n)=T(0)+T(n-1)+O(n)T(n)=T(0)+T(n−1)+O(n)
- 平均情况:T(n)=1n∑i=0n−1(T(i)+T(n−i−1))+O(n)T(n)=\frac{1}{n} \sum_{i=0}^{n-1}(T(i)+T(n-i-1))+O(n)T(n)=n1∑i=0n−1(T(i)+T(n−i−1))+O(n)
- selection问题
- 要求不能使用排序
- 理想情况:T(n)=T(n/2)+O(n)T(n)=T(n/2)+O(n)T(n)=T(n/2)+O(n)
- 平均情况:T(n)≤T(3n/4)+O(n)T(n) \le T(3n/4)+O(n)T(n)≤T(3n/4)+O(n)
- 快速幂
- T(n)=T(n/2)+O(1)T(n)=T(n/2)+O(1)T(n)=T(n/2)+O(1)
- 循环排序列表的最小值
- T(n)=2T(n/2)+O(1)T(n)=2T(n/2)+O(1)T(n)=2T(n/2)+O(1)
- k路合并
- T(k)=2T(k/2)+O(nk)T(k)=2T(k/2)+O(nk)T(k)=2T(k/2)+O(nk)
- 两个升序列表中第k小的元素
- T(m+n)={T(m/2+n)+O(1)T(m+n/2)+O(1)T(m+n)=\left\{\begin{align*} & T(m/2+n)+O(1) \\ & T(m+n/2)+O(1) \end{align*} \right.T(m+n)={T(m/2+n)+O(1)T(m+n/2)+O(1)
- 数组的主元素
- T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)
- 大数相乘
2.贪心算法
- 思想
- 通过每一步的最优解达到全局的最优解
- 适用条件
- 最优子结构
- 贪心选择性质
- 案例
- 活动安排
- 每次选择最早结束的活动
- 最优装载
- 每次选择最轻的货箱
- 分数背包问题
- 每次选择性价比最高的物品
- 找零问题
- 每次选择面值最大的硬币
- Huffman编码
- 每次合并频率最小的两棵子树
- 满二叉树
- 单源最短路径
- Dijkstra算法
- 每条边的权重都是非负值
- 每次选择未访问且距离最近的点,对其出发的所有边进行松弛操作
- 最小生成树
- Kruskal算法
- 每次选择距离最短且不在同一个连通分支的边
- 并查集
- Prim算法
- 每次选择横跨切割且距离最短的边
- 已选择的顶点集合,未选择的顶点集合
- Kruskal算法
- 多机调度问题
- NP-complete
- 每次选择时间最长的作业
- 图着色问题,使用Δ(G)+1\Delta(G)+1Δ(G)+1种颜色
- 每次选择可用的最小颜色
- 磁带最优存储问题
- 每次选择单位访问概率下最长的文件
- 活动安排
- 实现
- Kruskal
class Edge: def __init__(self, x, y, val): self.x = x self.y = y self.val = val def build_graph(n): edges = [] edges.append(Edge(1, 2, 1)) edges.append(Edge(1, 3, 1)) edges.append(Edge(1, 5, 2)) edges.append(Edge(2, 6, 1)) edges.append(Edge(2, 4, 2)) edges.append(Edge(2, 3, 2)) edges.append(Edge(3, 4, 1)) edges.append(Edge(4, 5, 1)) edges.append(Edge(5, 6, 2)) edges.append(Edge(5, 7, 1)) edges.append(Edge(6, 7, 1)) return edges class UnionFind: def __init__(self, n): self.father = [i for i in range(n)] def find(self, u): if u != self.father[u]: self.father[u] = self.find(self.father[u]) return self.father[u] def union(self, u, v): u = self.find(u) v = self.find(v) if (u == v): return self.father[v] = u def kruskal(edges, n): result = 0 edges.sort(key=lambda edge: edge.val) uf = UnionFind(n + 1) for edge in edges: x = uf.find(edge.x) y = uf.find(edge.y) if x != y: print("x: {}, y: {}, val: {}".format(edge.x, edge.y, edge.val)) result += edge.val uf.union(x, y) return result graph = build_graph(n) kruskal(graph, n) - Prim
def build_graph(n): graph = [[1e6 for _ in range(n + 1)] for _ in range(n + 1)] graph[1][2] = 1 graph[1][3] = 1 graph[1][5] = 2 graph[2][6] = 1 graph[2][4] = 2 graph[2][3] = 2 graph[3][4] = 1 graph[4][5] = 1 graph[5][6] = 2 graph[5][7] = 1 graph[6][7] = 1 return graph def prim(graph, n): result = 0 minDist = [1e6 for _ in range(n + 1)] isInTree = [False for _ in range(n + 1)] for _ in range(1, n + 1): cur = -1 minVal = 1e6 + 1 for j in range(1, n + 1): if not isInTree[j] and minDist[j] < minVal: cur = j minVal = minDist[j] isInTree[cur] = True print('cur: {}, minVal: {}'.format(cur, minVal)) result += minVal for j in range(1, n + 1): if not isInTree[j] and graph[cur][j] < minDist[j]: minDist[j] = graph[cur][j] return result n = 7 graph = build_graph(n) prim(graph, n)
- Kruskal
3.动态规划
- 思想
- 通过子问题自底向上求解原问题
- 适用条件
- 最优子结构
- 子问题重叠
- 案例
-
最短路径
- s=(i)s=(i)s=(i), i为第i个节点
- f(i)=minj∈adj(i)dij+f(j)f(i)=\underset{j \in adj(i)}{min}{d_{ij}+f(j)}f(i)=j∈adj(i)mindij+f(j), f为第i个节点到目标节点的最短距离
-
01背包
- N种物品,有各自的重量和价值。现在有一个容量为V的背包,问背包能装物品的最大价值
- 物品只能取一次
- dp(i)dp(i)dp(i):容量为 i 的背包的最大价值
- 状态转移方程:dp(i)=max(dp[i],dp(i−w[j])+v[j])dp(i) = max(dp[i], dp(i - w[j]) + v[j])dp(i)=max(dp[i],dp(i−w[j])+v[j])
-
完全背包问题
- n种物品,有各自的重量和价值。现在有一个容量为V的背包,问背包能装物品的最大价值
- 物品可以重复选取
- dp(i)dp(i)dp(i):容量为 i 的背包的最大价值
- 状态转移方程:dp(i)=max(dp[i],dp(i−w[j])+v[j])dp(i) = max(dp[i], dp(i - w[j]) + v[j])dp(i)=max(dp[i],dp(i−w[j])+v[j])
-
系统可靠性问题
- k种部件,每种部件之间串联,部件内部并联。现有总金额V用于购买部件,求一个购买方案使系统可靠性最高
- dp(i,j)dp(i,j)dp(i,j):购买前 i 个部件,总金额为 j 时的最大可靠性
- 状态转移方程:
- dp(i,j)=maxk(dp(i−1,j−k∗p[i−1])∗pki),pki=1−(1−r[i−1])kdp(i,j) = max_{k}(dp(i - 1, j - k * p[i - 1]) * pki), pki = 1 - (1 - r[i - 1]) ^ kdp(i,j)=maxk(dp(i−1,j−k∗p[i−1])∗pki),pki=1−(1−r[i−1])k
-
编辑距离
- 两个字符串的差异程度的量化,一个字符串需要多少次操作后变成另一个字符串,操作包括插入删除或修改字符
- dp(i,j)dp(i,j)dp(i,j):str1前 i 个字符与str2前 j 个字符的最小差异
- 状态转移方程:
- str1[i−1]=str2[j−1]str1[i - 1] = str2[j - 1]str1[i−1]=str2[j−1]
- dp(i,j)=dp(i−1,j−1)dp(i, j) = dp(i - 1, j - 1)dp(i,j)=dp(i−1,j−1)
- str1[i−1]≠str2[j−1]str1[i - 1] \ne str2[j - 1]str1[i−1]=str2[j−1]
- dp(i,j)=min(dp(i−1,j−1)+cu,dp(i−1,j)+cd,dp(i,j−1)+ci)dp(i, j) = min(dp(i - 1, j - 1) + c_u, dp(i - 1, j) + c_d, dp(i, j - 1) + c_i)dp(i,j)=min(dp(i−1,j−1)+cu,dp(i−1,j)+cd,dp(i,j−1)+ci)
- str1[i−1]=str2[j−1]str1[i - 1] = str2[j - 1]str1[i−1]=str2[j−1]
-
最长公共子序列
- 可以不连续
- s=(i,j)s=(i,j)s=(i,j),str1前 i 个字符与str2的前 j 个字符的最长公共子序列长度
- 状态转移方程:
- str1[i]=str2[j]str1[i] = str2[j]str1[i]=str2[j]
- f(i,j)=f(i−1,j−1)+1f(i,j) = f(i-1,j-1)+1f(i,j)=f(i−1,j−1)+1
- str1[i]≠str2[j]str1[i] \ne str2[j]str1[i]=str2[j]
- f(i,j)=maxf(i,j−1),f(i−1,j)f(i,j) = max{f(i,j-1), f(i-1,j)}f(i,j)=maxf(i,j−1),f(i−1,j)
- str1[i]=str2[j]str1[i] = str2[j]str1[i]=str2[j]
-
最长重复子数组
- 连续
- dp(i,j)dp(i, j)dp(i,j):str1前 i 个字符与str2的前 j 个字符的最长公共子串长度
- 状态转移方程:
- str1[i]=str2[j]str1[i] = str2[j]str1[i]=str2[j]
- f(i,j)=f(i−1,j−1)+1f(i,j) = f(i-1,j-1)+1f(i,j)=f(i−1,j−1)+1
- str1[i]≠str2[j]str1[i] \ne str2[j]str1[i]=str2[j]
- f(i,j)=0f(i,j) = 0f(i,j)=0
- str1[i]=str2[j]str1[i] = str2[j]str1[i]=str2[j]
-
- 实现
- 01背包
volume = 12 weight = [4, 6, 2, 2, 5, 1] value = [8, 10, 6, 3, 7, 2] N, V = len(weight), volume dp = [0 for i in range(V + 1)] for j in range(N): for i in range(V, weight[j] - 1, -1): dp[i] = max(dp[i], dp[i - weight[j]] + value[j]) print(dp[V]) - 完全背包
volume = 12 weight = [4, 6, 2, 2, 5, 1] value = [8, 10, 6, 3, 7, 2] N, V = len(weight), volume dp = [0 for i in range(V + 1)] for j in range(N): for i in range(weight[j], V + 1): dp[i] = max(dp[i], dp[i - weight[j]] + value[j]) print(dp[V]) - 系统可靠性
import math p = [30, 15, 20] # 金额 r = [0.9, 0.8, 0.5] # 可靠性 N, V = len(p), 105 dp = [[0 for _ in range(V + 1)] for _ in range(N + 1)] for i in range(N + 1): dp[i][0] = 0 for j in range(V + 1): dp[0][j] = 1 for i in range(1, N + 1): for j in range(1, V + 1): for k in range(math.floor(j / p[i - 1]) + 1): pki = 1 - (1 - r[i - 1]) ** k dp[i][j] = max(dp[i][j], dp[i - 1][j - k * p[i - 1]] * pki) print(dp[N][V]) - 编辑距离
str1 = 'love' str2 = 'lolpe' cost_insert = 1 cost_delete = 1 cost_update = 2 M, N = len(str1), len(str2) dp = [[0 for _ in range(N + 1)] for _ in range(M + 1)] for i in range(M + 1): dp[i][0] = cost_insert * i for j in range(N + 1): dp[0][j] = cost_delete * j for i in range(1, M + 1): for j in range(1, N + 1): if str1[i - 1] == str2[j - 1]: dp[i][j] = dp[i - 1][j - 1]) else: dp[i][j] = min(dp[i - 1][j - 1] + cost_update, dp[i][j - 1] + cost_insert, dp[i - 1][j] + cost_delete) print(dp[M][N]) - 最长公共子序列
str1 = 'love' str2 = 'lolpe' M, N = len(str1), len(str2) dp = [[0 for _ in range(N + 1)] for _ in range(M + 1)] for i in range(1, M + 1): for j in range(1, N + 1): if str1[i - 1] == str2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) print(dp[M][N]) - 最长重复子数组
str1 = 'love' str2 = 'lolpe' M, N = len(str1), len(str2) maxlen = 0 dp = [[0 for _ in range(N + 1)] for _ in range(M + 1)] for i in range(1, M + 1): for j in range(1, N + 1): if str1[i - 1] == str2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = 0 if dp[i][j] > maxlen: maxlen = dp[i][j] print(maxlen)
- 01背包
4.搜索和遍历
- 图的搜索
- 无知搜索
- dfs:用栈实现
- bfs:用队列实现
- 有知搜索:先验知识
- 启发式搜索
- A*:用优先队列实现
- 无知搜索
- 二叉树遍历
- 前序遍历、中序遍历、后序遍历、层序遍历
- 双连通分量
- 双连通图:一个无向连通图是双连通图当且仅当不含有割点(关节点)
- 割点的判断
- 根节点:至少包含两个孩子
- 非根节点:孩子的最低深度优先数大于等于自己的深度优先数
-
- 求出深度优先生成树
-
- 后序遍历顺序求出最低深度优先数
- low[u]=min{d[u],low[w],d[x]}low[u]=min\{d[u],low[w],d[x]\}low[u]=min{d[u],low[w],d[x]}
-
- 实现
- dfs
def build_graph(n): graph = [[0 for _ in range(n + 1)] for _ in range(n + 1)] graph[1][2] = 1 graph[1][3] = 1 graph[1][4] = 1 graph[1][5] = 1 graph[2][4] = 1 graph[3][5] = 1 graph[4][5] = 1 return graph def dfs(graph, n, start): visited = [False for _ in range(n + 1)] stack = [] visited[start] = True stack.append(start) while len(stack) != 0: i = stack.pop() print(i) for j in range(n + 1): if not visited[j] and graph[i][j]: visited[j] = True stack.append(j) n = 5 graph = build_graph(n) dfs(graph, n, 1) - bfs
def build_graph(n): graph = [[0 for _ in range(n + 1)] for _ in range(n + 1)] graph[1][2] = 1 graph[1][3] = 1 graph[1][4] = 1 graph[1][5] = 1 graph[2][4] = 1 graph[3][5] = 1 graph[4][5] = 1 return graph def bfs(graph, n, start): visited = [False for _ in range(n + 1)] queue = [] visited[start] = True queue.append(start) while len(queue) != 0: i = queue.pop(0) print(i) for j in range(n + 1): if not visited[j] and graph[i][j]: visited[j] = True queue.append(j) n = 5 graph = build_graph(n) bfs(graph, n, 1) - 二叉树遍历(递归)
def inorder(root): if root is None: return inorder(root.left) print(root.value, end=' ') inorder(root.right) def preorder(root): if root is None: return print(root.value, end=' ') preorder(root.left) preorder(root.right) def postorder(root): if root is None: return postorder(root.left) postorder(root.right) print(root.value, end=' ') - 二叉树的遍历(迭代)
def inorder(root): stack = [] if (root != null): stack.append(root) while len(stack) != 0: node = stack.pop() if node != null: if node.right != null: stack.append(node.right) stack.append(node) stack.append(null) if node.left != null: stack.append(node.left) else: tmp = stack.pop() print(tmp.val, end=' ') def preorder(root): stack = [] if (root != null): stack.append(root) while len(stack) != 0: node = stack.pop() if node != null: if node.right != null: stack.append(node.right) if node.left != null: stack.append(node.left) stack.append(node) stack.append(null) else: tmp = stack.pop() print(tmp.val, end=' ') def postorder(root): stack = [] if (root != null): stack.append(root) while len(stack) != 0: node = stack.pop() if node != null: stack.append(node) stack.append(null) if node.right != null: stack.append(node.right) if node.left != null: stack.append(node.left) else: tmp = stack.pop() print(tmp.val, end=' ') def levelorder(root): queue = [] if (root != null): queue.append(root) while len(stack) != 0: count = len(stack) while count > 0: node = queue.pop(0) print(node.val, end=' ') if node.left != null: queue.append(node.left) if node.right != null: queue.append(node.right) count = count - 1
- dfs
5.回溯
- 算法思想
- 按深度优先的方式搜索解空间,不满足限界函数时回溯,找出满足条件的所有解
- 形式
- 递归回溯
- 迭代回溯
- 案例
- 汉诺塔问题
- 求出一个方案,将起始柱子上的盘子全部移到目标柱子上
- 证明:当存在第三个空的缓冲柱子时,能将n个盘子从起始柱子移到目标柱子
- 数学归纳法:
- 当n==1时,显然成立
- 设当n==k时,若存在第三个空的缓冲柱子,能将k个盘子从起始柱子移到目标柱子
- 当n==k+1时,先将k个盘子从起始柱子移到第三个空的缓冲柱子(利用假设条件),再将剩下的一个盘子移到目标柱子,最后将缓冲柱子上的k个盘子移到目标柱子(再次利用假设条件)
- 装载问题
- n个集装箱装上两艘轮船,求出装载方案
- 符号三角形问题
- 加号和减号的数量相同
- N皇后问题
- 行、列、斜线不允许出现两个皇后,求出所有排列方案
- 0-1背包问题
- 背包容量有限,每个物品装或者不装,求所装物品的最大价值
- 最大团问题
- 最大团:顶点数量最多的完全子图
- 着色问题
- 图中相邻顶点的颜色不允许重复,求颜色种类的最小值
- 旅行商问题
- 哈密顿环:遍历图中每个顶点有且仅有一次,最后回到出发点
- 圆排列问题
- n个圆的排列中,找出最短长度的圆排列
- 连续邮资问题
- n种邮票,最多贴m张,允许重复,能够表示的最大连续邮资区间
- 批处理作业调度问题
- n个作业需要先后在两台机器上处理,设计调度方案,使完成时间和最小
- 汉诺塔问题
- 实现
- 汉诺塔问题
class Plate: def __init__(self, size): self.size = size class Pole: def __init__(self, name, n): self.name = name self.plates = [Plate(i) for i in range(n, 0, -1)] class Hanoi: def __init__(self, n): self.start = Pole('start', n) self.end = Pole('end', 0) self.temp = Pole('temp', 0) def move(self, start, end): plate = start.plates.pop() end.plates.append(plate) print('move {} from {} to {}'.format(plate.size, start.name, end.name)) def move_n(self, start, end, temp, n): if n == 0: return self.move_n(start, temp, end, n - 1) self.move(start, end) self.move_n(temp, end, start, n - 1) n = 3 hanoi = Hanoi(n) hanoi.move_n(hanoi.start, hanoi.end, hanoi.temp, n) - N皇后
class n_queens: def __init__(self, n): self.n = n self.solutions = [] self.places = [0 for i in range(n)] def valid(self, k): for i in range(k): if abs(self.places[i] - self.places[k]) == abs(i - k) or self.places[i] == self.places[k]: return False return True def dfs(self, k): if k == self.n: print('places:', self.places) self.solutions.append(self.places.copy()) return for j in range(self.n): self.places[k] = j if self.valid(k): self.dfs(k + 1) def solve(self): self.dfs(0) - 子集
class sumOfSub: def __init__(self, arr, target): self.arr = arr self.target = target self.select = [0 for _ in range(len(arr))] self.total = 0 self.solutions = [] def dfs(self, k): if self.total >= self.target: if self.total == self.target: self.solutions.append(self.select.copy()) return if k < len(self.arr): self.select[k] = 1 self.total = self.total + self.arr[k] self.dfs(k+1) self.select[k] = 0 self.total = self.total - self.arr[k] self.dfs(k+1) def solve(self): self.dfs(0)
- 汉诺塔问题
6.分支限界
- 算法思想
- 按广度优先的方式搜索解空间,不满足上界函数时剪枝,找出满足条件的一个解
- 形式
- FIFO队列
- 优先队列
- 案例
- 装载问题
- 优先队列:存储当前节点的重量上界
- 扩展:每次选择重量上界最高的节点进行扩展
- 01背包问题
- 预处理:物品按单位重量价值降序排列
- 优先队列:存储当前节点的价值上界
- 扩展:每次选择价值上界最高的节点进行扩展
- 单源最短路径
- 问题描述:两点之间的最短路径
- 优先队列:存储当前节点已经走过的路径长度
- 扩展:每次选择路径长度最短的节点进行扩展
- 布线问题
- 最大团问题
- 旅行商问题
- 电路板排列问题
- 批处理作业调度问题
- 装载问题
NP完全性理论
- 问题的复杂度
- O(logn),O(n),O(nlogn),O(n2),O(2n),O(n!)O(\log n), O(n), O(n\log n), O(n^2), O(2^n), O(n!)O(logn),O(n),O(nlogn),O(n2),O(2n),O(n!)
- 当问题规模很大时,O(2n),O(n!)O(2^n), O(n!)O(2n),O(n!)算法在计算机上无解
- P、NP、NPC
- P问题
- 线性图灵机在多项式时间内找到一个解
- P问题是NP问题的子集
- NP问题
- 线性图灵机在多项式时间内验证一个解
- P=NP?P=NP?P=NP?
- NPC问题
- NP问题
- 所有NP问题可以在多项式时间内归约到它
- 归约:A问题可以归约为B,表示可以用B的解法来解决A,其中B的复杂度大于等于A
- 第一个NPC问题:逻辑电路问题
- NP-Hard问题
- 不一定是NP问题
- 所有NP问题可以在多项式时间内归约到它
- P问题
参考资料
- Cormen T H, Leiserson C E, Rivest R L, et al. Introduction to algorithms[M]. MIT press, 2022.
- Sahni S, Horowitz E, Rajasekaran S. Computer Algorithms[M]. WH Freeman Press, 1998.

3481

被折叠的 条评论
为什么被折叠?



