【练习】2021下半年数据结构刷题笔记和总结 (一)(图和动态规划)

文章目录

1.编程将一个字符串中所有空格替换为“%20”

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<cstring>



using namespace std;



const int maxd = 20;//最大深度
int m[1 << maxd];//最大结点个数为2的maxd次方-1

//编程将字符串中的空格用%20代替,并返回替换后的总长度

int Replace(char* p, int n) {
	//n为源字符串的总字符个数
	int num = 0;//空格个数
	for (int i = 0; i < n; i++) {
		if (p[i] == ' ') {
			num++;

		}
	}
	int TotalLength = n + num * 2;//一个空格用三个字符替代
	p[TotalLength] = '\0';//字符串结尾符号
	int k = TotalLength - 1;
	for (int i = n - 1; i >= 0; i--) {
		//从后向前扫描
		if (p[i] == ' ') {
			p[k] = '0';
			p[k - 1] = '2';
			p[k - 2] = '%';
			k = k - 3;
		}
		else {
			p[k] = p[i];
			k--;
		}
	}
	return TotalLength;
}
int main()
{
	char* str;
	str = new char[200];//分配足够的空间
	strcpy(str, "my name is");
	int n = strlen(str);
	cout<<"total:"<<Replace(str, n);
	cout << str;


}

在这里插入图片描述

2.定两个字符串,判断一个字符串是否是另一个字符串的排列

给定两个字符串,判断一个字符串是否是另一个字符串的排列
假设字符串中仅仅只有ASCII字符
方法一,将两个字符串排序,比较排序后是否相同
方法二 统计s字符串中每一个字符出现的次数,再存放到number数组中,扫描t递减每种字符串出现的个数,如果出现某个字符串出现的个数小于0,则返回假
两个算法都要提前判断长度是否相等,不相等返回假

#include <iostream>
#include<cstring>

#include<algorithm>
#include<string>

using namespace std;

bool Judge1(string s, string t) {
	if (s.length() != t.length()) return false;
	sort(s.begin(), s.end());
	sort(t.begin(), t.end());
	if (s == t)return true;
	return false;
}
bool Judge2(string s, string t) {
	if (s.length() != t.length()) return false;
	int num[128] = { 0 };
	for (int i = 0; i < s.length(); i++) {
		num[s[i]]++;
	}
	for (int i = 0; i < t.length(); i++) {
		num[t[i]]--;
		if (num[t[i]] < 0) {
			return false;
		}
	}
	return true;
}
int main()
{

	string s = "11123";
	string t = "23111";
	cout << Judge1(s, t);
	cout << endl;
	cout << Judge2(s, t);


}

3.求一个房间内有数个钩子,给定一定长度的绳索,要把所有的钩子用绳索连接,问是否能连接成功

绳索可以分段,每段可以连接两个钩子,只要它们的距离小于等于这段绳子
输入的数据集第一行为两个整数,钩子数目和绳子总长度
钩子为0时输入结束
钩子用整数坐标表示,每一个钩子有x和y坐标
结果:
在这里插入图片描述
在这里插入图片描述

思路:求出最小生成树,如果最小生成树的所有边长小于等于绳子长度,则输出success
利用并查集,先将钩子的距离进行排序,两个相邻的钩子合并
注意:算出所有顶点之间的距离再排序,但实际中所需要算的总长度并不是把所有顶点的距离加起来,只需要加n-1条边的距离即可

