总复习(各算法的基本思路和模板)

目录

搜索

一,深搜(DFS)

二,广搜

三,记忆化搜索

并查集

KMP

哈希

最小生成树

Prim

 kruskal


搜索

一,深搜(DFS)

要点:就是要走就先走完一整条路,再返回去走别的可能的路

方法:

1.访问指定的起始顶点
2.若当前访问的顶点的邻接顶点有未被访问的(通过标记数组实现),则任选一个去访问;反之,退回到最近访问过的顶点;直到与起始点相通的全部顶点访问完毕
3.若图中还有顶点未被访问,再选其中一个顶点作为起始顶点进行访问,进行步骤2;反之,遍历结束。

模板:

1:首先将遍历过的进行标记
2:将当前状态赋值
3:递归搜索
4:回到当前状态以后,之前的标记要消除

注意:使用递归

模板代码如下(例题):

全排列

#include <iostream>
#include <algorithm>

using namespace std;

int arr[1000];
int book[1000]; //用来表示此数字有没有被用过,初始化为0表示没用过
int n;
void dfs(int step)//step表示当前处于第step个盒子
{
    if(step==n+1) //先写退出条件,让答案输出
    {
        for(int i  = 1; i <= n; i++) cout<< arr[i] << ' ';
        cout << endl;
        return;  //此题目一定要写,不返回的话会一直搜索下去然后卡死
    }//让它返回到第n个盒子继续处理
    for(int i = 1; i <= n; i++)
    {//遍历盒子如果book[i]显示为0的话表示没用过,可放到盒子里去
        if(!book[i])
        {
            book[i] = 1; //先让他标记为1表示用过了
            arr[step] = i;//放到盒子里去
            dfs(step+1);//继续搜索下一个地方
            book[i] = 0;//消除之前的标记
        }
    }
}
int main()
{
    cin>>n;
    dfs(1);
    return 0;
}

整数划分

#include <iostream>
#include <cstdio>

using namespace std;

int arr[1000];
int n;
int sum;

void dfs(int step, int k)
{
    if(step == k+1) //退出条件, 第step个盒子变成k+1个
    {
        int num = 0;
        for(int i = 1; i <= n; i++)
            num += arr[i];
        if(num == n)
        {
            printf("%d=%d",n,arr[1]);
            for(int i = 2; i <= k; i++)
                printf("+%d",arr[i]);
            cout << endl;
            sum++;
        }
        return;
    }
    for(int i = 1; i <= n; i++)
    {
        arr[step] = i;//数字可以重复使用,没有必要去标记
        if(arr[step] >= arr[step - 1])
        {//因为1 2 3和3 2 1是一样的,所以要制定一个规则让后一个数大于等于前一个数
            dfs(step + 1, k);
        }
    }
}

int main()
{
    cin>>n;
    for(int i = 1; i <= n; i++)
        dfs(1,i);
    cout << sum;
    return 0;
}

迷宫问题:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll n, m, min = 0x3fffffff;
ll arr[501][501], book[501][501];//book为标记数组
ll dx[] = {0,1,0,-1};//x方向上的偏移量
ll dy[] = {1,0,-1,0};//y方向上的偏移量
void dfs(int x, int y, ll step)//x,y表示当前所在的点的横纵坐标,step表示当前的步数
{
    ll tx, ty, k;
    if (x == n - 1 && y == n - 1)//如果当前所在位置是终点,说明已经走完
    {
        if (step < min)//如果比最短路径还要小,更新min
            min = step;
        return;//注意
    }
    for (k = 0; k < 4; k++) //跟新下一个坐标
    {
        tx = x + dx[k];//利用此方法,算出下一个需要走的点的坐标
        ty = y + dy[k];
        if (tx<0 || tx>n-1 || ty<0 || ty>n-1)//判断边界,如果越界就不执行
            continue;
        if (book[tx][ty] == 0 && arr[tx][ty] == 0) //当前没有走过并且当前可以走不是墙
        {
            book[tx][ty] = 1;  //标记为走过
            dfs(tx, ty, step + 1);//搜素下个点
            book[tx][ty] = 0;  //恢复
        }
    }
}

int main(void)
{
    ll i, j;
    scanf("%lld", &n);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
            scanf("%lld", &arr[i][j]);
    }
    book[0][0] = 1;//起点首先标记为走过
    dfs(0, 0, 0);//从起点开始搜索
    printf("%lld\n", min);
    return 0;
}

二,广搜

要点:和深搜不同,它是一层一层地搜索

方法:
从图的某一结点出发,首先一次访问该节点的所有邻接点,再按这些顶点被访问的先后次序依次访问与他们相邻接且未被访问的所有的点,重复此过程,直至所有顶点均被访问为止。

注意:循环,队列

迷宫问题:

#include <iostream>
#include <algorithm>

