一、设计题目
★问题描述:
设x1,x2,…,xn是实直线上的n个点。用固定长度的闭区间覆盖这n个点,至少需要多少个这样的固定长度闭区间?设计解此问题的有效算法,并证明算法的正确性。
★算法设计:
对于给定的实直线上的n个点和闭区间的长度k,计算覆盖点集的最少区间数。
★数据输入:
由文件input.txt给出输入数据。第1行有2个正整数n和k,表示有n个点,且固定长度闭区间的长度为k,接下来的1行中,有n个整数,表示n个点在实直线上的坐标(可能相同)。
★结果输出:
输入文件示例 输出文件示例
input.txt output.txt
7 3 3
1 2 3 4 5 -2 6
二、程序功能简介
得到n个数值表示直线上的点,其中n个数值可能会相同,用一个固定长度为k的区间将这n个数值的点覆盖,求解覆盖所有点所需要的区间数目。
三、主体内容
3.1设计分析
从题目以及测试数据可以得到大概思路:先将n个点进行从小到大排序,并放置到x轴上,然后问题就可以转换为用长度为k的区间覆盖x轴上的所有点。如下图1所示:
图1
在区间覆盖的时候,存在着许多种情况,但为了保证所用区间数目尽量少,那么就要尽量保证区间端点与被覆盖点重合。那么,就可以假设第一个覆盖区间的端点为n个点中的最左边Xmin,然后在第一个区间之后继续寻找被覆盖点,作为第二个覆盖区间端点,这样直到n个点中最右边Xmax也被区间覆盖,结束运算。如图2-1、2-2、2-3:
图2-1
图2-2
图2-3
3.2程序结构
![](https://img-blog.csdnimg.cn/20190112130950314.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190112130950388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
3.3各模块的功能及程序说明
模块1:从文件中获取数据
/**********************************/
int n, k;
ifstream infile("input.txt");
infile>>n;
infile>>k;
int *num=new int[n];
for(int i=0; i<n; i++)
infile>>num[i];
程序说明:读入文件"input.txt"需与源代码文件在同一个文件夹目录下
/************************************/
模块2:调用函数并将函数返回值写入输出文件
/************************************/
ofstream outfile("output.txt");
outfile<<minCover(num, n, k);
程序说明:写入文件"output.txt"需与源代码文件在同一个文件夹目录下
/************************************/
模块3:函数体调用
/************************************/
int minCover(int num[], int n, int k)
程序说明:minCover()函数计算结果并将结果以函数返回值形式返回到主函数
/************************************/
模块4:核心算法
/************************************/
for(int i=0; i<n; i++)
if(num[i]-tmp > k)
{
res++;
tmp=num[i];
}
return res+1;
程序说明:当遍历到当前点与tmp起点位置的距离大于区间长度k时,更新起点tmp,并且在返回时+1,因为最后的退出循环时,i=n点时的区间没在for循环里面计算。
/************************************/
3.4源程序
/******************************************************
题目: 设x1,x2,…,xn是实直线上的n个点。
用固定长度的闭区间覆盖这n个点,至少需要多少个这样的固定长度闭区间?
设计解此问题的有效算法,并证明算法的正确性
******************************************************/
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
//minCOver()函数计算结果,其中num[]存储读取的数据
int minCover(int num[], int n, int k)
{
int res=0, tmp; //1.定义两个变量res和tmp,res记录区间数目,tmp作为中间值存储每次检索区间时的起点
sort(num, num+n); //2.先利用sort()快排函数将数组从小到大排练
tmp=num[0]; //3.先将最小数作为区间覆盖的第一个点
for(int i=0; i<n; i++) //4.遍历每一个点
if(num[i]-tmp > k) //5.当遍历到当前点与tmp起点位置的距离大于区间长度k时
{
res++; //6.当遍历到当前点与tmp起点位置的距离大于区间长度k时,区间数目+1
tmp=num[i]; //7.更新起点tmp
}
return res+1; //8."+1"解释: 因为最后的退出循环时,i=n点时的区间没在for循环里面计算
}
int main()
{
//1.从input.txt文件里面获取数据
int n, k; //1.1 定义n和k,n表示n个点,k表示固定长度为k
ifstream infile("input.txt"); //1.2 定义一个输入文件流,以输入方式打开文件"input.txt"
infile>>n; //1.3 从文件"input.txt"里面读取第一个数据作为n
infile>>k; //1.4 从文件"input.txt"里面读取第二个数据作为k
int *num=new int[n]; //1.5 动态创建一个大小为n的数组
for(int i=0; i<n; i++) //1.6 循环文件"input.txt"里面的数据
infile>>num[i]; //1.7 逐一读取文件"input.txt"里面的数据
//2.将结果存进output.txt文件
ofstream outfile("output.txt"); //2.1 定义输出文件流对象,打开文件"output.txt"
outfile<<minCover(num, n, k); //2.2 将函数minCover()的返回结果存进文件"output.txt"
//3.关闭文件流
infile.close(); //3.1 关闭输入文件流
outfile.close(); //3.2 关闭输出文件流
return 0;
}
3.5运行结果截图
测试数据 | 输入文件 (input.txt) | 输出文件 (output.txt) |
1 | 7 3 1 2 3 4 5 -2 6 | 3 |
2 | 4 2 -6 -3 0 3 | 4 |
3 | 10 2 10 5 3 6 9 2 -1 5 6 7 | 4 |
![](https://img-blog.csdnimg.cn/20190112130950323.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190112130950321.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190112130950324.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
3.6设计体会
当我看到题目的时候,感觉题目很简单,有大概思路,但由于考虑到的结果太多,不知道如何进行选择时,就一直卡在那里。因为当区间覆盖最左边的点时,假设区间长度为3,那么长度为3的区间可以将最左边的点覆盖在最左边、也可以是最右边抑或中间。这是长度为3的情况。假如长度为4,那么情况就更多了……所以这个问题一直让我对题目无从下手。最后只能上网参考一下别人的代码,发现是将最左边的点覆盖在最左边是最优的情况,得到思路后就比较容易地写出代码了。
但在证明过程中,由于网上的内容看得更加太深奥了,假如以网上的说法来编写证明过程,这显然是很难符合要求的,所以我就根据自己的理解和想法,把证明以另一种方式来描述。
最后对实训期间所做的内容一个总结:就是让我认识到自己算法能力的薄弱,希望在接下来的大学时光里能够不要落下算法,因为算法是程序的核心。
四、附录
4.1程序流程图
![](https://img-blog.csdnimg.cn/20190112130950386.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190112130950325.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RZRl93aW5k,size_16,color_FFFFFF,t_70)
4.2主要过程列表
如图4.1-1,在main()函数里面获取到n、k、num[]数组内容,然后调用函数minCover()计算结果,将结果以函数返回值返回到main()函数,并写入到输出文件"output.txt"。其中,函数代码逻辑图,如图4.1-2所示,从最左边开始遍历到最右边,当遍历到一个点与tmp值的距离大于固定区间长度时,区间数目+1,直到达到最右边的数值时,并且在退出循环后,res需执行+1操作,因为最后的退出循环时,i=n点时的区间没在for循环里面计算,最后返回结果到main()函数。
4.3程序中的主要变量、函数
int n; // n表示n个点
int k; // k表示固定长度为k
int *num=new int[n]; //动态创建一个大小为n的数组,存储n个点
ifstream infile("input.txt"); //定义一个输入文件流,以输入方式打开文件"input.txt"
ofstream outfile("output.txt"); //定义输出文件流对象,打开文件"output.txt"
int res=0; // res记录区间数目
int tmp; // tmp作为中间值存储每次检索区间时的起点