交换完成以后, 将左侧元素右边的所有数字直接从降序改成升序, 不就是一 个较小的数了么。 总结总结。 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 还大的元素,这个 //一定能找到。 int rightIndex = -1; for (int i = ary.Length - 1; i >= leftIndex; i--) { if (ary[i] > ary[leftIndex]) { rightIndex = i; break; } } //如果将整个序列看成一个数字,那么 //交换左(小)右(大),那么新数一 //定比原来的数大,肯定在排列的时候 //会排在原来的数的后面 //找到左元素和右元素以后,交换它们 Swap(ref ary[leftIndex], ref ary[rightIndex]); //反转从左元素以后的所有元素 //为什么需要反转 //因为刚才的交换已经让整个数比原来大 //了一个数量级,为了找到比原来的数大 //一点的新数,那么就将大的位和小的位 //交换。原因是在左数的新位置以后的 //元素原来都是从大到小排列的,所以反 //转就可以让他们变成一个最小的值。 Reverse(ary, leftIndex + 1, ary.Length-1); return true; } ///
/// 将序列反转 ///
/// 需要反转的序列 /// 起始索引 /// 结束索引 static void Reverse(int[] ary, int start, int end) { for (int i = 0; i <= (end - start) / 2; i++) { Swap(ref ary[start + i], ref ary[end - i ]); } } static void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } 为嘛叫“字典”? 俺猜的:按照先后次序排列出来每个结果,跟字典类似。 优势:一次产生一个结果,可以使用循环将所有的结果一次性的产生出来,不使用递 归,资源使用上,比较有效率;另外对于序列中有重复元素的问题,字典法轻松解决,不存 在问题。 劣势:序列开始的时候,必须是有序的,如果不是有序的,那么最终产生的排列数量 就不足,因而对于无序的序列来说,有一次预先排序的过程,这是额外的成本。