You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言实现渗流蒙特卡洛模拟结果与预期不符排查

问题背景

我正在学习Coursera平台开设的普林斯顿大学算法课程,课程教学使用Java语言,我选择使用自己更熟悉的C语言跟随练习。其中一项作业要求编写程序,通过*Monte Carlo simulation(蒙特卡洛模拟)*估算渗流阈值,作业规范地址:作业规范页。
我已经完成了全部代码编写,但程序输出和课程提供的参考实现结果不一致(结果并非完全错误,但存在明显偏差),运行结果对比如下:

运行结果对比

课程参考实现运行结果

~/Desktop/percolation> java-algs4 PercolationStats 200 100
mean                    = 0.5929934999999997
stddev                  = 0.00876990421552567
95% confidence interval = [0.5912745987737567, 0.5947124012262428]

~/Desktop/percolation> java-algs4 PercolationStats 2 100000
mean                    = 0.6669475
stddev                  = 0.11775205263262094
95% confidence interval = [0.666217665216461, 0.6676773347835391]

个人实现运行结果

~/percolation> ./percolation_stats 200 100
mean                    = 0.628564
stddev                  = 0.206286
95% confidence interval = [0.588132, 0.668996]

~/percolation> ./percolation_stats 2 100000
mean                    = 0.728548
stddev                  = 0.189745
95% confidence interval = [0.727371, 0.729724]

完整代码

percolation_stats.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "percolation.h"

double mean(double *, int);
double stddev(double *, int);
double confidencelo(double *, int);
double confidencehi(double *, int);

int main(int argc, char **argv) {
    int n, T, row, col;
    percolation *p;
    double *samp;

    srand((unsigned) time(NULL));
    sscanf(argv[1], "%d", &n);
    sscanf(argv[2], "%d", &T);
    samp = malloc(T * sizeof *samp);
    for (int i = 0; i < T; ++i) {
        p = creategrid(n);
        while (!percolates(p)) {
            do {
                row = rand() % n + 1;
                col = rand() % n + 1;
            } while (is_open(p, row, col));
            open(p, row, col);
        }
        samp[i] = (double) number_of_open_sites(p) / (n * n);
    }
    printf("mean                    = %g\n", mean(samp, T));
    printf("stddev                  = %g\n", stddev(samp, T));
    printf("95%% confidence interval = [%g, %g]\n", confidencelo(samp, T),
           confidencehi(samp, T));
    return 0;
}

// mean: sample mean of percolation threshold
double mean(double *a, int len) {
    double sum = 0.0;

    for (int i = 0; i < len; ++i) {
        sum += a[i];
    }
    return sum / len;
}

// stddev: sample standard deviation of percolation threshold
double stddev(double *a, int len) {
    double mean(double *, int);
    double sum = 0.0;
    double avg = mean(a, len);

    for (int i = 0; i < len; ++i) {
        sum += (a[i] - avg) * (a[i] - avg);
    }
    return sqrt(sum / (len - 1));
}

// confidencelo: low endpoint of 95% confidence interval
double confidencelo(double *a, int len) {
    double mean(double *, int);
    double stddev(double *, int);

    return mean(a, len) - (1.96 * stddev(a, len)) / sqrt(len);
}

// confidencehi: high endpoint of 95% confidence interval
double confidencehi(double *a, int len) {
    double mean(double *, int);
    double stddev(double *, int);

    return mean(a, len) + (1.96 * stddev(a, len)) / sqrt(len);
}

percolation.h

#include <stdbool.h>

typedef struct percolation percolation;

percolation *creategrid(int);
void open(percolation *, int, int);
bool is_open(percolation *, int, int);
bool is_full(percolation *, int, int);
int number_of_open_sites(percolation *);
bool percolates(percolation *);

percolation.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "quick_union.h"

#define pos(p, x, y) (((x) - 1) * (p)->width + ((y) - 1))

typedef struct percolation {
    int width;
    int open_sites;
    UF *uf;
    bool *open;
} percolation;

// creategrid: creates n-by-n grid, with all sites initially blocked
percolation *creategrid(int n) {
    if (n <= 0) {
        fprintf(stderr, "open: illegal argument\n");
        exit(1);
    }
    percolation *p;

    p = malloc(sizeof *p);    
    p->width = n;
    p->open_sites = 0;
    p->uf = createuf(n * n + 2);
    p->open = malloc(n * n * sizeof *p->open);
    for (int i = 0; i < n * n; ++i) {
        p->open[i] = false;
    }
    for (int i = 0; i < n; ++i) {
        connect(p->uf, n * n, pos(p, 1, i));
        connect(p->uf, n * n + 1, pos(p, n, i));
    }
    return p;
}

// open: opens the site (row, col) if it is not open already
void open(percolation *p, int row, int col) {
    if (row < 1 || row > p->width || col < 1 || col > p->width) {
        fprintf(stderr, "open: illegal argument\n");
        exit(1);
    }
    if (p->open[pos(p, row, col)]) {
        return;
    }
    p->open[pos(p, row, col)] = true;
    ++p->open_sites;
    if (col > 1 && p->open[pos(p, row, col - 1)]) {
        connect(p->uf, pos(p, row, col), pos(p, row, col - 1));
    }
    if (col < p->width && p->open[pos(p, row, col + 1)]) {
        connect(p->uf, pos(p, row, col), pos(p, row, col + 1));
    }
    if (row > 1 && p->open[pos(p, row - 1, col)]) {
        connect(p->uf, pos(p, row, col), pos(p, row - 1, col));
    }
    if (row < p->width && p->open[pos(p, row + 1, col)]) {
        connect(p->uf, pos(p, row, col), pos(p, row + 1, col));
    }
}