using namespace std;
int arr[501][501];
int book[501][501];
int dx[] ={ 0,1,0,-1 };//规定四个方向
int dy[]= { 1,0,-1,0 };
struct f {
    int x;
    int y;
    int s;
}map[2500];
int n;
void bfs()
{
    int tail = 1, head = 1;//将起点入队
    map[tail].x = 0, map[tail].y = 0;
    map[tail].s = 0;
    book[0][0] = 1;//标记起点
    tail++;
    while (head < tail)
    {
        int k = 0;
        for (int i = 0; i < 4; i++)//遍历四个方向
        {
            int nx = map[head].x + dx[i];//计算下一个方向
            int ny = map[head].y + dy[i];
            if (nx<0 || nx>n - 1 || ny<0 || ny>n - 1)
                continue;
            if (book[nx][ny] == 0 && arr[nx][ny] == 0)
            {
                book[nx][ny] = 1;
                map[tail].x = nx;//更新队列
                map[tail].y = ny;
                map[tail].s = map[head].s + 1;//等于父亲步数加一
                tail++;
            }
            if (nx == n-1 && ny == n-1)//如果等于终点
            {
                k = 1;
                cout << map[tail - 1].s;//注意要减一
                break;
            }
        }   if (k == 1)
            break;
        head++;//四个方向探索完毕后,head++模拟出队效果

    }
}
int main(void)
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            scanf("%d", &arr[i][j]);
    }
    bfs();
    return 0;
}

三,记忆化搜索

注意:动态规划问题可以使用记忆化搜索 + 递归来求解。也是 dfs 优化的方式,两者本无区别。

仙岛求药
少年李逍遥的婶婶病了,王小虎介绍他去一趟仙灵岛,向仙女姐姐要仙丹救婶婶。叛逆但孝顺的李逍遥闯进了仙灵岛,克服了千险万难来到岛的中心,发现仙药摆在了迷阵的深处。迷阵由 M×NM \times NM×N 个方格组成,有的方格内有可以瞬秒李逍遥的怪物,而有的方格内则是安全。现在李逍遥想尽快找到仙药,显然他应避开有怪物的方格,并经过最少的方格,而且那里会有神秘人物等待着他。现在要求你来帮助他实现这个目标。

输入格式

第一行输入两个非零整数 MMM 和 NNN,两者均不大于 202020。MMM 表示迷阵行数, NNN 表示迷阵列数。

接下来有 MMM 行, 每行包含 NNN 个字符,不同字符分别代表不同含义:

‘@’:少年李逍遥所在的位置;2) ‘.’:可以安全通行的方格;3) ‘#’:有怪物的方格;4) ‘*’:仙药所在位置。
输出格式

输出一行,该行包含李逍遥找到仙药需要穿过的最少的方格数目(计数包括初始位置的方块)。如果他不可能找到仙药, 则输出 −1-1−1。

输出时每行末尾的多余空格,不影响答案正确性

样例输入1

8 8
.@##…#
#…#.#
#.#.##…
…#.###.
#.#…#.
…###.#.
…#.*…
.#…###

样例输出1

10

样例输入2

6 5
.*.#.
.#…
…##.

.#…
…@

样例输出2

8

样例输入3

9 6

.#…#.
.#.*.#
.####.
…#…
…#…
…#…
…#…
#.@.##
.#…#.
样例输出3
-1


分析:

这是一道典型的搜索题目,不过如果你只贴上dfs的模板指定是过不了的,必须进行优化,但是使用BFS的话就直接可以过了,我们知道dfs的思路是不撞南墙不回头,但其实有些路径走到一半就知道这是一条不归路了,我们可以用一个数组来储存到达每一个坐标点的最短路径,当搜索的时候如果现在的步数加一大于到达下一个坐标位置的最短路径,则无需继续,直接改变方向。

代码如下:

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
char a[25][25];  //地图
int step[25][25];  //记录每一个走到的位置的最短路径
int dx[5]={1,0,0,-1};  //下一个位置
int dy[5]={0,1,-1,0};
int n,m;  //地图大小
bool flag = false;  //标记
void dfs(int x,int y)
{
    if(a[x][y]=='*'){  //到达终点返回
        flag = true;
        return ;
    }
    for(int i=0;i<4;i++){
//就是优化到这里了,加了一个判断如果当前位置的步数+1 > 下一个位置的最小步数,就不用走了否则就更新下一个位置的最小步数

        int tx=x+dx[i];
        int ty=y+dy[i];
        if(tx>=0 && tx<n && ty<m && ty>=0 && a[tx][ty]!='#'&& step[x][y]+1<step[tx][ty]){
            step[tx][ty] = step[x][y]+1;  //更新这个位置的最小坐标
        	dfs(tx,ty);  //继续搜索下一位置
        }
    }
}
int main()
{
    int qx,zx,qy,zy;
    cin >> n >> m;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            step[i][j]=1<<30; //初始化每一个坐标的最小路径,初始值很大
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            cin >> a[i][j];
            if(a[i][j]=='@'){
                qx = i; qy = j; //找到起始位置,把起始位置的最短路径设置为0
                step[qx][qy]=0;
            }
            if(a[i][j]=='*'){  //找到终点
                zx = i; zy = j;
            }
        }
    }
    dfs(qx,qy);
    if(flag)
        cout << step[zx][zy];
    else
        cout << "-1";
    return 0;
}


 

