堆算法的 C# 实现不起作用
C# implementation of Heap's algorithm doesn't work
我试图用 C# 编写堆算法的实现,但无法正常工作。我正在尝试创建一个通用实现,它将找到字符串的所有排列,并将它们添加到列表中。
我是这样开始的:
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);
}
}
该算法确实产生了正确数量的排列(在本例中为六个),但排列本身不正确:
ABC BAC CBA
BCA ABC BAC
我不认为我偏离了 pseudocode example of Heap's algorithm on Wikipedia,并且由于该算法的递归性质(概念化非常棘手),我很难调试它。
任何人都可以提供任何关于问题可能是什么的见解吗?
P.S。不是作业,只是为了好玩。
您的算法基于传递 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
从数组长度导出它,但是,我不想不必要地更改您的代码。
要事第一:调试。在处理递归时,调试代码的最简单方法是在 IDE 中设置断点并逐步执行它,注意代码的行为是否符合您的预期。这使您可以在每一步查看变量的值。
您会发现在任何地方传递您的字符串都不会产生您期望的结果,因为您传递的是它的副本而不是实际值。如果您改为通过引用传递(不确定 C# 是否允许这样做),您将执行您期望的操作。
我会改为通过引用传递参数;这会产生预期的输出。
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);
}
}
也许我的实现可以帮助你...
我认为这是最快的...
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: Whosebug user: Pengyang :
/// </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");
}
}
}
我试图用 C# 编写堆算法的实现,但无法正常工作。我正在尝试创建一个通用实现,它将找到字符串的所有排列,并将它们添加到列表中。
我是这样开始的:
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);
}
}
该算法确实产生了正确数量的排列(在本例中为六个),但排列本身不正确:
ABC BAC CBA
BCA ABC BAC
我不认为我偏离了 pseudocode example of Heap's algorithm on Wikipedia,并且由于该算法的递归性质(概念化非常棘手),我很难调试它。
任何人都可以提供任何关于问题可能是什么的见解吗?
P.S。不是作业,只是为了好玩。
您的算法基于传递 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
从数组长度导出它,但是,我不想不必要地更改您的代码。
要事第一:调试。在处理递归时,调试代码的最简单方法是在 IDE 中设置断点并逐步执行它,注意代码的行为是否符合您的预期。这使您可以在每一步查看变量的值。
您会发现在任何地方传递您的字符串都不会产生您期望的结果,因为您传递的是它的副本而不是实际值。如果您改为通过引用传递(不确定 C# 是否允许这样做),您将执行您期望的操作。
我会改为通过引用传递参数;这会产生预期的输出。
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);
}
}
也许我的实现可以帮助你...
我认为这是最快的...
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: Whosebug user: Pengyang :
/// </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");
}
}
}