【问题标题】:Concatenating arrays in place就地串联数组
【发布时间】:2017-10-09 20:28:16
【问题描述】:

我在实现一个偶尔必须对齐的循环缓冲区时遇到了问题。

假设我有两个数组,leftArrrightArr。我想将右数组移动到byteArr,将左数组移动到byteArr + 右数组的长度。 leftArrrightArr 都大于 byteArrrightArr 大于 leftArr。 (这和rotating a circular buffer不太一样,因为左数组不需要从byteArr开始)虽然左右数组不重叠,但byteArr处存储的组合数组可能与当前数组重叠,存储在leftArrrightArr。从byteArrrightArr + rightArrLen 的所有内存都可以安全地写入。一种可能的实现是:

void align(char* byteArr, char* leftArr, int leftArrLen, char* rightArr, int rightArrLen) {
  char *t = malloc(rightArrLen + leftArrLen);

  // form concatenated data
  memcpy(t, right, rightArrLen);
  memcpy(t + rightArrLen, left, leftArrLen);

  // now replace
  memcpy(byteArr, t, rightArrLen + leftArrLen);
  free(t);
}

但是,我必须以恒定的内存复杂度来完成此操作。

我目前的样子是这样的:

void align(char* byteArr, char* leftArr, int leftArrLen, char* rightArr, int rightArrLen)
{
    // first I check to see if some combination of memmove and memcpy will suffice, if not:
    unsigned int lStart = leftArr - byteArr;
    unsigned int lEnd = lStart + leftArrLen;
    unsigned int rStart = rightArr - byteArr;
    unsigned int rEnd = rStart + rightArrLen;
    unsigned int lShift = rEnd - rStart - lStart;
    unsigned int rShift = -rStart;
    char temp1;
    char temp2;
    unsigned int nextIndex;
    bool alreadyMoved;

    // move the right array
    for( unsigned int i = 0; i < rEnd - rStart; i++ )
    {
        alreadyMoved = false;

        for( unsigned int j = i; j < rEnd - rStart; j-= rShift )
        {
            if(    lStart <= j + rStart - lShift
                && j + rStart - lShift < lEnd
                && lStart <= (j + rStart) % lShift
                && (j + rStart) % lShift < lEnd
                && (j + rStart) % lShift < i )
            {
                alreadyMoved = true;
            }
        }

        if(alreadyMoved)
        {
            // byte has already been moved
            continue;
        }

        nextIndex = i - rShift;
        temp1 = byteArr[nextIndex];
        while( rStart <= nextIndex && nextIndex < rEnd )
        {
            nextIndex += rShift;
            temp2 = byteArr[nextIndex];
            byteArr[nextIndex] = temp1;
            temp1 = temp2;
            while( lStart <= nextIndex && nextIndex < lEnd )
            {
                nextIndex += lShift;
                temp2 = byteArr[nextIndex];
                byteArr[nextIndex] = temp1;
                temp1 = temp2;
            }
            if( nextIndex <= i - rShift )
            {
                // byte has already been moved
                break;
            }
        }
    }

    // move the left array
    for( unsigned int i = lStart; i < lShift && i < lEnd; i++ )
    {
        if( i >= rEnd - rStart )
        {
            nextIndex = i + lShift;
            temp1 = byteArr[nextIndex];
            byteArr[nextIndex] = byteArr[i];
            while( nextIndex < lEnd )
            {
                nextIndex += lShift;
                temp2 = byteArr[nextIndex];
                byteArr[nextIndex] = temp1;
                temp1 = temp2;
            }
        }
    }
}

此代码在lStart = 0, lLength = 11, rStart = 26, rLength = 70 的情况下有效,但在lStart = 0, lLength = 46, rStart = 47, rLength = 53 的情况下失败。我可以看到的解决方案是添加逻辑来确定正确数组中的字节何时已被移动。虽然这对我来说是可能的,但我想知道是否有更简单的解决方案来解决这个问题,它以恒定的内存复杂性运行并且没有额外的读写操作?

这是一个测试实现的程序:

bool testAlign(int lStart, int lLength, int rStart, int rLength)
{
    char* byteArr = (char*) malloc(100 * sizeof(char));
    char* leftArr = byteArr + lStart;
    char* rightArr = byteArr + rStart;
    for(int i = 0; i < rLength; i++)
    {
        rightArr[i] = i;
    }
    for(int i = 0; i < lLength; i++)
    {
        leftArr[i] = i + rLength;
    }
    align(byteArr, leftArr, lLength, rightArr, rLength);
    for(int i = 0; i < lLength + rLength; i++)
    {
        if(byteArr[i] != i) return false;
    }
    return true;
}

【问题讨论】:

  • 所以leftArrrightArr 总是somewhere 指向以byteArr 开头的同一个内存块?
  • 看起来代码可以以void foo(char* byteArr, unsigned lStart, unsigned rStart, unsigned leftArrLen, unsigned rightArrLen);开头
  • @chux 推断得很好(在您删除的答案中),但是当不知道 char* byteArr 的长度时存在危险 - 缺少函数参数。
  • @WeatherVane 虽然byteArr 确实没有长度,但对于我的目的来说,它确实“足够长”。
  • 可以将left+left_length 假定为&lt;= right 吗?否则为什么叫他们左,右?

标签: c arrays algorithm buffer


【解决方案1】:

想象将byteArr 划分为多个区域(不一定按比例):

X1 左 X2 右 |---|--------|---|------|

X1 和 X2 是左侧数组开始之前和两个数组之间的 byteArr 中的间隙。在一般情况下,这四个区域中的任何一个或所有区域的长度都可能为零。

然后你可以这样继续:

  1. 首先部分或全部填充byteArr 中的前导空格
    • 如果 Left 的长度为零,则通过 memmove() 将 Right 移到前面(如有必要)。完成。
    • 否则,如果 X1 与右数组的长度相同或更大,则通过memcpy() 将右数组移动到该空间中,并且可能通过memmove() 向上移动左数组以邻接它。完成。
    • 否则,将右侧阵列的前导部分移动到该空间中,生成以下布局。如果 X1 的长度为零,则 R1 的长度也为零,X2' == X2,R2 == Right。
R1 左 X2' R2 |---|--------|------|---|
  1. 现在有两种选择

    • 如果 R2 的长度与 Left 相同或更长,则将 Left 与 R2 的初始部分交换以生成(仍不按比例):
R1' X2'' 左 R2' |--------|-----|--------|--|
  • 否则,将 Left 的初始部分与所有 R2 交换以生成(仍不按比例):
R1' L2 X2'' L1 |--------|---|--------|----|
  1. 现在认识到,在任何一种情况下,您都会遇到与原始问题形式相同的严格较小的问题,其中新的byteArr 是原始问题的尾部,紧接区域 R1' 之后开始。在第一种情况下,新的leftArr 是(最终)左侧区域,新的rightArr 是区域 R2'。在另一种情况下,新的leftArr 是区域L2,新的rightArr 是区域L1。重置参数以反映这个新问题,然后循环回到步骤 (1)。

请注意,我说 loop 回到第 1 步。当然,您可以递归地(尾)实现此算法,但是要实现恒定的空间使用,您需要依靠您的编译器优化尾递归,否则会消耗与两个子数组中较大的子数组与较小子数组的长度比成正比的辅助空间。

【讨论】:

  • 绝对可以在恒定空间中工作,但最坏情况下的时间复杂度似乎比在步骤 1 之后简单地旋转要糟糕得多(我不确定),我相信这会使用恒定空间.
  • 我的错,这是O(n + m),因为每次交换/移动都涉及将元素移动到正确的位置。
  • 我同意你的分析。事实上,如果我将 n 作为左右数组的组合大小,我认为我的算法的工作量受 O(n) 的限制,并且用旋转替换步骤 2 和 3 并不能提供更严格的限制.我倾向于同意旋转选项可能具有较低的系数,但我注意到在左右数组具有相同长度并且没有间隙的特殊情况下,这两种选择实际上是相同的。
  • 取决于缓存,由于局部性,交换可能比旋转快得多。
  • 如果你想这样做,我不反对。但是您的代表不够高,无法在未经审核的情况下进行编辑,并且审核者很有可能会拒绝此类编辑。
猜你喜欢
  • 2016-08-21
  • 1970-01-01
  • 2014-10-29
  • 2011-10-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多