dijkstra(单源最短路径):

策略:

设集合S存放已被访问的顶点,然后执行n次下面的两个步骤

(1)每次从集合V-S(未访问节点)选择与起点s的距离最小的一个顶点(记为u),访问并加入集合S(已访问节点)。

(2)之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。

constint MAXV = 1000;//最大顶点数
constint INF = 0x3f3f3f3f;//设INF为一个很大的数

int n, G[MAXV][MAXV];//n为定点数,MAXV为最大定点数
int d[MAXV];//起点到各点的最短路径长度
bool vis[MAXV] = {false};//标记数组,vis[i]==true表示已访问。初值均为false

void Dijkstra(int s){//s为起点
    fill(d, d + MAXV, INF);//fill函数将整个d数组赋为INF(慎用memset)
    d[s] = 0;//起点s到达自身的距离为0
    for(int i = 0; i < n; i++){//循环n次
        int u = -1, MIN = INF;//u使d[u]最小,MIN存放该最小的d[u]
        for(int j = 0; j < n; j++){//
            if(vis[j] == false && d[j] < MIN){
                u = j;
                MIN = d[j];
            } 
        }
        //找不到小于INF的d[u],说明剩下的顶点和起点s不连通
        if(u == -1) return;
        vis[u] = true;//标记u为已访问
        for(int v = 0; v < n; v++){//从u出发能达到的所有顶点v,更新d[v]
            //如果v未访问&&u能到达v&&以u为中介点可以使d[v]更优
            if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
                d[v] = d[u] + G[u][v];//优化d[v]
            }
        }
    }
}

 

图的DFS(求联通分量数):

①邻接矩阵

const int MAXV = 1000;//最大顶点数
const int INF = 0x3f3f3f3f;//设INF为一个很大的数

int n, G[MAXV][MAXV];//n为定点数,MAXV为最大定点数
bool vis[MAXV] = {false};//如果顶点i已被访问,则vis[i]==true。初值为false

void DFS(int u, int depth) {//u为当前访问的顶点标号,depth为深度
    vis[u] = true;//设置u已被访问
    //如果需要对u进行一些操作,可以在这里进行
    //下面对所有从u出发能到达的分支进行枚举
    for (int v = 0; v < n; v++) {//对每个顶点v
        if (vis[v] == false && G[u][v] != INF) {//如果v未被访问,且u可达v
            DFS(v, depth + 1);//访问v,深度加1
        }
    }
}

void DFSTrace(){//遍历图G
    for(int u = 0; u < n; u++){//对每个顶点u
        //int count=0//用来统计联通分量数
        if(vis[u] == false){//如果u未被访问
            DFS(u, 1);//访问u和所在的连通块,1表示初始为1层
            //count++;
        }
    }
}

②邻接表

vector<int> Adj[MAXV];//图G的邻接表
int n;//n为定点数,MAXV为最大顶点数
bool vis[MAXV] = { false };//如果顶点i已被访问,则vis[i]==true,初值为fales

void DFS(int u, int depth) {//u为当前访问的顶点标号,depth为深度
    vis[u] = true;//设置u已被访问
    /*如果需要对u进行一些操作,可以在此处进行*/
    for (int i = 0; i < Adj[u].size(); i++) {//对从u出发可以到达的所有顶线v
        int v = Adj[u][i];
        if (vis[v] == false) {//如果v未被访问
            DFS(v, depth + 1);//访问v,深度加1
        }
    }
}

void DFSTrave() {//遍历图G
    for (int u = 0; u < n; u++) {//对每个顶点u
        if (vis[u] == false) {//如果u未被访问
            DFS(u, 1);//访问u和u所在的连通块,1表示初始为第一层
        }
    }
}

图的BFS

int n, G[MAXV][MAXV];//n为顶点数,MAXV为最大顶点数
bool inq[MAXV] = { false };//若顶点i曾入过队列,则inq[i]==true,初值为false

void BFS(int u) {//遍历u所在的连通块
	queue<int> q;//定义队列q
	q.push(u);//将初始点u入队
	inq[u] = true;//设置u已被加入过队列
	while (!q.empty()) {//只要队列非空
		int u = q.front();//取出队首元素
		q.pop();//将队首元素出队
		for (int v = 0; v < n; v++) {
			//如果u的邻接点v未曾加入过队列
			if (inq[v] == false && G[u][v] != INF) {
				q.push(v);//将v入队
				inq[v] = true;//标记v已被加入过队列
			}
		}
	}
}

