【问题标题】:Optimization O(n^2) to O(n) (Unsorted String)优化 O(n^2) 到 O(n)(未排序的字符串)
【发布时间】:2012-11-26 02:18:20
【问题描述】:

我在这里遇到了优化问题。我想让这段代码在 O(n) 中运行,我已经尝试了几个小时。

字节数组 c 包含一个字符串,e 包含相同的字符串,但已排序。整数数组 nc 和 ne 包含字符串中的索引,例如

c:
s l e e p i n g
nc:
0 0 0 1 0 0 0 0 
e:
e e g i l n p s
ne:
0 1 0 0 0 0 0 0

现在的问题是 get_next_index 是线性的 - 有没有办法解决这个问题?

void decode_block(int p) {
    BYTE xj = c[p];
    int nxj = nc[p];

    for (int i = 0; i < block_size; i++) {
        result[i] = xj;
        int q = get_next_index(xj, nxj, c, nc);
        xj = e[q];
        nxj = ne[q];
    }

    fwrite(result, sizeof (BYTE), block_size, stdout);
    fflush(stdout);
}

int get_next_index(BYTE xj, int nxj, BYTE* c, int* nc) {
    int i = 0;
    while ( ( xj != c[i] ) || ( nxj != nc[i] ) ) {
      i++;
    }
    return i;
}

这是Burrows-Wheeler 实现的一部分

开头是

xj = c[p]
nxj = nc[p]

接下来我要 block_size (= 长度 c = 长度 nc = 长度 e = 长度 ne) 次

  • 将结果 xj 存储在结果中

  • 找到 c[i] == xj 的数字索引

  • xj 现在是 e[i]

ne 和 nc 仅用于确保 e 和 c 中的每个字符都是唯一的 (e_0 != e_1)。

【问题讨论】:

  • 你能再明确一点吗?您是要对未排序的数组进行排序,还是什么?
  • 我添加了更多信息
  • 嗯..在给定enenc的情况下,这里的问题是恢复c吗? (示例数据似乎不可能。)或者在给定cnc 的情况下构造ene? (一点也不难,但是 O(N ln N)。)
  • 只给出了c,e是通过排序c构造的,ne和nc是通过对e和c的简单循环构造的

标签: c arrays sorting optimization


【解决方案1】:

由于你的宇宙(即一个字符)很小,我认为你可以摆脱线性时间。你需要一个链表和 任何序列容器一个查找表。

首先,您遍历已排序的字符串并填充一个查找表,该表允许您找到给定字符的第一个列表元素。例如,您的查找表可能类似于std::array&lt;std::list&lt;size_t&gt;,(1&lt;&lt;sizeof(char))&gt; lookup。如果您不想要list,您也可以使用std::deque 甚至std::pair&lt;std::vector,size_t&gt;,而第二项表示向量的第一个有效条目的索引(这样您就不需要弹出稍后添加元素,但只是增加索引)。

因此,对于排序字符串中的每个元素 c,您将其附加到 lookup[c] 中的容器中。

现在,当您遍历 unsorted 数组时,对于每个元素,您可以在查找表中查找相应的索引。完成后,在查找表中弹出最前面的元素。

这一切都是线性的时间和空间。


澄清;初始化查找表时:

// Instead of a list, a deque will likely perform better,
// but you have to test this yourself in your particular case.
std::array<std::list<size_t>,(1<<sizeof(char))> lookup;
for (size_t i = 0; i < sortedLength; i++) {
  lookup[sorted[i]].push_back(i);
}

当在 unsorted 数组中找到索引 i 的“第一个索引”时:

size_t const j = lookup[unsorted[i]].front();
lookup[unsorted[i]].pop_front();
return j;

【讨论】:

  • 我并没有真正明白......我在查找中究竟存储了什么?我应该使用什么样的迭代器?
  • @user720491:你有没有阅读最新的编辑。第一个版本是乱码。
  • @user720491:查看实现草图,它应该使方法更清晰。
  • 代码是C,所以不知道如何实现。这不考虑索引(nc 和 ne)吗?
  • @user720491:哎呀,对不起,我忘记了这个问题被标记为c,而不是c++。但算法保持不变,只是在 C++ 中要短得多。
