数据结构第四次上机

一、7-1 连通分量 (100 分)

输入格式:

第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.

输出格式:

1行,1个整数,表示所求连通分量的数目。

输入样例:

在这里给出一组输入。例如:

6 5
1 3
1 2
2 3
4 5
5 6

输出样例:

在这里给出相应的输出。例如:
2

思路:

1. 存储问题: 存储图的结构有连接链表与连接矩阵,本题选择连接矩阵存储图。特别的,本题需要存储的是无向图,而连接链表存储的边是又向的,故两点之间的边需要存为双向路。

cin >> m >> n;
//从m到n
edge* q = new edge;
......
g->verlist[m-1].next=q;
//从n到m
edge* q = new edge;
......
g->verlist[n-1].next=q;

2.遍历联通分支的方法: 图的遍历算法每次都只能遍历一个联通分量,DFSBFS在这里使用任意一种都可以达到目的,并且使用DFS的递归算法在本题中也不会超时。 调用的次数即为连通分支的个数。

具体代码:

1.存图:
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
//存储图的结构
typedef struct edge {
	int ii;
	int w;
	struct edge* next;
}  edge;
typedef struct vertex {
	int ii;
	struct edge* next;
}vertex;
typedef struct graph {
	int vv, ee;
	vector<vertex> verlist;
}graph;
graph* g = new graph;

int main()
{
	scanf("%d%d",&g->vv,&g->ee);
	for (int i = 1; i <= g->vv; i++)
	{
		vertex* p = new vertex;
		p->ii = i ;
		p->next = NULL;
		g->verlist.push_back(*p);
	}
	for (int i = 0; i < g->ee; i++)
	{
		int m, n;
		cin >> m >> n; 
		//存储从m到n的有向边
		edge* q = new edge;
		q->ii = n;
		q->w = 0;//无权边
		q->next = NULL;
		if (!g->verlist[m-1].next)//如果遍链表为空
			g->verlist[m-1].next = q;
		else//非空则找到最后的位置
		{
			edge* head = g->verlist[m-1].next;
			while (head->next)
				head = head->next;
			head->next = q;
		}
		//存储从n到m的有向边
		q = new edge;
		q->ii = m;
		q->w= 0;
		q->next = NULL;
		if (!g->verlist[n-1].next)
			g->verlist[n-1].next = q;
		else
		{
			edge* head = g->verlist[n-1].next;
			while (head->next)
				head = head->next;
			head->next = q;
		}
	}
}
2.(1)DFS遍历
int vis[50005];
void DFS(int v)
{
	vis[v] = 1;
	for (edge* pp = g->verlist[v - 1].next; pp; pp = pp->next)
	{
		if (!vis[pp->ii])
		{
			DFS(pp->ii);
		}
	}
}
int main()
{
//存图
	int n=0;
	for(int i=0;i<g->vv;i++)
	{
		if(vis[i+1]==0)
		{
		vis[i+1]=1;	
		DFS(i+1);
		n++;
		}
	}
	printf("%d",n);
}
(2)BFS遍历
vis[50005]
queue<int>qq;
int main()
{
	//存图
	int n=0;
	for(int i=0;i<g->vv;i++)
	{
		if(vis[i+1]==0)
		{
			vis[i+1]=1;
			qq.push(i+1);
			while (!qq.empty())
			{
				int v = qq.front();
				qq.pop();
				edge* p = g->verlist[v - 1].next;
				for (p; p; p = p->next)
				{
					if (vis[p->ii] == 0)
					{
						vis[p->ii] = 1;
						qq.push(p->ii);
					}
				}
			}
			n++;
		}
	}
	printf("%d",n)
}

二、整数拆分 (100 分)

整数拆分是一个古老又有趣的问题。请给出将正整数 n 拆分成 k 个正整数的所有不重复方案。例如,将 5 拆分成 2 个正整数的不重复方案,有如下2组:(1,4)和(2,3)。注意(1,4) 和(4,1)被视为同一方案。每种方案按递增序输出,所有方案按方案递增序输出。

输入格式:

1行,2个整数n和k,用空格分隔, 1≤k≤n≤50.

输出格式

若干行,每行一个拆分方案,方案中的数用空格分隔。
最后一行,给出不同拆分方案的总数。

输入样例:

在这里给出一组输入。例如:

5 2

输出样例:

在这里给出相应的输出。例如:

1 4
2 3
2

思路:

1. 将一个数分解为多个数的和是一个典型的使用递归方法解决的问题。链接为我之前总结的思路与方法,本题在数据结构中使用高级语言设计同样的方法是不会超时的。
链接: [递归例题总结]–正整数n,按第一项递减的顺序依次输出其和等于n的所有不增的正整数和] https://blog.csdn.net/weixin_51376942/article/details/112204620?spm=1001.2014.3001.5501.

具体代码

