繁体   English   中英

两个字符串的就地交错

[英]In-place interleaving of the two halves of a string

给出一串均匀的大小,说:

abcdef123456

我将如何交错这两半,这样相同的字符串就会变成这样:

a1b2c3d4e5f6

我试图开发一种算法,但不能。 有人会给我一些关于如何进行的提示吗? 我需要这样做而不创建额外的字符串变量或数组。 一个或两个变量很好。

我只是不想要一个有效的代码(或算法),我需要开发一个算法并在数学上证明它的正确性。

您可以在O(N * log(N))时间内完成:

Want: abcdefgh12345678 -> a1b2c3d4e5f6g7h8

a b c d e f g h
  1 2 3 4 5 6 7 8

  4 1-sized swaps:

a 1 c 3 e 5 g 7
  b 2 d 4 f 6 h 8

a1  c3  e5  g7
    b2  d4  f6  h8

  2 2-sized swaps:

a1  b2  e5  f6
    c3  d4  g7  h8

a1b2  e5f6
      c3d4  g7h8

  1 4-sized swap:

a1b2  c3d4
      e5f6  g7h8

a1b2c3d4
        e5f6g7h8

在C中实施:

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

void swap(void* pa, void* pb, size_t sz)
{
  char *p1 = pa, *p2 = pb;
  while (sz--)
  {
    char tmp = *p1;
    *p1++ = *p2;
    *p2++ = tmp;
  }
}

void interleave(char* s, size_t len)
{
  size_t start, step, i, j;

  if (len <= 2)
    return;

  if (len & (len - 1))
    return; // only power of 2 lengths are supported

  for (start = 1, step = 2;
       step < len;
       start *= 2, step *= 2)
  {
    for (i = start, j = len / 2;
         i < len / 2;
         i += step, j += step)
    {
      swap(s + i,
           s + j,
           step / 2);
    }
  }
}

char testData[][64 + 1] =
{
  { "Aa" },
  { "ABab" },
  { "ABCDabcd" },
  { "ABCDEFGHabcdefgh" },
  { "ABCDEFGHIJKLMNOPabcdefghijklmnop" },
  { "ABCDEFGHIJKLMNOPQRSTUVWXYZ0<({[/abcdefghijklmnopqrstuvwxyz1>)}]\\" },
};

int main(void)
{
  unsigned i;

  for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
  {
    printf("%s -> ", testData[i]);
    interleave(testData[i], strlen(testData[i]));
    printf("%s\n", testData[i]);
  }

  return 0;
}

输出( ideone ):

Aa -> Aa
ABab -> AaBb
ABCDabcd -> AaBbCcDd
ABCDEFGHabcdefgh -> AaBbCcDdEeFfGgHh
ABCDEFGHIJKLMNOPabcdefghijklmnop -> AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPp
ABCDEFGHIJKLMNOPQRSTUVWXYZ0<({[/abcdefghijklmnopqrstuvwxyz1>)}]\ -> AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz01<>(){}[]/\

一般来说,这个问题非常困难 - 而且它减少了找到排列周期 它们的数量和长度因长度而异。

用于10和12个入口阵列的就地交错的循环

第一个和最后一个循环总是退化; 10个入口阵列有2个长度为6和2的循环,12个入口阵列有一个长度为10的循环。

循环一个做:

 for (i=j; next=get_next(i) != j; i=next) swap(i,next);

即使下一个函数可以作为N的一些相对简单的公式来实现,但问题被推迟到对已交换的索引进行书籍核算。 在10个条目的左侧情况下,应该[快速]找到循环的起始位置(它们例如是1和3)。

好吧让我们重新开始。 以下是我们要做的事情:

def interleave(string):
    i = (len(string)/2) - 1
    j = i+1

    while(i > 0):
        k = i
        while(k < j):
            tmp = string[k]
            string[k] = string[k+1]
            string[k+1] = tmp
            k+=2 #increment by 2 since were swapping every OTHER character
        i-=1 #move lower bound by one
        j+=1 #move upper bound by one

这是该计划将要做的一个例子。 我们将使用变量ijk ij分别是下限和上限,其中k将是我们交换的指数。

`abcd1234`

i = 3 //got this from (length(string)/2) -1

j = 4 //this is really i+1 to begin with

k = 3 //k always starts off reset to whatever i is 

swap d and 1
increment k by 2 (k = 3 + 2 = 5), since k > j we stop swapping

result `abc1d234` after the first swap

i = 3 - 1 //decrement i
j = 4 + 1 //increment j
k= 2 //reset k to i

swap c and 1, increment k (k = 2 + 2 = 4), we can swap again since k < j
swap d and 2, increment k (k = 4 + 2 = 6), k > j so we stop
//notice at EACH SWAP, the swap is occurring at index `k` and `k+1`

result `ab1c2d34`

i = 2 - 1
j = 5 + 1
k = 1

swap b and 1, increment k (k = 1 + 2 = 3), k < j so continue
swap c and 2, increment k (k = 3 + 2 = 5), k < j so continue
swap d and 3, increment k (k = 5 + 2 = 7), k > j so were done

result `a1b2c3d4`

