我试图在C#中编写Heap算法的实现,但这种实现无法正常工作。我正在尝试创建一个通用实现,它将查找字符串的所有排列,并将它们添加到列表中。
我是这样开始的:
List<string> permutations = new List<string>();
GenerateHeapPermutations(3, "ABC", permutations);
foreach (var p in permutations)
{
Console.WriteLine(p);
}
Console.ReadKey();
这是我的实施:
public static void GenerateHeapPermutations(int n, string s, List<string> sList)
{
if (n == 1)
{
sList.Add(s);
}
else
{
for (int i = 0; i < n - 1; i++)
{
GenerateHeapPermutations(n - 1, s, sList);
if (n % 2 == 0)
{
// swap the positions of two characters
var charArray = s.ToCharArray();
var temp = charArray[i];
charArray[i] = charArray[n - 1];
charArray[n - 1] = temp;
s = new String(charArray);
}
else
{
var charArray = s.ToCharArray();
var temp = charArray[0];
charArray[0] = charArray[n - 1];
charArray[n - 1] = temp;
s = new String(charArray);
}
}
GenerateHeapPermutations(n - 1, s, sList);
}
}
该算法确实产生了正确的排列数(在本例中为6),但排列本身是不正确的:
ABC BAC CBA
BCA ABC BAC
我不认为我偏离了pseudocode example of Heap's algorithm on Wikipedia,而且我很难调试这个因为这个算法的递归性质(概念化非常棘手)。
任何人都可以提供任何有关问题的见解吗?
P.S。不是作业,只是为了好玩。
答案 0 :(得分:9)
您的算法基于传递string
而不是实际数组。
传递string
时会获取字符串的副本,因此更改复制的字符串不会更改传递的实际字符串。
将string
更改为char array
时,问题就解决了。
public static void Main()
{
List<string> permutations = new List<string>();
GenerateHeapPermutations(3, new [] { 'A', 'B', 'C' }, permutations);
foreach (var p in permutations)
{
Console.WriteLine(p);
}
Console.ReadKey();
}
public static void GenerateHeapPermutations(int n, char[] charArray, List<string> sList)
{
if (n == 1)
{
sList.Add(new string(charArray));
}
else
{
for (int i = 0; i < n - 1; i++)
{
GenerateHeapPermutations(n - 1, charArray, sList);
int indexToSwapWithLast = (n%2 == 0 ? i : 0);
// swap the positions of two characters
var temp = charArray[indexToSwapWithLast];
charArray[indexToSwapWithLast] = charArray[n - 1];
charArray[n - 1] = temp;
}
GenerateHeapPermutations(n - 1, charArray, sList);
}
}
注意:您可以使用n
删除冗余数字charArray.Length
输入,并从数组长度派生,但我不想不必要地更改你的代码。
答案 1 :(得分:3)
首先要做的事:调试。在处理递归时,调试代码的最简单方法是在IDE中设置断点并逐步逐步执行,记下代码的行为方式。这允许您在每一步查看变量的值。
你会发现在任何地方传递你的字符串并没有产生你所期望的,因为你传递的是它的副本而不是实际值。如果你通过引用传递(不确定C#是否允许),你就会做你期望的事。
答案 2 :(得分:1)
我会通过引用传递参数;这产生了预期的输出。
string sample = "ABC";
List<string> permutations = new List<string>();
GenerateHeapPermutations(3, ref sample, permutations);
foreach (var p in permutations)
{
System.Console.WriteLine(p);
}
System.Console.ReadKey();
public static void GenerateHeapPermutations(int n, ref string s, List<string> sList)
{
if (n == 1)
{
sList.Add(s);
}
else
{
for (int i = 0; i < n - 1; i++)
{
GenerateHeapPermutations(n - 1, ref s, sList);
if (n % 2 == 0)
{
// swap the positions of two characters
var charArray = s.ToCharArray();
var temp = charArray[i];
charArray[i] = charArray[n - 1];
charArray[n - 1] = temp;
s = new String(charArray);
}
else
{
var charArray = s.ToCharArray();
var temp = charArray[0];
charArray[0] = charArray[n - 1];
charArray[n - 1] = temp;
s = new String(charArray);
}
}
GenerateHeapPermutations(n - 1, ref s, sList);
}
}
答案 3 :(得分:0)
也许我的实施可以帮助你...
我认为这是最快的......
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
namespace WpfPermutations
{
/// <summary>
/// EO: 2016-04-14
/// Generator of all permutations of an array of anything.
/// Base on Heap's Algorithm. See: https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3
/// </summary>
public static class Permutations
{
/// <summary>
/// Heap's algorithm to find all pmermutations. Non recursive, more efficient.
/// </summary>
/// <param name="items">Items to permute in each possible ways</param>
/// <param name="funcExecuteAndTellIfShouldStop"></param>
/// <returns>Return true if cancelled</returns>
public static bool ForAllPermutation<T>(T[] items, Func<T[], bool> funcExecuteAndTellIfShouldStop)
{
int countOfItem = items.Length;
if (countOfItem <= 1)
{
return funcExecuteAndTellIfShouldStop(items);
}
var indexes = new int[countOfItem];
for (int i = 0; i < countOfItem; i++)
{
indexes[i] = 0;
}
if (funcExecuteAndTellIfShouldStop(items))
{
return true;
}
for (int i = 1; i < countOfItem;)
{
if (indexes[i] < i)
{ // On the web there is an implementation with a multiplication which should be less efficient.
if ((i & 1) == 1) // if (i % 2 == 1) ... more efficient ??? At least the same.
{
Swap(ref items[i], ref items[indexes[i]]);
}
else
{
Swap(ref items[i], ref items[0]);
}
if (funcExecuteAndTellIfShouldStop(items))
{
return true;
}
indexes[i]++;
i = 1;
}
else
{
indexes[i++] = 0;
}
}
return false;
}
/// <summary>
/// This function is to show a linq way but is far less efficient
/// From: StackOverflow user: Pengyang : http://stackoverflow.com/questions/756055/listing-all-permutations-of-a-string-integer
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="length"></param>
/// <returns></returns>
static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
{
if (length == 1) return list.Select(t => new T[] { t });
return GetPermutations(list, length - 1)
.SelectMany(t => list.Where(e => !t.Contains(e)),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
/// <summary>
/// Swap 2 elements of same type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
/// <summary>
/// Func to show how to call. It does a little test for an array of 4 items.
/// </summary>
public static void Test()
{
ForAllPermutation("123".ToCharArray(), (vals) =>
{
Console.WriteLine(String.Join("", vals));
return false;
});
int[] values = new int[] { 0, 1, 2, 4 };
Console.WriteLine("Ouellet heap's algorithm implementation");
ForAllPermutation(values, (vals) =>
{
Console.WriteLine(String.Join("", vals));
return false;
});
Console.WriteLine("Linq algorithm");
foreach (var v in GetPermutations(values, values.Length))
{
Console.WriteLine(String.Join("", v));
}
// Performance Heap's against Linq version : huge differences
int count = 0;
values = new int[10];
for (int n = 0; n < values.Length; n++)
{
values[n] = n;
}
Stopwatch stopWatch = new Stopwatch();
ForAllPermutation(values, (vals) =>
{
foreach (var v in vals)
{
count++;
}
return false;
});
stopWatch.Stop();
Console.WriteLine($"Ouellet heap's algorithm implementation {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
count = 0;
stopWatch.Reset();
stopWatch.Start();
foreach (var vals in GetPermutations(values, values.Length))
{
foreach (var v in vals)
{
count++;
}
}
stopWatch.Stop();
Console.WriteLine($"Linq {count} items in {stopWatch.ElapsedMilliseconds} millisecs");
}
}
}