C/C++——排列算法

C/C++——排列算法

题目描述:

        (排列数)输入两个正整数 𝑛,𝑚(1≤𝑛≤20,1≤𝑚≤𝑛)n,m(1≤n≤20,1≤m≤n),在 1∼𝑛1∼n 中任取 𝑚m 个数,按字典序从小到大输出所有这样的排列。例如:

        输入:3 2

        输出:1 2 1 3 2 1 2 3 3 1 3 2

题目分析:

        偶然间看到这道NOIP 2012提高组初赛的试题,觉得还挺有趣的。之前一直都是做全排列的题,这次看到排列的题还一下子没转过来。其实这道题目可以理解为有n个编号为1~n的小球需要放在m个编号为1~m的盒子里,每个盒子只能放一个小球,让你把所有可能的结果按照字典序从小到大输出。其中n>=m。就是求A_{n}^{m}

        因为A_{n}^{m}=\frac{n!}{(n-m)!},所以这道题的时间复杂度就是O(n!),所以n和m的值不会太大。因为要输出所有的情况,所以可以直接暴力。

算法1

        一开始我选择的是直接从字典序最小的时候开始遍历,时间复杂度变为O(nm),这比O(n!)还要大。

# include "stdio.h"
# include "time.h"

# define maxsize 25

int n, m;
long long count = 0;
int visited[maxsize]={0}, arr[maxsize]={0};

void permutation(int index)
{
	for(int i=1;i<=n;i++)
	{
		if(visited[i] == 0)
		{
			if(index<m)
			{
				visited[i] = 1;
				arr[index] = i;
				permutation(index+1);
				visited[i] = 0;
			}
			else if(index == m)
			{
				arr[index] = i;
//				for(int j = 1;j<=m;j++)
//					printf("%d ", arr[j]);
				count++;
//				printf("\n");
			}
					
		}	
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	int index = 1;
	clock_t start, end;
    double cpu_time_used;
	
	start = clock();  // 开始计时
    permutation(index);
    end = clock();  // 结束计时
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;  // 计算时间
    printf("程序执行时间为: %f seconds\n", cpu_time_used);
    
	printf("%lld", count);
	return 0;
}

算法2

        上面的算法时间复杂度会变成O(nm)是因为在我已经选择了一个小球也就是选择了一个编号之后,我并没有将其剔出待选集,也就少了一个筛选操作,这其实并没有对整个过程的正确模拟。

        所以我想,可不可以增加一个筛选的操作但又不增加时间复杂度呢。这让我想起了单调队列优化,可以将所有小球当作一个有序链表,如果已经放了一个小球,直接用链表的删除操作删除对应节点,而删除操作是不增加时间复杂度的。想法可行,直接开始优化。

# include "stdio.h"
# include "stdlib.h"
# include "time.h"

# define maxsize 25

int n, m;
long long count = 0;
int visited[maxsize]={0}, arr[maxsize]={0};

typedef struct List{
	int num;
	struct List *next;
}List, *L;

L head=NULL;

void create(L &head)
{
	L p, q;
	head = (L)malloc(sizeof(List));
	head->next = NULL;
	q = head;
	for(int i =1;i<=n;i++)
	{
		p = (L)malloc(sizeof(List));
		p->next = NULL;
		p->num = i;
		q->next = p;
		q = p;
	}
}

void push(L &p, L &q)
{
	q->next = p->next;
	p->next = q;
}

void pop(L &p, L &q)
{
	p->next = q->next;
	q->next = NULL;
}

void permutation(int index)
{
	L p = head, q = head->next;
	int i = 0;
	
	while(q)
	{
		i = q->num;
		if(index < m)
		{
			pop(p, q);
			arr[index] = i;
			permutation(index+1);
			push(p ,q);
		}
		else if(index == m)
		{
			arr[index] = i;
//			for(int j = 1;j<=m;j++)
//				printf("%d ", arr[j]);
			count++;
//			printf("\n");
		}			

		p = q;
		q = q->next;
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	int index = 1;
	clock_t start, end;
    double cpu_time_used;
	
	create(head);
	
	start = clock();  // 开始计时
    permutation(index);
    end = clock();  // 结束计时
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;  // 计算时间
    printf("程序执行时间为: %f seconds\n", cpu_time_used);
	
	
	printf("%lld", count);
	return 0;
}

结果分析:

        算法优化完成后并在运行函数前后加入时间获取,观察一下优化效果。

        当输入20 6时,程序运行27907200次,未优化前耗时0.104秒,优化后只用了0.072秒,大约提升了大约0.032/0.104≈30.8%的效率。

        当输入20 7时,程序运行390700800次,未优化前耗时1.654秒,优化后只用了1.062秒,大约提升了大约0.592/1.654≈35.8%的效率。

        可以发现,当运行次数越多,提升的效率越多,符合O(nm)与O(n!)之间差距递增的趋势,说明优化有效。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值