对于使用递归解决排列和组合的问题,俺看了很多篇参考资料,可惜的是有点难以理解别人的写法,跟MSDN一样,字都是中文,可是合起来就不知道是啥意思了,同样都是代码,每一句都能看明白,可就是不知道,他在这里为啥要写这一句,这一句在整个程序中的地位,还是脑子不好使,中学的时候数学没学好,这么些年又没好好的锻炼脑子,生锈了。
对于全排列来说,咱们还是从最简单的开始吧。
序列中只有一个元素:那么全排列就只有一种,{1}就是这个序列本身。
序列中有两个元素:那么全排列有两种方式,{1,2},{2,1}。
序列中有三个元素:那么全排列有六种方式,{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}。
如果将排列的结果做成一个整数的话,那么对于三个元素的全排列结果应该是:{123},{132},{213},{231},{312},{321},这六个数有没有什么特点?当然有。
1.它们都是由1,2,3这几个字符组成的。
2.3>2>1。
3.123<132<213<231<312<321。
这个垃圾结论能替我们解决问题吗?当然能。
还记得我们怎么理解二进制的吗?还记得我们怎么理解八进制的吗?还记得我们怎么理解十六进制的吗?二进制中包含两个字符:0,1。
八进制中包含八个字符:0,1,2,3,4,5,6,7。
十六进制中包含十六个字符:0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。
俺的乖乖,数字么呢?字母都来咧,那些个A呀,B呀,C呀,只是一些符号而已,它们在十六进制中代表的是10,11,12,13,14,15而已。
为嘛非得用ABCDEF呢?能不能用其他的字符呢?当然可以。
甚至于我们把ABCDEF可以改成“啊吧才的饿飞”,只有它依然代表的是10,11,12,13,14,15就行了。
为嘛会用的上ABCDEF呢?呵呵,简单了,因为咱们平常用的数字中没有一个单独的符号用来表达10,11,12,13,14,15而已,咱们为这些值找了个代表而已。
好了,扯的够远了,往回扯。
回到八进制中,为嘛八进制中没有ABCDEF呢?简单的回答是:咱们平常用的数字可以完全拿来表达八进制中的每个单独的数字,就是说,够用了,用不着折腾了复杂的回答是:可以有ABCDEF这些字母,反正这些字母仅仅是个代表而已。
改成{1,2,3,4,5,6,7,8}行不?当然行。
不就是个符号么。
二进制的改成{1,2}行不,也行;改成{2,3}行不,也行。
无论是{1,2}还是{2,3}仅仅是个符号,咱们要做的工作是保证符号中的大小关系,比如1<2,2<3就行了。
那么再次变态一点:{1,4}行不?当然行,对于二进制来说,只要1<4就行了。
那么{3,8}也行喽?当然。
好了,我们已经够变态的了,不妨再变态一点。
既然都已经有了二进制,八进制,十六进制,为嘛不能整个三进制呢?三进制中有几个符号呢?根据二进制,八进制,十六进制推理而来,三进制当中肯定有三个符号。
那是那三个呢?呵呵,你喜欢哪三个呢?俺喜欢{0,1,2}行不?行,呵呵,这娃还比较传统。
那俺要是喜欢{1,2,3}行不?行,呵呵,娃可教也。
二进制中的两位数有哪些啊(使用的符号为{0,1})?不就是:10,11,01,00么。
那么数字不重复的两位数的二进制数有哪些?不就是:10,01么,00和11都有重复。
对于三进制中的不重复的三位数有哪些呢?不就是:{123},{132},{213},{231},{312},{321}。
可是还有很多三位数没写出来呢?知道。
按照完全的三进制来说,123的下一个数字应该是131。
可是如果我们手头的可以使用的字符有限,不是每个字符都可以使用很多次的话,那我们的三位数的序列就应该是{123},{132},{213},{231},{312},{321}。
如果我们将{123},{132},{213},{231},{312},{321}看做连续的三位数,那么{123}<{132}<{213}<{231}<{312}<{321},那么比123大的下一个数就是132,比132大的下一个数就是213......到了这里,我们的结论是什么?结论是:如果要做n个元素的全排列,那么排列的结果一定是一组用n个元素表达的数字的数组,而且每个数字中用上了这n个元素,没有遗漏,没有重复,而且这个数组应该是升序的(或降序),第一个数字小于第二个数字,第二个数字小于第三个数字......第n-1个数字小于第n个数字。
目标有了,现在必须得想办法拼出咱们的目标。
抽象一下问题:如果我们已经有了一个数字了,通过什么手段得到一个比这个数字大一点点的新数字。
如果我们能解决这个问题,再次将新数字作为基础,找一个比新数字大一点点的新新数字。
剩下的工作就是使劲循环,这样我们就可以找到从某个数字开始的所有的比它大的数字了。
那啥时候可以停止了呢?直到找不到一个比现有数字还大的数字,就结束,换种说法,直到找到的数字是最大的。
使用给定的那么几个元素怎么能排出来一个最大的数字呢?呵呵,这不就很简单么。
把最大的元素放在最高位,把次大的放在第二位,把次次大的放在第三位......把最小的放在最低位。
呵呵,看懂了没?不就是一个将所有的元素以降序方式排列么。
如果我们要全排列,那就应该从最小值跑到最大值就可以了,最大值就是降序排列,那最小值很显然的应该是升序排列。
还是从实例开始吧。
二进制:最小值是01,最大值10;分析出从小数得到大数的过程中一定有交换。
三进制:{1,2,3,}{1,3,2,}:有交换,交换2,3。
{2,1,3,}:有交换,交换的比较复杂。
{2,3,1,}:有交换,交换1,3。
{3,1,2,}:有交换,交换的比较复杂。
{3,2,1,}:有交换,交换1,2。
中间有两步“交换的比较复杂”,比较难于总结,先放下。
四进制:{1,2,3,4,}{1,2,4,3,}:有交换,交换3,4。
{1,3,2,4,}:有交换,交换的比较复杂。
{1,3,4,2,}:有交换,交换2,4。
{1,4,2,3,}:有交换,交换的比较复杂。
{1,4,3,2,}:有交换,交换2,3。
{2,1,3,4,}:有交换,交换的比较复杂。
{2,1,4,3,}:有交换,交换3,4。
{2,3,1,4,}:有交换,交换的比较复杂。
{2,3,4,1,}:有交换,交换1,4。
{2,4,1,3,}:有交换,交换的比较复杂。
{2,4,3,1,}:有交换,交换1,3。
{3,1,2,4,}:有交换,交换的比较复杂。
{3,1,4,2,}:有交换,交换2,4。
{3,2,1,4,}:有交换,交换的比较复杂。
{3,2,4,1,}:有交换,交换1,4。
{3,4,1,2,}:有交换,交换的比较复杂。
{3,4,2,1,}:有交换,交换1,2。
{4,1,2,3,}:有交换,交换的比较复杂。
{4,1,3,2,}:有交换,交换2,3。
{4,2,1,3,}:有交换,交换的比较复杂。
{4,2,3,1,}:有交换,交换1,3。
{4,3,1,2,}:有交换,交换的比较复杂。
{4,3,2,1,}:有交换,交换1,2。
从简单的交换看,每次交换都是把大的数交换到前面了,小的数交换到后面了,如果规律相同的话,那么“有交换,交换的比较复杂。
”也应该遵循将大的数交换到前面,小的数交换到后面。
那我们随便找一个“有交换,交换的比较复杂。
”过程来详细分析。
比如从{3,1,4,2,}怎么到的{3,2,1,4,},先找到不动的元素3,反正它没有参与交换,所以,干掉它;问题简化为:{1,4,2}怎么到的{2,1,4},根据交换的规律,一定是大的向前,小的向后。
咱们从后向前看,前面的元素有没有比后面小的?有,谁?1么,那1应该和谁换,应该和比1大的最小的那个进行交换,那这个数是谁?是2,那就换吧,换完的结果是{2,4,1},这个结果和咱们的目标有一点点差别,就是1和4的位置有差别,咱们的目标是{2,1,4},可是中间结果是{2,4,1},那很容易啊,不就是把刚才中间结果中的1和4换一下就OK了。
问题在于:为嘛要换呢?回到咱们的目标上,咱们想找一个数,比142稍微大一点点的数,不是找一个比142大很多的数,在得到全排列的过程,其实是一个将数组的元素从升序更改到降序的过程,如果没有完全排好的话,那么一定有左侧元素小于右侧元素的现象,在找到的左侧元素的右边一定是已经降序排列的,要找一个比当前数大的数,那可以将找到的左侧元素和右侧序列中的一个元素进行交换,以达到比原数大的效果,可是选哪个元素呢?只能选一个比左侧元素还大的元素交换过去才能比原来的数大,那么这样交换完成的数的确比原来的数大;但是不是仅仅大于原来的数吗?呵呵,难说,因为左侧元素的右侧已经以降序的方式排列,那么它们组成的数字肯定不是较小的数字,而是较大的数字,怎么能让这个数字是个比原数稍微大一点点的数呢?交换完成以后,将左侧元素右边的所有数字直接从降序改成升序,不就是一个较小的数了么。
总结总结。
1.从右向左找,找到第一个比下一个元素还小的地方,记下位置,标注为左元素。
2.从右向左找,找到第一个比左元素大的元素,记下位置,标注为右元素。
3.交换左元素和右元素。
4.不管现在左元素位置上放的是谁,将左元素右边的序列逆序。
5.这样就得到了一个新数了。
6.可以继续重复1-5,来继续得到下一个排列。
7.如果再也找不到一个比下一个元素还小的地方,那么意味着这个序列已经降序了,排列完成了,那就结束吧。
可算到代码实现了。
static void Main(string[] args){int[] ary ={ 1, 2, 3,4 };while (true){Print(ary);if (!Next(ary)){break;}}}static void Print(int[] ary){Console.Write("{");for (int i = 0; i < ary.Length; i++){Console.Write("{0},", ary[i]);}Console.Write("}");Console.WriteLine();}static bool Next(int[] ary){//因为序列一开始是升序的,等排序完成以后就应该是降序的//所以排完的标志是序列中从后向前找,找不到后面比前面大//的元素,如果真的找不到了,那就结束了。
int leftIndex = -1;for (int i = ary.Length - 1; i > 0; i--){if (ary[i - 1] < ary[i]){leftIndex = i - 1;break;}}//找不到了,那就结束了if (leftIndex < 0){return false;}//如果能运行到这里,说明序列还没有被完全排完 //还有后面比前面大的情况//下面的工作是从序列的后面向前面找,找到刚才 //leftIndex的位置就结束了;找什么呢?找一个比 //leftIndex还大的元素,也就是在已经降序排列的 //那一部分中找一个比leftIndex还大的元素,这个 //一定能找到。