debugging 由于精确度问题导致答案错误?

bvhaajcl  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(131)

我正在对TSP实施贪婪方法:
1.从第一个节点开始。
1.转到最近的尚未访问的节点。(如果是多个,则转到索引最小的节点。)
1.不要忘记包括从节点1到最后访问的节点的距离。
然而,我的代码给出了错误的答案。我在Python中实现了同样的代码,Python代码给出了正确的答案。
在我的问题中,节点是二维平面上的坐标,距离是欧几里得距离。
我甚至将所有内容都改为long double,因为它更精确。
事实上,如果我颠倒for循环的顺序来反转方向,并添加一个额外的if语句来处理关系(我们希望最近节点的索引最小),它会给出一个非常不同的答案。
这是因为精度问题吗?
(Note:我必须打印floor(ans)
输入:Link
预期输出:1203406
实际输出:1200403

#include <iostream>
#include <cmath>
#include <vector>
#include <cassert>
#include <functional>

using namespace std;

int main() {
    freopen("input.txt", "r", stdin);

    int n;
    cin >> n;

    vector<pair<long double, long double>> points(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        assert(x == i + 1);

        cin >> points[i].first >> points[i].second;
    }

    // Returns the squared Euclidean Distance
    function<long double(int, int)> dis = [&](int x, int y) {
        long double ans = (points[x].first - points[y].first) * (points[x].first - points[y].first);
        ans += (points[x].second - points[y].second) * (points[x].second - points[y].second);
        return ans;
    };

    long double ans = 0;
    int last = 0;
    int cnt = n - 1;
    vector<int> taken(n, 0);
    taken[0] = 1;

    while (cnt > 0) {
        pair<long double, int> mn = {1e18, 1e9};
        for (int i = 0; i < n; ++i) {
            if (!taken[i]) {
                mn = min(mn, {dis(i, last), i});
            }
        }

        int nex = mn.second;
        taken[nex] = 1;
        cnt--;
        ans += sqrt(mn.first);
        last = nex;
    }

    ans += sqrt(dis(0, last));
    cout << ans << '\n';
    return 0;
}

UPD:Python代码:

import math

file = open("input.txt", "r")

n = int(file.readline())

a = []
for i in range(n):
    data = file.readline().split(" ")
    a.append([float(data[1]), float(data[2])])

for c in a:
    print(c)

def dis(x, y):
    cur_ans = (a[x][0] - a[y][0]) * (a[x][0] - a[y][0])
    cur_ans += (a[x][1] - a[y][1]) * (a[x][1] - a[y][1])
    cur_ans = math.sqrt(cur_ans)
    return cur_ans

ans = 0.0
last = 0
cnt = n - 1
take = []
for i in range(n):
    take.append(0)
take[0] = 1

while cnt > 0:
    idx = -1
    cur_dis = 1e18
    for i in range(n):
        if take[i] == 0:
            if dis(i, last) < cur_dis:
                cur_dis = dis(i, last)
                idx = i

    assert(idx != -1)
    take[idx] = 1
    cnt -= 1
    ans += cur_dis
    last = idx

ans += dis(0, last)

print(ans)

file.close()

# 1203406
uhry853o

uhry853o1#

是的,差异是由于舍入误差造成的,由于使用了long double,C代码产生的结果更精确。如果更改C代码,使其使用与Python * 相同的精度(IEEE-754,表示double精度)* 在这两种代码中,您会得到完全相同的舍入误差。把你的例子归结为4000点:https://godbolt.org/z/rddrdT54n
如果我在整个输入文件上运行相同的代码,我会在C和Python中得到1203406.5012708856 (不得不离线尝试,因为Godbolt很容易杀死进程)
请注意,理论上Python代码和C
代码并不完全相似,因为std::min按字典顺序比较元组和对。因此,如果有两个距离完全相等,std::min调用将选择两个索引中较小的一个。但实际上,这并没有什么区别。
现在我不认为你真的可以消除舍入误差。有一些技巧可以最小化它们。

  • 使用更高的精度(long double)是一种选择。但是这也会使你的代码变慢,这是一种折衷
  • 重新缩放点,使其相对于所有点的质心,并且单位反映问题(例如,不要用毫米、英里、公里或其他什么来思考,而是用“你的数据集的方差”来思考)你不能在你的欧几里德距离计算中摆脱数值抵消,但如果相对距离与坐标的绝对值相比很小,这种抵消通常更为严重。2下面是一个小的演示:

第一个

最后一点意见,与你的问题有点无关:将auto与lambda一起使用,即

auto dis = [&](int x, int y) {
    // ...
};

C++有许多不同种类的可调用对象 (functions,function pointer,functors,lambdas,...)std::function是一个有用的 Package 器,它可以用一个类型来表示具有相同签名的所有类型的可调用对象。(运行时多态性,所以如果你不需要std::function的类型擦除功能,只需将lambda存储在用auto声明变量中。

相关问题