C++算法之排列枚举

C++算法之排列枚举



一、介绍

枚举排列时,我们考虑了如下几项内容:

  • 排列的表示形式:数组。

  • 排序的大小:按照字典序大小规定。

字典序,又叫字母序,是规定两个序列比较大小的一种方式。其规则是对于两个序列ab

从第一个字母开始比较,如果在第i个位置满足,i没有超过两个序列的长度,小于i处两个序列对应位置的元素都相等,而第i位两个序列对应位置元素不等的话,则若a[i] < b[i],那么序列a小于序列b,否则序列b小于序列a。
若从第一个位置直到其中一个序列的最后一个位置都相等的话,则比较ab的长度,若a的长度小于b,则序列a小于序列b(此时ab的前缀),而如果b序列的长度小于a,那么序列b小于序列a
若两个序列长度相等,并且所有元素相等,则序列a等于序列b。 举例:

abc < bbc	// 因为第一个字母a < b
ab < abc	// 因为两个串前面所有对应位置字母相同,但第一个串长度小于第二个串
ac > abb	// 因为第二个字母c > b

生成下一个排列的方式:调用STL中的next_permutation函数。

二、取宝石问题

假设在一个大房间有n个宝石,每一处宝石用一个坐标(x, y)表示。如果你从任意一处宝石的地方出发,依次经过每个放宝石的地方并取走宝石,最终要求回到出发地点,问最短需要走的距离是多少。

在这个情境里,经过不同地点的顺序会改变最终的行走距离。所以,我们要枚举的就是经过1~n一共n个位置的顺序。

next_permutation函数解决“取宝石问题”,因为要用枚举法解决第一个问题,所以,代入到题目的情境中,我们可以设计如下算法:

1、枚举所有n个点的排列
2、维护最短距离。检查新枚举的排列产生的行走距离是否比之前的最短距离还短。如果短,就更新答案。

三、代码实现

代码如下(示例):

#include <bits/stdc++.h>
#define N 15
using namespace std;
int n, id[N];
double x[N], y[N];

// 求两个点(x_1, y_1)和(x_2, y_2)之间的直线距离
double dis(double x_1, double y_1, double x_2, double y_2) {
    double dx = x_1 - x_2;
    double dy = y_1 - y_2;
    return sqrt(dx * dx + dy * dy);
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> x[i] >> y[i];
        id[i] = i;	// 因为我们枚举标号的排列,所以要将标号存进数组里
    }
    
    double ans = -1;    // 因为最开始ans中没有值,所以我们可以将其设置为一个不合法的值	
    // 用do...while循环是为了防止第一次调用时数组id中的值已经被重排
    // 所以会导致标号为1, 2, ..., n的排列没有被计算。
    do {
        // 求解按照id[1], id[2], ..., id[n], id[1]作为行走路线的总距离。
        double cur = dis(x[id[1]], y[id[1]], x[id[n]], y[id[n]]);
        for (int i = 1; i < n; ++i)
            cur += dis(x[id[i]], y[id[i]], x[id[i + 1]], y[id[i + 1]]);
        
        // 如果当前路线的总距离小于之前最优解,就更新。
        if (ans < 0 || cur < ans) ans = cur;
    } while (next_permutation(id + 1, id + n + 1));	
    
    // 输出答案,这里因为是浮点数,所以我们设置精度为4。
    cout << setprecision(4) << ans << endl;
    return 0;
}

四、复杂度分析

使用next_permutation函数枚举排列代码的复杂度分析:do while 循环的循环次数,也就是长度为n的排列个数为n!。调用next_permutation函数一次的复杂度为O(n),所以枚举排列的复杂度为O(n!×n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值