#define maxn 100
#define INF 0x7fffffff
int root[maxn];
int Rank[maxn];
typedef struct {
    int x, y;
}point;
struct edge {
    int p1, p2;
    double val;//定义边类型
};
struct MGraph {
    double edges[maxn][maxn]; //边数组
    int n;//顶点数目
};//定义图的邻接矩阵
double distance(point a, point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
MGraph g;
double sum;//全局变量
//并查集的基本运算算法
void init(int x) {
    root[x] = x;
    Rank[x] = 0;
}
int find(int x) {
    if (x != root[x]) {
        root[x] = find(root[x]);
    }
    return root[x];
}
void Union(int x, int y) {
    if ((x = find(x)) == (y = find(y))) {
        return;
    }
    if (Rank[x] > Rank[y])
        root[y] = x;
    else {
        root[x] = y;
        if (Rank[x] == Rank[y])
            Rank[y]++;
    }

}

bool cmp(edge x, edge y) {
    return x.val < y.val;//排序比较:按从小到大排序
}

void kruskal(MGraph g) {
    edge e[maxn * maxn];
    int i, j, k = 0;
    for (i = 0; i < g.n; i++) {
        for (j = 0; j < g.n; j++) {
            if (g.edges[i][j] != 0 && g.edges[i][j] != INF) {//如果两个顶点之间是可连通的
                e[k].p1 = i;
                e[k].p2 = j;//提取边数组并加入到e中
                e[k].val = g.edges[i][j];
                k++;
            }
        }
        sort(e, e + k, cmp);
        for (i = 0; i < g.n; i++)init(i);
        k = 1, j = 0;
        point a[maxn];
      while( k < g.n) {
            if (find(e[j].p1) != find(e[j].p2)) {
                k++;
                sum += e[j].val;
                Union(e[j].p1, e[j].p2);               
            }
            j++;
        }
    }
}






int main()

{
    int n, L, i, j;
    point a[maxn];
    while(true){
        cin >> n;
        if (n == 0)break;
        cin >> L;
        g.n = n;
        for (i = 0; i < n; i++) {
            cin >> a[i].x >> a[i].y;
        }
        for (i = 0; i < n; i++) {
          for(j = 0;j < n;j++)
          { 
            //  g.edges[i][j] = INF;
              if (i == j)g.edges[i][j] = 0;
              else g.edges[i][j] = distance(a[i], a[j]);

          }
        }
    }
    sum = 0;
    kruskal(g);
    if (sum < L)cout << "success";
    else cout << "fail";
}
  sort(e, e + k, cmp);这里算出来的k为n*n,其中g.edges[i][j]=g.edges[j][i]
     if (find(e[j].p1) != find(e[j].p2)) {
            k++;
            要先判断两条边是否连通,如果连通的话则不要进行k++

个人总结:
一个边有两个顶点,一个顶点有x坐标和y坐标,在定义边类型时用的是int表示顶点而不是point表示顶点,个人认为是因为这样表示方便合并的操作,且在edge[i]中的i表示点的编号,每个点的edge[i]都不一样,edges[i][j]则表示两个点之间的距离,即为distance(a[i],a[j])

4.prim算法求出一个图中所有最小生成树

采用递归算法,在加入一条最短路径到最小生成树后继续遍历剩下未加入最小生成树的路径


//用普利姆算法构造出给定带权连通图G中所有最小生成树
//用vector<int>e向量表示一条边,一棵最小生成树由若干条边构成,即vector<vector<int>>用向量minitree表示,对于每一条边都用e[0]表示起点编号,e[1]表示终点编号
//全部最小生成树用vector<vector<vector<int>>>alltree表示
//对于带权连通图G=(V,E),用数组U划分两个顶点集合,U[i]=1表示顶点i属于U集合,U[i]=0表示顶点i属于V-U集合,prim算法指定源点为v,U[v]=1
#define maxn 100
#define INF 0x7fffffff
int root[maxn];
int Rank[maxn];
typedef struct {
    int x, y;
}point;
struct edge {
    int p1, p2;
    double val;//定义边类型
};
struct MGraph {
    double edges[maxn][maxn]; //边数组
    int n;//顶点数目
};//定义图的邻接矩阵
double distance(point a, point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
MGraph g;
vector<vector<vector<int>>>alltree;

void DisplayprimTree() {//用于输出所有的最小生成树
    for (int i = 0; i < alltree.size(); i++) {
        for (int j = 0; j < alltree[i].size(); j++) {
            cout << alltree[i][j][0] << "," << alltree[i][j][1];//每一条边都只有0和1,对于一个最小生成树来说一条边的j不相同
        }
    cout << endl;
    }

}
bool IfInTree(vector<int>bian, vector<vector<int>>tree) {
    //判断某一条边是否在最小生成树中
    for (int i = 0; i < tree.size(); i++) {
        if (tree[i][0] == bian[0] && tree[i][1] == bian[1])//判断方法是两个边的起点和终点是否相同
            return true;
    }
    return false;
}
bool sameTree(vector<vector<int>>treeA, vector<vector<int>>treeB) {
    //判断两棵树是否为同一棵树 利用判断边的函数ifintree进行判断
    for (int i = 0; i < treeB.size(); i++) {
        if (!IfInTree((treeB[i]), treeA))return false;//如果B有一条边不在树A中,则不是同一棵树
    }
    return true;

}
void addminTree(vector<vector<int>>tree) {
    int flag = 0;
    //添加不重复的最小生成树,如果用于存放最小生成树的数组alltree容量大小为0,则直接添加最小生成树,否则需要进行判断,判断目前要添加的最小生成树是否已经在alltree数组中
    if (alltree.size() == 0) alltree.push_back(tree);
    for (int i = 0; i < alltree.size(); i++) {
        if (sameTree(alltree[i], tree)) {
            flag = 1;
            break;
        }
    }
    if (!flag) alltree.push_back(tree);
}


void Prim1(int U[], int rest,vector<vector<int>>mintree) {//rest表示最小生成树还有多少条边没有构造
    //递归构造所有的最小生成树
    if (rest == 0) { 
        //有一棵最小生成树已经生成结束,加入到存放所有最小生成树的vector中
        alltree.push_back(mintree);
        return; }
    int minedge = INF;
    int i, j;
    //当rest=0表示构造好一棵最小生成树
    for (i = 0; i < g.n; i++) {
        if(U[i] == 1)
        for (j = 0; j < g.n; j++) {//求U和v-U集合之间的最小边长
            if (U[j] == 0) {
                if (g.edges[i][j] < INF && g.edges[i][j] < minedge)
                    minedge = g.edges[i][j];
            }
        }
    }
    vector<int>edge;//求U与V-U集合之间最小边长的边
    for (i = 0; i < g.n; i++) {
        if (U[i] == 1) {
            for (j = 0; j < g.n; j++) {
                if (g.edges[i][j] == minedge) {
                    U[j] = 1;
                    edge.clear();//对于每一条边都要更新起点编号和终点编号
                    if (i < j) {
                        edge.push_back(i);
                        edge.push_back(j);//为了方便表示规定起点编号小于终点
                    }
                    else {
                        edge.push_back(j);
                        edge.push_back(i);
                    }
                }
                mintree.push_back(edge);//最小生成树中加入一条边
                Prim1(U, rest-1,mintree);//继续递归剩下的边构造最小生成树
                U[j] = 0;//恢复环境
                mintree.pop_back();//恢复环境
            }
        }
    }
}
void Prim(int v) {
    int i, j;
    vector<vector<int>>mintree;
    int u[maxn];
    memset(u, 0, sizeof(u));
    u[v] = 1;
    
    Prim1(u, g.n-1, mintree);
}
void CreateMat(MGraph  g, int A[][4], int n,int e) {
    g.n = n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
        {
            g.edges[i][j] = A[i][j];
        }
    }
}
int main()

{
    int A[4][4] = {
        {0,1,1,INF},
        {1,0,1,2},
        {1,1,0,2},{INF,2,2,0}
    };

    int n = 4, e = 5;//四个顶点五条边
    CreateMat(g, A, n, e);
    int v = 3;
    Prim(v);//源点
    DisplayprimTree();
}

5.N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2,求编号为0的城市到其他城市的最短距离。

输入描述:第1行两个正整数N(2≤N≤100)、M(M≤500),表示有N个城市、M条道路,接下来M行两个整数,表示相连的两个城市的编号(时间限制:1s,空间限制:2768KB)
输出描述:N-1行,表示0号城市到其他城市的最短路,如果无法到达,输出-1,数直太大的以MOD100000的结果输出
样例输入:
4 4
1 2
2 3
1 3
0 1
输出:8 9 11

需要让后面的路径越来越长,如果前面的顶点a到顶点b有一条比较短的路径,后面又有一条比较长的路径,则需要过滤后者,这里用并查集实现长路径的过滤。
在这里插入图片描述

int find(int x) {
    if (x != root[x]) {
        root[x] = find(root[x]);
    }
    return root[x];
}


int main()

{
    int A[maxn][maxn], S[maxn], dist[maxn];//S数组表示是否访问过该顶点,访问过则置为1
    //并查集过滤长路径
    int n, m, i, j, a, b, cost;
    cin >> n >> m;//n为城市个数 m为道路个数
    for (i = 0; i < n; i++) {
        root[i] = i;
        S[i] = 0;//初始化root和数组S
    }
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) 
            A[i][j] = INF;
            A[i][i] = 0;//初始化邻接矩阵 注意这条语句不在j的for循环中
        
    }
    cost = 1;//边的长度从1开始
    for (i = 0; i < m; i++) {
        cin >> a >> b;
        int x = find(a);
        int y = find(b);
        if (x != y) {
            //一条有效边
            root[x] = y;
            A[a][b] = A[b][a] = cost;
        }
        cost = cost * 2 % 100000;//cost增大两倍
    }
    S[0] = 1;
    for (i = 0; i < n; i++)
        dist[i] = A[0][i];
    for (i=0; i < n; i++) {
        int min = INF,u=0;
        for (j = 0; j < n; j++) {
            if (S[j] == 0 && dist[j] < INF) {
                u = j;
             //   min = dist[j];
            }
        }
        S[u] = 1;
        for (j = 1; j < n; j++) {
            if (S[j] == 0 && A[u][j]<INF&&(dist[u] + A[u][j] < dist[j])) {
                dist[j] = dist[u] + A[u][j];
            }
        }

    }
    for (i = 1; i < n; i++) {
        if (dist[i] == INF)
            cout << -1 << endl;
        else
        {


            cout << dist[i] << endl;
        }
    }
}

注意:
if (x != y) {
//一条有效边
root[x] = y;}
root[x] = y;这句话很重要,如果没有这句话,譬如输入0 2 和 2 0那么0到2之间的距离就会变大,导致输出结果不正确
且注意无向图中矩阵为对称矩阵, A[a][b] = A[b][a] = cost;

