水仙花数的帖子不让回了,满3次了,只好发个新帖

绿色夹克衫 2011-04-30 01:16:18
又加强了一下提前剪枝,目前效率已经不错了,计算全部88个水仙花数,一共不到1分钟,只有1没有算出来,所以是87个。
算39位的大概5-6秒吧!


1. 2
2. 3
3. 4
4. 5
5. 6
6. 7
7. 8
8. 9
9. 153
10. 370
11. 371
12. 407
13. 1634
14. 8208
15. 9474
16. 54748
17. 92727
18. 93084
19. 548834
20. 1741725
21. 4210818
22. 9800817
23. 9926315
24. 24678050
25. 24678051
26. 88593477
27. 146511208
28. 472335975
29. 534494836
30. 912985153
31. 4679307774
32. 32164049650
33. 32164049651
34. 40028394225
35. 42678290603
36. 44708635679
37. 49388550606
38. 82693916578
39. 94204591914
40. 28116440335967
41. 4338281769391370
42. 4338281769391371
43. 21897142587612075
44. 35641594208964132
45. 35875699062250035
46. 1517841543307505039
47. 3289582984443187032
48. 4498128791164624869
49. 4929273885928088826
50. 63105425988599693916
51. 128468643043731391252
52. 449177399146038697307
53. 21887696841122916288858
54. 27879694893054074471405
55. 27907865009977052567814
56. 28361281321319229463398
57. 35452590104031691935943
58. 174088005938065293023722
59. 188451485447897896036875
60. 239313664430041569350093
61. 1550475334214501539088894
62. 1553242162893771850669378
63. 3706907995955475988644380
64. 3706907995955475988644381
65. 4422095118095899619457938
66. 121204998563613372405438066
67. 121270696006801314328439376
68. 128851796696487777842012787
69. 174650464499531377631639254
70. 177265453171792792366489765
71. 14607640612971980372614873089
72. 19008174136254279995012734740
73. 19008174136254279995012734741
74. 23866716435523975980390369295
75. 1145037275765491025924292050346
76. 1927890457142960697580636236639
77. 2309092682616190307509695338915
78. 17333509997782249308725103962772
79. 186709961001538790100634132976990
80. 186709961001538790100634132976991
81. 1122763285329372541592822900204593
82. 12639369517103790328947807201478392
83. 12679937780272278566303885594196922
84. 1219167219625434121569735803609966019
85. 12815792078366059955099770545296129367
86. 115132219018763992565095597973971522400
87. 115132219018763992565095597973971522401


using System;
using System.Numerics;

namespace CSharpTest
{
class Program
{
private static BigInteger[] PowerOf10;
private static BigInteger[,] PreTable;
private static BigInteger[,] PreTable2;
private static int[,] PreTable3;
private static int[] Selected = new int[10];
private static int Length;
private static int Count = 0;

public static void Main()
{
DateTime begin = DateTime.Now;

for (int i = 1; i < 40; i++)
{
InitPre(i);
Search(9, 0, i);
}

Console.WriteLine(DateTime.Now - begin);
}

private static void InitPre(int n)
{
PowerOf10 = new BigInteger[n + 1];
PowerOf10[0] = 1;
Length = n;

for (int i = 1; i <= n; i++)
PowerOf10[i] = PowerOf10[i - 1] * 10;

PreTable = new BigInteger[10, n + 1];
PreTable2 = new BigInteger[10, n + 1];
PreTable3 = new int[10, n + 1];

for (int i = 0; i < 10; i++)
{
for (int j = 0; j <= n; j++)
{
PreTable[i, j] = BigInteger.Pow(i, n) * j;
PreTable2[i, j] = PowerOf10[Length - 1] - PreTable[i, j];

for (int k = n; k >= 0; k--)
{
if (PowerOf10[k] < PreTable[i, j])
{
PreTable3[i, j] = k;
break;
}
}
}
}
}

private static bool PreCheck(int currentIndex, BigInteger sum, int remainCount)
{
if (sum < PreTable[currentIndex, remainCount])
return true;

BigInteger max = sum + PreTable[currentIndex, remainCount];
max /= PowerOf10[PreTable3[currentIndex, remainCount]];
sum /= PowerOf10[PreTable3[currentIndex, remainCount]];

while (max != sum)
{
max /= 10;
sum /= 10;
}

if (max == 0)
return true;

int[] counter = GetCounter(max);

for (int i = 9; i > currentIndex; i--)
if (counter[i] > Selected[i])
return false;

for (int i = 0; i <= currentIndex; i++)
remainCount -= counter[i];

return remainCount >= 0;
}

private static void Search(int currentIndex, BigInteger sum, int remainCount)
{
if (sum >= PowerOf10[Length])
return;

if (remainCount == 0)
{
if (sum > PowerOf10[Length - 1] && Check(sum))
{
Count++;
Console.WriteLine("{0}. {1}", Count, sum);
}

return;
}

if (!PreCheck(currentIndex, sum, remainCount))
return;

if (sum < PreTable2[currentIndex, remainCount])
return;

if (currentIndex == 0)
{
Selected[0] = remainCount;
Search(-1, sum, 0);
}
else
{
for (int i = 0; i <= remainCount; i++)
{
Selected[currentIndex] = i;
Search(currentIndex - 1, sum + PreTable[currentIndex, i], remainCount - i);
}
}

Selected[currentIndex] = 0;
}

private static bool Check(BigInteger sum)
{
int[] counter = GetCounter(sum);

for (int i = 0; i < 10; i++)
{
if (Selected[i] != counter[i])
return false;
}

return true;
}

public static int[] GetCounter(BigInteger value)
{
int[] counter = new int[10];
char[] sumChar = value.ToString().ToCharArray();

for (int i = 0; i < sumChar.Length; i++)
counter[sumChar[i] - '0']++;

return counter;
}
}
}
...全文
1236 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
绿色夹克衫 2012-06-23
  • 打赏
  • 举报
回复
ls说一下细节吧!
boxer_tony 2012-05-04
  • 打赏
  • 举报
回复
综合楼主的剪枝,再加上对每个数字可能取的个数范围的限定,效率进一步调高,目前我的程序算39位需要400多毫秒,从1循环到60位,总共需要8秒多。
veryshowboy1 2012-01-07
  • 打赏
  • 举报
回复
百度百科上说水仙花数至少3位数
tylanbin 2011-06-13
  • 打赏
  • 举报
回复
各种膜拜……神一般的存在……
绿色夹克衫 2011-06-07
  • 打赏
  • 举报
回复
15楼的程序有注释,19楼有思路

[Quote=引用 26 楼 dzz10 的回复:]

哎哟,还是没得注释和思路得,我纠结啊!!! LZ给上注释和思路嘛.
[/Quote]
dzz10 2011-06-07
  • 打赏
  • 举报
回复
哎哟,还是没得注释和思路得,我纠结啊!!! LZ给上注释和思路嘛.
wangjie_123456 2011-05-12
  • 打赏
  • 举报
回复
我没明白precheck中的第一个if是为什么?盛请lz说说当时是怎麽想到这一句的??
v5 2011-05-11
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 keeya0416 的回复:]
LZ 果然 V5 迅猛
吾等五体投地
[/Quote]

顶起。。。。
zyj_604 2011-05-11
  • 打赏
  • 举报
回复
学习啊,还不知道水仙花是什么题目呢,呵呵
a343862324 2011-05-11
  • 打赏
  • 举报
回复
"new_006" 我给他留言时也讨论了 precheck里的第一个if语句 其实可以不用
还有我在注释里也提到了 楼主的提前剪枝..
"设sum_a=99900当前情况能表示的最大数为99988,则当前情况的数肯定在这之间即999XX,而当前情况9已经选了且只选了1次,则矛盾。"
可能是我想讲详细点 反而有点罗嗦了 导致没有全部看完吧..
其时楼主这个算法有很多不错的地方
要仔细推敲 要不然不容易完全掌握咧·
(我后来又改了一点就不贴了)
绿色夹克衫 2011-05-10
  • 打赏
  • 举报
回复
to:a343862324

我的PreTable写的挺乱的,其实就是把一些重复运算多次的结果提前算出来了。
alphaxiang 2011-05-10
  • 打赏
  • 举报
回复
"此时可确定结果中一定有967345123,就是利用这个条件做提前的剪枝工作。" 呵呵,谢谢这句
绿色夹克衫 2011-05-10
  • 打赏
  • 举报
回复
第一个if没有应该也可以的,在最初几步的时候,用这个条件返回运算速度比较快,因为当sum < 后面可能出现的最大值的时候,就没有必要做更深入的处理了。这个提前剪枝的思路也比较简单,就是算一下当前的sum和后面可能出现的最大值比如

sum = 967345123441823944234
max = 967345123999999999999

此时可确定结果中一定有967345123,就是利用这个条件做提前的剪枝工作。

[Quote=引用 11 楼 new_006 的回复:]
楼主的算法也是在通过枚举结果是由哪些i^n之和组成。在Search的for循环中直接枚举9^n,8^n....的个数,并将选取的个数记录在select中用于与最后得到的结果比对。各个数出现的次数与选取的次数一致就说明找到一个解。sum记录当前已经枚举了的结果。在枚举过程中遇到明显不可能是结果的组合提前返回。 不过我没明白precheck中的第一个if是为什么。还望楼主出来指点。
[/Quote]
X-xk 2011-05-06
  • 打赏
  • 举报
