堆算法的 C# 实现不起作用
Posted
技术标签:
【中文标题】堆算法的 C# 实现不起作用【英文标题】:C# implementation of Heap's algorithm doesn't work 【发布时间】:2015-09-28 06:10:48 【问题描述】:我试图用 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,而且由于该算法的递归性质(概念化非常棘手),我很难调试它。
谁能提供任何关于问题可能是什么的见解?
附注不是作业,只是为了好玩。
【问题讨论】:
来自伪代码:procedure generate(n : integer, A : array of any):
,但你有GenerateHeapPermutations(int n, string s, List<string> sList)
- 为什么要额外的字符串参数?
@Tim 他只是在保存置换后的字符串。
Alex,我已经编辑了我的代码,所以我不会重复自己。
【参考方案1】:
您的算法基于传递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
从数组长度派生它,但是,我不想不必要地更改您的代码.
【讨论】:
这确实有效,但我很难理解为什么。您能否更详细地说明为什么此代码的基于string
的版本不起作用?
这篇文章很好地解释了按引用传递字符串与按值传递字符串的区别。关键是你最初传入常量“ABC”的方式。 ***.com/questions/10792603/…
@alex,我会在几个小时内添加一个进一步的解释(出去:))【参考方案2】:
第一件事:调试。在处理递归时,调试代码的最简单方法是在 IDE 中设置断点并逐步执行它,记下代码的行为方式符合您的预期。这使您可以在每一步查看变量的值。
你会发现到处传递你的字符串并没有产生你期望的结果,因为你传递的是它的副本而不是实际值。如果您改为通过引用传递(不确定 C# 是否允许这样做),您将做您所期望的。
【讨论】:
这个答案以及this MSDN tutorial 最终帮助我实现了算法的基于字符串的解决方案实现 - 谢谢!【参考方案3】:我会改为通过引用传递参数;这会产生预期的输出。
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);
【讨论】:
这与我最终解决问题的方式相同 - 谢谢!【参考方案4】:也许我的实现可以帮助你...
我觉得是最快的……
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: *** user: Pengyang : http://***.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");
【讨论】:
以上是关于堆算法的 C# 实现不起作用的主要内容,如果未能解决你的问题,请参考以下文章