cost = cost * 2 % 100000;//cost增大两倍:用于表示道路长度为2的k次方,%100000是因为题目要求数值太大的以mod 100000的结果输出。
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++)
A[i][j] = INF;
A[i][i] = 0;//初始化邻接矩阵 注意这条语句不在j的for循环中

}

注意初始化邻接矩阵时括号的位置。相异顶点距离初始为INF,顶点到自身距离为0

6.【dijkstra】给你n个点、m条无向边的图,每条边都有长度d和花费p 要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

再给你一个起点s和一个终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。
输入描述:输入n、m,点的编号是1~n,然后是m行,每行4个数a、b.dp表示a和b之间有一条边,且其长度为d、花费为p最后一行是两个数s、t.表示起点s终点t。当n和m为0时输入结束(1<n≤100,0<m<100000,st)
输出描述:输出一行有两个数,分别表示最短距离及其花费。
输入样例:
3 2
1 2 5 6
2 3 4 5
1 3
0 0
输出:
9 11

int n, m;
int Dist[N][N], Cost[N][N];
int s, t;
void dijkstra(int s) {
            int dist[N];
            int cost[N];
            int visited[N];
            int mindist, mincost, u;
            int i, j;
            for (i = 1; i <= n; i++) {
                dist[i] = Dist[s][i];
                cost[i] = Cost[s][i];
                visited[i] = 0;
            }
            dist[s] = cost[s] = 0;
            visited[s] = 1;
            for (i = 0; i <n; i++) {//答案上是for(i = 0;i < m;i++)一直没明白为什么要加一个这个循环 推测应该是因为一共有m条边 
                mindist = INF;
                for (j = 1; j <= n; j++)
                    if (visited[j] == 0 && mindist > dist[j])
                        mindist = dist[j];
                if (mindist == INF)break;
                mincost = INF; u = -1;
                for (j = 1; j <= n; j++) {
                    if (visited[j] == 0 && mindist == dist[j] && mincost > cost[j]) {
                        mincost = cost[j];
                        u = j;
                    }
                }
                visited[u] = 1;//将点u加入已经访问的集合
                for (j = 1; j <= n; j++) {//考虑顶点u,求s到每个顶点j的最短路径长度和花费
                    int d = mindist + Dist[u][j];
                    int c = mincost + Cost[u][j];
                    if (visited[j] == 0 && d < dist[j]) {
                        dist[j] = d;
                        cost[j] = c;
                    }
                    else if (visited[j] == 0 && d == dist[j] && c < cost[j])
                        cost[j] = c;
                }
                if (visited[t] == 1) {
                    cout << dist[t] << "," << cost[t];
                    return;
                }
            }
        }
int main()

{
    int a, b, d, p;
    while (scanf("%d%d", &n, &m) != EOF) {
        if (m == 0 && n == 0)break;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                Dist[i][j] = INF;
                Cost[i][j] = INF;
            }

        }
        for (int i = 0; i < m; i++) {
            cin >> a >> b >> d >> p;
            Dist[a][b] = Dist[b][a] = d;
            Cost[a][b] = Cost[b][a] = p;
        }
        cin >> s >> t;
        dijkstra(s);
    }

7.【贝尔曼福德】一行两个整数,N为景点,M为行数,每行a b d表示经典a和b之间单向路径长度,问最长路径

最后一行输入s和t,表示比赛的起点和终点,且不会出现环
思路:将所有的边改成负值,用贝尔曼福德算法求出顶点s到其他顶点的最短路径(负的最短是绝对值最大的)

#define INF 0x7fffffff
const int N = 1002;
int n, m;
//最长路径长度
int dist[N];
int s, t;//起点和终点
int A[N][N];
void BellmanFord(int v) {
    int k, u;
    for (int i = 0; i < n; i++) {
        dist[i] = A[v][i];//对dist初始化
    }
    //v为起点
    for (k = 2; k < n; k++) {
        for (u = 0; u < n; u++) {
            //修改所有非顶点的dist[u]
            if (u != v) {
                for (int i = 0; i < n; i++)
                    if (A[i][u]<INF && dist[u]>dist[i] + A[i][u])
                        dist[u] = dist[i] + A[i][u];
            }
        }
    }
}
int main()

{

    int a, b, d;
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (i != j)
                A[i][j] = INF;
            else
                A[i][j] = 0;
    for (int i = 0; i < m; i++) {
        cin >> a >> b >> d;
        A[a][b] = -d;//注意这里要加一个负号

    }
    cin >> s >> t;
    BellmanFord(s);
    }

8.修改dijksra算法,求源点到某个顶点j的所有最短路径。

//修改dijksra算法,求源点到某个顶点j的所有最短路径。一条最短路径用vector<int>来保存,vector<vector<int>>来保存源点到顶点的多条最短路径
//所有最短路径用vector<vector<vector<int>>>nodes来保存,nodes[j]表示从源点v到顶点j的所有最短路径 因此得到的是源点到所有顶点的最短路径
#include<vector>
int num = 5;//实际带权有向图的顶点个数
int dist[10];
int v = 0;//指定源点为顶点0
vector<vector<vector<int>>>nodes(num);
int G[10][10] = {
    {0,4,5,3,INF},
    {
        INF,0,1,INF,3},
    {INF,INF,0,INF,2},
     {INF,INF,2,0,4},
    {INF,INF,INF,INF,0}
};//一个用于测试的测试图邻接矩阵
void Display(int j) {
    //先输出特殊情况
    if (j == v) {
        cout << "源点到自身路径长度为0\n";
        return;
    }
    if (dist[j] == INF) {
        printf("源点%d到%d没有路径\n", &j, &v);
        return;
    }

    vector<vector<int>>paths = nodes[j];;
    
   vector<int>minpath;
  
   cout <<"到"<< j<<"最短路径长度:" << dist[j] << endl;
   cout << "一共有" << paths.size() << "条最短路径" << endl;
   for (int i = 0; i < paths.size(); i++)
   {
       minpath = paths[i];
       for (int k = 0; k < minpath.size()-1; k++) {
           cout << "->" << minpath[k] ;
       }
       cout << "->" << minpath[minpath.size() - 1];
       cout << endl;
   }

}