并查集

这个小算法对于我来说是挺简单的,主要就是分为两步,合并和查询,合并那里又可以成为路径压缩。

模板如下:

int find(int x)//查找
{
	return pre[x] == 0 ? x : pre[x] = find(pre[x]);
}

void join(int x, int y) //合并
{
	int fx = find(x);
	int fy = find(y);
	if (fx!=fy){
		pre[fx] = fy;
		//合并集合,计算个数
		//size[fy] += size[fx];
	}
}

KMP

要点:
对于kmp最主要的有两个要点,一个是next数组,一个就是kmp的实现,首先要先求出next数组,然后再进行kmp算法的实现。

那么对于next数组怎么求呢?模板如下:
 

void getNext(int m){
	int j = 0;
	// 初始化next[0]的值
	kmp_next[0] = 0;
	for(int i=1; i<m; ++i){
		// 当这一位不匹配时,将j指向此位之前最大公共前后缀的位置
		while(j>0 && b[i]!=b[j]) j=kmp_next[j-1];
		// 如果这一位匹配,那么将j+1,继续判断下一位
		if(b[i]==b[j]) ++j;
		// 更新next[i]的值
		kmp_next[i] = j;
	}
}

对于Kmp算法一般有两种题型:

一,寻找匹配位置

int kmp(int n,int m){
	int i, j = 0;
	// 初始化位置p = -1
	int p = -1;
	// 初始化next数组
	getNext(m);
	for(i=0; i<n; ++i){
		// 当这一位不匹配时,将j指向此位之前最大公共前后缀的位置
		while(j>0 && b[j]!=a[i]) j=kmp_next[j-1];
		// 如果这一位匹配,那么将j+1,继续判断下一位
		if(b[j]==a[i]) ++j;
		// 如果是子串(m位完全匹配),则更新位置p的值,并中断程序
		if(j==m){
			p = i - m + 1;
			break;
		}
	}
	// 返回位置p的值
	return p;
}

二,计算匹配次数

int kmp(int n,int m){
    // 初始化答案数量res = 0
	int i, j = 0, res = 0;
	// 初始化next数组
	getNext(m);
	for(i=0; i<n; ++i){
		// 当这一位不匹配时,将j指向此位之前最大公共前后缀的位置
		while(j>0 && b[j]!=a[i]) j=kmp_next[j-1];
		// 如果这一位匹配,那么将j+1,继续判断下一位
		if(b[j]==a[i]) ++j;
		// 如果是子串(m位完全匹配),则答案数量增加,res = res + 1
		if(j==m) ++res;
	}
	// 返回答案数量
	return res;
}

哈希

要点:

就是把字符串改写成一个数字。

基本代码如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1000000007
#define M 14371003
int a[10005];
int hash(char s[])
{
	int len = strlen(s), ans = 0;
	for (int i = 0; i < len; i++)
		ans = ((long long)ans * M + s[i]) % N;
	return ans;
}
int main()
{
	char s[1600]; int n, ans = 1;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%s", s);
		a[i] = hash(s);
	}
	std::sort(a, a + n);//排序方便查询
	for (int i = 1; i < n; i++)
		if (a[i] != a[i - 1])ans++;
	printf("%d", ans);
	return 0;
}

一维哈希要点(两个):

typedef long long int LL;
const int M=233;//是我们自己选择的进制数,一般可以选233,2333,10007等质数
const int mod=1e9+7;//一般选一个比较大的质数
LL get(string s)//获取字符串对应的哈希值
{
	LL sum=0;
	for(int i=0;i<s.size();i++) sum=(sum*M+s[i]-'a')%mod;
	return sum;
}
typedef unsigned long long int ull;
const int N=1e5+10;
const int M=233;
ull h[N],base[N];
ull query(int l,int r)//获取字符串[l,r]的哈希值
{
    return h[r]-h[l-1]*base[r-l+1];
}
void init(string s)//初始化哈希
{
    int n=s.size();
    s="0"+s;//让其下标从1开始
    base[0]=1;
    for(int i=1;i<=n;i++)
    {
        h[i]=h[i-1]*M+s[i];
        base[i]=base[i-1]*M;// base[i]=M^i
    }
}
ull merge(int l1, int r1, int l2, int r2)
//求[l1,r1],[l2,r2]子串并的哈希值
{
    return query(l1, r1) * base[r2 - l2 + 1] + query(l2, r2);
}

