65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的各种字符串反转算法

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.61/5 (13投票s)

2010年5月6日

CPOL

3分钟阅读

viewsIcon

64884

downloadIcon

257

C# 中各种字符串反转算法的代码示例

引言

本文阐述了用 C# 实现的不同字符串反转算法,并从时间和空间使用考虑了它们的性能和效率。

背景

FCL(Framework Class Library)不提供任何内置的字符串反转方法。我一直在研究 C# 中各种可能的字符串反转算法,这些算法可以在空间和时间约束方面进行比较。

使用代码

首先,让我们看看一个非常基础和简单的反转 C# 中给定字符串的方法。这使用交换机制来反转字符串。将输入字符串传递给方法并转换为 CharArray。然后对字符数组逐个字符地执行交换,然后将其转换为字符串并作为反转的字符串返回。这是一种相当平庸但最常用的反转技术,因为它具有平均性能,并且需要两次复制的开销(一个到字符数组,另一个到原始字符串,因为字符串是不可变的)。

/// <summary>
/// String Reversal with Swap algorithm - trivial one.. average performance and the overhead of copying twice 
/// (one to char array and other to original as strings are immutable)
/// Extra variable is used as temp unnecessarily
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithSwapAlgo(string source)
{
        char[] inputstream = source.ToCharArray();
    for (int i = 0, j = inputstream.Length - 1; i < j; i++, j--)
    {
        char temp = inputstream[i];
        inputstream[i] = inputstream[j];
        inputstream[j] = temp;
    }
    return new string(inputstream);
}

这个使用 CopyTo Char 数组。同样,一个平凡的方法,性能一般。注意“注释”部分。与之前的技术唯一的区别在于此处使用原地交换,而没有任何临时变量。内存效率高,但与之前的性能相比,差异不大。

/// <summary>
/// String Reversal with Copy to Char Array - trivial one.. average performance and the overhead of copying twice 
/// (one to char array and other to original as strings are immutable)
/// No use of temp variable so memory efficient normal reversal
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithCharArray(string source)
{
    char[] inputstream = source.ToCharArray();
    for (int i = 0, j = inputstream.Length - 1; i < j; i++, j--)
    {
        inputstream[j] = source[i];
        inputstream[i] = source[j];
    }
    return new string(inputstream);
}

现在让我们尝试一些不同的方法。这次使用递归。这可能是你能找到的最简单的递归反转方法,而且就在字符串反转中!尽管在这里我使用 C# 中的 SubString 函数来实现分割/交换。

/// <summary>
/// String Reversal with Recursion - no extra memory usage although it uses stack internally. 
/// Perf wise works well for smaller strings but average for large strings
/// </summary>
/// <param name="source"></param>
/// <param name="len"></param>
/// <returns></returns>

private static string ReverseWithRecursion(string source, int len)
{
    if (len == 1)
        return source;
    else
        return ReverseWithRecursion(source.Substring(1, source.Length-1),--len) + source[0].ToString();
}

跳出思维定式!使用堆栈作为数据结构的字符串反转。FILO 实际上是反转字符数组背后的概念。首先将字符串中的字符推入堆栈,然后弹出它们,这样得到的字符串就会自动反转。性能几乎与普通反转相同。请注意,使用了两个循环。

/// <summary>
/// String Reversal with Stack - uses FILO structure for string reversal. 
/// Performance is almost equal to the normal reversals. 
/// Note that two loops are being used
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithStack(string source)
{
    Stack<string> inputStack = new Stack<string>();
    for (int i = 0; i < source.Length; i++)
        inputStack.Push(source.Substring(i, 1));
    string resultstring = string.Empty;
    for (int i = 0; i < source.Length; i++)
        resultstring += inputStack.Pop();
    return resultstring;
}

让我们使用 Char 数组使用复制重新审视我们最初的平凡方法,这次优化它们,而没有两次复制的开销。我们避免了 CopyTo Char 数组,而是使用了一个新的 char[] 缓冲区。如果你已经注意到 for 循环中的条件。它是 i<=j。为什么我们需要额外的迭代?那是因为如果字符串中的字符数为奇数,我们需要恢复中间的字符。有效,因为它不使用复制到字符数组,并且字符串是不可变的,因此在新 char[] 和原始字符串状态在整个反转过程中分别维护。

