192上机作业题解

题解前话:题目据说大多出自leetcode,而对于大多数计算机系生如果有一定leetcode基础会对招聘有加分,所以各位要重视这些题。
然后,我不是图论选手,我的图论知识比较贫乏,有的题最优解我更倾向于简单算法。

开始正文,先讲一下输入问题,题目里有‘,’这样的输入方式,是比较特别的,你可以用scanf的性质把‘,’读取。这里详情可以了解以下scanf函数:转载别人的博客

最短路径

我选择的是Floyd,但是这题时间最优应该是SPFA,效率适中我会考虑dijistra(迪杰斯特拉),由于能力有限我就讲一下Floyd,有兴趣各位可以自己补。

Floyd(弗洛伊德)的本质就是对于所有 i i i点,枚举出所有边 j − k j-k jk与其相连,判定这个 j − k j-k jk线路是否存在经过了 i i i点可以提高效率。因此Floyd首先要做的事情是初始化,将点与点孤立,也就是边无穷大。然后将联通的点和点利用邻接矩阵存储距离。

这里解释一下邻接矩阵,是一个存图的常用算法。对于表示 a i j a_{ij} aij我们存储 i − j i-j ij的路径(长度)。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
int n;
double a[200][200];
const int inf = 0x3f3f3f3f;
//重点的三个函数(核心部分)
int main() {
    int m;
    int x, y;
    int flag = 1;
    cin >> n;
    cin >> m;
    cin >> x >> y;
    //以上为输入情况
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
            a[i][j] = inf;//固定无穷大常数
        }
    }
    for (int i = 1; i <= m; i++) {
        int t, r;
        scanf("%d %d", &t, &r);
        scanf("%lf", &a[t][r]);
        //a[t][r] = 1;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            for (int k = 0; k < n; k++)
            {
                //如果发现经过i点的两条边相加之和小于这两个点之间的距离,那么更新数据

                if (a[j][k] > a[j][i] + a[i][k])
                    a[j][k] = a[j][i] + a[i][k];
            }
        }
    }
    if (a[x][y]<=inf)printf("%.2f", a[x][y]);
    else cout << "false" << endl;
}

课程排序(刚才锅了,假算法)

刚预习了一遍拓扑排序就来写题解我也是心大。

首先拓扑排序的本质就是从每个入度为零的地方起步,然后目标遍历所有点。

注释1:入度,代表某点进入该点的次数。

那么如果不从入度为0的地方起步可以么,按照题意来说应该不可以。因此只需要找到所有的入度为零的地方,找找他能走到哪,把那些到过的点标记,计算一共标记了几次点。

解法1:暴力遍历:

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1000;
int T[maxn][maxn];//标记图
int D[maxn];//标记顶点的入度
int n;//顶点个数
int m;//边的个数
int arr[maxn];//记录排序后的顺序
int num = 0;
int topSort()
{
	for (int i = 0; i < n; i++)//得到每个节点
	{
		int j;
		for (j = 1; j <= n; j++)//遍历找到入度为0的结点
		{
			if (D[j] == 0)
			{
				arr[num++] = j;//标记到arr数组中
				D[j]--;//标记已使用
				break;
			}
		}
		for (int k = 1; k <= n; k++)//遍历所有边
		{
			if (T[j][k] == 1)//找到以j开头的边
			{
				T[j][k]--;//去掉该边
				D[k]--;//k顶点入度减一
			}
		}
	}
	return 0;
}

int main()
{
	int x, y;
	memset(T, 0, sizeof(T));
	memset(D, 0, sizeof(D));
	
	cin >> n;

	cin >> m;

	for (int i = 0; i < m; i++)
	{
		scanf("%d,%d", &x, &y);
		T[x][y] = 1;//标记边
		D[y]++;//记录入度
	}
	topSort();
	if (num == n)
	{
		cout << "true\n";
	}
	else
	{
		cout << "false" << endl;
	}
	return 0;
}

