1、集合的全排列问题
设集合 R={ r1,r2,r3 … r n},显然全排列的数目为 n!。那么如何显示集合的所有全排列呢?
``可以用递归的方式实现。
void perm(int list[],int k,int m)
{
if(k==m) //构成一次全排列
{
for(int i=0;i<m;i++)
cout<<list[i];
}
else //
{
for(int j=k;j<=m;j++)
{
swap(list[k],list[j]);//交换位置
perm(list,k+1,m);//进入下一个交换递归
swap(list[k],list[j]);//恢复现场
}
}
}
2、整数划分问题
将正整数n表示为一系列正整数之和,
n=n1+n2+n3+n4+......+nk (其中,n1>=n2>=n3>=n4........>=nk>0,k>=1)
正整数n的这种表示成为正整数n的划分。正整数n的不同划分个数成为正整数n的划分数,记作p(n)。
int spilt(int m,int n)//m的n划分个数为spilt(m,n);
{
if(m==1||n==1) return 1;
else if(m<=n) return spilt(m,m);
else if(m==n) return spilt(m,m-1)+1;
else return spilt(m,n-1)+spilt(m-n,n);//前一个式子没有n,后一个式子是对n的划分
}
3、选择问题
选择问题(selection problem)是求一个n个数列表的第k个最小元素的问题。这个数字被称为第k个顺序统计量(order statistic)。当然,对于k=1或者k=n的情况,我们可以扫描整个列表,找出最小或者最大的元素。对于其他情况,我们可以对列表进行排序,然后返回第k个元素。
select(int left,int right,int k)//初始时left=0,right=n-1
{
if(left>=right) return a[left];
int i=left;
int j=right+1;
int pivot=a[left];
while(true)
{
do(i++)
while(a[i]<pivot);
do(j--)
while(a[j]>pivot);
if(i>=j)
break;
swap(a[i],a[j]);
}
if(j-left+1==k) return pivot;
a[left]=a[j];
a[j]=pivot;
if(j-left+1<k)
return select(j+1,right,k-j+left-1);
else return select(left,j-1,k);
}
4.循环赛日程表
设有n=2k个运动员要进行网球循环赛。现要设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次;
(3)循环赛一共进行n-1天。
#include <stdio.h>
#define MAX 100
int a[MAX][MAX];
void Copy(int tox,int toy,int fromx,int fromy,int r)
{
for(int i=0;i<r;i++)
for(int j=0;j<r;j++)
a[tox+i][toy+j]=a[fromx+i][fromy+j];
}
//构造循环赛日程表,选手的数量n=2^k
void Table(int k)
{
int i,r;
int n=1<<k;
//构造正方形表格的第一行数据
for(i=0;i<n;i++)
a[0][i]=i+1;
//采用分治算法,构造整个循环赛程
for(r=1;r<n;r<<=1)
for(i=0;i<n;i+=2*r)
{
Copy(r,r+i,0,i,r);
Copy(r,i,0,r+i,r);
}
printf("参赛人数为:%d\n(第i行第j列表示和第i个选手在第j天比赛的选手序号)\n",n);
for(i=0;i<=n-1;i++)
for(r=0;r<=n-1;r++)
{
printf("%d ",a[i][r]);
if(r==n-1)
printf("\n");
}
}
int main() {
int i,r,k;
int n=2^k;
printf("比赛选手个数为n(n=2^k),请输入参数K(K>0):\n");
scanf("%d",&k);
if(k!=0)
Table(k);
return 0;
}
5.选择问题
求第k小的整数
int select(int left,int right,int k)
{
if(left>=right) return a[left];
int i=left;
int j=right;
int pivot=a[left];
while(true)
{
do{
i++;
}
while(a[i]<pivot);
do{
j--;
}
while(a[j]>pivot);
if(i>=j)
break;
swap(a[i],a[j]);
}
if(j-left+1==k) return pivot;
a[left]=a[j];
a[j]=pivot;
if(j-left+1<k)
return select(j+1,right,k-j+left-1);
else return select(left,j-1,k);
}
6.半数集问题
一、问题描述:
给定一个自然数n,由n 开始可以依次产生半数集set(n)中的数如下。
(1) n∈set(n);
(2) 在n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如,set(6)={6,16,26,126,36,136}。半数集set(6)中有6 个元素。
注意半数集是多重集。
对于给定的自然数n,计算半数集set(n)中的元素个数。
int comp(int n)
{
int ans=1;
if(a[n]>0) return a[n];
for(int i=1;i<n/2;i++)
ans+=comp(i);
a[n]=ans;
return ans;
}
7.最大字段和问题
给定由n个整数组成的序列(a1, a2, …, an),最大子段和问题要求该序列形如
的最大值(1≤i≤j≤n),当序列中所有整数均为负整数时,其最大子段和为0。例如,序列(-20, 11, -4, 13, -5, -2)的
最大子段和为
int MaxSum(int a[ ], int left, int right)
{
sum=0;
if (left= =right) { //如果序列长度为1,直接求解
if (a[left]>0) sum=a[left];
else sum=0;
}
else {
center=(left+right)/2; //划分
leftsum=MaxSum(a, left, center);
//对应情况①,递归求解
rightsum=MaxSum(a, center+1, right);
//对应情况②,递归求解
s1=0; lefts=0; //以下对应情况③,先求解s1
for (i=center; i>=left; i--)
{
lefts+=a[i];
if (lefts>s1) s1=lefts;
}
s2=0; rights=0; //再求解s2
for (j=center+1; j<=right; j++)
{
rights+=a[j];
if (rights>s2) s2=rights;
}
sum=s1+s2; //计算情况③的最大子段和
if (sum<leftsum) sum=leftsum;
//合并,在sum、leftsum和rightsum中取较大者
if (sum<rightsum) sum=rightsum;
}
return sum;
}