#include <stdio.h>
#include <iostream>
using namespace std;
int n,k,num;
int d[100]; 
void divid(int nn,int kk)
{
	if(kk==k)
	{
		d[k]=nn;
		num++;
		for(int i=1;i<=kk;i++)
		{
			printf("%d",d[i]);
			if(i==kk)printf("\n");
			else printf(" ");
		}
		return ;
	}
	if(nn==0&&kk!=k) return ;
	for(int i=d[kk-1];i<=nn/(k-kk+1);i++)//保证分解的数递增
	{
		d[kk]=i;
		divid(nn-i,kk+1);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	d[0]=1;
	divid(n,1);
	printf("%d",num);
	return 0;
} 

7-3 数字变换 (100 分)

利用变换规则,一个数可以变换成另一个数。变换规则如下:(1)x 变为x+1;(2)x 变为2x;(3)x 变为 x-1。给定两个数x 和 y,至少经过几步变换能让 x 变换成 y.

输入格式

1行,2个整数x和y,用空格分隔, 1≤x,y≤100000.

输出格式

第1行,1个整数s,表示变换的最小步数。
第2行,s个数,用空格分隔,表示最少变换时每步变换的结果。规则使用优先级顺序: (1),(2),(3)。

输入样例:

在这里给出一组输入。例如:

2 14

输出样例:

在这里给出相应的输出。例如:

4
3 6 7 14

思路

1. 从初始的x使用三种变换规则得到的数,再次依次使用三种变换规则直到第一次得到目标数字,整个计算的过程中实际形成了一个图,而计算的过程实际上是一次对图的BFS遍历,所以,本题实际上是在一张潜在的图上找到从x到y的最短路,并输出这条路上的记录的所有值。
2. 引入一个记忆数组,对于已经入队的数字,不再重复入队,否则图将扩展为一个三叉树,多次的重复计算在本题中会导致第三个内存超限(即使内存足够,也会计算超时),第二个答案错误。
3. 引入一个存路径的数组和一个存储计算所得的全部值的数组,递归从头到位输出路径上的值。

注意:

全局数据的下表范围标记数组v[] 下标的最大值为计算过程中所得的最大值,本题2000010即可。路径数组path[] 的下标的最大值为计算过程中所得的所有值,本题中开到1e5+5即可。

具体代码:

#include <stdio.h>
#include <queue>
using namespace std;
typedef struct point{
	 int x;
	int i;
	point(int n=0,int m=0):x(n),i(m){}
	} poi;
queue <poi>q;
int a[100005]/*存储计算所得所有值*/,path[100005]/*存储路径*/,v[200010]/*标记数组*/,step;
void print(int i)
{
	if(path[i]!=-1) 
	{
		step++;
		print(path[i]);
	}
	else 
	{
		printf("%d\n",step);
		return ;
	}
	printf("%d",a[i]);
    if(step--!=1) printf(" "); 
} 
int main()
{
	int x,y;
	scanf("%d%d",&x,&y);
	int i=0;
	path[i]=-1;
	a[i]=x;
	if(x==y)
    {
    printf("0\n");
    return 0;}
	q.push(poi(x,i++));
	while(1)
	{
		poi xx=q.front();
		q.pop();
		if(!v[xx.x+1])
		{
		q.push(poi(xx.x+1,i));
		v[xx.x+1]=1;
		a[i]=xx.x+1;
		path[i++]=xx.i;
		if(xx.x+1==y) break;
		}
	
		if(!v[xx.x*2]&&xx.x*2<2*y)
		{
		q.push(poi(xx.x*2,i));
		v[xx.x*2]=1;
		a[i]=xx.x*2;
		path[i++]=xx.i;
		if(xx.x*2==y) break;
		}
		
		if(!v[xx.x-1]&&xx.x>0)
		{
		q.push(poi(xx.x-1,i)); 
		v[xx.x-1]=1;
		a[i]=xx.x-1;
		path[i++]=xx.i;
		if(xx.x-1==y) break;
		}
		
	}
	print(i-1);
}

7-4 旅行 I (100 分)

五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。将所有城市编号为1到n,你出发的城市编号是s。你想知道,到其它城市的最小费用分别是多少。如果可能,你想途中多旅行一些城市,在最小费用情况下,到各个城市的途中最多能经过多少城市。

输入格式:

第1行,3个整数n、m、s,用空格分隔,分别表示城市数、交通方式总数、出发城市编号, 1≤s≤n≤10000, 1≤m≤100000 。

第2到m+1行,每行三个整数u、v和w,用空格分隔,表示城市u和城市v的一种双向交通方式费用为w , 1≤w≤10000。

输出格式:

第1行,若干个整数Pi,用空格分隔,Pi表示s能到达的城市i的最小费用,1≤i≤n,按城市号递增顺序。

第2行,若干个整数Ci,Ci表示在最小费用情况下,s到城市i的最多经过的城市数,1≤i≤n,按城市号递增顺序。

输入样例:

在这里给出一组输入。例如:

5 5 1
1 2 2
1 4 5
2 3 4
3 5 7
4 5 8

输出样例:

在这里给出相应的输出。例如:

0 2 6 5 13
0 1 2 1 3

思路:

1. 本题实质上是求单元最短路问题,可用的算法有1.Dijkstra(无法处理负权边)2.Bellman—ford算法(可以处理负权边)3.SPFA算法(不适合负回路)。本题具有实际意义,所以不会出现负权边,故Dijkstra算法是可行的。但介于基础的Dijistra算法的时间效率低(虽然在本题中并不会超时),需要使用堆优化
2. 由于本题要求输出最短且路由点更多的路,所以无需存储路径的path数组,而需要一个记录个点到源点路径上的点的个数。故在松弛步骤时,当到达某点的旧路代价与新路代价相同时,若新路路由的点更多重新赋值该点路由点的值

注意:

普通dijikstra算法与堆优化的Dijikstra算法的循环条件是不同的,沿用基础Dijistra算法的循环条件会导致生成错误的单元最短路。在本题中会使用例三与用例四答案错误。

具体代码:

#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#define MAX 1e8;
using namespace std;
typedef struct edge {
	int ii;
	int cost;
	struct edge* next;
}  edge;
typedef struct vertex {
	int ii;
	struct edge* next;
}vertex;
typedef struct graph {
	int vv, ee;
	vector<vertex> verlist;
}graph;
graph* g = new graph;
typedef struct qbase {
	int key, value;
}qbase;
struct cmp {
	bool operator()(const qbase a, const qbase b)
	{
		return a.value > b.value;
	}
};

priority_queue<qbase, vector<qbase>, cmp> qq;//使用优先对列进行队优化
int path[10005], ll[10005],s;
long long dis[10005];
int DIJ()
{
	for (int i = 0; i < 10005; i++)
	{
		dis[i] = MAX;
		ll[i] = 0;
	}
	dis[s] = 0;

	qbase aaa;
	aaa.key = s;
	aaa.value = 0;
	qq.push(aaa);

	while(!qq.empty())
	{
		long long min = qq.top().value;
		int kk = qq.top().key;
		qq.pop();
		edge* p = g->verlist[kk - 1].next;
		for (p; p; p = p->next)
		{
			if (dis[p->ii] > dis[kk] + p->cost)
			{
				ll[p->ii] = ll[kk] + 1;
				dis[p->ii] = dis[kk] + p->cost;
				//松弛后的点入队
				qbase tmp;
				tmp.key = p->ii;
				tmp.value = dis[p->ii];
				qq.push(tmp);
			}
			else if (dis[p->ii] == dis[kk] + p->cost)
			{
				if (ll[p->ii] < ll[kk] + 1)
					ll[p->ii] = ll[kk] + 1;
			}
		}
	}
	return 0;
}
int main()
{
	scanf("%d%d%d", &g->vv, &g->ee, &s);
	for (int i = 1; i <= g->vv; i++)
	{
		vertex* p = new vertex;
		p->ii = i;
		p->next = NULL;
		g->verlist.push_back(*p);
	}
	for (int i = 0; i < g->ee; i++)
	{
		int m, n, c;
		cin >> m >> n >> c;
		edge* q = new edge;
		q->ii = n;
		q->cost = c;
		q->next = NULL;
		if (!g->verlist[m - 1].next)
		{
			g->verlist[m - 1].next = q;
		}
		else
		{
			edge* head = g->verlist[m - 1].next;
			while (head->next)
			{
				head = head->next;
			}
			head->next = q;
		}

		q = new edge;
		q->ii = m;
		q->cost = c;
		q->next = NULL;
		if (!g->verlist[n - 1].next)
		{
			g->verlist[n - 1].next = q;
		}
		else
		{
			edge* head = g->verlist[n - 1].next;
			while (head->next)
			{
				head = head->next;
			}
			head->next = q;
		}
	}//建图
	DIJ();
	for (int i = 0; i < g->vv; i++)
	{
		printf("%lld", dis[i + 1]);
		if (i != g->vv - 1) printf(" ");
	}
	printf("\n");
	for (int i = 1; i <= g->vv; i++)
	{
		printf("%d", ll[i]);
		if (i != g->vv)printf(" ");
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,我无法提供关于具体学校或课程的第四次上机内容。但是,我可以为您提供一些关于数据结构的基本知识和常见的上机题目类型。 数据结构是计算机科学中非常重要的一门课程,它研究如何组织和存储数据以及如何有效地操作和管理数据。第四次上机可能涉及以下内容之一: 1. 树和图的应用:树和图是常见的数据结构,可以用于解决各种实际问题,比如搜索、排序、最短路径等。在上机中,您可能需要实现树或图的基本操作,如插入、删除、遍历等。 2. 排序算法:排序是数据结构中的一个重要问题,常见的排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序等。您可能需要实现其中一个排序算法,并对给定的数据进行排序。 3. 查找算法:查找是另一个常见的问题,常见的查找算法有线性查找、二分查找、哈希查找等。您可能需要实现其中一个查找算法,并根据给定的条件查找特定的数据。 4. 动态规划:动态规划是一种解决最优化问题的方法,它将问题分解为子问题,并通过保存子问题的解来避免重复计算。您可能需要使用动态规划来解决给定的问题,并实现相应的算法。 以上只是一些可能的内容,具体的上机要求可能会根据课程教学大纲和教师的要求而有所不同。如果您有具体的问题或需要更详细的帮助,请提供更多信息,我将尽力回答您的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值