// is_open: is the site (row, col) open?
bool is_open(percolation *p, int row, int col) {
    if (row < 1 || row > p->width || col < 1 || col > p->width) {
        fprintf(stderr, "is_open: illegal argument\n");
        exit(1);
    }
    return p->open[pos(p, row, col)];
}

// is_full: is the site (row, col) full?
bool is_full(percolation *p, int row, int col) {
    if (row < 1 || row > p->width || col < 1 || col > p->width) {
        fprintf(stderr, "is_full: illegal argument\n");
        exit(1);
    }
    return !p->open[pos(p, row, col)];
}

// number_of_open_sites: returns the number of open sites
int number_of_open_sites(percolation *p) {
    return p->open_sites;
}

// percolates: does the system percolate?
bool percolates(percolation *p) {
    return connected(p->uf, p->width * p->width, p->width + p->width + 1);
}

quick_union.h

#include <stdbool.h>

typedef struct UF UF;

UF *createuf(int);
void connect(UF *, int, int);
bool connected(UF *, int, int);
int find(UF *, int);

quick_union.c

#include <stdlib.h>
#include <stdbool.h>

#define max(a, b) (((a) > (b)) ? (a) : (b))

typedef struct UF {
    int count;
    int *id;
    int *sz;
    int *largest;
} UF;

// createuf: return pointer to UF with n elements
UF *createuf(int n) {
    UF *uf;

    uf = malloc(sizeof *uf);
    uf->count = n;
    uf->id = malloc(n * sizeof *uf->id);
    uf->sz = malloc(n * sizeof *uf->sz);
    uf->largest = malloc(n * sizeof *uf->largest);
    for (int i = 0; i < n; ++i) {
        uf->id[i] = uf->largest[i] = i;
        uf->sz[i] = 1;
    }
    return uf;
}

// connect: connect elements p and q
void connect(UF *uf, int p, int q) {
    int root(UF *, int);
    int i = root(uf, p);
    int j = root(uf, q);

    if (i == j) {
        return;
    }
    if (uf->sz[i] <= uf->sz[j]) {
        uf->id[i] = j;
        uf->sz[j] += uf->sz[i];
        uf->largest[j] = max(uf->largest[i], uf->largest[j]);
    } else {
        uf->id[j] = i;
        uf->sz[i] += uf->sz[j];
        uf->largest[i] = max(uf->largest[j], uf->largest[i]);
    }
}

// connected: return true if elements p and q are connected
bool connected(UF *uf, int p, int q) {
    int root(UF *, int);

    return root(uf, p) == root(uf, q);
}

// find: return largest element in i's connected component
int find(UF *uf, int i) {
    int root(UF *, int);

    return uf->largest[root(uf, i)];
}

// root: return root element of i
int root(UF *uf, int i) {
    while (i != uf->id[i]) {
        uf->id[i] = uf->id[uf->id[i]];
        i = uf->id[i];
    }
    return i;
}
错误定位

代码共有4处核心错误,直接导致结果偏差:

  1. creategrid中pos宏传参错误,引发内存越界+连通逻辑混乱
    pos宏的行、列参数要求是1-based(从1开始计数),但初始化连接虚拟节点的循环中,列参数直接传了0-based的循环变量i:
  • i=0时列参数为0,计算出的索引为-1,connect操作越界写入堆内存,破坏程序数据
  • 所有顶行、底行的连接整体偏移一列,最后一列格子未被连接,底虚拟节点还被错误连到顶行格子,完全打乱连通性判断。
    正确写法为列参数传i+1:
for (int i = 0; i < n; ++i) {
    connect(p->uf, n * n, pos(p, 1, i+1));
    connect(p->uf, n * n + 1, pos(p, n, i+1));
}
  1. percolates函数底部虚拟节点索引计算错误
    定义的顶部虚拟节点索引为n*n,底部虚拟节点索引为n*n+1,但函数中错误写为p->width + p->width +1,n>2时该值和真实索引完全不匹配(如n=200时真实索引为40001,错误值为401,对应网格内普通格子),导致需要打开远多于正常值的格子才能触发渗流判断,均值偏高。
    正确写法:
bool percolates(percolation *p) {
    return connected(p->uf, p->width * p->width, p->width * p->width + 1);
}
  1. 初始化阶段提前连接所有顶/底行格子引发回流问题
    初始化时不管格子是否打开,就将所有顶行格子连到顶部虚拟节点、底行格子连到底部虚拟节点。Union-Find不区分格子打开状态,初始化连接后同一行格子会通过虚拟节点提前连通,还会出现回流问题:底部连通的格子通过虚拟节点反向连通到未和顶部连通的区域,导致判断失真。
    正确逻辑是初始化时不提前连接任何格子到虚拟节点,仅在格子被open时,如果它在第一行则连接顶部虚拟节点,在最后一行则连接底部虚拟节点。

  2. is_full函数实现不符合定义
    按照作业要求,full指「打开的格子能够连通到顶部」,当前实现将未打开的格子判定为full,和定义完全相反。正确实现需要先判断格子是否打开,再判断是否和顶部虚拟节点连通。


内容的提问来源于stack exchange,提问作者vim_overlord

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:27:24