1.题目
设有n=2^k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能参赛一次;
(3)循环赛在n-1天内结束
2.问题分析
按分治策略,将所有的选手分为两半,
n个选手的比赛日程表就可以通过为
n/2个选手设计的比赛日程表来决定。
递归地用对选手进行分割,直到只剩下1个选手时,比赛日程表则不再安排
3.什么是分治
分治的核心思想就是:递归(分解)+ 合并:
递归分解:将原问题(大问题)分解成和原问题相似的子问题(小问题)。递归分解首先需要明确的就是递归函数的定义(一般和题目给出的函数类似)是什么,先不用管此时函数的内部是怎么实现的,明白函数的定义也就知道了我们需要给函数传递的参数是什么,而这个参数一般就是我们将原问题划分为子问题的依据。拿后面要讲解的归并排序和LeetCode 395.至少有K个重复字符的最长子串来举例:对于归并排序,假设我们有待排序序列:[a, b , c, d, e, f],很自然就能想到需要定义一个能够对任意长度的待排序序列进行排序的函数,此时的参数就是任意长度的待排序序列,因此将原问题分解就可以通过将待排序序列不断分解为更短的子序列;同样对于至少有K个重复字符的最长子串来说,我们需要求出某个字符串中至少包含K个重复字符的最长子串,我们就可以定义递归函数来做这个事情,于是就有参数任意长度的字符串,所以我们分解原问题也就是将字符串分解不同的子串。
合并:递归函数会对每个子问题求解出一个子解,合并就是将各个子问题的答案进行合并,求出原问题的答案,注意合并的形式可能是对各子问题的子解求最大值/最小值,也可能是将各子解合并在一起,需要根据具体题目进行分析。
分治算法所能解决的问题一般具有以下几个特征:
⑴原问题的规模缩小到一定的程度就可以很容易地解决
⑵原问题可以分解为若干个规模较小的相同问题,即原问题具有最优子结构性质
⑶利用原问题分解出的子问题的解可以合并为原问题的解
⑷原问题分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题(这条特征涉及到分治法的效率,如果各个子问题不独立,也就是子问题划分有重合部分,则分治法要重复的求解1公共子问题的解,此时虽然也可用分治法,但采用动态规划更好)
4.算法实现思路
1.对表进行分析
2.对表的实现
1.递归
2.循环
5算法实现代码
1.递归
#include<iostream>
#include<cmath>
using namespace std;
int a[100][100];
void table(int k, int d)
// 边长 步长
{
if (k == d)
return;
int i, j;
for (i = 0; i < d; i++)
{
for (j = 0; j < d; j++)
{
a[i + d][j + d] = a[i][j];
a[i][j + d] = a[i][j] + d;
a[i + d][j] = a[i][j] + d;
}
}
table(k, d * 2);
}
int main()
{
//输入人数
int n;
cout << "学生人数k=2^n,请输入k:";
int k;
cin >> n;
k = pow(2, n);
//判断只有一个人时
if (k == 1)
a[0][0] = 0;
else
a[0][0] = 1;
//递归
table(k, 1);
//输出
for (int i = 0; i < k; i++)
{
for (int j = 0; j < k; j++)
{
cout << a[i][j]<<' ';
}
cout << endl;
}
}
2.循环
#include<iostream>
#include<cmath>
#define N 50
using namespace std;
int a[N][N];
void Table(int k);
void print(int k);
int main()
{
int k;
cout << "\t\t****************************************\n";
cout << "\t\t**\t\t循环赛日程表 **\n";
cout << "\t\t****************************************\n\n";
cout << "设参赛选手的人数为n(n=2^k),请输入k 的值:";
do
{
cin >> k;
if (k != 0)
{
Table(k);
print(k);
}
else
cout << "您输入的数据有误,请重新输入!" << endl;
} while (k != 0);
}
void Table(int k)
{
int n = 1;//数组下标从1开始
for (int i = 1; i <= k; i++)
n *= 2;//求总人数
for (int i = 1; i <= n; i++)
a[1][i] = i;//初始化,第一行等于1--8
int m = 1;//填充起始位置(用来控制每一次填表时i行j列的起始填充位置)
for (int s = 1; s <= k; s++)//总共循环k次(s指对称赋值的总循环次数,即分成几大步进行制作日程表)
{
n = n / 2;
for (int t = 1; t <= n; t++)//分的块数(t指明内部对称赋值的循环次数)
{
for (int i = m + 1; i <= 2 * m; i++)
for (int j = m + 1; j <= 2 * m; j++)
{
a[i][j + (t - 1) * m * 2] = a[i - m][j + (t - 1) * m * 2 - m];//右上角=左下角
a[i][j + (t - 1) * m * 2 - m] = a[i - m][j + (t - 1) * m * 2];//右下角=左上角
}
}
m *= 2;//更新填充起始位置
}
}
void print(int k)
{
int i, j;
int n = pow(2, k);
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
cout << a[i][j] << ' ';
}
cout << "\n";
}
}
6.时间\空间复杂度
1.递归
1.空间复杂度
运行算法时,所用的数组是全局变量,所用空间不随着某个 变量的改变而变化,是一个常数,所以空间复杂度为O(1)文章来源:https://www.toymoban.com/news/detail-418677.html
2.时间复杂度
(2)时间复杂度:
n=2^k
递归: 先打印21矩阵,然后递归打印22矩阵,递归k次
打印时划分成四个区域,左上角区域是已完成区域(d^2)
F=O(12+22+42+……(2(n-1))^2)
F=O(20+22+24+26+……+2^(2n-2))
所以时间复杂度是O(2(2k))=O(n2)
2.循环
1.空间复杂度
运行算法时,所用的数组是全局变量,所用空间不随着某个 变量的改变而变化,是一个常数,所以空间复杂度为O(1)
2.时间复杂度
总人数:n=2^k k:输入值
分治:先打印一行,然后分成n/2份,然后进行填充,接着分成n/4份,以此类推(2(k-1)+……20),然后填充每一小块 m^2,F=O( ((2(k-1))*(12) + (2(k-2))(22) +……+ (20)*(2(k-1))2)),所以时间复杂度是O(2(2k))=O(n^2)文章来源地址https://www.toymoban.com/news/detail-418677.html
到了这里,关于循环赛日程表 (递归与分治)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!