二维矩阵哈希:

typedef long long int ll;
const int N=1010;
ll h[N][N],base1[N],base2[N];
int a[N][N],n,m;
void init()//构建
{
    base1[0]=base2[0]=1;
    for(int i=1;i<N;i++)
    {
        base1[i]=base1[i-1]*131;
        base2[i]=base2[i-1]*233;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            h[i][j]=h[i][j-1]*131+a[i][j];//行哈希
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            h[i][j]=h[i-1][j]*233+h[i][j];//列哈希
}
ll query(int x1,int y1,int x2,int y2)//查询矩阵的哈希值
{
    return h[x2][y2]-h[x2][y1-1]*base1[y2-y1+1]-h[x1-1][y2]*base2[x2-x1+1]
    +h[x1-1][y1-1]*base1[y2-y1+1]*base2[x2-x1 + 1];
}

最小生成树

Prim

要点:

① 先建立一个只有一个结点的树,这个结点可以是原图中任 意的一个结点。

② 使用一条边扩展这个树,要求这条边一个顶点在树中另一 个顶点不在树中,并且这条边的权值要求最小。

③ 重复步骤②直到所有顶点都在树中。

模板如下:

#include<stdio.h>
#define MAX 100
#define MAXCOST 100000

int graph[MAX][MAX];

void prim(int graph[][MAX], int n)
{
    int lowcost[MAX];//lowcost[i]:表示以i为终点的边的最小权值,当lowcost[i]=0表示i点加入了MST
    int mst[MAX];//表示对应lowcost[i]的起点,当mst[i]=0表示起点i加入MST
    int i, j, min, minid, sum = 0;
    for (i = 2; i <= n; i++)
    {
        lowcost[i] = graph[1][i];//lowcost存放顶点1可达点的路径长度
        mst[i] = 1;//初始化以1位起始点
    }
    mst[1] = 0;
    for (i = 2; i <= n; i++)
    {
        min = MAXCOST;
        minid = 0;
        for (j = 2; j <= n; j++)
        {
            if (lowcost[j] < min && lowcost[j] != 0)
            {
                min = lowcost[j];//找出权值最短的路径长度
                minid = j; //找出最小的ID
            }
        }
        printf("V%d-V%d=%d\n",mst[minid],minid,min);
        sum += min;//求和
        lowcost[minid] = 0;//该处最短路径置为0
        for (j = 2; j <= n; j++)
        {
            if (graph[minid][j] < lowcost[j])//对这一点直达的顶点进行路径更新
            {
                lowcost[j] = graph[minid][j];
                mst[j] = minid;
            }
        }
    }
    printf("最小权值之和=%d\n",sum);
}
int main()
{
    int i, j, k, m, n;
    int x, y, cost;
    scanf("%d%d",&m,&n);//m=顶点的个数,n=边的个数

    for (i = 1; i <= m; i++)//初始化图
    {
        for (j = 1; j <= m; j++)
        {
            graph[i][j] = MAXCOST;
        }
    }
    for (k = 1; k <= n; k++)
    {
    scanf("%d%d%d",&i,&j,&cost);
        graph[i][j] = cost;
        graph[j][i] = cost;
    }

    prim(graph, m);
    return 0;
}

 kruskal

要点:

Kruskal 算法是能够在O(mlogm) 的时间内得到一个最小生成树的算 法。它主要是基于贪心的思想:

① 将边按照边权从小到大排序,并建立一个没有边的图T。

② 选出一条没有被选过的边权最小的边。

③ 如果这条边两个顶点在T 中所在的连通块不相同,那么将 它加入图T, 相同就跳过。

④ 重复②和③直到图T 连通为止。
其实这里只需要维护连通性,可以不需要真正建立图T,还可以用并查集 来维护。

模板如下:

#include <stdio.h>
#define MAXE 100
#define MAXV 100
typedef struct{
	int vex1;                     //边的起始顶点
	int vex2;                      //边的终止顶点
	int weight;                    //边的权值
}Edge;
void kruskal(Edge E[],int n,int e)
{
	int i,j,m1,m2,sn1,sn2,k,sum=0;
	int vset[n+1];
	//借用一个辅助数组vset[i]用来判断某边是否加入了最小生成树集合
	//就是把每个顶点都看成一个连通分量,并查集数组初始化
	for(i=1;i<=n;i++)        //初始化辅助数组
		vset[i]=i;
	k=1;//表示当前构造最小生成树的第k条边,初值为1
  	j=0;//E中边的下标,初值为0
   while(k<e)//生成的边数小于e时继续循环
   {
       m1=E[j].vex1;
       m2=E[j].vex2;//取一条边的两个邻接点
       sn1=vset[m1];
       sn2=vset[m2];
	       //分别得到两个顶点所属的集合编号
	    if(sn1!=sn2)//两顶点分属于不同的集合,该边是最小生成树的一条边
	    {//防止出现闭合回路
			printf("V%d-V%d=%d\n",m1,m2,E[j].weight);
			sum+=E[j].weight;
			k++;                //生成边数增加
			if(k>=n)
				break;
			for(i=1;i<=n;i++)    //两个集合统一编号
				if (vset[i]==sn2)  //集合编号为sn2的改为sn1
					vset[i]=sn1;
	    }
     j++;                  //扫描下一条边
   }
    printf("最小权值之和=%d\n",sum);
}
//以下为快排
int fun(Edge arr[],int low,int high)
 {
 	int key;
 	Edge lowx;
 	lowx=arr[low];
 	key=arr[low].weight;
 	while(low<high)
 	{
 		while(low<high && arr[high].weight>=key)
 			high--;
 		if(low<high)
 			arr[low++]=arr[high];

 		while(low<high && arr[low].weight<=key)
 			low++;
 		if(low<high)
 			arr[high--]=arr[low];
	 }
	 arr[low]=lowx;
	 return low;
  }
void quick_sort(Edge arr[],int start,int end)
{
	int pos;
	if(start<end)
	{
	pos=fun(arr,start,end);
	quick_sort(arr,start,pos-1);
	quick_sort(arr,pos+1,end);
	}
}
int main()
{
	Edge E[MAXE];
	int nume,numn;
    //freopen("1.txt","r",stdin);//文件输入
	printf("输入顶数和边数:\n");
	scanf("%d%d",&numn,&nume);
	for(int i=0;i<nume;i++)
		scanf("%d%d%d",&E[i].vex1,&E[i].vex2,&E[i].weight);
	quick_sort(E,0,nume-1);
	kruskal(E,numn,nume);
}

最短路径

Dijkstra

邻接矩阵

#include<iostream>
using namespace std;
#define MAXSIZE 1000
#define NoEdge 1000
int n,m;
int G[MAXSIZE][MAXSIZE];  //权重
int dist[MAXSIZE];    //单源点最短路径 
bool visited[MAXSIZE];
int FindMinAdj()
{
	int min_i=-1,min=NoEdge;  //找离已经有的最短路径结点最近的点 
	for(int i=0;i<n;i++)
	{
		if(!visited[i]&&dist[i]<min)
		{
			min_i=i;min=dist[i];
		}
	}
	return min_i;
}
void Solve(int s)
{
	for(int i=0;i<n;i++)   //初始化就当做只有s结点到各结点的情况,考虑路径,路径个数等 
	{
		dist[i]=G[s][i];
		visited[i]=false;	
	}
	visited[s]=true;
	while(true)
	{
		int min_i=FindMinAdj();    //找离已经确定的最短路径最近的结点 
		if(min_i==-1) break;
	//	cout<<min_i<<endl;
		visited[min_i]=true;
	
		for(int i=0;i<n;i++)
		{
			if(!visited[i]&&G[min_i][i]!=NoEdge)
			{
				if(dist[min_i]+G[min_i][i]<dist[i])   //最短路径优先考虑 
				{
					dist[i]=dist[min_i]+G[min_i][i];  //更新最短路径长度 
				}
			}
		}
	}
}
int main()
{
	int s;
	cin>>n>>m>>s;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		G[i][j]=NoEdge;
	}
	for(int i=0;i<m;i++)
	{
		int v1,v2,len;cin>>v1>>v2>>len;
		G[v1][v2]=len;
		G[v2][v1]=len; 
	}
	Solve(s);  //求起点s到其他所有顶点的最短路径 
	return 0;
	
} 