void Dijskra() {
    int visited[maxn];
    int mindist, u;
    for (int i = 0; i < num; i++)
    {
        visited[i] = 0;
        dist[i] = INF;
    }
    dist[v] = 0;//初始化 到自身的距离为0 
    visited[v] = 1;//初始化 自身已经访问
    //首先把源点到所有邻接点i的最短距离初始化为源点到顶点i的距离
    for (int i = 0; i < num; i++) {
        if (G[v][i] != INF) {
            dist[i] = G[v][i];//注意这句话,加上之后修改了dist[i]的值,即v的邻接点到v的长度
            vector<vector<int>>path;
            vector<int>mpath;
            mpath.clear();
            path.clear();
            mpath.push_back(v);
            mpath.push_back(i);
            path.push_back(mpath);
            nodes[i] = path;//初始化后nodes[i]=(v,i)
        }
    }
    for (int i = 0; i < num; i++) {
       mindist = INF;//注意循环num次,num为顶点数量 每次循环都要初始化mindist
      u = -1;
      for (int j = 1; j < num; j++) {//求源点v到达到达最小路径的顶点u
          if (!visited[j] && mindist > dist[j]) {
              u = j;
              mindist = dist[j];
          }

          if (u == -1) break;
          visited[u] = 1;//置为已经访问
          for (int j = 0; j < num; j++) {
              if (!visited[j] &&G[u][j]<INF ) {
                  if (dist[j] > mindist + G[u][j])
                  {
                      dist[j] = mindist + G[u][j];
                      //如果源点到j的路径经过u时路径更短,则修改v->j的全部路径为v->uj
                      vector<vector<int>>allpath = nodes[u];
                      vector<int>apath;
                      nodes[j].clear();//源点到j的路径要重新更新,故先清空
                      for (int k = 0; k < allpath.size(); k++)
                      {
                          apath = allpath[k];//从v到u的每一条最短路径都要添加j vector<int>是一条最短路径
                          apath.push_back(j);
                          nodes[j].push_back(apath);
                      }
                  }
                  else if (dist[j] == mindist + G[u][j]) {
                      //如果v->j不经过u的路径也是最短的
                      vector<vector<int>>allpath = nodes[u];
                      vector<int>apath;
                      for (int k = 0; k < allpath.size(); k++)
                      {
                          apath = allpath[k];//从v到u的每一条最短路径都要添加j 构成v->j的新路径 这一步没有nodes[j].clear()操作 相当于直接添加一条新路径
                          apath.push_back(j);
                          nodes[j].push_back(apath);
                      }

                  }
              }
          
          }
         
      }
    }

}
int main()

{

    int a, b, d;
    Dijskra();
    for (int i = 0; i < num; i++)
        Display(i);//输出源点到每一个顶点i的所有最短路径
}

在这里插入图片描述

9.用邻接表优化Dijskra算法


#include<queue>
typedef struct anode {
    int adjvex;//该边的邻接点编号
    struct anode* nexarc;//指向下一条边的指针
    int weight;//该边的相关信息,比如权值
}arcnode;//边结点类型
typedef struct vnode {
    //InfoTyoe info; 顶点的其他信息
    arcnode* firstarc;//指向第一个边结点
}Vnode;//邻接表头结点类型
typedef struct {
    vnode adjlist[10000];//邻接表头结点数组
    int n, e;//图中顶点数n和边数e
}adjgraph;//完整的图邻接表类型

adjgraph* g;
struct  Node//定义一个堆中的顶点类型
{
    int i, dis;//i:顶点编号 dis:dist[i]的值
    friend bool operator<(const Node& a, const Node& b) {
        return a.dis > b.dis;
 }
};

void Dijskra(int v, int dist[]) {
    arcnode* p = g->adjlist[v].firstarc;
    priority_queue<Node>qu;//定义优先级队列
    int visited[maxn];

    Node e;
    memset(visited, 0, sizeof(visited));
    visited[v] = 1;
    for (int i = 0; i < g->n; i++)
        dist[i] = INF;
    for (int i = 0; i < g->n; i++) {//初始化数组dist[],即初始化距离,记住是从源点v到其他点的v 此时相当于把输入的值赋给dist
        int w = p->adjvex;
        e.i = p->adjvex;//第一个邻接点的编号
        e.dis = p->weight;
        qu.push(e);//将v的出边顶点进队
        dist[w] = p->weight;
        p = p->nexarc;//指向下一个边结点
    }
    for (int i = 0; i < g->n-1; i++) {
        e = qu.top(); qu.pop();//此时由于重载了运算符,弹出的就是最小路径的结点
        int u = e.i;//v为此结点的编号
      
        p = g->adjlist[u].firstarc;
        while (p != NULL) {//考虑从顶点u出发的所有相邻点 
            int w = p->adjvex;//设w为相邻点,考虑借助u修改从源点到w点的最短距离

            if (visited[u] == 0 && dist[u] + p->weight < dist[w])//在未访问的点中更逊距离
            {
                dist[w] = dist[u] + p->weight;
                e.i = w;
                e.dis = dist[w];
                qu.push(e);//修改最短路径,并将修改了最短路径的顶点入队
            }
            p = p->nexarc;
        }
    }
}
void display(dist[],int v){
//输出最短路径
for(int i = 0;i < g->n;i++){
if(i!=v)
cout<<"到"<<i<<"最短路径为:"<<dist[i]<<endl;
}


10.游艇租赁(5.29日)

本题来自趣学算法第4.5节
设置了n个游艇出租站,游客可以在租用后下游的任意一个游艇出租站归还游艇,从i站点到j站点分别有各自的租金,计算从出租站i到出租站j所需的最少租金
中途可以停靠站点
从一个站点到另一个站点时中途可以停靠很多站点,可以考虑从第一个站到第n站的最优解是否一定包含前n-1的最优解,即是否具有最优子结构和重叠性,如果是,就可以用动态规划解
运行结果:
在这里插入图片描述

//最小租金求解 n表示一共有n个出租站,设计二维数组m[][],初始化时用来记录从i到j的租金,在不同规模的子问题中按照递推公式,如果比原值m[][]小,
//则更新m[][],同时用s[][]记录停靠的站点号,直接最后得到的r[1][n]即为最后结果
int m[maxn][maxn];
int s[maxn][maxn];
int n;
void rent()
{
    for (int d = 3; d <= n; d++) {
        for (int i = 1; i <= n - d + 1; i++)//编号从一号站点开始
        {
            int j = i + d - 1;//注意j和i在每一轮d固定时距离是固定的
            for (int k = i + 1; k < j; k++) {
                //对于i到j之间的每个点都进行递推
                if (m[i][k] + m[k][j] < m[i][j])
                {
                    m[i][j] = m[i][k] + m[k][j];
                    s[i][j] = k;//表示更新此条最短路径是经过了k点
                }
            }
        }
    }
}
//根据s[][]数组构造最优解,s[i][j]将问题分解为两个子问题(i,...,s[i][j])、(s[i][j],...,j)递归求解这两个子问题,当s[i][j]=0时说明中间没有经过任何站点,直达站点j,输出j即可
void print(int i, int j) {
    if (s[i][j] == 0) {
        cout << "--" << j;
        return;

    }
    print(i, s[i][j]);
    print(s[i][j], j);
}
int main()

{

    cout << "输入站点数:";
    cin >> n;
    cout << "依次输入各个站点之间的租金";
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            cin >> m[i][j];
        }
    }
    rent();
    cout << "花费的最少租金" << m[1][n] << endl;
    cout << "最少租金经过的站点" << 1;
    print(1, n);
    return 0;
  

在未使用rent()算法之前m[1][n]距离是18

11.最优三角剖分

