Sevendays

不积跬步,无以至千里;不积小流,无以成江海。
posts - 7, comments - 25, trackbacks - 0, articles - 1

 

题目: 输入一个整数N,程序输出数字1,2,3…N的全部排列。例如,如果N=3,那么数字1,2,3的全部排列如下:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2

3 2 1

 

 

这道算法题,当我看到时说实在的优有点懵,感觉不是那么难,但是又无从下手。昨天晚上忽然心血来潮写了下。

思路:分析题目,可以将整数N转换成一个有N个结点的,然后从第一个结点开始,一个一个加入需要生成的序列中,例如,首先添加1,生成的路径只有一种,然后将2添加进来,这样对已有的路径中添加2,在1的前面都可以添加,这样就生成了两种路径了,此次类推。。。

 

                                                                                                       已有的 路径

添加1开始:                                                                                             1

添加2(这时有两个位置可以插入2)                                          2,1                                  1,2

添加3(这时每个已有路径里可以插入3的位置有三个位置) 3,2,1   2,3,1     2,1,3         3,1,2    1,3,2   1,2,3

…………                                                                              …………                               …………

以下是源码:

 

代码
1 public class Seven
2 {
3 private StringBuilder pathInfoStr = new StringBuilder();
4
5 private int cnt = 0;//记录路径的个数
6  
7 public int Cnt
8 {
9 get { return cnt; }
10 set { cnt = value; }
11 }
12 /// <summary>
13 /// 输入N的值
14 /// </summary>
15   public int N { get; set; }
16
17
18 int idnALL = 0;
19 t;> List<List<int>> allPathInfo = new List<List<int>>();
20
21 /// <summary>
22 /// 生成各种路径
23 /// </summary>
24   private void Create()
25 {
26 int i = 1;
27 while (i <= N)
28 {
29 if (allPathInfo.Count == 0)
30 {
31 List<int> tt = new List<int>();
32 tt.Add(i);
33 allPathInfo.Add(tt);
34 }
35 else
36 {
37 idnALL = allPathInfo.Count;
38 for (int j = 0; j < idnALL; j++)
39 {
40 List<int> path = allPathInfo[j];
41 int idx = 0;
42 while (idx < path.Count)
43 {
44 List<int> newPath = path.GetRange(0, path.Count);
45 newPath.Insert(idx, i);
46 allPathInfo.Add(newPath);
47 idx++;
48 }
49 path.Insert(idx, i);
50 }
51 }
52 i++;
53 }
54 }
55 /// <summary>
56 /// 将完整路径信息转换成字符串输出
57 /// </summary>
58   private void showPathString()
59 {
60 foreach (List<int> tempList in allPathInfo)
61 {
62 foreach (int v in tempList)
63 {
64 pathInfoStr.Append(v.ToString());
65 pathInfoStr.Append(" ");
66 }
67 pathInfoStr.Append("\n<br/>");
68 cnt++;
69 }
70 }
71
72 /// <summary>
73 /// 生成路径并转换成字符串输出
74 /// </summary>
75 /// <returns></returns>
76   public string getResult()
77 {
78 Create();
79 showPathString();
80 return pathInfoStr.ToString();
81 }
82 }

 

 

标签: 算法, C#

Feedback

#1楼  回复 引用 查看   

2010-03-18 23:21 by 暮夏      
用递归,可以让代码简洁点。

#2楼[楼主]  回复 引用 查看   

2010-03-18 23:34 by Sevendays      
@暮夏
呵呵,递归写不出来,貌似我思考的方式不同,递归一直是死穴~

#3楼  回复 引用 查看   

2010-03-19 00:05 by Frank Xu Lei      
分享啦,不错不错。支持

#4楼[楼主]  回复 引用 查看   

2010-03-19 00:12 by Sevendays      
@Frank Xu Lei
哈哈,写的可能不好,不过暂时想不出其他办法~看看谁有更好的思路~

#5楼  回复 引用 查看   