邻接表实现

#include<bits/stdc++.h>
using namespace std;
#define MAXSIZE 10005
#define INFI 100000000
int n,m;
typedef struct Node
{
	int id;
	int W;
}AdjNode,*Adj;
vector<Adj>List[MAXSIZE];  //vector数组实现邻接表 
int dist[MAXSIZE];
bool visited[MAXSIZE];
int FindMinAdj()
{
	int min_i=-1,min=INFI;  //找离已经有的最短路径结点最近的点 
	for(int i=0;i<n;i++)
	{
		if(!visited[i]&&dist[i]<min)
		{
			min_i=i;min=dist[i];
		}
	}
	return min_i;
}
void Dijistra()   
{
	for(int i=1;i<=n;i++)
	{
			for(int j=1;j<=n;j++) //初始化 
			{
				dist[j]=INFI;
				visited[j]=false;
			}
			dist[i]=0;
			
			while(true)  
			{
				int min_i=FindMinAdj();    //找离已经确定的最短路径最近的结点 
				if(min_i==-1) break;
				
				visited[min_i]=true;  //纳入集合 
				int len=List[min_i].size();
				for(int j=0;j<len;j++)   //判断该点受影响的邻接点 
				{
					int t=List[min_i][j]->id;
					if(!visited[t]&&dist[min_i]+List[min_i][j]->W<dist[t])
					{
						dist[t]=dist[min_i]+List[min_i][j]->W;
					}
				}
			} 
			
			for(int j=1;j<=n;j++)   
			{                        
				cout<<dist[j]<<" ";
			}
			
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=0;i<m;i++)
	{
		int u,v,w;
		//cin>>u>>v>>w;
		scanf("%d %d %d",&u,&v,&w);
		if(u==v) continue;
		Adj a=new AdjNode;  //无向图,两个顶点都要进行操作 
		a->id=v;
		a->W=w;
		List[u].push_back(a);
		Adj b=new AdjNode;
		b->id=u;
		b->W=w;
		List[v].push_back(b);
	}
	Dijistra(); 
	return 0;
}

