KMP
· · · kmp算法是一种字符串匹配算法,用于在一个文本串中查找模式串的位置,出现的次数等;其中求解next数组是核心(只与模式串有关),若记模式串为p,next[i] = j 表示p[i]之前的子串中,存在长度为j的相同前缀和后缀,即p[0]–p[j-1]与p[i-j]~p[i-1]相同;如果p[j] = p[i],则有next[i+1] = j+1,否则子串的最长公共前后缀长度必定小于j+1;充分利用已经匹配的字符和模式串的特征来减少指针回退,对于p[i]前的子串的公共前后缀,再分别寻找这两部分的公共前后缀,共四段,这四段相同,通过j = next[j]和next数组本身的定义,回溯到第一段后面一个位置,再判断此时p[j]是否等于p[i],若不等,以此方式递归查找,直到相等或j<0;然后根据求得的next数组与next数组本身的定义来完成与文本串的匹配
C++
void get_next(string str2, int next[]) {
int i = 0, j = -1; // i为模式串指针,j表示最长公共前后缀长度
next[0] = -1; // 表示一个通配符,可和任意字符匹配
while (i < str2.length()) {
if (j == -1 || str2[i] == str2[j]) {
next[++i] = ++j;
}
else {
j = next[j]; //回退
}
}
}
int kmp(string str1, string str2, int next[]) {
int len1 = str1.length(), len2 = str2.length();
int i = 0, j = 0; // i,j分别表示文本串和模式串下标指针
while (i < len1 && j < len2) {
if (j == -1 || str1[i] == str2[j]) {
i++, j++;
}
else {
j = next[j];
}
}
if (j == len2) //匹配成功,返回模式串第一次出现的首字符位置
return i - j + 1;
else //失败
return -1;
}
Java
public static void get_next(String s2, int[] next) {
int i = 0, j = -1;
next[0] = -1;
while(i < s2.length()) {
if(j == -1 || s2.charAt(i) == s2.charAt(j)) {
next[++i] = ++j;
}
else {
j = next[j];
}
}
}
public static int kmp(String s1, String s2, int[] next) {
int len1 = s1.length(), len2 = s2.length();
int i = 0, j = 0;
while(i < len1 && j < len2) {
if(j == -1 || s1.charAt(i) == s2.charAt(j)) {
i++; j++;
}
else {
j = next[j];
}
}
if(j == len2) {
return i-j+1;
}
else {
return -1;
}
}
Python
def get_next(s2, next):
i,j = 0,-1
next.append(-1)
while i < len(s2):
if j == -1 or s2[i] == s2[j]:
i,j = i+1,j+1
next.append(j)
else:
j = next[j]
def kmp(s1, s2, next):
i,j = 0,0
while i < len(s1) and j <len(s2):
if j == -1 or s1[i] == s2[j]:
i,j = i+1,j+1
else:
j = next[j]
if j == len(s2):
return i-j+1
else:
return -1
最长公共子序列
· · ·这是典型的利用动态规划思想求解字符串的问题,目标是求出两个字符串的最长公共子序列;字符串的一个子序列是原始字符串删除一些字符而不改变剩余字符相对位置形成的新字符串;根据求解目标和线性dp的核心特点,制定dp状态:dp[i][j]表示第一个串的前i个字符与第二个串的前j个字符的最长公共子序列长度;假定两个字符串分别为s1,s2;讨论:如果s1[i]≠s2[j],即s1[i]无法与s2[j]匹配,此时状态转移到dp[i-1][j]或dp[i][j-1],即dp[i][j] = max(dp[i-1][j],dp[i][j-1]),如果s1[i]==s2[j],此时根据前面记忆化的结果,有:dp[i][j] = dp[i-1][j-1]+1;另外注意考虑边界:当i=0或j=0,dp[i][j]=0;该问题(LCS)作为双串匹配的最基本dp问题,状态转移思想应用广泛
C++
int LCS(string s1, string s2) {
int n = s1.length(), m = s2.length();
vector<vector<int> > dp(n + 1, vector<int>(m + 1, 0)); // 初始化置零,考虑到i=0或j=0的情况
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if (s1[i - 1] == s2[j - 1]) { // 注意此处下标表示要-1
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); // 结合上一个转移方程,三个取最大
}
}
}
return dp[n][m]; // 返回最长公共长度
}
Java
public static int LCS(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[][] dp = new int[n+1][m+1]; // 默认初始化为0
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
if(s1.charAt(i-1) == s2.charAt(j-1)) {
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-1]+1);
}
}
}
return dp[n][m];
}
Python
def LCS(s1, s2):
n,m = len(s1),len(s2)
dp = [[0 for i in range(m+1)] for j in range(n+1)]
for i in range(1,n+1):
for j in range(1,m+1):
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
if s1[i-1] == s2[j-1]:
dp[i][j] = max(dp[i][j], dp[i-1][j-1]+1)
return dp[n][m]
单调栈
· · · 单调栈,单调队列,优先队列都是十分优秀的数据结构,在很多实际问题中有着广泛的应用;单调栈:栈内元素保持一定单调性的栈,包括单调递增栈和单调递减栈;单调栈有很多的应用场景,灵活变动性很大;一个最基础的应用就是对于一个数组中所有元素,每个元素只访问一次,找到每个元素右边比它大的元素下标之差,若不存在,则为0;下面就以解决此问题为例
C++
vector<int> Increase_Stack(vector<int>& array) {
stack<int> s;
vector<int> result(array.size()); // 存储元素索引,并非元素
for (int i = array.size() - 1; i >= 0; i--) {
// 单调递增栈,若当前元素大于栈顶元素,出栈
while (!s.empty() && array[s.top()] <= array[i]) {
s.pop();
}
/* 栈空则表示该元素右边不存在比它大的元素,否则,栈顶元素即是
当前元素右边最近的比它大的元素(根据入栈的顺序可知)*/
result[i] = s.empty() ? 0 : (s.top() - i);
s.push(i);
}
return result;
}
Java
public static int[] Increase_Stack(int[] array){
Stack<Integer> s = new Stack<Integer>();
int[] result = new int[array.length];
for(int i = array.length -1 ; i >= 0; i--) {
while(!s.isEmpty() && array[s.peek()] <= array[i]) {
s.pop();
}
result[i] = s.isEmpty() ? 0 : (s.peek() - i);
s.push(i);
}
return result;
}
Python
def Increase_Stack(array):
stack,result = [],{} # 用列表实现栈,字典存储元素索引
for i in range(len(array)-1, -1, -1):
while stack and array[stack[-1]] <= array[i]:
stack.pop()
if stack:
result[i] = stack[-1] - i
else:
result[i] = 0
stack.append(i)
return result
并查集
· · · 这是一种极其优雅的数据结构,其有着十分广泛的应用,比如:判断一个图是否存在环路,判断一个图是否为连通图,求最少需要添加几条线才能使一个图成为连通图(具体应用为城市道路问题)等。因在kruskal算法中用到并查集,在此引入求解最小生成树问题的两种算法,prim算法:从某一点出发,贪心选择与该点连通且未曾选入的边权最小的顶点加入集合,对每次新选入的点可能造成的影响来修改侯选边的边权和前驱结点,直到选入n-1个点。kruskal算法:也运用了贪心策略,它是一种按权值的递增次序依次选择合适的边来构造最小生成树的方法,其核心是判断并入的边会不会产生环;并查集的常见操作为查询,合并,添加等,在查询的时候完成路径压缩可降低在后面搜索的复杂度;下面给出并查集常见操作
C++
void init(int vset[], int n) {
for (int i = 0; i < n; i++) {
vset[i] = i; // 初始化将双亲结点指向自己,方便后面的查询判断
}
}
int find(int vset[], int x) { // 递归查询x的根结点,回溯时压缩路径
if (x == vset[x]) return x;
else return vset[x] = find(vset, vset[x]);
}
// 上面查询部分也可转成非递归写法
int find(int vset[], int x) {
int temp, root = x;
while (root != vset[root]) { // 查找根结点
root = vset[root];
}
while (x != root) { // 非递归路径压缩
temp = vset[x];
vset[x] = root;
x = temp;
}
return root;
}
inline bool is_connect(int vset[], int a, int b) { // 判断a,b两点是否连通
return find(vset, a) == find(vset, b);
}
void node_union(int vset[], int a, int b) { // 连通a,b两点
int x = find(vset, a), y = find(vset, b);
vset[x] = y;
}
Java
public class Union {
private int[] vset;
public Union(int n){
this.vset = new int[n];
}
public void init(int n) {
for(int i = 0; i < n; i++) {
vset[i] = i;
}
}
public int find(int x) { //查询部分用非递归写法,防止栈溢出
int temp, root = x;
while (root != vset[root]) {
root = vset[root];
}
while (x != root) {
temp = vset[x];
vset[x] = root;
x = temp;
}
return root;
}
public boolean is_connect(int a, int b) { // 判断a,b是否连通
return find(a) == find(b);
}
public void node_union(int a, int b) { // 连通a,b两个结点
int x = find(a), y = find(b);
vset[x] = y;
}
}
Python
class Union(object):
'''并查集类(基本操作)'''
def __init__(self, n):
self.vset = [i for i in range(n)]
def find(self, x): # 尾递归,回溯时压缩路径
if x == self.vset[x]:
return x
else:
self.vset[x]=self.find(self.vset[x])
return self.vset[x]
def is_connect(self, a, b):
return self.find(a) == self.find(b)
def node_union(self, a, b):
x,y = self.find(a),self.find(b)
self.vset[x] = y
Floyd
· · · 处理最短路问题时,常用这四种算法进行求解:Dijkstra是基于贪心策略的求解一个结点到其它结点的最短路径,其特点是应用广搜的思想,核心是考虑新加入结点的影响而修改前驱和记录的最短路长度;Floyd算法是基于动态规划思想的多源最短路径算法,用于求解任意两点之间的最短路径,利用滚动数组从三维降成二维,合理定义状态和根据关系寻找状态转移方程是关键;Dijkstra算法局限性最大,不允许存在负权边;而Floyd算法允许有负权边,不允许有负权环;要求解存在负权边甚至负权环的图时,就要用到Bellman-Ford算法和SPFA算法了,而后者SPFA算法是在前者的基础上进行了剪枝优化;下面给出Floyd算法核心代码
C++
// 维护路径矩阵
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
/* 从i号顶点到j号顶点经不经过k号顶点取二者的最短路径,
并利用滚动数组降维 */
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
}
}
}
Java
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = Math.min(a[i][j], a[i][k] + a[k][j]);
}
}
}
Python
for k in range(n):
for i in range(n):
for j in range(n):
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
记忆化搜索
· · · 该算法是搜索和动态规划的结合,通过记忆化已经搜索过的情况并直接利用该值从而避免大量的重复搜索,极大的提高了效率。这是一种很训练思维的算法,能够巧妙的解决很多实际问题。该算法综合了搜索剪枝的特性和动态规划记忆化的特性,具有很大的实用性。下面就以一个经典的滑雪问题为例,实现该算法的应用。问题大致描述:给定一个二维矩阵,从其中某个点出发,可到达上下左右四个相邻的点,当且仅当到达的点对应的数比前一个数小,求该矩阵中最长的一条此种路径
C++
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e2+5;
int a[maxn][maxn], dp[maxn][maxn], n, m;
int dx[4] = { -1,1,0,0 }, dy[4] = { 0,0,-1,1 };
void init(){ // 初始化二维矩阵
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
}
int mem_dfs(int x, int y) {
// 若是已经访问过的路径,直接返回记忆化存储结果
if (dp[x][y]) return dp[x][y];
int ans = 0;
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
//如果该点满足访问条件(没越界且数值比前一个点更小,访问并更新从该点出发的最长路径
if (xx > 0 && xx <= n && yy > 0 && yy <= m && a[xx][yy] < a[x][y]){
ans = max(mem_dfs(xx, yy), ans);
}
}
dp[x][y] = ans + 1; // 不满足条件时,加上本身点长度1,即为从该点出发的最大值
return dp[x][y];
}
int solve(){
int res = 0;
// 计算从每个点出发所得的最大长度,并更新最大值
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
res = max(mem_dfs(i, j), res);
}
}
cout << res; // 输出最优解
}
int main(){
init();
solve();
return 0;
}
Java
import java.util.Scanner;
public class Mem_Dfs {
private int n, m;
private int[][] a;
private int[][] dp;
private static int[] dx = {-1,1,0,0}, dy = {0,0,-1,1};
public Mem_Dfs(int n, int m) {
this.n = n;
this.m = m;
this.a = new int[n+1][m+1];
this.dp = new int[n+1][m+1];
}
public int me_dfs(int x, int y) {
if(dp[x][y] != 0) {
return dp[x][y];
}
int res = 0;
for(int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
if(xx > 0 && xx <= n && yy > 0 && yy <= m && a[xx][yy] < a[x][y]) {
res = Math.max(me_dfs(xx, yy), res);
}
}
dp[x][y] = res + 1;
return dp[x][y];
}
public void solve() {
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
ans = Math.max(me_dfs(i, j), ans);
}
}
System.out.println(ans);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
Mem_Dfs m_dfs = new Mem_Dfs(n, m);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
m_dfs.a[i][j] = in.nextInt();
}
}
m_dfs.solve();
}
}
Python
n,m = [int(num) for num in input().split()]
dp = [[0 for j in range(m)] for i in range(n)]
a = [[int(i) for i in input().split()] for j in range(n)]
dx,dy = [-1,1,0,0],[0,0,-1,1]
def mem_dfs(x, y):
if dp[x][y]:
return dp[x][y]
cnt = 0
for i in range(4):
xx,yy = dx[i] + x,dy[i] + y
if xx >= 0 and xx < n and yy >= 0 and yy < m:
if(a[xx][yy] < a[x][y]):
cnt = max(mem_dfs(xx, yy), cnt)
dp[x][y] = cnt + 1
return dp[x][y]
def solve():
ans = 0
for i in range(n):
for j in range(m):
ans = max(mem_dfs(i, j), ans)
print(ans)
def main():
solve()
if __name__ == '__main__':
main()
背包问题
该类问题是动态规划的入门问题,决定性思想和求解思路广泛应用于各个领域;
01背包特点:每种物品只有一件,即只存在该种物品放与不放的决策;
完全背包特点:每种物品无穷多件,相比01背包仅有状态转移方程发生改变;
多重背包特点:每种物品的数量是确定的,可以将其朴素拆分成01背包进行求解;
对以下程序做特别说明:n表示物品种数,m表示背包容量,w数组表示物品容量,v数组表示物品价值,dp数组存储每种状态下的最优解;以下程序都利用滚动数组做空间优化,二维变一维
C++
// 01背包
void beibao_1(int dp[], int w[], int v[], int n, int m) {
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
// 本次决策只与上一次的状态有关,且由状态转移的结果可知,需要逆向枚举
dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
}
}
}
// 完全背包
void beibao_2(int dp[], int w[], int v[], int m, int n) {
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= m; j++) {
//正向枚举,确保能够充分利用前面记忆化的最优解
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
// 多重背包(a[]存储每种物品有多少件)
void beibao_3(int dp[], int w[], int v[], int a[], int m, int n) {
// 朴素拆分成01背包进行求解
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 0; k <= a[i]; k++) {
if (j >= w[i] * k) {
// 存储放当前物品k件与不放的最优解
dp[j] = max(dp[j - w[i] * k] + v[i] * k, dp[j]);
}
}
}
}
}
Java
// 01背包
public static void beibao_1(int[] dp, int[] w, int[] v, int n, int m) {
for (int i = 1; i <= n; i++) {
// 逆向枚举
for (int j = m; j >= w[i]; j--) {
dp[j] = Math.max(dp[j - w[i]] + v[i], dp[j]);
}
}
}
// 完全背包
public static void beibao_2(int[] dp, int[] w, int[] v, int n, int m) {
for (int i = 1; i <= n; i++) {
// 正向枚举
for (int j = w[i]; j <= m; j++) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
// 多重背包
public static void beibao_3(int[] dp, int[] w, int[] v, int[] a, int n, int m) {
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
// 朴素拆分成01
for (int k = 0; k <= a[i]; k++) {
if (j >= w[i] * k) {
dp[j] = Math.max(dp[j - w[i] * k] + v[i] * k, dp[j]);
}
}
}
}
}
Python
# 01背包
def beibao_1(dp, w, v, n, m):
for i in range(1, n+1):
'''此处w与v的索引都是从0开始的,故要-1'''
for j in range(m, w[i-1]-1, -1):
dp[j] = max(dp[j - w[i-1]] + v[i-1], dp[j]);
# 完全背包
def beibao_2(dp, w, v, n, m):
for i in range(1, n+1):
for j in range(w[i-1], m+1):
dp[j] = max(dp[j], dp[j - w[i-1]] + v[i-1]);
# 多重背包
def beibao_3(dp, w, v, a, n, m):
for i in range(1, n+1):
for j in range(m, -1, -1):
'''同理此处a索引也是从0开始'''
for k in range(a[i-1]+1):
if j >= w[i-1]*k:
dp[j] = max(dp[j - w[i-1] * k] + v[i-1] * k, dp[j]);
博弈论基础
博弈问题特点:两名选手交替操作,每次一步且在有限的合法集合中选取一种进行,合法操作只取决于情况本身,与选手无关;
几种经典博弈问题:
巴士博弈:一堆n个物品,两个人轮流从中取出1~m个,最后取光者胜;
分析:n与m的关系可以表示成:n=k*(m+1)+r 和 n=k*(m+1),当为第一种情况,先者拿走r个,无论后者拿走多少了,先者都可以确保每轮拿走m+1个,从而最后获得胜利,以同样的方法来分析第二种情况,后者便变成了先者的第一种情况,此时后者会获胜
威佐夫博弈:两堆各若干物品,两人轮流从任意一堆中至少取出一个或者从两堆中取出相同多的物品,规定每次至少取一个,至多不限,最后取光者胜;
分析:利用数学归纳法,先统计出必输局势,可以看到每组的第一个是前面没有出现的最小正整数,
两堆可分别表示为a=[k*(1+sqrt(5)/2], b=a+k,k=0,1,2,3…,根据两者差值*黄金分割比是否等于最小值来判断是先手赢还是后手赢
斐波那契博弈:一堆n个物品,两人轮流取,先取者第一次可以取任意多个,但不能取完,以后每次取的物品数不能超过上次取物品数的两倍,取完者胜
分析:根据问题特点,可以判断先手胜当且仅当n不是斐波那契数
尼姆博弈:有n堆物品,两人轮流取,每次取某堆中不少于1个,最后取完者胜
分析:尼姆博弈是一个很重要的博弈问题,通过对可解状态的统计分析,我们可以发现将n堆物品数量全部异或后结果为0先手必败,否则必胜
很多博弈问题并非这几种标准博弈问题,我们需要通过SG函数将其转换成尼姆博弈进行求解
C++
// 巴士博弈
bool bashi(int n, int m) { // n个物品,每次每人最多取m个
if (n % (m + 1))
return true; //此时先手赢
else
return false; // 否则后手赢
}
// 威佐夫博弈
bool wezuofu(int a, int b) {// 两堆物品的个数分别为a,b
double r = (sqrt(5) + 1) / 2;
int d = abs(a - b) * r;
if (d != min(a, b)) //
return true; // 此时先手赢
else
return false; // 后手赢
}
// 斐波那契博弈
bool fabonacci(int f[], int n) { // n个物品
f[0] = f[1] = 1;
for (int i = 2; f[i - 1] < n; i++) {
f[i] = f[i - 1] + f[i - 2];
if (f[i] == n)
return true; // 先手赢
}
return false; // 后手赢
}
// 尼姆博弈
bool nim(int a[], int n) { // a数组存储n堆物品的数量
int res = 0;
for (int i = 1; i <= n; i++) {
res ^= a[i]; // n堆物品数量异或
}
if (res)
return true; // 先手赢
else
return false; // 后手赢
}
Java
// 巴士博弈
public static boolean bashi(int n, int m) {
if (n % (m + 1) != 0)
return true;
else
return false;
}
// 威佐夫博弈
public static boolean wezuofu(int a, int b) {
double r = (Math.sqrt(5) + 1) / 2;
int d = (int)(Math.abs(a - b) * r);
if (d != Math.min(a, b))
return true;
else
return false;
}
// 斐波那契博弈
public static boolean fabonacci(int f[], int n) {
f[0] = f[1] = 1;
for (int i = 2; f[i - 1] < n; i++) {
f[i] = f[i - 1] + f[i - 2];
if (f[i] == n)
return true;
}
return false;
}
// 尼姆博弈
public static boolean nim(int a[], int n) {
int res = 0;
for (int i = 1; i <= n; i++) {
res ^= a[i];
}
if (res != 0)
return true;
else
return false;
}
Python
# 巴士博弈
def bashi(n, m):
if n%(m+1):
return True
else:
return False
# 威佐夫博弈
def wezuofu(a, b):
r = float((math.sqrt(5)+1)/2)
d = int(abs(a - b)*r)
if d != min(a, b):
return True
else:
return False
# 斐波那契博弈
def fabonacci(n):
f = [1,1]
i = 2
while f[i-1] < n:
f.append(f[i-1] + f[i-2])
if f[i] == n:
return True
i += 1
return False
# 尼姆博弈
def nim(a, n):
res = 0
for i in range(n):
res ^= a[i]
if res:
return True
else:
return False
排序
三种低效排序:
冒泡排序:相邻元素比较,每一趟一个元素归位;以升序为例:向下冒大泡(待排序列最大元素从后往前不断归位);向上冒小泡(待排序列从前往后不断归位)
选择排序:每次选择目标数归位(最大值/最小值),在待排序列中找到目标数下标,与待放位置的元素交换
插入排序:从第二个元素开始处理,每次将其插入在前面已排好序的相应位置
C++
// 冒泡排序
void bubble_sort(int a[], int length) {
for (int i = 0; i < length - 1; i++) { // n-1轮比较,可优化
for (int j = 0; j < length - i - 1; j++) { // 向下冒大泡,从后向前归位
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]); //swap函数在<algorithm>头文件中
}
}
}
}
// 选择排序
void select_sort(int a[], int length) {
for (int i = 0; i < length - 1; i++) { // n-1个元素归位,第n个元素自动归位
int index = i;
for (int j = i + 1; j < length; j++) { // 每轮比较找到最小值下标
if (a[j] < a[index]) {
index = j;
}
}
swap(a[i], a[index]);
}
}
// 插入排序
void insert_sort(int a[], int length) {
for (int i = 1; i < length; i++) { // 从第二个元素开始插入
int temp = a[i], j;
// 在前面已排好序的序列中找到待插入位置
for (j = i - 1; j >= 0 && temp < a[j]; j--) {
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
Java
// 冒泡排序
public static void bubble_sort(int[] a) {
for(int i = 0; i < a.length-1; i++) {
for(int j = 0; j < a.length-i-1; j++) {
if(a[j] > a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
// 选择排序
public static void select_sort(int[] a) {
for(int i = 0; i < a.length-1; i++) {
int index = i;
for(int j = i+1; j < a.length; j++) {
if(a[j] < a[index]) {
index = j;
}
}
int temp = a[index];
a[index] = a[i];
a[i] = temp;
}
}
// 插入排序
public static void insert_sort(int[] a) {
for(int i = 1; i < a.length; i++) {
int temp = a[i], j;
for(j = i-1; j >= 0 && temp < a[j]; j--) {
a[j+1] = a[j];
}
a[j+1] = temp;
}
}
Python
# 冒泡排序
def bubble_sort(a):
for i in range(0,len(a)):
for j in range(0,len(a)-i-1):
if a[j] > a[j+1]:
a[j],a[j+1] = a[j+1],a[j]
# 选择排序
def select_sort(a):
for i in range(0,len(a)-1):
index = i
for j in range(i+1,len(a)):
if a[j] < a[index]:
index = j
a[i],a[index] = a[index],a[i]
# 插入排序
def insert_sort(a):
for i in range(1,len(a)):
temp = a[i]
j = i-1
while j >= 0 and temp < a[j]:
a[j+1] = a[j]
j -= 1
a[j+1] = temp
快速排序:以首元素为基准点,通过首尾指针将序列不断划分成大小两个部分,分别对划分后的序列做同样处理,递归至该子序列有序
C++
void quick_sort(int a[], int left, int right) {
int temp = a[left]; // 以首元素为基准点
int i = left, j = right;
if (i >= j) { // 序列长度为0或1必为有序序列,不用再处理
return;
}
while (i < j) { // 找到首元素待放位置,左小,右大
while (i < j && a[j] >= temp) {
j--;
}
a[i] = a[j];
while (i < j && a[i] <= temp) {
i++;
}
a[j] = a[i];
}
a[i] = temp; // 首元素归位
quick_sort(a, left, i - 1);
quick_sort(a, i + 1, right);
}
Java
public static void quick_sort(int[] a, int left, int right) {
int temp = a[left];
int i = left, j = right;
if(i >= j) {
return ;
}
while(i < j) {
while(i < j && a[j] >= temp) {
j--;
}
a[i] = a[j];
while(i < j && a[i] <= temp) {
i++;
}
a[j] = a[i];
}
a[i] = temp;
quick_sort(a, left, i-1);
quick_sort(a, i+1, right);
}
Python
def quick_sort(a, left, right):
temp,i,j = a[left],left,right
if i >= j:
return
while i < j:
while i < j and a[j] >= temp:
j -= 1
a[i] = a[j]
while i < j and a[i] <= temp:
i += 1
a[j] = a[i]
a[i] = temp
quick_sort(a, left, i-1)
quick_sort(a, i+1, right)
归并排序:利用二分思想,将待排序列递归分割,直到序列长度为1(有序),然后有序子序列两两合并
C++
void merge(int a[], int left, int mid, int right) {
int* temp = new int[right - left + 1]; // 合并成新序列辅助空间
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) { // 先处理完长度较小的序列
if (a[i] <= a[j]) {
temp[k++] = a[i++];
}
else {
temp[k++] = a[j++];
}
}
// 剩下序列为有序序列,直接归位
while (i <= mid) {
temp[k++] = a[i++];
}
while (j <= right) {
temp[k++] = a[j++];
}
for (i = 0, j = left; i < k; i++, j++) {
a[j] = temp[i];
}
}
void merge_sort(int a[], int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
merge_sort(a, left, mid);
merge_sort(a, mid + 1, right);
merge(a, left, mid, right);
}
Java
public static void merge_sort(int[] a, int left, int right) {
if(left >= right) {
return ;
}
int mid = (left+right)/2;
merge_sort(a, left, mid);
merge_sort(a, mid+1, right);
merge(a, left, mid, right);
}
public static void merge(int[] a, int left, int mid, int right) {
int[] temp = new int[right-left+1];
int i = left, j = mid+1, k = 0;
while(i <= mid && j <= right) {
if(a[i] <= a[j]) {
temp[k++] = a[i++];
}
else {
temp[k++] = a[j++];
}
}
while(i <= mid) {
temp[k++] = a[i++];
}
while(j <= right) {
temp[k++] = a[j++];
}
for(i = 0, j = left; i < k; i++, j++) {
a[j] = temp[i];
}
}
Python
def merge_sort(a, left, right):
if left >= right:
return
mid = int((left+right)/2)
merge_sort(a, left, mid)
merge_sort(a, mid+1, right)
merge(a, left, mid, right)
def merge(a, left, mid, right):
temp = []
i,j = left,mid+1
while i <= mid and j <= right:
if a[i] <= a[j]:
temp.append(a[i])
i += 1
else:
temp.append(a[j])
j += 1
temp += a[i:mid+1]+a[j:right+1]
a[left:right+1] = temp
希尔排序:按下标的希尔增量对序列进行分组,对每组使用直接插入排序算法排序,随着增量减少,每组序列元素增多,增量为1时,恰为有序一组
C++
void shell_sort(int a[], int length) {
for (int h = length / 2; h > 0; h /= 2) { // 逐步缩小增量h
// 从第h个元素逐个对其所在组进行直接插入排序操作
for (int i = h; i < length; i++) {
int j = i;
while (j >= h && a[j] < a[j - h]) {
swap(a[j], a[j - h]);
j -= h;
}
}
}
}
Java
public static void shell_sort(int[] a) {
for(int h = a.length/2; h > 0; h /= 2) {
for(int i = h; i < a.length; i++) {
int j = i;
while(j >= h && a[j] < a[j-h]) {
int temp = a[j];
a[j] = a[j-h];
a[j-h] = temp;
j -= h;
}
}
}
}
Python
def shell_sort(a):
h = int(len(a)/2)
while h > 0:
for i in range(h,len(a)):
j = i
while j >= h and a[j] < a[j-h]:
a[j],a[j-h] = a[j-h],a[j]
j -= h
h = int(h/2)
二分查找(加强版)
· · · 二分查找常应用在一个有序序列中进行数据查找,在该序列中待查找数可能不存在,可能存在多个,要求查找该数据第一次出现的位置或者再加上最后一次出现的位置,对于此类问题,要在logn的时间内完成查找,就需要用到二分查找,下面就以寻找某给定数据在序列中第一次出现的位置为例
C++
// 在a数组中寻找x第一次出现的位置,并返回其下标
int find(int a[], int len, int x) {
int left = 0, right = len - 1;
// 非递归求解
while (left < right) {
int mid = (left + right) / 2;
if (a[mid] >= x) // 此处等号是关键,通过修改等号的位置,可以找到该数所在区间
right = mid; // 注意右端点的修改位置
else
left = mid + 1;
}
if (a[left] == x)
return left;
else
return -1; //表示该序列不存在待查找数据
}
Java
public static int find(int[] a, int x) {
int left = 0, right = a.length;
while(left < right) {
int mid = (left+right)/2;
if(a[mid] >= x)
right = mid;
else
left = mid + 1;
}
if(a[left] == x)
return left;
else
return -1;
}
Python
def find(a, x):
left,right = 0,len(a)-1
while left < right:
mid = int((left + right)/2)
if a[mid] >= x:
right = mid
else:
left = mid + 1
if a[left] == x:
return left
else:
return -1
埃氏筛
· · · 埃氏筛和欧拉筛是两种常见的素数筛法,埃氏筛原理:如果某数是合数,那么它的倍数也一定是合数,根据这个原理可以避免很多不必要的检测;欧拉筛原理:在埃氏筛的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复筛除的目的,效率为线性,故又称线性筛
C++
void count_prime(int is_prime[], int n) { // 求出n以内的所有素数,初始化都为素数,默认为0
is_prime[0] = is_prime[1] = 1; // 0和1都不是素数
for (int i = 2; i <= n; i++) {
if (!is_prime[i]) { // 如果i是素数,那么i的倍数则不是素数
for (int j = i * i; j <= n; j += i) { // 注意从i*i开始筛是因为2到i-1的倍数已经筛过了
is_prime[j] = 1;
}
}
}
}
Java
public static void count_prime(boolean[] is_prime, int n) { // 初始化默认全是素数,表示成ture
is_prime[0] = is_prime[1] = false;
for(int i = 2; i <= n; i++) {
if(is_prime[i]) { // 如果i为素数
for(int j = i*i; j <= n; j += i) {
is_prime[j] = false;
}
}
}
}
Python
def count_prime(is_prime, n):
is_prime[0] = is_prime[1] = 1
for i in range(2,n+1):
if not is_prime[i]:
j = i*i
while j <= n:
is_prime[j] = 1
j += i
唯一分解定理
· · · 对于任何一个大于1的正整数,都存在一个标准的分解式:N=p1a1*p2a2…pn^an(其中pn为质数,an为指数);此定理表明:任何一个大于1的正整数都可以表示为素数的积;该定理有着广泛的应用,设X(n)代表n的正因子的数量,X(n)=(a1+1)(a2+1)…*(an+1),下面就给出求解一个数正因子数量的代码
C++
// 求解x正因子的数量
int get_count(int prime[],int cnt, int x) { // prime为素数数组,cnt为素数个数
int ans = 1;
for (int i = 1; i <= cnt && prime[i] <= x; i++) {
int sum = 0; // 表示当前质因数的幂指数
while (x % prime[i] == 0) {
sum++;
x /= prime[i];
}
ans *= (sum + 1); // 直接利用定理计算
}
if (x > 1) // 此时最后结果肯定是素数,根据定理,故乘2
ans *= 2;
return ans;
}
Java
public static int get_count(int[] prime, int cnt, int x) {
int res = 0;
for(int i = 1; i <= cnt && prime[i] <= x; i++) {
int sum = 0;
while(x % prime[i] == 0) {
sum++;
x /= prime[i];
}
res *= (sum+1);
}
if(x > 1)
res *= 2;
return res;
}
Python
def get_count(prime, cnt, x):
ans,i = 0,1
while i <= cnt and prime[i] <= x:
sum = 0
while(x % prime[i] == 0):
sum += 1
x /= prime[i]
ans *= (sum+1)
if x > 1:
ans *= 2
return ans
总结:虽然只涉及了常用算法的冰山一角,但这些思想在后面很多算法的理解上有着很深刻的应用