2010-03-19 01:01 by elite_lcf      
c++递归实现
c++递归实现全排列

#include<iostream>
using namespace std;

void swap(int *p1,int *p2)
{
	//交换p1和p2指向的值
	int tmp=*p1;
	*p1=*p2;
	*p2=tmp;
}
void output(int *p,int n)
{
	while(n>0)
	{
		cout<<*p;
		p++;
		n--;
	}
	cout<<"\n";
}

void fill(int *p1,int *p2,int len,int n)
{//p1指向已填满的部分,始终指向第一个;
	//p2指向尚余下的部分,待插入那个;
	//len表示已填部分的长度,n表示数组长度
	if(len==n-1)
		{
			output(p1,n);//输出全部数组
		}
	else
	{
		int *p3,*p4;
		int *pp=(int *)malloc(n*sizeof(int));
		for(int i=0;i<n;i++)
		{
			*(pp+i)=*(p1+i);
		}
		p3=pp;
		while(*p3!=*p2)
		{
			p3++;
		}
		p4=p3;

		for(int i=0;i<n-len;i++)
		{			
			swap(p3,p4);
			p4++;
			fill(pp,p3+1,len+1,n);
		}
	}
}

void inti(int *p,int n)
{//将数组中所有的元素全部初始化,
	int num=1;
	while(num<=n)
	{
		*p=num++;
		p++;
		//cout<<num<<endl;
	}
}

int main()
{
	int *p,n;
	cout<<"请输入全排列规模:";
	cin>>n;
	p=(int *)malloc(n*sizeof(int));
	inti(p,n);
	fill(p,p,0,n);
	return 0;
}

#6楼  回复 引用 查看   

2010-03-19 09:46 by 孙会生      
这不就是排列组合的问题么,记得前几天有人实现过阶乘,现在你又来研究输出,不错不错..

#7楼  回复 引用 查看   

2010-03-19 12:54 by 不得闲      
貌似以前学C语言的时候,书上竟是这样的题目!

#8楼  回复 引用 查看   

2010-03-19 13:16 by Arthas-Cui      
有个简单的但是效率最低的办法:
类似于数据库里cross join那样先做一次交叉连接,
这样, 比如1到10,
就会产生10的10次方种结果。
那么, 凡是包含重复数据的, 比如第一列和第三列的数值相等,
把这一行去掉不输出就行了。

写起来代码应该是最少的, 不过代价是重复数据暴多, 所以效率是最低的。
可以适当优化一次。
因为我们在做cross join的时候, 是用循环跑的。
所以说, 检测到, 比如第一个和第二个是重复的, 那么整个循环可以全部continue掉。 因为只要有一个重复, 下面的不管怎么排列组合都是要被去掉的。

可能能再把性能拉回来一点。不知道能不能再把性能给提回来。

方法有点非主流, 而且纯粹是纸上谈兵, 不知道是否可行。

#9楼  回复 引用 查看   

2010-03-19 13:36 by AutumnWinter      
引用Arthas-Cui:
有个简单的但是效率最低的办法:
类似于数据库里cross join那样先做一次交叉连接,
这样, 比如1到10,
就会产生10的10次方种结果。
那么, 凡是包含重复数据的, 比如第一列和第三列的数值相等,
把这一行去掉不输出就行了。

写起来代码应该是最少的, 不过代价是重复数据暴多, 所以效率是最低的。
可以适当优化一次。
因为我们在做cross join的时候, 是用循环跑的。
所以说, 检测到, 比如第一个和第二个是重复的, 那么整个循环可以全部continue掉。 因为只要有一个重复, 下面的不管怎么排列组合都是要被去掉的。

可能能再把性能拉回来一点。不知道能不能再把性能给提回来。

方法有点非主流, 而且纯粹是纸上谈兵, 不知道是否可行。


可以采用回溯法去重,不过本质也是递归
#include <iostream>
using namespace std ;