/// <summary>
/// String Reversal without Copy to Char Array - 
/// efficient because it doesnt use copying to char array and 
/// strings are immutable so new char[] and original string states are maintained separately throughout reversal
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithCharArrayWithoutCopy(string source)
{
    char[] inputstream = new char[source.Length];
    for (int i = 0, j = inputstream.Length - 1; i <= j; i++, j--)
    {
        inputstream[j] = source[i];
        inputstream[i] = source[j];
    }
    return new string(inputstream);
}

另一种反转字符串的创新方法。你知道这可以通过数组反转来实现吗!数组反转是 FCL 提供的一个内置方法。这非常快,因为它使用原生代码来实际执行反转。目前最有效!

/// <summary>
/// String Reversal without Copy to Char Array - 
/// most efficient as it uses TrySZReverse of ArrayHelpers in native code
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithArrayReversal(string source)
{
    char[] inputstream = source.ToCharArray();
    Array.Reverse(inputstream);
    return new string(inputstream);
}
 

还有很酷的一个。使用 BitWise XOR!它是如何工作的?

XOR 表示 [0 XOR 0 = 0],[0 XOR 1 = 1],[1 XOR 0 = 1],[1 XOR 1 = 0]。

现在假设我们有两个二进制值 A = 1 0 0 和 B = 1 1 1,我们想使用 XOR 来交换它们。我们该怎么做?在纸上试试,你会感到惊讶!

/// <summary>
/// String Reversal with Bitwise XORing.. cool stuff and quick one too without extra memory usage!!
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWithBitwiseXOR(string source)
{
    char[] inputstream = source.ToCharArray();
    int length = source.Length - 1;
    for (int i = 0; i < length; i++, length--)
    {
        inputstream[i] ^= inputstream[length];
        inputstream[length] ^= inputstream[i];
        inputstream[i] ^= inputstream[length];
    }
    return new string(inputstream);
}

这个方法是由我的朋友 Murli Krishna 建议的。

public static string StringReversal(string str)
{
    char[] revStr = new char[str.Length];
    GCHandle handle = GCHandle.Alloc(str, GCHandleType.Pinned);
    IntPtr pointer = handle.AddrOfPinnedObject();
    for (int i = str.Length-1,j=0; i >= 0; --i,++j)
    {
        revStr[j] =(char) Marshal.ReadByte(pointer, 2 * i);
    }
    handle.Free();
    return new string(revStr); 
}
 

欢迎提出更多此类方法的建议!

本文的另一个更新在这里。我添加了一种算法来原地反转语句中的所有单词。想法是首先将语句(一组字符串)转换为字符数组流,然后反转该流。之后,提取单个单词(现在已反转),然后逐个反转它们,最终得到一个反转的语句。在下面找到代码。

/// <summary>
/// Operation to reverse the sequence of words in a statement (string). This is an in-place strings reversal algorithm (words reversal)
/// </summary>
/// <param name="source"></param>
/// <returns></returns>

private static string ReverseWordsInString(string source)
{
	char[] reversedCharArray = ReverseWithArrayReversal(source).ToCharArray();
	int wordStart = 0;
	while (wordStart < reversedCharArray.Length)
	{
		while(wordStart < reversedCharArray.Length - 1 && !char.IsLetter(reversedCharArray[wordStart]))
		{
			wordStart++;
		}
		int wordEnd = wordStart;
		while(wordEnd < reversedCharArray.Length - 1 && char.IsLetter(reversedCharArray[wordEnd +1]))
		{
			wordEnd++;
		}
		if(wordEnd > wordStart)
		{
			int start = wordStart;
			int end = wordEnd;
			while (start < end)
			{
				char temp = reversedCharArray[start];
				reversedCharArray[start] = reversedCharArray[end];
				reversedCharArray[end] = temp;
				start++;
				end--;
			}
		}
		wordStart = wordEnd + 1;
	}
	return new string(reversedCharArray);
}
© . All rights reserved.