如果我们给定凸多边形及定义在边、弦上的权值,即任意两点之间定义一个数值作为权值。
什么是凸多边形最优三角剖分? 一个凸多边形的三角剖分有很多种,最优三角剖分就是划分的各三角形上权函数之和最小的三角剖分。 再回到切披萨的问题上来,我们可以把披萨看作一个凸多边形,任何两个顶点的连线对应的权值代表上面的蔬菜和肉片数,我们希望沿着两个不相邻的顶点切成小三角形,尽可能少地切碎披萨上面的蔬菜和肉片。那么,该问题可以归结为凸多边形的最优三角剖分问题。 假设把披萨看作一个凸多边形,标注各顶点为{v0,v1,…,vn}。那么怎么得到它的最优三角剖分呢? 首先分析该问题是否具有最优子结构性质。 (1)分析最优解的结构特征 假设已经知道了在第k个顶点切开会得到最优解,那么原问题就变成了两个子问题和一个三角形,子问题分别是{v0,v1,…,vk}和{vk,vk+1,…,vn},三角形为v0vkvn,假设{v0,v1,…,vn}三角剖分的权值之和是c,{v0,v1,…,vk}三角剖分的权值之和是a,{vk,vk+1,…,vn}三角剖分的权函数之和是b,三角形v0vkvn的权值之和是w(v0vkvn),那么c=a+b+ w(v0vkvn)。因此我们只需要证明如果c是最优的,则a和b一定是最优的(即原问题的最优解包含子问题的最优解)。 反证法:如果a不是最优的,{v0,v1,…,vk}三角剖分一定存在一个最优解a’,a’<a,那么a’+b+w(v0vkvn)<c,所以c不是最优的,这与假设c是最优的矛盾,因此如果c是最优的,则a一定是最优的。同理可证b也是最优的。因此如果c是最优的,则a和b一定是最优的。 因此,凸多边形的最优三角剖分问题具有最优子结构性质。

#include<iostream>
using namespace std;
#define maxn 1000
int n, m[maxn][maxn];//n为顶点数目
//我们的m[i][j]实际上表达的是{vi-1,vi,vj} 
int s[maxn][maxn], g[maxn][maxn];//s用于保存最优策略,二维数组g表示顶点之间的连接权值 
void computer() {
    for (int i =1; i <= n; i++)
    {
        m[i][i] = 0;//先初始化
        s[i][i] = 0;

    }

    for (int d = 2; d <= n; d++)//d为i到j的规模,当d=2时实际上是三个顶点,故从d=2开始
        for (int i = 1; i <= n-d+1; i++)//注意下标i从1开始 如果没有等号会发现无法正常输出
        {
            int j = i + d - 1;
            m[i][j] = m[i + 1][j] + g[i - 1][i] + g[i - 1][j] + g[i][j];
            s[i][j] = i;//注意这个句子 
            for (int k = i + 1; k < j; k++)//枚举划分点
            {
                int temp = m[i][k] + m[k + 1][j] + g[i - 1][k] + g[i - 1][j] + g[k][j];
                if (temp < m[i][j])
                {
                    m[i][j] = temp;
                    s[i][j] = k;//记得要记录划分点
                }
            }
        }
}

void print(int i, int j)
{
    if (i == j) return;
    if (s[i][j] > i) {
        cout << "{v" << i - 1 << "v" << s[i][j] << "}" << endl;

    }
    if (s[i][j] + 1 < j)
        //表示s[i][j]+1到j之间存在顶点
        cout << "{v" << s[i][j] << "v" << j << "}" << endl;
    print(i, s[i][j]);
    print(s[i][j] + 1, j);
}
int main() {
    int i, j;
    cout << "顶点个数:";
    cin >> n;
    n--;
    cout << "依次输入各顶点的连接权值";
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= n; j++)
            cin >> g[i][j];
    computer();
    cout << m[1][n] << endl;
    print(1, n);//打印路径
}

语句m[i][k]+m[k+1][j] + g[i - 1][k] + g[i - 1][j] + g[k][j];是算法的基本语句,在三层for循环中嵌套 最坏情况下递归次数为o(n³)

12.安装部件问题(本题输出顺序还不对)

8-50★★★★】安装机关所需要N个部件,告诉了大家安装的两个注意事项:
·某些部件在安装时需要另一些部件已安装。例如要安装滑轮必须先安装好支架
·当一些部件都可以安装时,应当先安装编号较小的部件。
因为要安装的部件非常多,所以他们想请你写个程序来帮忙计算出机关安装的正确顺序,你能做到吗?
输入描述:输入的第1行是一个数T,表示了要安装的不同机关的数目。接下来包含
T个机关的信息,每组的第1行是两个数N和M(1≤N≤100000,0≤M≤100000),接下
来M行的两个数A和B1(1≤A1,B1≤N),表示要安装第A个部件必须先安装第B个部件。
个-1。
输出描述:包含T行,每行是一个排列,表示一个安装的顺序。如果无解,就输出-1,输入样例:
3
6 7
2 1
3 2
5 1
4 2
6 3
4 5
6 4
5 0
3 3
1 2
2 3
3 1
输出:1 2 3 5 4 6
1 2 3 4 5
-1

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 100005

//某些部件安装时需要先有另一些部件安装
//当一些部件都可以安装时,先安装编号较小的部件
//如果不能产生完整的拓扑序列 返回 - 1
typedef struct ANode {
	int adjvex;//顶点编号
	struct ANode* nextarc;
}ArcNode;//边结点类型
typedef struct Vnode {
	ArcNode* first;//指向第一个边结点
	int count;//存放顶点的入度
};//头结点类型
typedef struct {
	Vnode adjlist[maxn];//头结点数组
	int n, e;//顶点个数和边数
}AdjGraph;//图的邻接表类型 邻接表表示图时每一个头结点都对应一个单链表,每个单链表有一个头结点,所有这些头结点构成头结点数组

bool cmp(int a, int b) {
	return a > b;//按照元素递减顺序进行排序
}

void TopSort(AdjGraph* G, vector<int>& seq)
{
	ArcNode* p;
	for (int i = 1; i <= G->n; i++)//i从1开始 故可以取等号
	{
		G->adjlist[i].count = 0;//初始化顶点的入度
	}
	for (int i = 1; i <= G->n; i++)//顺序遍历,将每个结点都当成头结点来访问一遍
	{
		p = G->adjlist[i].first;
		while (p != nullptr)
		{
			G->adjlist[p->adjvex].count++;
			p = p->nextarc;//p->adjvex为邻接点编号
		}

	}
	int jj;
	int top = -1;//初始时的栈顶指针
	int st[maxn];//用数组模拟栈
	//按编号从大到小将所有入度为0的顶点进栈
	for (int i = G->n; i > 0; i--)
	{
		if (G->adjlist[i].count == 0)
		{
			top++;
			st[top] = i;//这样出栈的时候编号是从小到大依次出栈的 可能有多个入度为0的顶点,这样设计确保编号最后从小到大输出
		}
	}
	while (top != -1)
	{
		jj = st[top];
		
		seq.push_back(jj);

		
		p = G->adjlist[jj].first;
		top--;
		while (p!=NULL)
		{
			G->adjlist[p->adjvex].count--;//将顶点i的出边邻接点入度都减一
			
			if (G->adjlist[p->adjvex].count == 0)
			{
				top++;
				st[top] = p->adjvex;//如果有一个新的入度为0的点则进栈,栈顶指针加一
			}
			p = p->nextarc;
		}

		if (top != -1)
		{
			sort(st, st+top, cmp);//每次有退栈或者进栈了一个顶点都要排序一遍 将最小编号顶点放在栈顶
			cout << top;
		}
	}

	
}
int main() {
	int T, N, M;//T:要安装的不同机关数目  N为部件数 M为行数
	//一共有若干个机关,每个机关由若干部件构成,对于一个机关,创建对应的有向图,对该图进行拓扑排序,用vector<int>向量保存拓扑排序
	AdjGraph* G;
	ArcNode* p;
	vector<int>seq;
	cin >> T;
	int a, b;
	for(int z = 0;z < T;z++)
	{
		G = (AdjGraph*)malloc(sizeof(AdjGraph));
		cin >> G->n >> M;
		for (int j = 1; j <= G->n; j++)//顶点编号从1开始
			G->adjlist[j].first = NULL;//每次都要初始化头结点指向的第一个边结点
		for (int i = 1; i <= M; i++)
		{
			cin >> a >> b;
			p = (ArcNode*)malloc(sizeof(ArcNode));//每次都分配空间
			p->adjvex = b;
			p->nextarc = G->adjlist[a].first;
			G->adjlist[a].first = p;//用头插法建立边单链表

		}
		seq.clear();//对于每一个机关都有一组拓扑排序
	
		TopSort(G, seq);
		if (seq.size() != G->n) cout << -1;//如果不相等,表示不能产生完整的拓扑序列 返回-1
		else{
			for (int i = 0; i < seq.size(); i++)
		
			cout << seq[i] << " ";
			cout << endl;
		}
		free(G);//每次输入机关都要建立新的邻接表表示的图

	}
}