至于证明程序正确性,请参阅此链接 它解释了如何通过循环不变来证明这是正确的。

一个粗略的证据如下:

  1. 初始化:在循环的第一次迭代之前,我们可以看到i被设置为(length(string)/2) - 1 在进入循环之前,我们可以看到i <= length(字符串)。
  2. 保养。 在每次迭代之后, i递减( i = i-1, i=i-2,... )并且必须存在i<length(string)
  3. 终止:由于i是正整数的递减序列,因此循环不变i > 0最终将等于false,循环将退出。

解决方案是J. Ellis和M. Markov。 原位,稳定的合并通过完美的shu ffl e。 计算机杂志。 43(1):40-53,(2000)。

另见这里的各种讨论:

  1. https://cs.stackexchange.com/questions/332/in-place-algorithm-for-interleaving-an-array/400#400
  2. https://cstheory.stackexchange.com/questions/13943/linear-time-in-place-riffle-shuffle-algorithm

好的,这是草稿。 你说你不只是想要一个算法,但你正在提示,所以请考虑这个算法提示:

长度是N.

k = N / 2 - 1。

1)从中间开始,并且移位(通过连续交换相邻的对元素)位置N / 2k处的元素向左(第一次:'1'到达位置1)。

2)--k。 是k == 0? 放弃。

3)在N / 2处移动(通过交换)元素(第一次:'f'到达位置N-1)k位于右侧。

4)--k。

编辑 :上面的算法是正确的,如下面的代码所示。 实际上证明它是正确的是超出我的能力,但有趣的小问题。

#include <iostream>
#include <algorithm>

int main(void)
{
    std::string s("abcdefghij1234567890");
    int N = s.size();
    int k = N/2 - 1;
    while (true)
    {

        for (int j=0; j<k; ++j)
        {
            int i = N/2 - j;
            std::swap(s[i], s[i-1]);
        }

        --k;

        if (k==0) break;

        for (int j=0; j<k; ++j)
        {
            int i = N/2 + j;
            std::swap(s[i], s[i+1]);
        }

        --k;
    }

   std::cout << s << std::endl;

   return 0;
}

这是一个算法和工作代码。 它就位,O(N),并且在概念上很简单。

  1. 遍历阵列的前半部分,将项目交换到位。
    • 在我们需要它们之前,在左半部分开始的项目将被交换到右侧,因此我们使用技巧来确定它们被交换到的位置。
  2. 当我们到达中点时,将未放置的左侧项目解读为右侧。
    • 使用相同技巧的变体来找到正确的解扰顺序。
  3. 重复剩余的半数组。

这通过阵列进行不超过N + N / 2交换,并且不需要临时存储。

诀窍是找到交换项的索引。 左项目交换到右侧项目腾出的交换空间。 交换空间按以下顺序增长:

  • 添加项目到最后(进入右项目腾出的空间)
  • 使用最旧的现有(左)项目交换项目。

按顺序添加项目1..N给出:
1 2 23 43 435 465 4657 ...
每一步改变的指数是:
0 0 1 0 2 1 3 ...

此序列正好是OEIS A025480 ,可以在O(1)摊销时间内计算:

def next_index(n):
    while n&1: n=n>>1
    return n>>1

一旦我们在交换N个项目后到达中点,我们需要解读。 交换空间将包含N / 2个项目,其中应该在偏移量i处的项目的实际索引由next_index(N/2+i) 我们可以通过交换空间前进,将物品放回原位。 唯一的复杂因素是,随着我们前进,我们最终可能会找到目标索引剩余的源索引,因此已经在其他地方进行了交换。 但我们可以通过再次查找上一个索引来找出它的位置。

def unscramble(start,i):
        j = next_index(start+i)
        while j<i: j = next_index(start+j)
        return j

请注意,这只是一个索引计算,而不是数据移动。 实际上,对于所有N,对next_index的调用next_index <3N。

这就是我们完成实施所需的全部内容:

def interleave(a, idx=0):
    if (len(a)<2): return
    midpt = len(a)//2 

    # the following line makes this an out-shuffle.
    # add a `not` to make an in-shuffle
    base = 1 if idx&1==0 else 0

    for i in range(base,midpt):
        j=next_index(i-base)
        swap(a,i,midpt+j)

    for i in range(larger_half(midpt)-1):
        j = unscramble( (midpt-base)//2, i);
        if (i!=j):
            swap(a, midpt+i, midpt+j)

    interleave(a[midpt:], idx+midpt)

最后的尾递归可以很容易地用循环代替。 Python的数组语法不太优雅。 另请注意,对于此递归版本,输入必须是numpy数组而不是python列表,因为标准列表切片会创建未传播回来的索引的副本。

这是验证正确性的快速测试。 (52张牌组的8次完美洗牌将其恢复到原始顺序)。

A = numpy.arange(52)
B = A.copy()
C =numpy.empty(52)

for _ in range(8):
    #manual interleave
    C[0::2]=numpy.array(A[:26])
    C[1::2]=numpy.array(A[26:])
    #our interleave
    interleave(A)
    print(A)
    assert(numpy.array_equal(A,C))

assert(numpy.array_equal(A, B))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM