对一道算法题的思考

xshy3412 2008-05-16 05:54:41
下面要说的这道题是多天前一位网友提问的,当时没仔细考虑。虽然早已经结帖了。但是最终好像大家都没有给出很可行的解决方法。
原问题是“写出用 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
这些字符拼出来的所有长度小于等于12位的文件名(扩展名不用考虑)。”


今天看算法书时,看到递归算法里面有一个应用,是讲“变位字”的。虽然不能直接拿来解决这个问题,但是经本人稍加改动,竟然可以完美解决上面的那个问题。
首先说一下变位字是怎么回事。假设想要列出一个指定单词的所有变位字,也就是列出该词的全排列,它们都是由原来这个单词的字母组成的。我们称这个工作是变位一个单词或称全排列一个单词的字母。用递归的方法来编程实现全排列单词的思路是:
假设这个词有n个字母:1,全排列最右边的n-1个字母。2,轮换所有n个字母。3,重复以上步骤n 次。很显然的递归思想。
轮换的意思是所有字母向左移一位,但是最左边的字母例外,它“轮换”至最右边字母的后边。下面是实现递归得到所有字母排列的
方法:

public static void doAnagram(int newSize)
{
if(newSize == 1) //if innermost,go no further
{
displayWord();//打印出目前字母数组的单词
return;
}
for (int j=0; j<newSize; j++) //for each position
{
doAnagram(newSize - 1); //anagram remaining

rotate(newSize); //rotate word
}
}

上面那个方法还调用了显示方法(显示目前的整个单词),和轮换方法rotate(),可以看出它是在递归的最里层实现打印整个单词的字母序列。既然递归的最里层可以打印整个单词,那么当从最里层返回时,是不是可以显示长度为n-1的单词,也就是说这个长度的单词字母是原单词的一部分,依次类推,每层的递归返回都可以显示更短长度的单词。因此要在上面方法的for循环体中,加入
显示方法,以显示由原单词的字母的一部分组合成的单词。而更进一步,如果想要做到长度小于等于某个数的字母组合,只需要在显示方法中进行显示之前判断一下当前层的深度。于是,这个问题就算完整地解决了。

import java.io.*;

class AnagramApp1
{
static int size;
static int count;
static char[] arrChar = new char[100];


public static void main(String[] args) throws IOException
{
System.out.print("Enter a word:");
String input = getString();
size = input.length();
count = 0;
for (int j=0; j<size; j++)
{
arrChar[j] = input.charAt(j);
}
doAnagram(size);
}

public static void doAnagram(int newSize)
{
if(newSize == 1) //if innermost,go no further
{
displayWord(newSize); //最后一层,输出的是全部字符的可能组合
return;
}
for (int j=0; j<newSize; j++) //for each position
{
doAnagram(newSize - 1); //anagram remaining

displayWord(newSize); //我加的显示语句,用于显示所有长度小于原单词长度的单词

rotate(newSize); //rotate word
}
}


public static void rotate(int newSize)
{
int j;
int position = size - newSize;
char temp = arrChar[position]; //save first letter

for (j=position+1; j<size; j++) //shift others left
{
arrChar[j-1] = arrChar[j];
}
arrChar[j-1] = temp; //put first on right
}

public static void displayWord(int newSize) //经过改造的显示方法,要接收一个参数以判断当前的递归层数
{
if(count < 99)
System.out.print(" ");
if(count < 9)
System.out.print(" ");

if(size-newSize+1 <= 2) //加入的判断,2就表示长度小于等于2的单词打印出来
{
System.out.print(++count + " ");

for(int j=0;j < size-newSize+1; j++)
{
System.out.print(arrChar[j]);
}
}

System.out.print(" ");
System.out.flush();
if(count%6 == 0)
System.out.println("");
}

public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
} //end class AnagramApp



以上代码在算法书作者的原代码基础上修改得到,为了测试方便,没有固定原单词,而是在控制台手工输入,也就是说只要输入由字母组成的字符串(当然也可以是“1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz”),就可以按要求打印出所有的字符组合,并且统计字符个数。当然为了便于验证,可以输入短一点的单词试验一下,看是否正确。