13.深度优先遍历方法求有向无环图的一个拓扑序列


typedef struct ANode {
	int adjvex;//顶点编号
	struct ANode* nextarc;
}ArcNode;//边结点类型
typedef struct Vnode {
	ArcNode* first;//指向第一个边结点
	int count;//存放顶点的入度
};//头结点类型
typedef struct {
	Vnode adjlist[maxn];//头结点数组
	int n, e;//顶点个数和边数
}AdjGraph;//图的邻接表类型 邻接表表示图时每一个头结点都对应一个单链表,每个单链表有一个头结点,所有这些头结点构成头结点数组


vector<int>seq;
int visited[maxn];
void DFS(AdjGraph* G, int u)
{
	visited[u] = 1;
	ArcNode* p, int w;
	p = G->adjlist[u].first;
	while (p)
	{
		int m = p->adjvex;
		DFS(G, m);
		p = p->nextarc;
	}
	seq.push_back(u);//先递归调用然后再将顶点u存放到seq中
}
void TopSort(AdjGraph* G)
{
	for (int i = 0; i < G->n; i++)
	{
		if (visited[i]) continue;
		DFS(G, i);//先从未访问过的顶点出发搜索,因为DFS(0)不一定访问所有顶点
	}
	for (int i = seq.size() - 1; i >= 0; i--)
	{
		cout << seq[i]<<" ";//输出拓扑序列
	}
}

14.用非递归算法深度优先遍历邻接表表示的图G

vector<int>seq;
int visited[maxn];
//从顶点v出发,图G采用邻接表存储,设计一个非递归深度优先遍历算法
void DFS1(AdjGraph* G, int v)
{
	int visited[maxn];
	stack<int>st;
	int x;
	ArcNode* p;
	memset(visited, 0, sizeof(visited));
	st.push(v);//初始顶点进栈
	while (!st.empty())
	{
		
		x = st.top();
		p = G->adjlist[x].first;
		while (p)
		{
			int w = p->adjvex;
			if (visited[p->adjvex]==0)//如果顶点没有访问
			{
				cout << w << ",";
				visited[w] = 1;//标志为已经访问
				st.push(w);
				break;
					
			}
			p = p->nextarc;

		}
		if (p == NULL) st.pop();

	}
}

15.邻接矩阵表示 设计算法采用深度优先遍历求有向图的根

int visited[maxn];
//从顶点v出发,图G采用邻接矩阵
typedef struct {
	int no;//顶点编号
	//infotype info; 顶点的其他信息
}vertextype;//顶点类型
typedef struct {
	int edges[maxn][maxn];//邻接矩阵数组 发现如果maxnn定义过大的话会报错
	int n, e;//顶点和边数
	vertextype vexs[maxn];//存放顶点信息
}matgraph;

//邻接矩阵表示 设计算法采用深度优先遍历求有向图的根 若有向图中存在一个顶点v,从v可以通过路径到达图中的其他所有顶点,则称v为该有向图的根
//visited全局数组,用visited中访问的个数来表示到达的顶点,与图G中的总顶点个数比较,决定该顶点是否为有向图的根
void MatDfs(matgraph g, int v)
{
	visited[v] = 1;
	for (int i = 0; i < g.n; i++)
	{
		//在邻接矩阵中找到所有v的邻接点
		if (g.edges[i][v] != 0 && g.edges[i][v] != INF && visited[i] == 0)
		{
			visited[i] = 1;
			MatDfs(g, i);//递归访问
		}
	}
}
int Root(matgraph g)
{
	
	int n;
	n = 0;

	for (int i = 0; i < g.n; i++)
	{
		memset(visited, 0, sizeof(visited));//注意初始化visited是写在循环里面而不是循环外面
		MatDfs(g, i);
		if (visited[i] == 1) n++;
		if (n == g.n) return i;//当访问过的顶点数等于图中总顶点数,表示已经找到了根

	}
	return -1;//没找到 表示没有根
}

(基础)深度优先遍历输出顶点

int visited[101] = {0},sum=0,e[101][101] = {0},n,m;//e表示图
void dfs(int cnt)
{
  cout<<cnt<<",";
  //cnt表示当前访问的顶点
  sum++;//sum写在外面
  if(sum == n) return;

  for(int i = 1;i <= n;i++)
  if(visited[i]==0&&e[cnt][i] ==1 )
  {
      visited[i] = 1;
    dfs(i);//代表有一条边
  }
}
#define INF 1e6
    int main() 
{
  cin>>n>>m;
  int a,b;
  //一共n个顶点,m条边
  for(int i = 0;i < n;i++)
  for(int j = 0;j < n;j++)
  if(i == j) e[i][j] = 0;
  else e[i][j] = INF;
for(int i = 0;i < m;i++)
{
  
  cin>>a>>b;//表示一条边的两个顶点
  e[a][b] = e[b][a] = 1;//两个顶点之间有一条边
}
visited[1] =1;//从1开始遍历
dfs(1);
}

5 5
1 2
1 3
1 5
2 4
3 5
输出1,2,4,3,5,
经过实验,sum写在循环里面,在main里面sum++;也可以输出正确结果。
在这里插入图片描述

(基础)广度优先遍历图输出顶点

#define INF 1e6
    int main() 
{
  int visited[101] = {0},sum=0,e[101][101] = {0},n,m;//e表示图
  int que[101];//队列
  int tail = 1,head =1;

  cin>>n>>m;
  int a,b;
  //一共n个顶点,m条边
  for(int i = 0;i < n;i++)
  for(int j = 0;j < n;j++)
  if(i == j) e[i][j] = 0;
  else e[i][j] = INF;
for(int i = 0;i < m;i++)
{
  
  cin>>a>>b;//表示一条边的两个顶点
  e[a][b] = e[b][a] = 1;//两个顶点之间有一条边
}

visited[1] =1;//从1开始遍历 首先设置为已经访问
que[tail] = 1;//顶点进入队列
tail++;//队尾加一
while(head < tail)
{
 
  for(int i = 1;i <= n;i++)
  {
    //每个顶点都试一遍 队头顶点所有相邻的顶点全部入队
    if(visited[i] == 0 && e[que[head]][i] ==1)
    {
      visited[i] = 1;
      que[tail] = i;
      tail++;
    }
  }
  if(tail > n) break;//表示所有顶点都访问过
  head++;//队头加一表示出队
}
for(int i = 1; i < tail;i++)
cout<<que[i]<<",";//输出队列 注意从1开始

}