有句话说的好,只要循环次数够多,再怎么难的NP难题也可以解开,因此这个暴力做法就是利用大量的循环访问实现枚举效果,做到算无遗策。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 9;
int ind[maxn], n, m;
queue<int> q;
vector<int> g[maxn];
int main() {
	scanf("%d%d", &n, &m);
	while (m--) {
		int u, v;
		scanf("%d,%d", &u, &v);
		ind[v]++;
		g[u].push_back(v);             //可以理解为二维数组
	}
	for (int i = 0; i < n; i++)
		if (ind[i] == 0) q.push(i);    //把入度为0的结果放入队列
	while (q.size()) {                 //如果队列不为空
		int v = q.front();             //把队列的头读出来执行走路模拟
		q.pop();                       //出列
		for (int i : g[v])             //遍历
			if (--ind[i] == 0) q.push(i);//入列,减少入度
	}
	for (int i = 0; i < n; i++)
		if (ind[i]) {
			puts("false");//存在入度不为0
			return 0;
		}
	puts("true");
}

这个做法利用了BFS,也就是将你会执行的步骤压在队列的最下面,从上往下有序执行,实现搜索每一个点。

并查集应用:判圈

课程排序

我的做法是并查集找联通情况,通过判断连线的两个点是否存在同一个祖先,来判定是否成环。

我们先从并查集开始讲,并查集其实是三个函数组成的。分别是,初始化,查找祖先,判定是否有共同祖先,如果不是,把他们俩连起来挂在一个祖先名下。

初始化:让每个点的祖先都是自己。
查找祖先:写一个递归函数,判定自己的上一级是谁,让上一级去追查上一级,层层询问,问到某个人是自己的祖先停止,然后告诉下面的人,你们的祖先是这个,下次就不用问多次询问上级了。
打架:现在有两个人他们相遇了,要打一架,但他们怕自己打了自家人,就问问上级这个是不是自己人,如果是,那就握手言和(成环了),如果不是,这两个人不打不相识,把自己拜入对方家谱中。

如果还是不太懂可以看这篇CSDN,我当初也是看这个自学的。
转载:并查集(他也是转载)

这里代码贴个板子给大家学习一下,板子算是我压缩用来当黑箱用的,所以比较短:

const int N = 1001;
//重点的三个函数(核心部分)
int fa[N];

void init() {                       //初始化
	for (int i = 0; i < N; i++)
		fa[i] = i;
}

int findfa(int x) {                  //找到某个数x的祖先
	return x == fa[x] ? x : (fa[x] = findfa(fa[x]));
}

void Union(int x, int y) {            //把x和y合并(二者之间连一条线)
	int fx = findfa(x), fy = findfa(y);
	if (fx != fy) fa[fx] = fy;              //注意是fa[fx]=fy, 而不是fa[x]=fy; 当然了fa[fy]=fx也行
}

所以这题也就用到了这个思想,如果成环就不能排序,那么就判定是否有成环的情况就可以了。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int x[2000];
const int N = 1001;
int fa[N];
int r[N][N];
void init() {                       //初始化
	for (int i = 0; i < N; i++)
		fa[i] = i;
}
int findfa(int x) {                  //找到某个数x的祖先
	return x == fa[x] ? x : (fa[x] = findfa(fa[x]));
}
void Union(int x, int y) {            //把x和y合并(二者之间连一条线)
	int fx = findfa(x), fy = findfa(y);
	if (fx != fy) fa[fx] = fy;              //注意是fa[fx]=fy, 而不是fa[x]=fy; 当然了fa[fy]=fx也行
}
int main() {
	init();
	int a, b, c;
	cin >> a >> b >> c;
	for (int i = 0; i < b; i++) {
		int n, m;
		cin >> n >> m;
		r[n][m] = 1;
		r[m][n] = 1;
		Union(n, m);//连线
	}
	for (int i = 0; i < c; i++) {
		int n, m;
		cin >> n >> m;//判定是否存在连线或者成环
		if (r[n][m] == 1)cout << "YES" << endl;
		else if (findfa(n) == findfa(m))cout << "YES" << endl;
		else cout << "NO" << endl;
	}
}

省份数量

强连通分量判定,最熟悉的算法是缩点,也就是离散化,为了给大家巩固一下并查集算法,我就继续用并查集讲了。

已知无向图可以转化为有向图,也就可以转化为并查集寻找 祖先为自身 的个数。

为什么呢,我们继续打架,每两个城市相连,代表他们要打一次架。这时候我们心中存一群母树,每个点独立一棵,每次两个人打架就把这两个点连起来,固定一个点为母树(作为祖先),然后每次打架都让他们问问他们的上级谁是领导,把这些人都挂在领导的名下,这些独立的点逐渐就挂在了联通的几棵树上了,那么母树的数量就成了联通的量了。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 10000
vector<long long > g[MAX_N];
int color[MAX_N];
const int N = 1001;
//重点的三个函数(核心部分)
int fa[N];