回复
楼主· 真的。说实话。 在菜鸟的眼里 你就是神奇的存在、 我也想成为神奇啊!
alphaxiang 2011-05-05
  • 打赏
  • 举报
回复
还是没明白那个precheck里的第一个if语句
a343862324 2011-05-05
  • 打赏
  • 举报
回复
if (sum.compareTo(PowerOf10[Length - 1]) > 0 && Check(sum)) {// 见结束条件5
Count++;
System.out.print(Count + " ");
System.out.println(sum);
}


不能打印1 是因为这里 改成 这样就行 最小值先前有判断 虽然代码在后面
if (remainCount == 0) {// 没数可选时
if (Check(sum)) {// 见结束条件5
Count++;
System.out.print(Count + " ");
System.out.println(sum);
}
return;
}

打印不出0是这里 由于楼主算法的原因PowerOf10[0]不能等于0 则只能在相应的地方特殊对待了
if (sum.add(PreTable[currentIndex][remainCount]).compareTo(
PowerOf10[Length - 1]) < 0)// 见结束条件2
return;
a343862324 2011-05-05
  • 打赏
  • 举报
回复
我根据我的理解改成了java版的,写了注释。
有错误的,或可以优化的地方欢迎交流分享。

import java.math.BigInteger;

/**
* 水仙花数:N位整数,它等于各位上的数字的N次方之和,例如有一个N位数字,a1a2a3a4.....aN = a1^N +a2^N+......aN^N
*
* 算法原理: 注意:以下 sum 为各位上的数字的N次方之和 sum_a为集合a中的数的sum
*
* 对于任意的N位数字,定义形如315,351,513等这样的数字都属于“1出现1次,3出现1次,5出现1次”的集合a
* 明显可以看出“包含在集合a中的数的sum都相同”,即sum_a="1^N(位数)*T1(1出现的次数)+3^N*T3+5^N*T5",
* 观察得,如果集合a包含水仙花数,则该水仙花数=水仙花数的sum(水仙花数定义)=sum_a(充要条件)。
* 可以随便举个反例215,512,125在集合b中,但b的sum_a=134明显不属于集合b,所以b不是包含水仙花数的集合
* 总结:将寻找水仙花数,转换为寻找包含水仙花数的集合,从而减少判断的次数。
*
* 结束条件:(楼主在这里进行了优化) 首先不是一次选完,而是从0到N个9,0到N个8...这样选,总数由remainCount控制 设当前情况为集合a
* 1.如果当sum_a大于最大数即10^N-1,矛盾 2.因最小的数字为10^(N -
* 1),注意到,如果选某数时sum_a小于最小值,则后面的情况不用讨论了。
* 例如3位数,已选1个3选2,发现sum_a最大为=3^3*1+2^3*2=43<100,可以断定不用选2,1,0了;
* (当前情况能表示的最大数:比如说3位数我选了1个9,8的情况选完了不行,现在开始选7,最大数就是977不可能是987)
* 3.判断sum_a和当前情况能表示的最大数首部相同部分中某数出现的次数是否比已经选择的集合中该数出现的次数多
* 设sum_a=99900当前情况能表示的最大数为99988,则当前情况的数肯定在这之间即999XX,而当前情况9已经选了且只选了1次,则矛盾。
* 4.同上:相同部分中还没选的数 的出现次数比剩余的总数还多 例如相同部分为789111,1还没选而且只剩2个数没选了,则矛盾
* 5.当选完所有数时如果sum_a属于集合a,则sum_a为水仙花数。
*
*/
public class NarcissisticNumber {
/**
* 记录10的0~N次方
*/
private static BigInteger[] PowerOf10;
/**
* 记录0到9中任意数字i的N次方乘以i出现的次数j的结果(i^N*j)
*/
private static BigInteger[][] PreTable;
/**
* 记录可能为水仙花数的下限与PreTable中对应数的差
*/
// private static BigInteger[][] PreTable2; 没什么用,变量定多了不容易理解
/**
* 记录离PreTable中对应数最近的10的k次方
*/
private static int[][] PreTable3;
/**
* 记录0到9中每个数出现的次数
*/
private static int[] Selected = new int[10];
/**
* 记录水仙花数的位数
*/
private static int Length;
/**
* 记录水仙花数出现的个数
*/
private static int Count = 0;
/**
* 记录当前的进制
*/
private static int NumberSystem = 10;

public static void main(String[] args) {
long time = System.nanoTime();
// for (int i = 1; i < 40; i++) {
NarcissisticNumber narcissisticNumber = new NarcissisticNumber(39);
narcissisticNumber.show();
// }
time = System.nanoTime() - time;
System.out.println("time:\t" + time / 1000000000.0 + "s");
}

// 初始化计算时使用的数据结构,这也是提高效率的地方
/**
* @param n
* 水仙花数的位数
*/
public NarcissisticNumber(int n) {
PowerOf10 = new BigInteger[n + 1];
PowerOf10[0] = BigInteger.ONE;
Length = n;

for (int i = 1; i <= n; i++){
PowerOf10[i] = PowerOf10[i - 1].multiply(BigInteger.TEN);
}

PreTable = new BigInteger[NumberSystem][n + 1];
// PreTable2 = new BigInteger[NumberSystem][n + 1];
PreTable3 = new int[NumberSystem][n + 1];

for (int i = 0; i < NumberSystem; i++) {
for (int j = 0; j <= n; j++) {
PreTable[i][j] = new BigInteger(new Integer(i).toString()).pow(
n).multiply(new BigInteger(new Integer(j).toString()));
// PreTable2[i][j] = PowerOf10[Length - 1]
// .subtract(PreTable[i][j]);

for (int k = n; k >= 0; k--) {
if (PowerOf10[k].compareTo(PreTable[i][j]) < 0) {
PreTable3[i][j] = k;
break;
}
}
}
}
}

private void show() {
Search(NumberSystem - 1, BigInteger.ZERO, Length);
}

/**
* @param currentIndex
* 记录当前正在选择的数字(0~9)
* @param sum
* 记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
* @param remainCount
* 记录还可选择多少数
*/
private static void Search(int currentIndex, BigInteger sum, int remainCount) {
if (sum.compareTo(PowerOf10[Length]) >= 0)// 见结束条件1
{
return;
}

if (remainCount == 0) {// 没数可选时
if (sum.compareTo(PowerOf10[Length - 1]) > 0 && Check(sum)) {// 见结束条件5
Count++;
System.out.print(Count + " ");
System.out.println(sum);
}
return;
}

if (!PreCheck(currentIndex, sum, remainCount))// 见结束条件3,4
return;

if (sum.add(PreTable[currentIndex][remainCount]).compareTo(
PowerOf10[Length - 1]) < 0)// 见结束条件2
return;

if (currentIndex == 0) {// 选到0这个数时的处理
Selected[0] = remainCount;
Search(-1, sum, 0);
} else {
for (int i = 0; i <= remainCount; i++) {// 穷举所选数可能出现的情况
Selected[currentIndex] = i;
Search(currentIndex - 1, sum.add(PreTable[currentIndex][i]),
remainCount - i);
}
}
// 到这里说明所选数currentIndex的所有情况都遍历了
Selected[currentIndex] = 0;
}

/**
* @param currentIndex
* 记录当前正在选择的数字(0~9)
* @param sum
* 记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
* @param remainCount
* 记录还可选择多少数
* @return 如果当前值符合条件返回true
*/
private static Boolean PreCheck(int currentIndex, BigInteger sum,
int remainCount) {
if (sum.compareTo(PreTable[currentIndex][remainCount]) < 0)// 判断当前值是否小于PreTable中对应元素的值
return true;// 说明还有很多数没选

BigInteger max = sum.add(PreTable[currentIndex][remainCount]);// 当前情况的最大值
max = max.divide(PowerOf10[PreTable3[currentIndex][remainCount]]);// 取前面一部分比较
sum = sum.divide(PowerOf10[PreTable3[currentIndex][remainCount]]);

while (!max.equals(sum)) {// 检验sum和max首部是否有相同的部分
max = max.divide(BigInteger.TEN);
sum = sum.divide(BigInteger.TEN);
}

if (max.equals(BigInteger.ZERO))// 无相同部分
return true;

int[] counter = GetCounter(max);

for (int i = 9; i > currentIndex; i--)
if (counter[i] > Selected[i])// 见结束条件3
return false;

for (int i = 0; i <= currentIndex; i++)
remainCount -= counter[i];

return remainCount >= 0;// 见结束条件4
}

/**
* @param sum
* 记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
* @return 如果sum存在于所选集合中返回true
*/
private static Boolean Check(BigInteger sum) {
int[] counter = GetCounter(sum);

for (int i = 0; i < NumberSystem; i++) {
if (Selected[i] != counter[i])
return false;
}

return true;
}

/**
* @param value
* 需要检验的数
* @return 返回value中0到9出现的次数的集合
*/
public static int[] GetCounter(BigInteger value) {
int[] counter = new int[NumberSystem];
char[] sumChar = value.toString().toCharArray();

for (int i = 0; i < sumChar.length; i++)
counter[sumChar[i] - '0']++;

return counter;
}
}
Sunday 2011-05-05
  • 打赏
  • 举报
回复
牛人也
java爱好者 2011-05-05
  • 打赏
  • 举报
回复
能讲一下思路不?谢谢
alphaxiang 2011-05-04
  • 打赏
  • 举报
回复
呵呵,对于一位的情况,可以直接用printf
加载更多回复(10)

33,010

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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