输入的数据和上面的一样,输出1,2,3,5,4,
注意上面这个tail是指向队尾(最后一位)的下一个位置

16.小石子合并

有n堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)。

假设有n堆石子,一字排开,合并相邻两堆的石子,每合并两堆石子有一个花费,最终合并后的最小花费和最大花费。

分析:
显然利用贪心法来求解错误的,贪心算法在子过程中得出的解只是局部最优,而不能保证全局的值最优,因此本题不可以使用贪心法求解。 如果使用暴力穷举的办法,会有大量的子问题重复,这种做法是不可取的,那么是否可以使用动态规划呢?我们要分析该问题是否具有最优子结构性质,它是使用动态规划的必要条件。
假设已经知道了n堆石子合并起来的花费是c,子问题1{ ai,a2,…,ak }石子合并起来的花费是a,子问题2{ ak+1,…,aj}石子合并起来的花费是b,{ ai,a2,…,aj }石子数量之和是w(i,j),那么c=a+b+ w(i,j)。因此我们只需要证明如果c是最优的,则a和b一定是最优的(即原问题的最优解包含子问题的最优解)。
设Min[i][j]代表从第i堆石子到第j堆石子合并的最小花费,Min[i][k]代表从第i堆石子到第k堆石子合并的最小花费,Min[k+1][j]代表从第k+1堆石子到第j堆石子合并的最小花费,w(i,j)代表从i堆到j堆的石子数量之和。列出递归式:

MIN={
0 ,//i = j时
min(Min[i][k] + min[k+1][j] +w(i,j)) i < j
}
(1)确定合适的数据结构 采用一维数组a[i]来记录第i堆石子(ai)的数量;sum[i]来记录前i堆(a1,a2,…,ai)石子的总数量;二维数组Min[i][j]、Max[i][j]来记录第i堆到第j堆ai,ai+1,…,ai堆石子合并的最小花费和最大花费。
(2)初始化 输入石子的堆数n,然后依次输入各堆石子的数量存储在a[i]中,令Min[i][i]=0,Max[i][i]=0,sum[0]=0,计算sum[i],其中i= 1,2,3,…,n。
(3)循环阶段 按照递归式计算2堆石子合并{ai,ai+1}的最小花费和最大花费,i=1,2,3,…,n-1。 按照递归式计算3堆石子合并{ai,ai+1,ai+2}的最小花费和最大花费,i=1,2,3,…,n-2。 以此类推,直到求出所有堆{a1,…,an}的最小花费和最大花费。
(4)构造最优解 Min[1][n]和Max[1][n]是n堆石子合并的最小花费和最大花费。如果还想知道具体的合并顺序,需要在求解的过程中记录最优决策,然后逆向构造最优解,可以使用类似矩阵连乘的构造方法,用括号来表达合并的先后顺序。

在这里插入图片描述

i=1,j=2:{a1,a2} k=1:Min[1][2]=Min[1][1]+Min[2][2]+sum[2] -sum[0]=13;     Max[1][2]=Max[1][1]+Max[2][2]+sum[2] -sum[0]=13。

i=2,j=3:{a2,a3} k=2:Min[2][3]=Min[2][2]+Min[3][3]+sum[3] -sum[1]=14;     Max[2][3]=Max[2][2]+Max[3][3]+sum[3] -sum[1]=14。
i=3,j=4:{a3,a4} k=3:Min[3][4]=Min[3][3]+Min[4][4]+sum[4] -sum[2]=15;     Max[3][4]=Max[3][3]+Max[4][4]+sum[4] -sum[2]=15。
i=4,j=5:{a4,a5} k=4:Min[4][5]=Min[4][4]+Min[5][5]+sum[5] -sum[3]=11;     Max[4][5]=Max[4][4]+Max[5][5]+sum[5] -sum[3]=11。
i=5,j=6:{a5,a6} k=5:Min[5][6]=Min[5][5]+Min[6][6]+sum[6] -sum[4]=5;     Max[5][6]=Max[5][5]+Max[6][6]+sum[6] -sum[4]=5。

代码:

const int INF = 1 << 30;
const int N = 205;
int Min[N][N],Max[N][N];
int sum[N];
int a[N];
void straight(int a[],int n)
{
  for(int i = 1; i <= n; i++)
  {
Min[i][i] = 0;
Max[i][i] = 0;

  }
  sum[0] = 0;
  for(int i = 1; i <= n; i++)
  sum[i] = sum[i-1] + a[i];

for(int v = 2; v <= n; v++)//枚举合并的堆数规模
{
  for(int i = 1; i <= n-v+1; i++)//枚举起始点
  {
int j = i+v-1;//枚举终点
int temp = sum[j] - sum[i-1];//记录i..j之间石子数之和
Min[i][j] = INF;//初始化为最大值
for(int k = i; k < j;k++)
{

  Min[i][j] = min(Min[i][j],Min[i][k] + Min[k+1][j]+temp);

}
  }
}

}

    int main() 
{

for(int i = 1;i  <=6;i++)
cin>>a[i];
straight(a,6);
cout<<Min[1][6];
}

 

5 8 6 9 2 3
输出84

17.迷宫从一个起点到终点的最短路径长度


int a[101][101],visited[101][101] = {0};//a表示路径中所有块,visited是否被标记过
int mixnum = 1e6;//最少步数
int toward[4][2]={{1,0},{0,1},{-1,0},{0,-1}};//四个方向
int p,q;//终点
int hang,lie;
void dfs(int step,int i,int j)
{
  int m,n;
if(i == p && j ==q)
{
  if(mixnum > step)//如果步数更小,更新最短路径
  mixnum = step;
  return; //记得要return
}
for(int z = 0;z <4;z++)
{
  m = i+toward[z][0];
  n = j+toward[z][1];
  if(m<1||n<1||m>hang||n>lie)
  continue;
  if(a[m][n] == 0 && visited[m][n] != 1)//如果不是障碍物而且没有被访问过
  {
    visited[m][n] = 1;//标记已经访问
  dfs(step+1,m,n);
  visited[m][n] = 0;//记得取消标记,这样才能回退
  }
}
return;
}


    int main() 
{

cin>>hang>>lie;//n为行,m为列
for(int i = 1;i <=hang;i++)
for(int j = 1;j<=lie;j++)
{
  cin>>a[i][j];//下标从1开始
}
int startx,starty;//起点
cin>>startx>>starty;//输入
cin>>p>>q;
visited[startx][starty] = 1;
dfs(0,startx,starty);
cout<<mixnum;
}

 