void init() {                       //初始化
    for (int i = 0; i < N; i++)
        fa[i] = i;
}

int findfa(int x) {                  //找到某个数x的祖先
    return x == fa[x] ? x : (fa[x] = findfa(fa[x]));
}

void Union(int x, int y) {            //把x和y合并(二者之间连一条线)
    int fx = findfa(x), fy = findfa(y);
    if (fx != fy) fa[fx] = fy;              //注意是fa[fx]=fy, 而不是fa[x]=fy; 当然了fa[fy]=fx也行
}
int a[200][200];
int main() {
    int n;
    cin >> n;
    init();
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n-1; j++) {
            scanf("%d," ,& a[i][j]);
        }
        scanf("%d", &a[i][n]);
      //  cout << a[i][n - 1];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < n; j++) {
            if (a[i][j] == 1) {
                Union(i, j);//找关系打架

            }
        }
        
    }
    int ans = 0;//数祖先是自己的个数
    for (int i = 1; i <= n; i++) {
        if (fa[i] == i)ans++;
    }
    cout << ans << endl;
}

二分图判断

这题有一个结论:二分图成立的前提是他们的环为偶数,然后可以理解为,每相邻两个点颜色不同(你可以把颜色不同理解为在不同的集合,二分图一共只有两个集合)。

因此这题可以用DFS染色做,也可以BFS染色做,终归就是染色出结果。
这里介绍BFS染色法:
从任意点出发,对于周围所有可以走的路都放入队列中,然后将自己当前站的位置染成一种颜色,对于下一次要走的路染成另一种颜色(这里可以用!运算来实现),因为队列本身的性质,必须要把当前所有的路都走完才能走下一次的路,因此,实现上用迭代的做法。

以下为我的染色代码:

int bfs_check(int x) {//判断是否为二分图 
	queue<int> q;
	q.push(x);
	color[x] = 1;
	while (!q.empty()) {
		int from = q.front();
		q.pop();
		int sz = g[from].size();
		for (int i = 0; i < sz; i++) {
			if (color[g[from][i]] == -1) {
				q.push(g[from][i]);
				color[g[from][i]] = !color[from];//相邻的点染成不同颜色 
			}
			if (color[g[from][i]] == color[from]) {//相邻的点颜色相同 
				return 0;//不是 
			}
		}
	}
	return 1;//是 
}

这个不贴完整代码,因为用的是vector存图,你们不方便改。

数据流的中位数

Leetcode 数据结构题

反正我喜欢用优先队列,那我也就讲优先队列的做法,具体实现大家可以自己摸索。

首先,c++库里有优先队列的成品,我就不另起炉灶,直接讲大小堆实现中位数判定。

一个堆存1 2 3 4 5 另一个堆存10 9 8 7 6。根据优先队列的特殊性质,你可以将他们输进去就排序,那我就设计了一个小堆装升序的小数字,一个大堆装降序的大数字。如果两个堆数据总和是奇数,那么输出多一点的堆的队列顶端,如果是偶数,就把两个队顶拿来平均以下。

最后解释一下这个大小堆的维护,空队列优先丢空队列,比小堆的最大值大就丢大堆,如果比大堆的最小值小就丢小堆。

大堆一边比小堆一边大1或两边size相等(这里也可以小堆比大堆,只要一致保持就可以了) 一直用if去判定就好了。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 10000
vector<long long > g[MAX_N];
int color[MAX_N];
priority_queue<long long >Big;
priority_queue<long long , vector<long long >, greater<long long > >Small;
void addNum(long long  num) {
    if (Small.empty() && Big.empty())      Big.push(num);
    else if (Small.empty())                Big.push(num);
    else if (Big.empty())                  Small.push(num);
    else if (num <= Small.top())           Big.push(num);
    else                                   Small.push(num);

    if (Small.size() > Big.size()) {
        Big.push(Small.top());
        Small.pop();
    }
    if (Big.size() - Small.size() > 1) {
        Small.push(Big.top());
        Big.pop();
    }
}
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
       long long  a;
        cin >> a;
        addNum(a);
        int sum = Small.size() + Big.size();
        if (sum & 1)
            printf("%.1f\n", (double)Big.top());
        else
            printf("%.1f\n",1.0 * (Small.top() + Big.top()) / 2);
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值