以上是本人的一点愚见,有何不妥之处还请大家指出,或者有更好的方法来解决那个问题,不妨可以一起讨论,共同提高。
...全文
1122 61 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
61 条回复
切换为时间正序
请发表友善的回复…
发表回复
ONLYBLUEMOON 2008-05-23
  • 打赏
  • 举报
回复
mark
胡矣 2008-05-21
  • 打赏
  • 举报
回复
oo
aipb2008 2008-05-21
  • 打赏
  • 举报
回复
先用“组合”,在用“排列”。两个算法简单版本都使用递归,如果用迭代,就稍微复杂些。
这是最直接的方法。

针对题目,也可以把组合排列放在一起考虑,这样应该会比单独2步更优。
DongloveRen 2008-05-21
  • 打赏
  • 举报
回复
mark
mayfar 2008-05-21
  • 打赏
  • 举报
回复
mark
kerosun 2008-05-21
  • 打赏
  • 举报
回复
先记录一下,以后学习。
andycpp 2008-05-20
  • 打赏
  • 举报
回复
mark
loujianchengdd 2008-05-20
  • 打赏
  • 举报
回复
mark
桃子 2008-05-20
  • 打赏
  • 举报
回复
mark
szVista 2008-05-20
  • 打赏
  • 举报
回复
http://javabelt.cn/viewthread.php?tid=61&extra=page%3D1

北大青鸟 资料集
mingxxw 2008-05-20
  • 打赏
  • 举报
回复
学习
apollolb2005 2008-05-19
  • 打赏
  • 举报
回复
关于 计算组合总量
也只考虑全排列 (不重复)的情况
c#的
ps: Java没怎么用过

static private Int64 Calc(Int64 lStrLen, Int64 lMaxLen)
{
Int64 lRet = 0;
for (Int64 i = 1; i <= lMaxLen; i++)
{
lRet += factorial(lStrLen, i);
}
return lRet;
}

static private Int64 factorial(Int64 iBase, Int64 lLen)
{
Int64 lRet = 1;
for (Int64 i = iBase; i > iBase - lLen; i--)
{
lRet *= i;
}
return lRet;
}
apollolb2005 2008-05-19
  • 打赏
  • 举报
回复
只考虑了全排列的算法
C#写的
static private void run()
{
Int32 iMaxLen = 3; //
String strSrc = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ";
GetData(String.Empty, strSrc, iMaxLen);
}

static private Boolean GetData(String strBase, String strSel, Int32 iMaxLen)
{
if (strBase.Length >= iMaxLen)
return true;
for (int i = 0; i < strSel.Length; i++)
{
Console.WriteLine(strBase + strSel.Substring(i, 1));
GetData(strBase + strSel.Substring(i, 1), strSel.Remove(i, 1), iMaxLen);
}
return true;
}
zhaoyong209 2008-05-18
  • 打赏
  • 举报
回复
看过niko7 写的最终代码以后,很有感触。看来算法始终是凌驾于语言之上的,好的算法与坏的算法的区别正如niko7 与我的区别。呵呵,先赞niko7 一个!!!
不过在运行代码之后,发现niko7 忽略了一个小问题,导致程序在某些情况下不能得到正确的结果。当简化合法字符为“abcd1234”,并设置maxLen为3时,简单的说,就是当解的数目小于3000个时,niko7 的解是错误的。错误原因也很简单,是因为niko7 将s.length() <= maxLen作为循环结束的判断条件,而忽略了一个重要的特点:内部的s.length()恒等于1。具体原因也很简单,不废话。因此实际上将s.length() <= maxLen作为循环结束的判断条件是没有任何意义的。因此在niko7的代码基础上做了小小改动,使其更加符合要求。不用于商业用途,还希望不要追究版权。。。。


