JLU 20级数据结构荣誉课 第四次解题报告

第四次解题报告


第一题  连通分量

  • 题目描述

无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。

输入格式:

第1行,1个整数n,表示森林的结点个数, 1≤n≤100000.

第2行,n个字符,用空格分隔,表示森林F的先根序列。字符为大小写字母及数字

第3行,n个整数,用空格分隔,表示森林F的先根序列中每个结点对应的度。

输出格式:

1行,n个字符,用空格分隔,表示森林F的层次遍历序列。

输入样例:

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

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

输出样例:

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

2
  •  题目分析

本题要求求出给定无向图中连通分量的数目。需要注意,无向图的连通分量与有向图的强连通分量有很大区别。

求解无向图的连通分量数较为简单,只需使用DFS或BFS进行搜索,每次搜索都能将相连的顶点遍历,故搜索的次数即为连通分量的个数。

每个顶点都会被处理一次,总的时间复杂度为O(n)。

  • 算法实现

    #include<bits/stdc++.h>
    using namespace std;
    
    vector<int> graph[50001];
    int visited[50001]={0};
    
    void DFS(int index)
    {
        visited[index]=1;
        for(int i=1;i<graph[index].size();i++)
        {
            if(visited[graph[index][i]]!=1)
                DFS(graph[index][i]);
        }
    }
    
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            graph[i].push_back(-1);
        }
        for(int i=1;i<=m;i++)
        {
            int cur;
            scanf("%d",&cur);
            int tmp;
            scanf("%d",&tmp);
            graph[cur].push_back(tmp);
            graph[tmp].push_back(cur);
        }
    
        int count_=0;
        for(int i=1;i<=n;i++)
        {
            if(visited[i]!=1)
            {
                DFS(i);
                count_++;
            }
        }
        printf("%d",count_);
        return 0;
    }
    

     

  • 核心数据结构

使用了vector来以邻接表方式存储图。图中每个顶点是否已经过用一个int数组标记。


第二题  整数拆分

  • 题目描述

整数拆分是一个古老又有趣的问题。请给出将正整数 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
  •  题目分析

本题是很经典的题目。在程序设计基础课程中就有涉及。当时,课本给出的方法是试探法,即满足条件时延长答案序列或输出,不满足时回溯。

不难发现,这种试探法的思想就是深度优先搜索的思想,当前深度不满足条件时就退回上一深度进行搜索。

此题用这样的搜索回溯的方法即可解决。将当前数拆为两个数,再如此递归调用,同时通过判断大小和巧妙设置初值保证序列递增。

本算法的复杂度尚未得出。

  • 算法实现

    #include<bits/stdc++.h>
    
    using namespace std;
    
    int k;
    int ans[51];
    int count_=0;
    
    void DFS(int left,int index)//left为待分解的值,index为当前答案的位置
    {
        if(index==k)//递归出口
        {
            if(left>=ans[index-1])//保证答案序列递增
            {
                count_++;
                for(int i=1;i<k;i++)
                {
                    printf("%d ",ans[i]);
                }
                printf("%d\n",left);
                return;
            }
            else
                return;
        }
    
        for(int cur=ans[index-1];cur<=left;cur++)//每次都从答案上一个值的相等值开始,保证答案序列递增
        {
            ans[index]=cur; //出现新值直接覆盖实现回溯
            DFS(left-cur,index+1);
        }
    }
    
    int main()
    {
        int n;
        scanf("%d%d",&n,&k);
        
        ans[0]=1;   //DFS中每次当前答案都需从上一答案得出,故需有ans[0]=1
        DFS(n,1);
    
        printf("%d",count_);
    
        return 0;
    }
    

     

  • 核心数据结构

全局数组ans用于保存当前分解结果。


第三题  数字变换

  • 题目描述