18.迷宫的广度优先(计算杀灭敌人最多的点

在这里插入图片描述
G表示敌人:

int toward[4][2]={{1,0},{0,1},{-1,0},{0,-1}};//四个方向
int p,q;//终点
int hang,lie;
int tail=1;
int head = 1;

char migong[101][101];
int getnum(int i,int j)
{
  cout<<migong[i][j];
  int total = 0;
  int x,y;
  x = i;
  y = j;
 while(migong[x][y] != '#')
 {
   if(migong[x][y]=='G')
   total++;
   x--;

 }
 x = i;
  while(migong[x][y] != '#')
 {
      if(migong[x][y]=='G')
   total++;
   x++;

 }
 x = i;
  while(migong[x][y] != '#')
 {
      if(migong[x][y]=='G')
   total++;
   y++;

 }
 y = j;
  while(migong[x][y] != '#')
 {
      if(migong[x][y]=='G')
   total++;
   y--;

 }
 return total;
}


struct node{
  int x,y;//横坐标和纵坐标
};
    int main() 
{
  int maxx,total = 0;
int stx,sty;//起点横纵坐标
cin>>hang>>lie;//n为行,m为列
cin>>stx>>sty;
int mx,my;//能杀灭最多敌人的横纵坐标点
mx = stx;
my = sty;

for(int i = 0;i < hang;i++)
for(int j = 0; j < lie;j++)
cin>>migong[i][j];
maxx=getnum(stx,sty);
cout<<maxx;
struct node que[401];//假设地图大小不超过20*20,因此总数不超过400
que[tail].x = stx;
que[tail].y = sty;
visited[que[tail].x][que[tail].y] = 1;
tail++;
while(head<tail){
for(int i = 0;i < 4;i++){
    int tx = toward[i][0]+que[head].x;
  int ty = toward[i][1]+que[head].y;//注意:是从head出队
if(tx<0||tx>=hang||ty<0||ty>=lie)
continue;
if(migong[tx][ty] =='.' &&visited[tx][ty]==0)
{

visited[tx][ty]=1;

que[tail].x=tx;//注意,tx ty是新的拓展点的横纵坐标 que[head].x 和que[head].y是旧坐标
que[tail].y = ty;
tail++;//注意这句话写在que[tail].y = ty;的后面
total=getnum(tx,ty);
if(total > maxx)
{
  maxx = total;
mx = tx;
my = ty;
}
}
}
head++;//注意:当一个点拓展结束后,必须要head++才能对后面的点拓展!!
}
cout<<mx<<" "<<my<<endl;
cout<<maxx;
}

19.求一字符串的最长回文子串

一开始自己的写法:

   string longestPalindrome(string s) {
string as = s;
	string temp,finalstr,temp2;
	int index=s.length();
    if(index==1) return s;
	int minlen = 0;
	reverse(as.begin(), as.end());
	
	for (int i = 0; i < as.size()-1; i++)
	{
		for (int j = i + 1; j < s.size(); j++)
		{
			temp = s.substr(i, j-i);
            temp2 = temp;
            reverse(temp2.begin(),temp2.end());
			if (temp.length() > minlen && temp == temp2)
			{
                finalstr = temp;
				minlen = j - i;
				index = i;
			}
		}
		
	}
	//return s.substr(index, minlen);
    return finalstr;
    }

但是每次都不通过,输入bb输出b(应该输出bb),然后无奈去看题解,说要用动态规划。设一个数组dp[i][j]来表示是否是回文串,

在这里插入图片描述

string longestPalindrome(string s) {
        int n = s.size();
        if (n < 2) {
            return s;
        }

        int maxLen = 1;
        int begin = 0;
        // dp[i][j] 表示 s[i..j] 是否是回文串
        vector<vector<int>> dp(n, vector<int>(n));
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 递推开始
        // 先枚举子串长度
        for (int L = 2; L <= n; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < n; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= n) {
                    break;
                }

                if (s[i] != s[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substr(begin, maxLen);
    }


注意:这个题目由于初始化条件 int maxLen = 1;
int begin = 0;自己一直没写对(写的maxlen=0,int begin没有初值),导致一直没有通过

19.原始森林找人·迷宫问题变种(不能遇到两次野狗,不能遇到一次金刚)

在这里插入图片描述

#include <cstring>
#define maxn 35
int a[][2] = {0,1,1,0,1,0,0,1};
char mi[maxn][maxn];//迷宫
int visited[maxn][maxn];
int mark,n,dog;
void dfs(int x,int y)
{
	int xx,yy;
	if(mark == 1) return;
	for(int i = 0;i < 4;i++)

	{
		xx = x+a[i][0];
		yy = y+a[i][1];
		if(xx>=0 &&yy>=0&&xx<n&&yy<n&&visited[xx][yy]==0)
		{
			if(mi[xx][yy]=='a')
			{
				mark = 1;
				break;//表示找到了人
			}
			else if(mi[xx][yy] == 'r'){
			visited[xx][yy] = 1;
dfs(xx,yy);
	visited[xx][yy] = 0;//如果没有找到,回退一格,注意复位,即不从这个格子走的下一个情况

		}
		
		else if(mi[xx][yy]=='d')//遇到狗
		{
			if(dog <= 1)
			{
				dog++;
				visited[xx][yy] = 1;
				dfs(xx,yy);
				dog--;//从野狗的位置回退 复位 也就是代表没有走这条路
				visited[xx][yy] = 0;//访问次数也要减一
			}
			
		}
		}
	}

}


int main()
{
	dog = 0;
mark = 0;
int total;
//cin>>total;//测试用例的数目
memset(visited,0,sizeof(visited));
cin>>n;//迷宫为n*n的格子
int x,y;//出发点的位置坐标
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
{
	cin>>mi[i][j];
}
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
{
	if(mi[i][j] == 'p')
	{
x = i;
y = j;
	}
	if(mi[i][j] == 'k')//表示金刚
	{
		visited[i][j] = 1;//直接置为1,不可访问点
	}
}
	visited[x][y] = 1;//出发点已经访问
	dfs(x,y);
	if(mark == 1)
	cout<<"yes";
	else
	cout<<"no";
}


20.求所有最长最短路径中最短的一条

设计算法找到医院建在哪个村庄才能使得距离医院最远的村庄到医院的路程最短
分析:flod算法可以求出每一个顶点与另外的所有顶点之间的最短路径,在这些所有最短路径中找到一对顶点距离最远,其中每一个顶点都有一个与它距离最远的路径,一共有n个最远的最短路径,在这n个路径中再找到最短的路径即可

int MaxMinPath(Matgragh g)
{
	int i,j,k;
	int a[maxn][maxn];
	int s,min = 32767;
	for(int i = 0;i < g.n;i++)
	for(int j = 0;j < g.n;j++)
	a[i][j] = g.edges[i][j];//将n个村庄间距离用二维数组a表示

	for(int k = 0;k < g.n;k++)
		for(int i = 0;i < g.n;i++)
	for(int j = 0;j < g.n;j++)
	{
		if(a[i][j] > a[i][k]+a[k][j])
		a[i][j] = a[i][k]+a[k][j];//求出最短路径
	}
	for(int i = 0;i < g.n;i++)
	{
		s = 0;//每个村庄都要循环一次
		for(int j = 0;j < g.n;j++)//求到达顶点i的一条最长最短路径
		if(a[j][i] > s)
		{
			s = a[j][i];
		}//注意这个for循环到这里就结束了
		if(s < min)
		{
			k = i;//在各个最长的路径中选一条最短的放在k中
			min = s;
		}
	}
return k;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值