假设您有一个无符号整数列表。假设某些元素等于0并且您想要将它们推回去。目前我使用此代码(list是指向大小为n的无符号整数列表的指针
for (i = 0; i < n; ++i)
{
if (list[i])
continue;
int j;
for (j = i + 1; j < n && !list[j]; ++j);
int z;
for (z = j + 1; z < n && list[z]; ++z);
if (j == n)
break;
memmove(&(list[i]), &(list[j]), sizeof(unsigned int) * (z - j)));
int s = z - j + i;
for(j = s; j < z; ++j)
list[j] = 0;
i = s - 1;
}
你能想到一种更有效的方法来执行这项任务吗?
该片段纯粹是理论上的,在生产代码中,list的每个元素都是64字节结构
编辑: 我会发布我的解决方案。非常感谢Jonathan Leffler。
void RemoveDeadParticles(int * list, int * n)
{
int i, j = *n - 1;
for (; j >= 0 && list[j] == 0; --j);
for (i = 0; i <= j; ++i)
{
if (list[i])
continue;
memcpy(&(list[i]), &(list[j]), sizeof(int));
list[j] = 0;
for (; j >= 0 && list[j] == 0; --j);
if (i == j)
break;
}
*n = i;
}
答案 0 :(得分:2)
我认为以下代码更好。它保留了非零元素的排序
int nextZero(int* list, int start, int n){
int i = start;
while(i < n && list[i])
i++;
return i;
}
int nextNonZero(int* list, int start, int n){
int i = start;
while(i < n && !list[i])
i++;
return i;
}
void pushbackzeros(int* list, int n){
int i = 0;
int j = 0;
while(i < n && j < n){
i = nextZero(list,i, n);
j = nextNonZero(list,i, n);
if(i >= n || j >=n)
return;
list[i] = list[j];
list[j] = 0;
}
}
这个想法:
i
)j
)复杂性:O(n)
。在最坏的情况下,每个索引最多访问4次(一次由i
访问,在函数中一次访问j
),然后在交换期间访问。
EDITED:
之前的代码是 已损坏 。这个仍然是O(n)
和模块化的。
编辑:
上面代码的复杂性是O(n ^ 2),因为索引j可以“返回”寻找 非零项目,即检查它已有的项目。当 next 零位于 next 非零之前时,会发生这种情况。修复很简单,
j = nextNonZero(list,MAX(i,j), n);
而不是
j = nextNonZero(list,i, n);
答案 1 :(得分:2)
下面的代码实现了我在评论中概述的线性算法:
如果你小心的话,有一个直线性O(N)算法;你的是O(N 2 )。鉴于顺序无关紧要,每次在数组中遇到零值时,都会将它与最后一个可能不为零的元素交换。这是一次通过阵列。需要注意边界条件。
需要护理;测试代码中list3[]
的酸性测试引起了悲痛,直到我得到了正确的边界条件。请注意,大小为0或1的列表已按正确顺序排列。
#include <stdio.h>
#define DIM(x) (sizeof(x)/sizeof(*(x)))
extern void shunt_zeroes(int *list, size_t n);
void shunt_zeroes(int *list, size_t n)
{
if (n > 1)
{
size_t tail = n;
for (size_t i = 0; i < tail - 1; i++)
{
if (list[i] == 0)
{
while (--tail > i + 1 && list[tail] == 0)
;
if (tail > i)
{
int t = list[i];
list[i] = list[tail];
list[tail] = t;
}
}
}
}
}
static void dump_list(const char *tag, int *list, size_t n)
{
printf("%-8s", tag);
for (size_t i = 0; i < n; i++)
{
printf("%d ", list[i]);
}
putchar('\n');
fflush(0);
}
static void test_list(int *list, size_t n)
{
dump_list("Before:", list, n);
shunt_zeroes(list, n);
dump_list("After:", list, n);
}
int main(void)
{
int list1[] = { 1, 0, 2, 0, 3, 0, 4, 0, 5 };
int list2[] = { 1, 2, 2, 0, 3, 0, 4, 0, 0 };
int list3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int list4[] = { 0, 1 };
int list5[] = { 0, 0 };
int list6[] = { 0 };
test_list(list1, DIM(list1));
test_list(list2, DIM(list2));
test_list(list3, DIM(list3));
test_list(list4, DIM(list4));
test_list(list5, DIM(list5));
test_list(list6, DIM(list6));
}
示例运行:
$ shuntzeroes
Before: 1 0 2 0 3 0 4 0 5
After: 1 5 2 4 3 0 0 0 0
Before: 1 2 2 0 3 0 4 0 0
After: 1 2 2 4 3 0 0 0 0
Before: 0 0 0 0 0 0 0 0 0
After: 0 0 0 0 0 0 0 0 0
Before: 0 1
After: 1 0
Before: 0 0
After: 0 0
Before: 0
After: 0
$
我断言问题中的原始代码和UmNyobe的answer中的原始代码是O(N 2 ),但这是O(N)。但是,在所有三种情况下,循环内都有一个循环;当其他人为O(N 2 )时,为什么这个答案是线性的?
好问题!
不同之处在于我的代码中的内部循环向后扫描数组,找到一个非零值与刚刚找到的零交换。这样做,减少了外循环要完成的工作。因此,i
索引向前扫描一次,tail
索引向后扫描一次,直到两者在中间相遇。相比之下,在原始代码中,内部循环从当前索引开始并且每次都向前工作,这导致二次行为。
UmNyobe和Argeman都声称code的答案中的UmNyobe是线性的,O(N),而不是二次,O(N 2 )我在评论中断言答案。鉴于两个反对意见,我写了一个程序来检查我的断言。
以下是充分证明这一点的测试结果。 "timer.h"
描述的代码是我的平台中立时序接口;它的代码可以根据要求提供(参见我的profile)。测试在具有2.3 GHz Intel Core i7,Mac OS X 10.7.5,GCC 4.7.1的MacBook Pro上进行。
我对UmNyobe代码所做的唯一更改是将数组索引从int
更改为size_t
,以便外部函数接口与我的相同,并且内部一致性。
测试代码包括一个预热练习,以显示函数产生相同的答案; UmNyobe的答案保留了阵列中的顺序而我的答案没有。我从时间数据中省略了这些信息。
$ make on2
gcc -O3 -g -I/Users/jleffler/inc -std=c99 -Wall -Wextra -L/Users/jleffler/lib/64 on2.c -ljl -o on2
$
在不使用UmNyobe修正算法的旧版测试工具上设置1:
shunt_zeroes: 100 0.000001
shunt_zeroes: 1000 0.000005
shunt_zeroes: 10000 0.000020
shunt_zeroes: 100000 0.000181
shunt_zeroes: 1000000 0.001468
pushbackzeros: 100 0.000001
pushbackzeros: 1000 0.000086
pushbackzeros: 10000 0.007003
pushbackzeros: 100000 0.624870
pushbackzeros: 1000000 46.928721
shunt_zeroes: 100 0.000000
shunt_zeroes: 1000 0.000002
shunt_zeroes: 10000 0.000011
shunt_zeroes: 100000 0.000113
shunt_zeroes: 1000000 0.000923
pushbackzeros: 100 0.000001
pushbackzeros: 1000 0.000097
pushbackzeros: 10000 0.007077
pushbackzeros: 100000 0.628327
pushbackzeros: 1000000 41.512151
机器上的背景负载最多;例如,我暂停了我通常在后台运行的Boinc计算。详细的时间并不像我想的那样稳定,但结论很清楚。
设置2:使用UmNyobe的修正算法
还包括Patrik的算法之前和之后,以及Wildplasser的算法(参见下面的源代码);测试程序已从on2
重命名为timezeromoves
。
$ ./timezeromoves -c -m 100000 -n 1
shunt_zeroes: (Jonathan)
shunt_zeroes: 100 0.000001
shunt_zeroes: 1000 0.000003
shunt_zeroes: 10000 0.000018
shunt_zeroes: 100000 0.000155
RemoveDead: (Patrik)
RemoveDead: 100 0.000001
RemoveDead: 1000 0.000004
RemoveDead: 10000 0.000018
RemoveDead: 100000 0.000159
pushbackzeros2: (UmNyobe)
pushbackzeros2: 100 0.000001
pushbackzeros2: 1000 0.000005
pushbackzeros2: 10000 0.000031
pushbackzeros2: 100000 0.000449
list_compact: (Wildplasser)
list_compact: 100 0.000004
list_compact: 1000 0.000005
list_compact: 10000 0.000036
list_compact: 100000 0.000385
shufflezeroes: (Patrik)
shufflezeroes: 100 0.000003
shufflezeroes: 1000 0.000069
shufflezeroes: 10000 0.006685
shufflezeroes: 100000 0.504551
pushbackzeros: (UmNyobe)
pushbackzeros: 100 0.000003
pushbackzeros: 1000 0.000126
pushbackzeros: 10000 0.011719
pushbackzeros: 100000 0.480458
$
这表明UmNyobe的修正算法是O(N),其他解决方案也是如此。原始代码显示为O(N 2 ),UmNyobe的原始算法也是如此。
这是修改后的测试程序(重命名为testzeromoves.c
)。算法实现位于顶部。测试工具位于评论“测试线束”之后。该命令可以进行检查或定时或两者兼而有之(默认);它默认执行两次迭代;默认情况下,它最多可达一百万个条目。您可以使用-c
标志来省略检查,使用-t
标志来省略计时,使用-n
标志来指定迭代次数,并使用-m
标志来指定最大尺寸。超过一百万,要小心;你可能会遇到吹嘘堆栈的VLA(可变长度数组)的问题。修改代码以使用malloc()
和free()
代替是很容易的;但这似乎没必要。
#include <string.h>
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
extern void shunt_zeroes(int *list, size_t n);
extern void pushbackzeros(int *list, size_t n);
extern void pushbackzeros2(int *list, size_t n);
extern void shufflezeroes(int *list, size_t n);
extern void RemoveDead(int *list, size_t n);
extern void list_compact(int *arr, size_t cnt);
void list_compact(int *arr, size_t cnt)
{
size_t dst,src,pos;
/* Skip leading filled area; find start of blanks */
for (pos=0; pos < cnt; pos++) {
if ( !arr[pos] ) break;
}
if (pos == cnt) return;
for(dst= pos; ++pos < cnt; ) {
/* Skip blanks; find start of filled area */
if ( !arr[pos] ) continue;
/* Find end of filled area */
for(src = pos; ++pos < cnt; ) {
if ( !arr[pos] ) break;
}
if (pos > src) {
memmove(arr+dst, arr+src, (pos-src) * sizeof arr[0] );
dst += pos-src;
}
}
}
/* Cannot change j to size_t safely; algorithm relies on it going negative */
void RemoveDead(int *list, size_t n)
{
int i, j = n - 1;
for (; j >= 0 && list[j] == 0; --j)
;
for (i = 0; i <= j; ++i)
{
if (list[i])
continue;
memcpy(&(list[i]), &(list[j]), sizeof(int));
list[j] = 0;
for (; j >= 0 && list[j] == 0; --j);
if (i == j)
break;
}
}
void shufflezeroes(int *list, size_t n)
{
for (size_t i = 0; i < n; ++i)
{
if (list[i])
continue;
size_t j;
for (j = i + 1; j < n && !list[j]; ++j)
;
size_t z;
for (z = j + 1; z < n && list[z]; ++z)
;
if (j == n)
break;
memmove(&(list[i]), &(list[j]), sizeof(int) * (z - j));
size_t s = z - j + i;
for(j = s; j < z; ++j)
list[j] = 0;
i = s - 1;
}
}
static int nextZero(int* list, size_t start, size_t n){
size_t i = start;
while(i < n && list[i])
i++;
return i;
}
static int nextNonZero(int* list, size_t start, size_t n){
size_t i = start;
while(i < n && !list[i])
i++;
return i;
}
void pushbackzeros(int* list, size_t n){
size_t i = 0;
size_t j = 0;
while(i < n && j < n){
i = nextZero(list,i, n);
j = nextNonZero(list,i, n);
if(i >= n || j >=n)
return;
list[i] = list[j];
list[j] = 0;
}
}
/* Amended algorithm */
void pushbackzeros2(int* list, size_t n){
size_t i = 0;
size_t j = 0;
while(i < n && j < n){
i = nextZero(list, i, n);
j = nextNonZero(list, MAX(i,j), n);
if(i >= n || j >=n)
return;
list[i] = list[j];
list[j] = 0;
}
}
void shunt_zeroes(int *list, size_t n)
{
if (n > 1)
{
size_t tail = n;
for (size_t i = 0; i < tail - 1; i++)
{
if (list[i] == 0)
{
while (--tail > i + 1 && list[tail] == 0)
;
if (tail > i)
{
int t = list[i];
list[i] = list[tail];
list[tail] = t;
}
}
}
}
}
/* Test Harness */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "timer.h"
#define DIM(x) (sizeof(x)/sizeof(*(x)))
typedef void (*Shunter)(int *list, size_t n);
typedef struct FUT /* FUT = Function Under Test */
{
Shunter function;
const char *name;
const char *author;
} FUT;
static int tflag = 1; /* timing */
static int cflag = 1; /* checking */
static size_t maxsize = 1000000;
static void dump_list(const char *tag, int *list, size_t n)
{
printf("%-8s", tag);
for (size_t i = 0; i < n; i++)
{
printf("%d ", list[i]);
}
putchar('\n');
fflush(0);
}
static void test_list(int *list, size_t n, Shunter s)
{
dump_list("Before:", list, n);
(*s)(list, n);
dump_list("After:", list, n);
}
static void list_of_tests(const FUT *f)
{
int list1[] = { 1, 0, 2, 0, 3, 0, 4, 0, 5 };
int list2[] = { 1, 2, 2, 0, 3, 0, 4, 0, 0 };
int list3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int list4[] = { 0, 1 };
int list5[] = { 0, 0 };
int list6[] = { 0 };
test_list(list1, DIM(list1), f->function);
test_list(list2, DIM(list2), f->function);
test_list(list3, DIM(list3), f->function);
test_list(list4, DIM(list4), f->function);
test_list(list5, DIM(list5), f->function);
test_list(list6, DIM(list6), f->function);
}
static void test_timer(int *list, size_t n, const FUT *f)
{
Clock t;
clk_init(&t);
clk_start(&t);
f->function(list, n);
clk_stop(&t);
char buffer[32];
printf("%-15s %7zu %10s\n", f->name, n, clk_elapsed_us(&t, buffer, sizeof(buffer)));
fflush(0);
}
static void gen_test(size_t n, const FUT *f)
{
int list[n];
for (size_t i = 0; i < n/2; i += 2)
{
list[2*i+0] = i;
list[2*i+1] = 0;
}
test_timer(list, n, f);
}
static void timed_run(const FUT *f)
{
printf("%s (%s)\n", f->name, f->author);
if (cflag)
list_of_tests(f);
if (tflag)
{
for (size_t n = 100; n <= maxsize; n *= 10)
gen_test(n, f);
}
}
static const char optstr[] = "cm:n:t";
static const char usestr[] = "[-ct][-m maxsize][-n iterations]";
int main(int argc, char **argv)
{
FUT functions[] =
{
{ shunt_zeroes, "shunt_zeroes:", "Jonathan" }, /* O(N) */
{ RemoveDead, "RemoveDead:", "Patrik" }, /* O(N) */
{ pushbackzeros2, "pushbackzeros2:", "UmNyobe" }, /* O(N) */
{ list_compact, "list_compact:", "Wildplasser" }, /* O(N) */
{ shufflezeroes, "shufflezeroes:", "Patrik" }, /* O(N^2) */
{ pushbackzeros, "pushbackzeros:", "UmNyobe" }, /* O(N^2) */
};
enum { NUM_FUNCTIONS = sizeof(functions)/sizeof(functions[0]) };
int opt;
int itercount = 2;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'c':
cflag = 0;
break;
case 't':
tflag = 0;
break;
case 'n':
itercount = atoi(optarg);
break;
case 'm':
maxsize = strtoull(optarg, 0, 0);
break;
default:
fprintf(stderr, "Usage: %s %s\n", argv[0], usestr);
return(EXIT_FAILURE);
}
}
for (int i = 0; i < itercount; i++)
{
for (int j = 0; j < NUM_FUNCTIONS; j++)
timed_run(&functions[j]);
if (tflag == 0)
break;
cflag = 0; /* Don't check on subsequent iterations */
}
return 0;
}
答案 2 :(得分:1)
这是我的尝试。 返回值是数组中成员的数量(必须忽略之后的任何内容!!):
#include <stdio.h>
#include <string.h>
size_t list_compact(int *arr, size_t cnt);
size_t list_compact(int *arr, size_t cnt)
{
size_t dst,src,pos;
/* Skip leading filled area; find start of blanks */
for (pos=0; pos < cnt; pos++) {
if ( !arr[pos] ) break;
}
if (pos == cnt) return pos;
for(dst= pos; ++pos < cnt; ) {
/* Skip blanks; find start of filled area */
if ( !arr[pos] ) continue;
/* Find end of filled area */
for(src = pos; ++pos < cnt; ) {
if ( !arr[pos] ) break;
}
if (pos > src) {
memcpy(arr+dst, arr+src, (pos-src) * sizeof arr[0] );
dst += pos-src;
}
}
#if MAINTAIN_ORIGINAL_API || JONATHAN_LEFFLFER
if (cnt > src) memset( arr + src, 0, (cnt-src) * sizeof arr[0] );
#endif
return dst;
}
更新:这是Jonathan Leffler shuffle方法的紧凑版本(不保持原始顺序):
size_t list_compact(int *arr, size_t cnt)
{
int *beg,*end;
if (!cnt) return 0;
for (beg = arr, end=arr+cnt-1; beg <= end; ) {
if ( *beg ) { beg++; continue; }
if ( !*end ) { end--; continue; }
*beg++ = *end--;
}
#if WANT_ZERO_THE_TAIL
if (beg < arr+cnt) memset(beg, 0, (arr+cnt-beg) *sizeof *arr);
return cnt;
#else
return beg - arr;
#endif
}
更新:(感谢Jonathan Leffler)memmove()应该是memcpy(),因为缓冲区不可能重叠。
GCC 4.6.1需要-minline-all-stringops来内联memcpy()。 memmove()永远不会内联,所以看来。
内联是一个性能获胜,因为函数调用开销与实际移动量相比非常大(仅sizeof(int)
)
答案 3 :(得分:0)
一个非常简单的O(n)算法是遍历列表,每次遇到零条目删除它,记录在此过程中删除的条目数M,以及完成遍历列表时在列表末尾添加M个零条目。
这需要对连续元素进行N次检查,其中N是列表的长度,M删除,M在列表的末尾插入。最坏情况如果整个列表填充零条目,您将执行N次读取,N次删除和N次插入。