堆算法的 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&lt;string&gt; 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# 实现不起作用的主要内容,如果未能解决你的问题,请参考以下文章

快速排序算法不起作用

使用 StreamWriter 不起作用 \n (C#)

C# SocketIOClientDotNet 似乎不起作用

拖放在 C# 中不起作用

Nativescript-vue 实现字体图标不起作用

为啥 C++ CLI 索引属性在 C# 中不起作用?