JLU数据结构上机实验4

7-1 连通分量

原题呈现:
在这里插入图片描述

解题思路 : 考虑使用dfs。从第1个点开始做dfs,将所有途经的点利用flag数组做标记。以此类推考虑第2、3…n个点,如果该点被标记过,跳过,否则从该点进行dfs。

以下是根据这一思路写出代码:

#include<bits/stdc++.h>
#define N 50005
using namespace std;
int num;
vector<int> edges[N]; // vector存图
int flag[N];

void add(int u, int v)// 简单加边
{
    edges[u].push_back(v);
}

void dfs(int now) {
    for (int i = 0 ; i < edges[now].size(); i++) { // 遍历每一条边
        if (flag[edges[now][i]] != 1) {
            flag[edges[now][i]] = 1;
            dfs(edges[now][i]);           
        }       
    }
}

int main() {
    int n, m, u, v;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d %d",&u,&v);
        add(u, v);
        add(v, u); // 双向加边
    }
    for (int i = 1; i <= n; i++) {
        if (!flag[i]) {
            dfs(i);
            num++;
        }          
    }
    printf("%d", num);
    return 0;
}

解后反思: 当时大脑短路对边进行标记而没有对点,导致没有AC。此外,注意无向图中一条边的存储需要添加两条有向边。

7-2 整数拆分

原题呈现:
在这里插入图片描述

解题思路 : 用递归写dfs,参数re表示剩余的加数的和,参数t表示当前是第几个加数,当re为0时检查当前是否已经是第(k+1)个加数(即前k个加数和为n)。

以下是根据这一思路写出代码:

#include <bits/stdc++.h>
using namespace std;

int res[51];
int num = 0;
int n, k;
void dfs(int re, int t)
{
	if (re == 0 && t != k + 1)
		return;
	if (re == 0 && t == k + 1)
	{
		num++;
		for (int i = 1; i <= k; ++i)
		{
			printf("%d", res[i]);
			if (i != k)
				printf(" ");
			else
				printf("\n");

		}
		return;
	}
	for (int i = res[t - 1]; i <= re; ++i)
	{
		res[t] = i;
		dfs(re - i, t + 1);
	}
}
int main()
{
	scanf("%d %d", &n, &k);
	for (int i = 1; i <= n; ++i)
	{
		res[1] = i;
		dfs(n - i, 2);
	}
	printf("%d\n",num);
}

解后反思: 注意拆分方案不能重复,因而保持加数从左至右不减。

7-3 数字变换

原题呈现:
在这里插入图片描述

解题思路 : 因为要求变换的最小步数而非所有可能的变换,所以该题是隐藏的bfs问题而非dfs问题。开一个数组a保存每个数是由哪个数得到的,这样从起点x做bfs,直到出现能在下一步得到终点y的数即可。

以下是根据这一思路写出的代码:

#include <bits/stdc++.h>
using namespace std;

int a[100001], num = 1, x, y, flag=0;
stack <int> s;
queue <int> q;

void bfs(int i) {
	q.push(i);
	while (1) {
		i = q.front();
		q.pop();
		if ((i + 1) <= 100000 && a[i + 1] == 0) {
			a[i + 1] = i;
			q.push(i + 1);
			if (i + 1 == y) {
				flag = 1;
				return;
			}
		}
		if ((i * 2) <= 100000 && a[i * 2] == 0) {
			a[i * 2] = i;
			q.push(i * 2);
			if (i * 2 == y) {
				flag = 1;
				return;
			}
		}
		if (i > 1 && (i - 1) <= 100000 && a[i - 1] == 0) {
			a[i - 1] = i;
			q.push(i - 1);
			if (i - 1 == y) {
				flag = 1;
				return;
			}
		}
	}
}

int main() {
	scanf("%d %d", &x, &y);
	a[x] = x;
	if (x > y) {
		printf("%d\n", x - y);
		for (int i = x - 1; i >= y; i--) {
			printf("%d", i);
			if (i != y)
				printf(" ");
		}

	}
	else if (x == y)
		printf("0\n");
	else {
		bfs(x);
		int tmp = y;
		while (tmp != x) {
			s.push(tmp);
			tmp = a[tmp];
		}
		int len = s.size();
        printf("%d\n",len);
		for (int i = 0; i < len; i++) {
			printf("%d", s.top());
			s.pop();
			if (i != len - 1)
				printf(" ");
		}
	}
}

解后反思: 1.注意特判x<y和x=y的情景。2.注意题中所给的三种变换有优先次序,因而对i做bfs时,按i+1,i*2,i-1的先后顺序入队。

7-4 序列乘积

原题呈现:
在这里插入图片描述

解题思路1 : dijkstra算法+堆优化 该算法无法处理边权为0或负的情况

以下是根据这一思路写出的代码:

#include<bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[100010], to[200010], nex[200010], v[200010], tl, num[100010];
long long d[100010];
bool b[100010];
void add(int x, int y, int w) {
    to[++tl] = y; // f到y的路径是输入中的第几条
    v[tl] = w;
    nex[tl] = fr[x]; //上一条从x出发的路径
    fr[x] = tl; //fr中存从x出发的路径是输入中的第几条
}
priority_queue< pair<long long, int> > q;//先比较第一个元素大小,相同再比第二个
int main() {
    int n, m, x, y, z, s;
    scanf("%d %d %d", &n, &m, &s);
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &x, &y, &z);
        add(x, y, z);
        add(y, x, z);
    }
    for (int i = 1; i <= n; i++)
        d[i] = 1e10;//用maxn初始化
    d[s] = 0;
    q.push(M(0, s));
    while (!q.empty()) {
        int x = q.top().second;
        q.pop();
        if (b[x])//判断该点是否被使用过
            continue;
        b[x] = 1;
        for (int i = fr[x]; i; i = nex[i]) {
            int y = to[i], l = v[i];
            if (d[y] > d[x] + l) {
                d[y] = d[x] + l;
                num[y] = num[x] + 1;
                q.push(M(-d[y], y));//懒得重载运算符&&以便于每次找最近的点
            }
            else if (d[y] == d[x] + l) {//因为要经过尽可能多的城市,所以取等的情况也要考虑
                d[y] = d[x] + l;
                if (num[x] + 1 > num[y])
                    num[y] = num[x] + 1;
                q.push(M(-d[y], y));
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        printf("%lld", d[i]);
        if (i != n)
            printf(" ");
    }       
    printf("\n");
    for (int i = 1; i <= n; i++) {
        printf("%d", num[i]);
        if (i != n)
            printf(" ");
    }
        
    return 0;
}

解题思路2 : Bellman-Ford算法+基本优化

以下是根据这一思路写出的代码:

#include <bits/stdc++.h>
using namespace std;
long long dis[10010];
int u[200100], v[200100], w[200100], num[10010], n, m, s, check;//我们定义一个check,优化用 
const int inf = 2147483647;
int main()
{
    cin >> n >> m >> s;//输入 
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &u[i], &v[i], &w[i]);//读入边 
        u[m + i] = v[i];
        v[m + i] = u[i];
        w[m + i] = w[i];
    }
    m=m*2;
        
    for (int i = 1; i <= n; i++)
        dis[i] = inf;//dis数组初始化 
    dis[s] = 0;
    for (int k = 1; k <= n - 1; k++)
    {
        check = 0;//check归零 
        for (int i = 1; i <= m; i++)
        {
            if (dis[v[i]] > dis[u[i]] + w[i])
            {
                dis[v[i]] = dis[u[i]] + w[i];
                num[v[i]] = num[u[i]] + 1;
                check = 1;//如果dis数值改变,check赋值为1 
            }
            else if (dis[v[i]] == dis[u[i]] + w[i])
            {
                if(num[v[i]] < num[u[i]] + 1)
                    num[v[i]] = num[u[i]] + 1;
            }
        }
        if (check == 0)
            break;//如果没变,直接跳出循环,不要浪费时间 
    }
    for (int i = 1; i <= n; i++) {
        printf("%lld", dis[i]);
        if (i != n)
            printf(" ");
    }
    printf("\n");
    for (int i = 1; i <= n; i++) {
        printf("%d", num[i]);
        if (i != n)
            printf(" ");
    }
}

解后反思: 1.无向图中一条边的存储需要添加两条有向边。 2.做松弛时如果发现两者相等,如果贸然改变/不改路线可能会使经过的点数变少,因而需要分情况讨论。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第一次实验目1 单链表相关算法的实验验证。 [实验目的] 验证单链表及其上的基本操作。 [实验内容及要求] 1、 定义单链表类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建单链表; 2)插入操作:分别在当前结点后、表头、表尾插入值为x的结点; 3)删除操作:分别删除表头结点、表尾结点和当前结点的后继结点; 4)存取操作:分别存取当前结点的值和单链表中第k个结点的值; 5)查找操作:查找值为x的元素在单链表中的位置(下标)。 目2 分别给出堆栈、队列相关算法的实验验证。 [实验目的] 验证堆栈、队列及其上的基本操作。 [实验内容及要求](以队列为例) 1、 定义队列类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建队列; 2)插入操作:向队尾插入值为x的元素; 3)删除操作:删除队首元素; 4)存取操作:读取队首元素。 第二次实验 目1 二叉树相关算法的实验验证。 [实验目的] 验证二叉树的链接存储结构及其上的基本操作。 [实验内容及要求] 1、 定义链接存储的二叉树类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建一棵二叉树,并对其初始化; 2)先根、中根、后根遍历二叉树(递归算法); 3)在二叉树中搜索给定结点的父结点; 4)搜索二叉树中符合数据域条件的结点; 5)从二叉树中删除给定结点及其左右子树。 目2 树和森林的遍历算法的实验验证。 [实验目的] 验证树和森林的遍历算法。 [实验内容及要求] 1、 定义左儿子—右兄弟链接存储的树类和森林类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建树和森林; 2)树和森林的先根遍历的递归和迭代算法; 3)树和森林的后根遍历的递归和迭代算法; 4)树和森林的层次遍历算法。 目3 二叉查找树的验证实验。 [实验目的] 验证二叉查找树及其相关操作。 [实验内容及要求] 1、 定义二叉查找树的类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)实现二叉查找树结构; 2) 实现二叉查找树的查找、插入和删除等算法; 第三次实验 目1 邻接表存储的图相关算法的实验验证。 [实验目的] 验证邻接表存的图及其上的基本操作。 [实验内容及要求] 1、 定义邻接表存储的图类。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建一个邻接表存储的图; 2)返回图中指定边的权值; 3)返回图中某顶点的第一个邻接顶点; 4)返回图中某顶点关于另一个顶点的下一个邻接顶点的序号; 5)插入操作:向图中插入一个顶点,插入一条边; 6)删除操作:从图中删除一个顶点,删除一条边。 目2 图的遍历算法的实验验证。 [实验目的] 验证图的遍历算法。 [实验内容及要求] 1、 定义邻接表存储的图。 2、 实验验证如下算法的正确性、各种功能及指标: 1)创建一个图; 2)图的深度优先遍历的递归算法; 3)图的深度优先遍历的迭代算法; 4)图的广度优先遍历算法。 第四次实验 折半插入排序,堆排序,快速排序 请阅读说明文档

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值