最小堆优化

#include<bits/stdc++.h>
using namespace std;
#define MAXSIZE 10005
#define INFI 100000000
int n,m;
typedef struct Node
{
	int id;
	int W;
}AdjNode,*Adj;
struct cmp{   //自定义优先级,返回true表示前者优先级更低(与排序刚好相反) 
    bool operator()(const Adj a,const Adj b){
        return a->W > b->W;
    }
};
vector<Adj>List[MAXSIZE];  //vector数组实现邻接表 
int dist[MAXSIZE];
bool visited[MAXSIZE];
priority_queue<Adj,vector<Adj>,cmp>que;   //按边权值从小到大排序队列 
void Dijistra()   
{
	for(int i=1;i<=n;i++)
	{
			for(int j=1;j<=n;j++) //初始化 
			{
				dist[j]=INFI;
				visited[j]=false;
			}
			dist[i]=0;
			Adj t=new AdjNode;
			t->id=i;
			t->W=0;
			que.push(t);      //将起点纳入最小堆中,堆顶元素为到当前到起点最近的点 
			while(!que.empty())  
			{
				Adj t_adj=que.top();  //获得堆顶元素(未收纳的顶点距离起点最近的点) 
				que.pop();   
				if(visited[t_adj->id]) continue;  //该点已经收纳到结果集合了,获取下一个 
				
				int min_i=t_adj->id;
				visited[min_i]=true;  //纳入集合 
				int len=List[min_i].size();
				for(int j=0;j<len;j++)   //判断该点受影响的邻接点 
				{
					int t=List[min_i][j]->id;
					if(!visited[t]&&dist[min_i]+List[min_i][j]->W<dist[t])
					{
						dist[t]=dist[min_i]+List[min_i][j]->W;
						Adj p=new AdjNode;
						p->id=t;
						p->W=dist[t];
						que.push(p);   //将离起点距离变短的点纳入最小堆 
					}
				}
			} 
			
			for(int j=1;j<=n;j++)   
			{                        
				cout<<dist[j]<<" ";
			}
			cout<<endl;
	}
}
int main()
{
	//cin>>n>>m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<m;i++)
	{
		int u,v,w;
		//cin>>u>>v>>w;
		scanf("%d %d %d",&u,&v,&w);
		if(u==v) continue;
		Adj a=new AdjNode;  //无向图,两个顶点都要进行操作 
		a->id=v;
		a->W=w;
		List[u].push_back(a);
		Adj b=new AdjNode;
		b->id=u;
		b->W=w;
		List[v].push_back(b);
	}
	Dijistra();
	return 0;
}

应用:多权值+多路径+路径输出

