【问题标题】:strstr() equivalent in C#C# 中的 strstr() 等价物
【发布时间】:2011-04-02 00:08:14
【问题描述】:

我有两个byte[],我想在第一个byte[](或其中的一个范围)中找到第二个byte[]的第一次出现。

我不想使用字符串来提高效率(将第一个 byte[] 转换为 string 效率会很低)。

基本上我相信这就是 strstr() 在 C 中所做的。

最好的方法是什么(因此它既高效又易于使用)?

它应该是这样的:

int GetOffsetOfArrayInArray(byte[] bigArray, int bigArrayOffset, int bigArrayCount, byte[] smallArray);

谢谢!

更新:

我想要一个比简单搜索更有效的解决方案。这意味着应该使用比较缓冲区可以更有效的事实 - memcmp() 比迭代字节更有效

另外,我知道有一些算法可以优化这样的场景:

  • 大数组:“12312351231234”
  • 小数组:“1231234”
  • 朴素算法: 7比较发现“1231235”与“1231234”不同,2比较发现下一个“1”,4比较发现“1235”与“1231”不同, 3比较查找下一个“1”,7比较查找匹配。总共 7+2+4+3+7=23 次比较。
  • 智能算法: 7比较发现“1231235”与“1231234”不同,直接跳转到下一个“1”(不比较),4比较发现“1235”与“不同” 1231”,直接跳出“5”,7比对。总共 7+4+7=18 次比较。

【问题讨论】:

  • 首先,使用转换对其进行编码,然后对其进行分析。如果您发现它处于性能瓶颈中,则只有对其进行优化,以免进行转换。将您的检查包装在一个函数调用中,这样您就可以在以后需要对其进行优化时简单地替换实现。
  • memcmp() 不是 strstr() 的替代方案。标准 CRT 不具备您正在寻找的功能。您可以从 msvcrt.dll 中 pinvoke strstr(),可在任何版本的 Windows 上使用。但是,它不会容忍包含零的 byte[]。
  • “memcmp() 比迭代字节更有效” - 如果(且仅当)它在幕后实现以进行字长比较或使用硬件的特殊功能。尝试优化 memcmp,其中输入缓冲区从不同的对齐方式开始是相当……繁琐的。

标签: c# .net arrays algorithm strstr


【解决方案1】:
int GetOffsetOfArrayInArray(byte[] bigArray, int bigArrayOffset, 
                               int bigArrayCount, byte[] smallArray)
{
     byte first = smallArray[0];
     bool cont= true;
     while (cont && 
            bigArrayOffset=Array.IndexOf(bigArray, first, bigArrayOffset) != -1)
     {
         if (bigArrayOffset + smallArray.Length > bigArray.Length)
         {
              bigArrayOffset = -1;
              break;
         }
         cont= false;
         for(int i=1; i< smallArray.Length; ++i)
         {
              if (bigArray[bigArrayOffset+i] != smallArray[i])
              { 
                 ++bigArrayOffset;
                 cont = true;
                 break;
              }
         }
     }
     return bigArrayOffset;
}

更新; (希望)修复了 Henk 提醒我的问题。

更新 2:解决原始问题的更新:

int GetOffsetOfArrayInArray(byte[] bigArray, int bigArrayOffset, 
                               int bigArrayCount, byte[] smallArray)
{
     int bigArrayEnd = Math.Min(bigArrayCount + bigArrayOffset, bigArray.Length)
     byte first = smallArray[0];
     bool cont= true;
     while (cont && 
            bigArrayOffset=Array.IndexOf(bigArray, first, bigArrayOffset) != -1)
     {
         int bookmark = bigArrauOffset + 1;
         bool bookmarkset = false;
         if (bigArrayOffset + smallArray.Length > bigArrayEnd )
         {
              bigArrayOffset = -1;
              break;
         }
         cont= false;
         for(int i=1; i< smallArray.Length; ++i)
         {
              if (!bookmarkset && bigArray[bigArrayOffset+i] == first)
              {
                   bookmark = bigArrayOffset+i;
                   bookmarkset = true;
              }
              if (bigArray[bigArrayOffset+i] != smallArray[i])
              { 
                 bigArrayOffset = bookmark;
                 cont = true;
                 break;
              }
         }
     }
     return bigArrayOffset;
}

【讨论】:

  • 我投票 +1,但我认为它不起作用(continue 适用于 for 循环,您希望它适用于 while)。
  • 只是一个细节:没有使用 bigArrayCount。
【解决方案2】:

我没有任何代码给你,但你会发现最快的解决方案的名称是Boyer-Moore 算法。它可以比 O(n) 做得更好。

Here 是 CodeProject 上的字符串实现。看起来转换为byte[] 应该不会太难。