public class test
{
public static void main(String[] args)
{
//允许组成的字符串的最大长度
int maxLen = 3;

//组成字符串的字符
char[] chars = "abcd1234".toCharArray();
//char[] chars = "012".toCharArray();
//0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

//这就是对应的目标进制
int radix = chars.length;

//下面开始枚举各种组合
String s = "";
long i = 0;
do
{
System.out.println((i+1) +"\t"+ number2String(i++, radix, chars));
}
while(i<count(radix,maxLen) && i<3000);//计算所有符合要求的排列,当排列多于3000个时只算前3000个
}

/**
* 为这个任务修正过的进制转换方法,转换出来的结果不能当成真正的数字使用
* @param num 待转换的数字
* @param radix 进制。注意与 chars 保持匹配!
* @param chars 表现数字用的字符。注意与 radix 保持匹配!
* @return
*/
public static String number2String(long num, int radix, char[] chars)
{
int cIndex = (int) (num % radix);
String s = String.valueOf(chars[cIndex]);
if (num >= radix)
{
s = number2String(((num - cIndex) / radix) - 1, radix, chars) + s;
}
return s;
}


/**
* 接收传入的标准字符chars长度和要求的文件名最大长度maxLen,返回总的组合数目.
* @param m 标准字符chars长度
* @param n 文件名最大长度maxLen
* @return
*/
public static long count(int m,int n)
{
long result=0;
for(int i=1;i<=n;i++)
result+=Math.pow(m,i);
return result;
}
}


zhaoyong209 2008-05-18
  • 打赏
  • 举报
回复
[Quote=引用 37 楼 xshy3412 的回复:]
引用 23 楼 zhaoyong209 的回复:
昨天晚上12点看了这个帖子的文字内容“写出用1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
这些字符拼出来的所有长度小于等于12位的文件名(扩展名不用考虑)。”
先是觉得过于简单,后来略想了一下,觉得唯一的难度如下:
1.代码长度优化。
2.最好能自己设定文件名长度而不用修改程序主体。
再想一下居然发现有些难度。重点在于不确定长度的多层循环控制。…
[/Quote]


我的原文是简单的说,当输入ab时来检测楼主的程序就会发现结果中没有aa和bb两个结果。重点在于没有


当然,同意楼主的说法,我们的理解不同。
niko7 2008-05-18
  • 打赏
  • 举报
回复
还是基于上述理解,针对题目直接建模,
用了两个 static private inner class,没有使用递归,又重新实现了一遍。
没有算法可言,就是建立了问题模型,有点偏离楼主的主题了,所以就不贴代码了,留个链接吧:
http://blog.csdn.net/niko7/archive/2008/05/18/2455743.aspx
niko7 2008-05-18
  • 打赏
  • 举报
回复
写出用1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
这些字符拼出来的所有长度小于等于12位的文件名(扩展名不用考虑)

应该是允许重复其中的字符的,aa,bb,ccc这种都应该算在里面。重点指出了长度小于等于。没有说文件名中不能出现重复的字符。
xshy3412 2008-05-18
  • 打赏
  • 举报
回复
[Quote=引用 23 楼 zhaoyong209 的回复:]
昨天晚上12点看了这个帖子的文字内容“写出用1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
这些字符拼出来的所有长度小于等于12位的文件名(扩展名不用考虑)。”
先是觉得过于简单,后来略想了一下,觉得唯一的难度如下:
1.代码长度优化。
2.最好能自己设定文件名长度而不用修改程序主体。
再想一下居然发现有些难度。重点在于不确定长度的多层循环控制。
于是睡觉,到中午12点起来以后一直工…
[/Quote]
对于这个问题的题意我想我们的理解不同,这个排列组合问题,我的理解是从所给字符中取出的字符就不再放回字符集合中,也就是说不管文件长度为多少,每个字符在文件名称中出现一次或零次。所以不会出现你说的aa,bb等这种情况。另外,我虽然是借用了“变位字”的函数不错,但我想你可能没有运行我的程序,不然你不会说“可是这个函数的结果有个很重要的特性:所有输入的字符必须在输出的字符中也显示,因此,结果与原问题相去甚远。”。我不是完全用了变位字来做,而是对它的输出进行了修改,所以输入ab的话,会有a和b两个串输出,而不是仅仅有ab和ba两个输出。
不过,我觉得你的理解很有道理,谢谢你的关注.
mdsp25xhm 2008-05-18
  • 打赏
  • 举报
回复
学习!
cchaha 2008-05-18
  • 打赏
  • 举报
回复
GOOD
加载更多回复(40)

62,634

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