【问题标题】:Program runs too slowly with large input - C程序在大输入下运行太慢 - C
【发布时间】:2015-11-23 16:23:59
【问题描述】:

该程序的目标是计算两个连续字母相同的实例数,并为每个测试用例打印该数字。输入最长可达 1,000,000 个字符(因此是保存输入的 char 数组的大小)。然而,有编码挑战的网站指出该程序在 2 秒运行时超时。我的问题是,如何优化该程序以更快地处理数据?问题是否源于大型 char 数组?

另外:对于 str[1000000] = "" 行,我收到编译器警告“赋值从指针生成整数而不进行强制转换”这是什么意思,应该如何处理?

输入: 测试用例数 大写字母 A 和 B 的字符串

输出: 每个测试用例中相邻的重复字母的数量,每个都在一个新行上。

代码:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

int main() {
    int n, c, a, results[10] = {};
    char str[1000000];
    scanf("%d", &n);
    for (c = 0; c < n; c++) {
        str[1000000] = "";
        scanf("%s", str);
        for (a = 0; a < (strlen(str)-1); a++) {
            if (str[a] == str[a+1]) { results[c] += 1; }
        }
    }
    for (c = 0; c < n; c++) {
        printf("%d\n", results[c]);    
    }

    return 0;
}

【问题讨论】:

  • 您需要以 nul 字符而不是字符串结尾:str[1000000] = '\0'; 这是数组的第 10000001 个元素,因此您需要再声明 str 一个。至于效率,请尝试将 strlen 计算为一个变量并在循环绑定中使用它,以确保它不会每次都重新计算。
  • 不要在for循环条件中使用strlen,而是先计算它并存储在变量中,然后针对该变量进行测试。
  • 你为什么要搞乱一个数组?如果您在阅读时跟踪骗子,那么您只需要知道前两个字符读取的是什么。 stdin 自己做缓冲。
  • 如果你只对两个连续的字母感兴趣,你只需要存储一个字母——你读到的最后一个。而且您不需要存储每个测试用例的结果,只需立即打印即可。
  • 不要每次循环调用strlen(str)。这使得你的循环O(n^2),因为strlen() 每次都必须搜索空字符(尽管有些编译器足够聪明来优化它)。

标签: c arrays string


【解决方案1】:

你不需要这条线

str[1000000] = "";

scanf() 在解析输入并将其写入str 时添加一个空终止符。这一行也超出了数组的末尾,因为数组的最后一个元素是str[999999]

您收到警告的原因是因为str[10000000] 的类型是char,但字符串文字的类型是char*

要加快程序速度,请取消对strlen() 的调用。

size_t len = strlen(str)-1;
for (a = 0; a < len; a++) {
    ...
}

【讨论】:

  • 注意:str[0] = 0; 是谨慎的,因为代码不会检查 scanf() 的结果。但最好检查一下它的结果。
  • 感谢您的帮助!在线编译器对我大喊大叫,因为我将 str 的类型更改为 char*。首先是什么让 char* 如此不同?它指向内存中的位置而不是变量本身的事实?此外,取出字符串长度测试就像一个魅力。我相信这些知识将在未来优化方面对我有很大帮助(不仅仅是 strlen 调用)。
  • @JoeyGrant 字符串是一个字符数组。 str 是一个字符串,"" 也是。 str[1000000] 是该字符串中的一个字符。如果你把它改成char *str[1000000],那就是一个指针数组,而不是一个字符串。
【解决方案2】:
str[1000000] = "";

这并没有像你认为的那样做,你正在溢出缓冲区,导致undefined behaviour。索引器的范围是 0 - sizeof(str) EXCLUSIVE。所以你要么添加一个 初始化时为 1000000 或使用 999999 来访问它。要摆脱编译器警告并生成更清晰的代码,请使用:

str[1000000] = '\0';

或者

str[999999] = '\0';

取决于你做了什么来修复它。

至于优化,您应该查看程序集并从那里开始。

【讨论】:

  • scanf() 不需要缓冲区为空终止符,它会自己添加空终止符。
  • @Barmar 详细信息:scanf("%s", str) 仅在保存一些非空格(可能)并且没有发生罕见的输入错误时添加空字符。所以确保str[0] = '\0';被初始化并不是那么糟糕。
【解决方案3】:

计算两个连续字母相同的实例数并为每个测试用例打印此数字

为了提高效率,代码需要@john bollinger & @molbdnilo 建议的新方法

void ReportPairs(const char *str, size_t n) {
  int previous = EOF;
  unsigned long repeat = 0;
  for (size_t i=0; i<n; i++) {
    int ch = (unsigned char) str[i];
    if (isalpha(ch) && ch == previous) {       
      repeat++;
    }
    previous = ch; 
  }
  printf("Pair count %lu\n", repeat);
} 

char *testcase1 = "test1122a33";
ReportPairs(testcase1, strlen(testcase1));

或直接来自输入和“每个测试用例,每个都在新的一行。”

int ReportPairs2(FILE *inf) {
  int previous = EOF;
  unsigned long repeat = 0;
  int ch;
  for ((ch = fgetc(inf)) != '\n') {
    if (ch == EOF) return ch;
    if (isalpha(ch) && ch == previous) {       
      repeat++;
    }
    previous = ch; 
  }
  printf("Pair count %lu\n", repeat);
  return ch;
} 

while (ReportPairs2(stdin) != EOF);

不清楚 OP 如何将“AAAA”计为 2 或 3。此代码将其计为 3。

【讨论】:

    【解决方案4】:

    显着提高代码运行时间的一种方法是限制从stdin 读取的次数。 (基本上以更大的块处理输入)。您可以通过多种方式执行此操作,但最有效的方法之一可能是使用fread。即使读取 8 字节 块也可以比一次读取一个字符提供很大的改进。仅考虑大写字母 [A-Z] 的此类实现的一个示例是:

    #include <stdio.h>
    
    #define RSIZE 8
    
    int main (void) {
    
        char qword[RSIZE] = {0};
        char last = 0;
        size_t i = 0;
        size_t nchr = 0;
        size_t dcount = 0;
    
        /* read up to 8-bytes at a time */
        while ((nchr = fread (qword, sizeof *qword, RSIZE, stdin)))
        {   /* compare each byte to byte before */
            for (i = 1; i < nchr && qword[i] && qword[i] != '\n'; i++)
            {   /* if not [A-Z] continue, else compare */
                if (qword[i-1] < 'A' || qword[i-1] > 'Z') continue;
                if (i == 1 && last == qword[i-1]) dcount++;
                if (qword[i-1] == qword[i]) dcount++;
            }
            last = qword[i-1];  /* save last for comparison w/next */
        }
    
        printf ("\n sequential duplicated characters [A-Z] : %zu\n\n",
                dcount);
    
        return 0;
    }
    

    868789 个字符的输出/时间

    $ time ./bin/find_dup_digits <dat/d434839c-d-input-d4340a6.txt
    
     sequential duplicated characters [A-Z] : 434893
    
    
    real    0m0.024s
    user    0m0.017s
    sys     0m0.005s
    

    注意:该字符串实际上是 '0's'1's 的字符串,使用 if (qword[i-1] &lt; '0' || qword[i-1] &gt; '9') continue; 的修改测试而不是 [A-Z]...continue 的测试运行,但您的结果与 @987654330 @ 和 'B's 应该几乎相同。 1000000 仍将显着低于 0.1 秒。您可以使用RSIZE 值来查看读取更大(建议的“2 的幂”)大小的字符是否有任何好处。 (注意: 这会将 AAAA 视为 3)希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-07
      • 1970-01-01
      • 2014-08-19
      • 2017-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多