【讨论】:

  • 在 .NET 中的实现会很棒。但是使用 memcmp 进行快速比较也会有所帮助......
  • “优于 O(n)”需要一些注意事项。 如果两个字符串的长度是相关的,因此目标字符串长度与正在搜索的字符串的长度成正比,并且您的输入被限制为最佳情况, 然后它是恒定时间。最坏情况是 O(n):比单纯搜索的 O(n*m) 最坏情况要好。
【解决方案3】:

这是我对解决方案的看法。它一分为二。第一部分主要寻找潜在的起点。如果找到一个,它会比较两端的列表(以降低循环计数,这基本上是一种没有分析器的微优化,但通常它更快)

int GetOffsetOfArrayInArray(byte[] bigArray,
                        int bigArrayOffset,
                        int bigArrayCount,
                        byte[] smallArray)
    {
        var length = smallArray.Length;
        var lastPossibleStart = bigArray.Length - length;
        var startByte = smallArray[0];

        for (var first = bigArrayOffset; first < lastPossibleStart; first++)
        {
           if (bigArray[first] == startByte &&
               check(bigArray, smallArray, first, length))
           {
              return first;
           }
        }
        return -1;
    }

    bool check(byte[] bigArray, byte[] smallArray, int first, int length)
    {
        var smallIndex = 0;
        var smallLast = length - 1;
        var last = first + length - 1;
        for (var i = first; smallIndex <= smallLast; i++)
        {
            if (bigArray[i] != smallArray[smallIndex] ||
                 bigArray[last] != smallArray[smallLast])
            {
                return false;
            }
            smallIndex = i - first + 1;
            last--;
            smallLast--;
        }
        return true;
    }
}

【讨论】:

  • 这有多个编译错误。当它们以最可能的预期方式修复时,就会出现数组越界错误。
  • 是的,那里有一些编译错误,我一定是“代码盲”我看不到 a-o-b
  • smallArray[first - bigArrayOffset] 怎么样?为简单起见,假设我们正在搜索整个大数组,因此 bigArrayOffset 为零。因此它只是smallArray[first],如果短模式在大数组中没有足够快地出现,first 显然会变得太大。
【解决方案4】:

在算法理论中,众所周知,优化速度会消耗内存,反之亦然。我的算法使用了更多的内存(不多),但作为回报只扫描一次大数组。

public static int GetOffsetOfArrayInArray(byte[] bigArray, int bigArrayOffset, int bigArrayCount, byte[] smallArray)
{
    // TODO: Check whether none of the variables are null or out of range.
    if (smallArray.Length == 0)
        return 0;

    List<int> starts = new List<int>();    // Limited number of elements.

    int offset = bigArrayOffset;
    // A single pass through the big array.
    while (offset < bigArrayOffset + bigArrayCount)
    {
        for (int i = 0; i < starts.Count; i++)
        {
            if (bigArray[offset] != smallArray[offset - starts[i]])
            {
                // Remove starts[i] from the list.
                starts.RemoveAt(i);
                i--;
            }
            else if (offset - starts[i] == smallArray.Length - 1)
            {
                // Found a match.
                return starts[i];
            }
        }
        if (bigArray[offset] == smallArray[0] &&
            offset <= (bigArrayOffset + bigArrayCount - smallArray.Length))
        {
            if (smallArray.Length > 1)
                // Add the start to the list.
                starts.Add(offset);
            else
                // Found a match.
                return offset;
        }
        offset++;
    }
    return -1;
}

列表starts 用于跟踪bigArraysmallArray 的潜在起始偏移量。它包含的元素永远不会超过smallArraysmallArray[0] 的出现次数(可以提前计算以优化列表并减少内存重新分配的次数)。当bigArray 中剩余的字节不足以包含smallArray 时,不尝试,当找到smallArray 时,算法停止。当到达bigArray 的末尾时,它也会停止。因此,最坏情况下的运行时间为 O(1),内存使用为 O(1)。

进一步可能的优化包括在不安全代码中使用指针,以及将列表替换为可以预先计算大小的固定数组(如前所述)。但是,因为在列表中删除了错误的偏移量(较小的内循环),并且在数组中必须跳过错误的偏移量(固定大小的内循环但可能更快的元素访问),您必须对哪个更快进行基准测试。

您是否期望smallArray 很大也很重要。当你这样做时,你可以在 while 循环中添加一个检查,检查是否starts.Length != 0 || offset &lt;= (bigArrayOffset + bigArrayCount - smallArray.Length)。否则循环可能会停止并且没有发现任何事件。

【讨论】:

    猜你喜欢
    • 2021-10-15
    • 2013-10-20
    • 2011-04-10
    • 2021-12-02
    • 2010-09-14
    • 2016-12-24
    • 2013-02-10
    • 2014-07-08
    相关资源
    最近更新 更多