#include<iostream>
using namespace std;
#define MAXSIZE 1000
#define NoEdge 1000
int n,m;
int G[MAXSIZE][MAXSIZE];  //权重1 
int Num[MAXSIZE];     //权重2 
int dist[MAXSIZE];    //单源点最短路径 
int Cnt[MAXSIZE];     //记录最短路径的条数 
int Path[MAXSIZE];    //存储最短路径 
bool visited[MAXSIZE];
int Que[MAXSIZE];     //权重2最优 
int FindMinAdj()
{
	int min_i=-1,min=NoEdge;  //找离已经有的最短路径结点最近的点 
	for(int i=0;i<n;i++)
	{
		if(!visited[i]&&dist[i]<min)
		{
			min_i=i;min=dist[i];
		}
	}
	return min_i;
}
void Solve(int s,int d)
{
	for(int i=0;i<n;i++)   //初始化就当做只有s结点到各结点的情况,考虑路径,路径个数等 
	{
		dist[i]=G[s][i];
		if(G[s][i]!=NoEdge)
		{	
			Path[i]=s;
			Cnt[i]=1;
			Que[i]=Num[i]+Num[s];
		} 
		else
		{
			Cnt[i]=0;
			Que[i]=Num[i]; 
		} 
		visited[i]=false;	
	}
	Cnt[s]=1;
	visited[s]=true;
	while(true)
	{
		int min_i=FindMinAdj();    //找离已经确定的最短路径最近的结点 
		if(min_i==-1) break;
	//	cout<<min_i<<endl;
		visited[min_i]=true;
	
		for(int i=0;i<n;i++)
		{
			if(!visited[i]&&G[min_i][i]!=NoEdge)
			{
				if(dist[min_i]+G[min_i][i]<dist[i])   //最短路径优先考虑 
				{
					dist[i]=dist[min_i]+G[min_i][i];  //更新最短路径长度 
					Que[i]=Que[min_i]+Num[i];         //更新最优权值2 
					Path[i]=min_i;       //更新最短路径 
					Cnt[i]=Cnt[min_i];   //更新最短路径条数 
				}
				else if(dist[min_i]+G[min_i][i]==dist[i])  //路径长度相等考虑第二个权值 
				{	
					Cnt[i]+=Cnt[min_i];  //更新最短路径条数 
					if(Que[min_i]+Num[i]>Que[i])
					{
						Que[i]=Que[min_i]+Num[i];
						Path[i]=min_i;   //更新最短路径 
					}
				}
			}
		}
}
	cout<<Cnt[d]<<" "<<Que[d]<<endl;
	int r=Path[d];
	cout<<d<<" ";
	while(r!=s)
	{
		cout<<r<<" ";
		r=Path[r];
	}
	cout<<s<<endl;
}
int main()
{
	int s,d;
	cin>>n>>m>>s>>d;
	for(int i=0;i<n;i++)
	cin>>Num[i];
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		G[i][j]=NoEdge;
	}
	for(int i=0;i<m;i++)
	{
		int v1,v2,len;cin>>v1>>v2>>len;
		G[v1][v2]=len;
		G[v2][v1]=len; 
	}
	Solve(d,s);
	return 0;
	
} 

SPFA算法

#include<bits/stdc++.h>
using namespace std;
#define MAXSIZE 10005
#define INFI 100000000
int n,m,k;
typedef struct Node
{
	int id;
	int W;
}AdjNode,*Adj;
vector<Adj>List[MAXSIZE];  //vector数组实现邻接表
int dist[MAXSIZE];
bool inq[MAXSIZE];
queue<int>node;    
void Spfa()   
{
	for(int i=1;i<=n;i++)
	{
		
			for(int j=1;j<=n;j++) //初始化 
			{
				dist[j]=INFI;
				inq[j]=false;
			}
			dist[i]=0;
			node.push(i);      //将起点纳入队列中 
			inq[i]=true;
			while(!node.empty())  
			{
				int min_i=node.front();  //得到队列元素并出队 
				node.pop();   
				
				inq[min_i]=false;  //元素出队 
				int len=List[min_i].size();
				for(int j=0;j<len;j++)   //对该点有影响的邻接点都进行松弛 
				{
					int t=List[min_i][j]->id;
					if(dist[t]>dist[min_i]+List[min_i][j]->W)
					{
						dist[t]=dist[min_i]+List[min_i][j]->W;
						if(!inq[t])    //把不在队列里的元素入队 
						{
							node.push(t);
							inq[t]=true;
						}
					}
						
				}
			}
			
			for(int j=1;j<=n;j++)  
			{                       
				cout<<dist[j]<<" "; 
			}
	}
}
int main()
{
	//cin>>n>>m>>k;
	scanf("%d %d",&n,&m);
	for(int i=0;i<m;i++)
	{
		int u,v,w;
		//cin>>u>>v>>w;
		scanf("%d %d %d",&u,&v,&w);
		if(u==v) continue;
		Adj a=new AdjNode;  //无向图,两个顶点都要进行操作 
		a->id=v;
		a->W=w;
		List[u].push_back(a);
		Adj b=new AdjNode;
		b->id=u;
		b->W=w;
		List[v].push_back(b);
	}
	Spfa();
	return 0;
}

Floyd算法