void Output(int a[], int n)
{
	for (int i = 0; i < n; i++)
		cout << a[i] ;
	cout << endl ;
}

bool IsOk(int a[], int maxIndex, int curValue)
{
	for (int i = 0; i < maxIndex; i++)
		if(a[i] == curValue)
			return false ;
	return true ;
}

void Perm(int a[], int n, int t)
{
	if(t == n)
		Output(a, t) ;
	else
	{
		for (int i = 1; i <= n; i++)
		{
			a[t] = i;
			if(IsOk(a, t, i))
				Perm(a, n, t + 1) ;
		}
	}
}

int main(void)
{
	int a[100] ;
	Perm(a, 4, 0) ;

	system("pause") ;
	return 0 ;
}

#10楼[楼主]  回复 引用 查看   

2010-03-19 13:54 by Sevendays      
@AutumnWinter
我刚开始也是想用回溯去写的,后来发现写不出来就放弃了,呵呵~
递归算法还是不错啊,看的简单,不过数据大时内存貌似会消耗太多~
愚见~

#11楼  回复 引用 查看   

2010-03-19 15:09 by 小丁的博客      
算法的思想就是排列组合:对于N = 3 ,第一个位置三种排法,第二个有两种,第三个只有一种,以此类推
public class Numbers {
        private int n;
        public int N { 
            get { return this.n; }
            set { 
                n = value;
                number = new List<int>();
                for(int i = 1; i <= n; i++)
                number.Add(i);
            }
        }
        private List<int> number = null;
        private List<int> result = new List<int>();
        private List<int> currentList = new List<int>();
        public void Cal() {
            int i = 0;
            while (i < N) {
                RealCal(number[i], currentList);
                currentList.Clear();
                result.Clear();
                i++;
            }
        }
        public void RealCal(int i,List<int> currentList) {
            result.Add(i);
            currentList.Add(i);

            if (result.Count == N) {
                ShowResult(result);
                return;
            }
           
            List<int> newList = GetOtherNumbers(currentList);
            foreach(int ii in newList){
                //递归调用
                RealCal(ii, currentList);
                //删除数值调用之后的list中的数值
                currentList.RemoveAt(currentList.IndexOf(ii));
                result.RemoveAt(result.IndexOf(ii));
            }

        }
        public void ShowResult(List<int> list) {
            foreach (int i in list) {
                Console.Write(i + " ");
            }
            Console.Write(System.Environment.NewLine);
        }
       //取得当前不在当前列表集中的其他数值集合
        public List<int> GetOtherNumbers(List<int> other) {
            List<int> list = new List<int>();
            foreach (int i in number) {
                list.Add(i);
            }
            foreach (int i in other) {
                list.RemoveAt(list.IndexOf(i));
                
            }
            return list;
        }
    }

#12楼  回复 引用 查看   

2010-03-19 15:37 by AutumnWinter      
引用Sevendays:
@AutumnWinter
我刚开始也是想用回溯去写的,后来发现写不出来就放弃了,呵呵~
递归算法还是不错啊,看的简单,不过数据大时内存貌似会消耗太多~
愚见~

全排列的计算,当n到达一定规模以后(>20),无论用不用递归,基本都差不多。都很耗时间。

#13楼  回复 引用 查看   

2010-03-20 18:08 by dreamwaker      
可以转换下思维,第一步,只排相对次序,然后交换,
比如123,接下来1和2交换,213,再下来2和3交换,得到132,
然后根据数独算法右移,重复的去除即可。
比如123,变成312,231,
213 变成321,132(重复)
132变成213(重复),321(重复)。

#14楼  回复 引用 查看   

2010-03-20 20:12 by amingo      
引用dreamwaker:
可以转换下思维,第一步,只排相对次序,然后交换,
比如123,接下来1和2交换,213,再下来2和3交换,得到132,
然后根据数独算法右移,重复的去除即可。
比如123,变成312,231,
213 变成321,132(重复)
132变成213(重复),321(重复)。

不错,可以深入思考试试