使用 for 循环内的开关搜索字符串效率低下吗?
Is searching a string using a switch inside a for loop inefficient?
最近做了一个在线编程评估,其中的问题本质上是“搜索指定整数的数字并递增计数”。
我的解决方案是将给定的数字转换为字符串,然后使用 for 循环和 switch 语句搜索字符串。
所以基本上:
for (int i = 0; i < str.length(); i++;)
{
switch(str[i]) {
case '1':
count++;
break;
case '4':
count++;
break;
case '8':
count++;
break;
// etc
}
}
我想知道的是,这是一种低效的方法吗?我相信时间复杂度是 O(n),但我可能是错的。如果效率低下,有什么更好的解决方案?
** 为澄清起见进行了编辑,添加了案例“4”和“8”
Is searching a string using a switch inside a for loop inefficient?
一般不会。
What I'm wondering is, is this an inefficient way of accomplishing this?
首先,开关过于复杂。 if 语句更简单:
if (str[i] == '1')
count++;
What about when there are multiple numbers to be matched?
那么 switch 可能更具可读性,但是您的示例已损坏,因为它多次计算 1 和 4。固定码:
switch(str[i]) {
case '1':
case '4':
case '8':
count++;
}
其次,这可能不是最佳选择。重复使用余数运算符得到最低有效位并除以 10,然后比较数字可能会更快一些。这本质上是字符串转换函数在内部所做的一部分。
为了比较渐近复杂度,这与您的解决方案具有相同的时间复杂度,O(log N),其中 N 是整数的大小。大小复杂度是恒定的,而您的解决方案需要 O(log N) 来存储字符串。请注意,如果由于范围有限而将其与原始整数一起使用,则渐近复杂度的比较基本上是无关紧要的。这对于任意精度算术很重要。
另一个潜在的优化是不在循环中使用分支,而是计算所有整数的频率,并在结束循环中仅在该循环中计算频率和开关。基准测试将判断这是否更快。
switch
一般不会低效;在内部,它通常是 implemented 作为各种选项的 jump-table,或者作为二进制搜索,或者作为一系列 if-then 语句,具体取决于 compiler/optimizer 的想法执行速度最快。
但是,在这种情况下,您可以通过完全避免 switch/case 块并使用 lookup-table 来提高效率,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int, char **)
{
const int BUFSIZE=1024*1024*10; // 10MB
char * str = new char[BUFSIZE];
for (int i=0; i<BUFSIZE; i++) str[i] = (rand()%127)+1;
str[BUFSIZE-1] = '[=10=]';
unsigned char mask[256];
memset(mask, 0, sizeof(mask));
mask['1'] = 1;
mask['4'] = 1;
mask['8'] = 1;
for (int i=0; i<BUFSIZE; i++) count += mask[(unsigned)str[i]];
printf("count=%i\n", count);
return 0;
}
在我的电脑上,这样做的速度比原来的 (switch-based) 方法快两倍多。
对于您只执行一次的小输入操作,效率通常不是问题。很可能对于单个整数,大部分 CPU 时间将花在加载代码上(因为它不会在 CPU 缓存中)。即便如此,也不会超过一微秒。
如果您有一个 std::vector<int>
具有一百万个值,那么效率很重要。在这种情况下,目标是计算答案的速度与 CPU 可以从内存中加载新整数的速度一样快 - 使用向量,速度非常快。
正如 eeroika 所说,你做了不必要的工作。您首先创建一个字符串,这意味着您决定每个 str[i]
它是什么数字,然后存储该数字。但是你并不关心大多数数字,对于你关心的数字也没有必要存储它们。你只需要计算你是否会存储它们。
这很重要,因为存储这些数字需要将它们写入内存(嗯,缓存)。另一方面,CPU 可以轻松跟踪几个计数器。
最近做了一个在线编程评估,其中的问题本质上是“搜索指定整数的数字并递增计数”。
我的解决方案是将给定的数字转换为字符串,然后使用 for 循环和 switch 语句搜索字符串。 所以基本上:
for (int i = 0; i < str.length(); i++;)
{
switch(str[i]) {
case '1':
count++;
break;
case '4':
count++;
break;
case '8':
count++;
break;
// etc
}
}
我想知道的是,这是一种低效的方法吗?我相信时间复杂度是 O(n),但我可能是错的。如果效率低下,有什么更好的解决方案?
** 为澄清起见进行了编辑,添加了案例“4”和“8”
Is searching a string using a switch inside a for loop inefficient?
一般不会。
What I'm wondering is, is this an inefficient way of accomplishing this?
首先,开关过于复杂。 if 语句更简单:
if (str[i] == '1')
count++;
What about when there are multiple numbers to be matched?
那么 switch 可能更具可读性,但是您的示例已损坏,因为它多次计算 1 和 4。固定码:
switch(str[i]) {
case '1':
case '4':
case '8':
count++;
}
其次,这可能不是最佳选择。重复使用余数运算符得到最低有效位并除以 10,然后比较数字可能会更快一些。这本质上是字符串转换函数在内部所做的一部分。
为了比较渐近复杂度,这与您的解决方案具有相同的时间复杂度,O(log N),其中 N 是整数的大小。大小复杂度是恒定的,而您的解决方案需要 O(log N) 来存储字符串。请注意,如果由于范围有限而将其与原始整数一起使用,则渐近复杂度的比较基本上是无关紧要的。这对于任意精度算术很重要。
另一个潜在的优化是不在循环中使用分支,而是计算所有整数的频率,并在结束循环中仅在该循环中计算频率和开关。基准测试将判断这是否更快。
switch
一般不会低效;在内部,它通常是 implemented 作为各种选项的 jump-table,或者作为二进制搜索,或者作为一系列 if-then 语句,具体取决于 compiler/optimizer 的想法执行速度最快。
但是,在这种情况下,您可以通过完全避免 switch/case 块并使用 lookup-table 来提高效率,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int, char **)
{
const int BUFSIZE=1024*1024*10; // 10MB
char * str = new char[BUFSIZE];
for (int i=0; i<BUFSIZE; i++) str[i] = (rand()%127)+1;
str[BUFSIZE-1] = '[=10=]';
unsigned char mask[256];
memset(mask, 0, sizeof(mask));
mask['1'] = 1;
mask['4'] = 1;
mask['8'] = 1;
for (int i=0; i<BUFSIZE; i++) count += mask[(unsigned)str[i]];
printf("count=%i\n", count);
return 0;
}
在我的电脑上,这样做的速度比原来的 (switch-based) 方法快两倍多。
对于您只执行一次的小输入操作,效率通常不是问题。很可能对于单个整数,大部分 CPU 时间将花在加载代码上(因为它不会在 CPU 缓存中)。即便如此,也不会超过一微秒。
如果您有一个 std::vector<int>
具有一百万个值,那么效率很重要。在这种情况下,目标是计算答案的速度与 CPU 可以从内存中加载新整数的速度一样快 - 使用向量,速度非常快。
正如 eeroika 所说,你做了不必要的工作。您首先创建一个字符串,这意味着您决定每个 str[i]
它是什么数字,然后存储该数字。但是你并不关心大多数数字,对于你关心的数字也没有必要存储它们。你只需要计算你是否会存储它们。
这很重要,因为存储这些数字需要将它们写入内存(嗯,缓存)。另一方面,CPU 可以轻松跟踪几个计数器。