利用变换规则,一个数可以变换成另一个数。变换规则如下:(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
  •  题目分析

由题知,每一个值在变换过程中都能产生三个新的值,所以变换对应了一颗三叉树。

求解最短的变换过程,从而就可以转化为在三叉树中通过层次遍历(BFS)寻找第一个值与答案相同的结点,并输出其所在的路径。

所以可以用一个数组来保存所有计算过程中得到的值,再用另一个数组保存每个值的前驱值,加以BFS的辅助队列,即可解决问题。

.由于树的高度根据x、y的值发生变化,本算法的时间复杂度较难估计。

算法实现

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

vector<int> result;     //保存变换过程中生成的所有结果
vector<int> former;     //保存每个结果的前驱值的下标
queue<int> assi;        //层次遍历/BFS辅助队列
stack<int> inverse;     //逆转最少变换序列所用栈

int main()
{
    int x,y;
    scanf("%d%d",&x,&y);

    result.push_back(x);    //初始化
    former.push_back(-1);
    assi.push(0);
    if(x==y)
        assi.pop();

    int ans=0;
    int count_=1;
    while(!assi.empty())
    {
        int tmp;
        tmp=assi.front();
        assi.pop();

        if(result[tmp]+1!=y)//变换1
        {
            result.push_back(result[tmp]+1);
            former.push_back(tmp);
            assi.push(count_);
            count_++;
        }
        else
        {
            ans=tmp;
            break;
        }

        if(result[tmp]*2!=y)//变换2
        {
            result.push_back(result[tmp]*2);
            former.push_back(tmp);
            assi.push(count_);
            count_++;
        }
        else
        {
            ans=tmp;
            break;
        }

        if(result[tmp]-1!=y)//变换3
        {
            result.push_back(result[tmp]-1);
            former.push_back(tmp);
            assi.push(count_);
            count_++;
        }
        else
        {
            ans=tmp;
            break;
        }
    }

    int steps=0;
    while(ans!=0)   //逆序存入变换中间值
    {
        steps++;
        inverse.push(ans);
        ans=former[ans];
    }
    if(x!=y)
        steps++;
    printf("%d\n",steps);
    while(!inverse.empty())     //输出答案
    {
        int tmp=inverse.top();
        inverse.pop();
        printf("%d ",result[tmp]);
    }
    printf("%d",y);
    return 0;
}
  • 核心数据结构

队列assi,用于完成层次遍历(BFS)。


第四题  旅行I

  • 题目描述

假期要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。

将所有城市编号为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
  •  题目分析

本题为标准的无向正权图单源最短路问题,唯一特别的是要求选出在若干最短路中经过结点最多的一条。

与此同时,并未要求输出最短路。故可把通常用于储存最短路的数组用于储存经过的结点数的最大值。

在进行松弛操作时,若当前路为最短路,则无条件更新经过结点数最大值;若与之前最短路长度相同,则需比较所经结点数并取较大者。

主要操作为Dijkstra算法,时间复杂度为O(n)。

算法实现

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

#define MAX 1e5

vector<int> graph[10001];
vector<int> weight[10001];
int visited[10001]={0};
int dist[10001];
int path[10001];


int main()
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++)
    {
        graph[i].push_back(0);
        weight[i].push_back(0);
    }
    for(int i=1;i<=m;i++)
    {
        int cur;
        scanf("%d",&cur);
        int tmp;
        scanf("%d",&tmp);
        graph[cur].push_back(tmp);
        graph[tmp].push_back(cur);
        int cost;
        scanf("%d",&cost);
        weight[cur].push_back(cost);
        weight[tmp].push_back(cost);
    }

    for(int i=1;i<=n;i++)//初始化
    {
        dist[i]=MAX;
        path[i]=0;
    }
    dist[s]=0;  //s为选择的源点

    for(int j=1;j<n;j++)//Dijkstra算法
    {
        int cur;
        int mindist=MAX;
        for(int i=1;i<=n;i++)
        {
            if(dist[i]<mindist&&visited[i]==0)
            {
                mindist=dist[i];
                cur=i;
            }
        }
        visited[cur]=1;
        for(int i=1;i<graph[cur].size();i++)
        {
            if(dist[cur]+weight[cur][i]==dist[graph[cur][i]])//=成立时,判断当前路径是否经过城市更多
            {
                if(path[graph[cur][i]]<path[cur]+1)
                    path[graph[cur][i]]=path[cur]+1;
            }
            else if(dist[cur]+weight[cur][i]<dist[graph[cur][i]])
            {
                dist[graph[cur][i]]=dist[cur]+weight[cur][i];
                path[graph[cur][i]]=path[cur]+1;
            }
        }
    }

    for(int i=1;i<=n;i++)
    {
        printf("%d",dist[i]);
        if(i!=n)
            printf(" ");
    }
    printf("\n");
    for(int i=1;i<=n;i++)
    {
        printf("%d",path[i]);
        if(i!=n)
            printf(" ");
    }
    return 0;
}
  • 核心数据结构

使用了标准模板库STL的vecotr容器来实现无向图以邻接表方式实现的存储。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值