#include<bits/stdc++.h>
using namespace std;
#define INIFITE 10000
#define MAXSIZE 105
int n,m;
int G[MAXSIZE][MAXSIZE];
int Min_Path[MAXSIZE][MAXSIZE];  //记录i到j的最短路径值, 
int path[MAXSIZE][MAXSIZE];    //记录最短路径,递归输出最短路径 
bool Floyd()    //求任意两点之间的最短距离O(n^3),比对每一个点调用单源点最短路径算法快一点      
{
	for(int i=1;i<=n;i++)  //初始化 
	{
		for(int j=1;j<=n;j++)
		{
			Min_Path[i][j]=G[i][j];
			path[i][j]=j;  //初始化i->j一步到位
		}
		
	}
	
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(i==j&&Min_Path[i][j]<0)   return false;  //发现负值圈,不能正常解决,返回错误标记 
				if(Min_Path[i][k]+Min_Path[k][j]<Min_Path[i][j])
				{
					Min_Path[i][j]=Min_Path[i][k]+Min_Path[k][j];
					path[i][j]=path[i][k];
				}
				
 			} 
			
		}
	}
	
	for(int i=1;i<=n;i++)  //输出最短路径值矩阵 
	{
		for(int j=1;j<=n;j++)
		{
			if(i==j) Min_Path[i][j]=INIFITE;
			cout<<Min_Path[i][j]<<" ";
		}
		cout<<endl;
	}
	
	cout<<"****"<<endl;
	for(int i=1;i<=n;i++)   //输出最短路径矩阵 
	{
		for(int j=1;j<=n;j++)
		cout<<path[i][j]<<" ";
		cout<<endl;
	}
	return true;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		G[i][j]=INIFITE;
	}
	for(int i=0;i<m;i++)
	{
		int v1,v2,weight;
		cin>>v1>>v2>>weight;
		G[v1][v2]=weight;
		G[v2][v1]=weight;
	}
	if(Floyd()) cout<<"yes"<<endl;  //没有负权值 
	int v1,v2;cin>>v1>>v2;  //输出v1到v2的路径值和路径 
	while(v1&&v2)  //递归输出最短路径
	{
		
		cout<<v1<<"到"<<v2<<"的最短路径值为:"<<Min_Path[v1][v2]<<"   ";
	   // cout<<v1<<"-->";
		int k=v1;    //输出v1到v2的最短路径 
	    while(k!=v2)
	    {
	    	cout<<k<<"-->";
	    	k=path[k][v2];
		}
		cout<<v2<<endl;
		cin>>v1>>v2;
	}
	
	return 0;
}

Bellman ford算法

#define INF 0x3f3f3f3f

struct Edge{
    int u;//起
    int v;//终
    int weight;//长度
};

Edge edge[maxm];//用来存储所有的边
int dis[maxn];//dis[i]表示源点到i的最短距离
int n,m;//n个点,m条边
int s;//源点

bool Bellmen_ford()
{
    for(int i=1;i<=n;i++)//初始化
        dis[i]=INF;

    dis[s]=0;//源节点到自己的距离为0

    for(int i=1;i<n;i++)//松弛过程,计算最短路径
    {
        for(int j=1;j<=m;j++)
        {
            if(dis[edge[j].v]>dis[edge[j].u]+edge[j].weight)//比较s->v与s->u->v大小
                dis[edge[j].v]=dis[edge[j].u]+edge[j].weight;
        }
    }

    for(int j=1;j<=m;j++)//判断是否有负边权的边
    {
        if(dis[edge[j].v]>dis[edge[j].u]+edge[j].weight)
            return false;   //设置为负无穷,最后数组中为负无穷的顶点就是负循环中的点
    }

    return true;
}

优先队列

堆排序

#include<stdio.h>
#include<stdlib.h>
 
void swap(int a[], int i, int j)
{
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}
 
//最大堆调整
void heapify(int a[], int len, int k)
{
    if (k < len)
    {
        int root = k;//根节点
        int left_child = 2 * k + 1;//左孩子
        int right_child = 2 * k + 2;//右孩子
        //查找左右孩子中大的孩子节点
        if (left_child < len && a[root] < a[left_child])
        {
            root = left_child;
        }
        if (right_child < len && a[root] < a[right_child])
        {
            root = right_child;
        }
        //交换
        if (root != k)
        {
            swap(a, k, root);
            //递归下去
            heapify(a, len, root);
        }
    }
}
 
//构建大顶堆
void creatHeap(int a[], int n)
{
    int last = n - 1;//最后一个节点的下标
    int parent = (last - 1) / 2;//最后一个节点的父节点的下标
    //从最后一个节点的父节点到根节点进行最大堆调整
    for (int i = parent; i >= 0; i--)
        heapify(a, n, i);
}
 
//堆排序
void heapSort(int a[], int n)
{
    creatHeap(a, n);
    for (int i = n - 1; i >= 0; i--)
    {
        //将根节点(最大值)和最后一个节点交换,也就是最大值到最后一个下标位置上 
        swap(a, i, 0);
        //因为此时根节点不有序,整体从根节点开始最大堆调整
        //而此时根结点小于所有父结点,因而在调整时只需考虑最大孩子的分支即可
        heapify(a, i, 0);
    }
}
 
int main()
{
    int arr[] = { 50, 45, 15, 25, 10 };
    int length = sizeof(arr) / sizeof(arr[0]);
    heapSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    system("pause");
    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值