void BFSTrave() {//遍历图G
	for (int u = 0; u < n; u++) {//枚举所有顶点
		if (inq[u] == false) {//如果u未曾加入过队列
			BFS(u);//遍历u所在的连通块
		}
	}
}

01背包:

递推:

dp[i][v]表示前i件物品(1≤i≤n,0≤v≤V)恰好装入容量为v的背包中所能获得的最大价值。

考虑对i件物品的选择策略:

1、不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得 的最大价值,也即dp[i-1][v]

2、放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i]

$$ dp[i][v]=max\left\{dp[i-1][v],dp[i-1][v-w[i]]+c[i]\right\}$$

$$(1\le i \le n ,w[i]\le v\le V)$$

滚动数组:

$$ dp[j]=max\left\{dp[j],dp[j-w[i]]+c[j]\right\} $$

#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 100;//物品最大件数
const int maxv = 1000;//V的上限

int w[maxn], c[maxn], dp[maxv];
int main() {
    int n, V;
    cin >> n >> V;
    for (int i = 0; i < n; i++) {
        cin >> c[i];
    }
    //边界
    for (int v = 0; v < V; v++) {
        dp[v] = 0;
    }
    for (int i = 1; i <= n; i++) {
        for (int v = V; v >= w[i]; v--) {
            //状态转移方程
            dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
        }
    }
    //寻找dp[0...V]中最大的即为答案
    int max = 0;
    for (int v = 0; v <= V; v++) {
        if (dp[V] > max) {
            max = dp[v];
        }
    }
    cout << max;
}

完全背包

区别于01背包,每种物品都有无穷件。

dp[i][v]表示前i件物品(1≤i≤n,0≤v≤V)恰好装入容量为v的背包中所能获得的最大价值。

递推:

考虑对i件物品的选择策略:

1、不放第i件物品,那么dp[i][v]=dp[i-1][v],这步和01背包是一样的。

2、放第i件物品,不同于01背包,而是转移到dp[i][v-w[i]],这是因为每种物品可以放任意件(注意有容量限制,因此还是有限的),放了第i件物品后还可以继续放第i件物品,直到第二维的v-w[i]无法保持大于等于0为止。

$$ dp[i][v]=max\left\{dp[i-1][v],dp[i][v-w[i]]+c[i]\right\}$$

$$(1\le i \le n ,w[i]\le v\le V)$$

边界:$dp[0][v]=0(0\le v\le V)$

状态转移方程:

$$ dp[j]=max\left\{dp[j],dp[j-w[i]]+c[j]\right\} $$

形成一维后和01背包完全相同,唯一的区别在于这里v的枚举顺序是正向枚举。

最小生成树:

prim算法:

using namespace std;

const int MAXV = 1000;//最大顶点数
const int INF = 100000000;//设INF为一个很大的数

int n, G[MAXV][MAXV];//n为顶点数,MAXV为最大顶点数
int d[MAXV];//顶点与集合S的最短距离
bool vis[MAXV] = { false };//标记数组,vis[i]==true表示已访问。初值均为false

int prim() {//默认0号为初始点,函数返回最小生成树的边权之和
    fill(d, d + MAXV, INF);//fill函数将整个d数组赋为INF(慎用memset)
    d[0] = 0;//只有0号顶点到集合S的距离为0,其余全为INF
    int ans = 0;//存放最小生成树的边权之和
    for (int i = 0; i < n; i++) {//循环n次
        int u = -1, MIN = INF;//u使d[u]最小,MIN存放该最小的d[u]
        for (int j = 0; j < n; j++) {//找到未访问的顶点中的d[]最小的
            if (vis[j] == false && d[j] < MIN) {
                u = j;
                MIN = d[j];
            }
        }
        //找不到小于INF的d[u],则剩下的顶点和集合S不连通
        if (u == -1)return -1;
        vis[u] = true;//标记u为已访问
        ans += d[u];//将与集合S距离最小的边加入最小生成树
        for (int v = 0; v < n; v++) {
            //v未访问&&u能到达v&&以u为中介点可以使v离集合S更近
            if (vis[v] == false && G[u][v] != INF && G[u][v] < d[v]) {
                d[v] = G[u][v];//将G[u][v]赋值给d[v]
            }
        }
    }
    return ans;//返回最小生成树的边权之和
}