【解决方案2】:

扫描xjnxj 一次并建立一个查找表。这是两个 O(n) 操作。

最明智的方法是使用二叉树,按xjnxj 的值排序。该节点将包含您寻找的索引。这会将您的查找减少到 O(lg n)。

【讨论】:

  • 能否提供更多有关查找表的信息?
  • 扫描 xj 和 nxj,假设 p = 2 然后 xj = e 和 nxj = 0。我现在如何构建查找表?
【解决方案3】:

这是我对 Burrowes-Wheeler 变换的完整实现:​​

u8* bwtCompareBuf;
u32 bwtCompareLen;

s32 bwtCompare( const void* v1, const void* v2 )
{
   u8* c1 = bwtCompareBuf + ((u32*)v1)[0];
   u8* c2 = bwtCompareBuf + ((u32*)v2)[0];

   for ( u32 i = 0; i < bwtCompareLen; i++ )
   {
      if ( c1[i] < c2[i] ) return -1;
      if ( c1[i] > c2[i] ) return +1;
   }
   return 0;
}

void bwtEncode( u8* inputBuffer, u32 len, u32& first )
{
   s8* tmpBuf = alloca( len * 2 );

   u32* indices = new  u32[len];

   for ( u32 i = 0; i < len; i++ ) indices[i] = i;

   bwtCompareBuf = tmpBuf;
   bwtCompareLen = len;
   qsort( indices.data(), len, sizeof( u32 ), bwtCompare );

   u8* tbuf = (u8*)tmpBuf + ( len - 1 );
   for ( u32 i = 0; i < len; i++ )
   {
      u32 idx = indices[i];
      if ( idx == 0 ) idx = len;
      inputBuffer[i] = tbuf[idx];
      if ( indices[i] == 1 ) first = i;
   }

   delete[] indices;
}

void bwtDecode( u8* inputBuffer, u32 len, u32 first )
{
   // To determine a character's position in the output string given
   // its position in the input string, we can use the knowledge about
   // the fact that the output string is sorted.  Each character 'c' will
   // show up in the output stream in in position i, where i is the sum
   // total of all characters in the input buffer that precede c in the
   // alphabet, plus the count of all occurences of 'c' previously in the
   // input stream.

   // compute the frequency of each character in the input buffer
   u32 freq[256] = { 0 };
   u32 count[256] = { 0 };
   for ( u32 i = 0; i < len; i++ )
      freq[inputBuffer[i]]++;

   // freq now holds a running total of all the characters less than i
   // in the input stream
   u32 sum = 0;
   for ( u32 i = 0; i < 256; i++ )
   {
      u32 tmp = sum;
      sum += freq[i];
      freq[i] = tmp;
   }

   // Now that the freq[] array is filled in, I have half the
   // information needed to position each 'c' in the input buffer.  The
   // next piece of information is simply the number of characters 'c'
   // that appear before this 'c' in the input stream.  I keep track of
   // that information in the count[] array as I go.  By adding those
   // two numbers together, I get the destination of each character in
   // the input buffer, and I just write it directly to the destination.
   u32* trans = new u32[len];
   for ( u32 i = 0; i < len; i++ )
   {
      u32 ch = inputBuffer[i];
      trans[count[ch] + freq[ch]] = i;
      count[ch]++;
   }

   u32 idx = first;
   s8* tbuf = alloca( len );
   memcpy( tbuf, inputBuffer, len );
   u8* srcBuf = (u8*)tbuf;
   for ( u32 i = 0; i < len; i++ )
   {
      inputBuffer[i] = srcBuf[idx];
      idx = trans[idx];
   }

   delete[] trans;
} 

O(n) 中的解码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-23
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 2021-06-18
    相关资源
    最近更新 更多