************拆数 [算法]************

耙子 2006-01-13 04:10:29
有一个数 7434609.84
可能是下列数字中某几个的和(下面是23个),现在要找出来这几个数(可能无解)。
785397.24
119895.08
169248.51
499034.71
119895.08
136191.34
67926.15
1591094.15
54577.09
100303.76
99320.54
200607.77
123298.81
239070.53
203183.91
203183.91
556250.16
645490.82
373588.93
399248.6
386732.04
455674.26
1404253.61
8933467

请大家给一个好的算法。我目前用的就是递归,甚至都看过了汇编代码,感觉除非从算法上优化否则没什么可能突破了。

我的机器是 Pentium 1.6G/512M/ winxp sp2
上面这组数我的程序已经算了50分钟了,仍没有结果,还在算……。

为了方便大家调试我给一组小的范例(有解的)
和是 2652883.34
数列(14个)
794563.61
386732.04
399248.60
416949.20
360949.84
165742.39
373588.93
645490.82
556250.16
203183.91
203183.91
1404253.61
492528.82
422149.48
455674.26

目前我的成绩是 14个数有解 0.02s,无解时9.2s (把第一个数随便改一下就无解了)

这是我实际工作中遇到的一个问题,请大家帮忙。
...全文
670 57 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
57 条回复
切换为时间正序
请发表友善的回复…
发表回复
耙子 2006-01-25
  • 打赏
  • 举报
回复
lw549(那个孩子他爹)

无论如何,还是谢谢你的参与 :)
lw549 2006-01-25
  • 打赏
  • 举报
回复
to pazee
不好意思,看错了,我的的确是两个数的
耙子 2006-01-24
  • 打赏
  • 举报
回复
没走,这几天忙了些,没上来。
谢谢各位的参与,改成了循环快了n倍。


to lw549(那个孩子他爹)
谢谢你的参与,不知道是我没看明白,还是你没看明白,你这个算法是两个数的?

目前从我的能力来看,现在的结果已经基本满意了,谢谢各位。

因为参与的人较多,这个帖子我均着给了,另开帖子给alphax和yayx。
alphax 2006-01-23
  • 打赏
  • 举报
回复
pazee大哥过年去了?
lw549 2006-01-23
  • 打赏
  • 举报
回复
没看完上面的回复,谈谈自己的想法

1.建立数组,排序
2.从两边向中间走
举例:
要找的和是6.6
要找的数如下(已排序)
1.1
2.2
3.3
4.4
5.5
6.6
7.7
8.8
9.9
先用1.1和9.9加,得到的和与6.6比较
如果大于,则从2.2开始和9.9相加,和6.6比较
如果小于,则从8.8开始和1.1相加,于6.6比较
这样一次下来,扫描到中间碰头的位置,就可以结束了,不会遗漏
Rail100 2006-01-21
  • 打赏
  • 举报
回复
学习学习
todouwang 2006-01-20
  • 打赏
  • 举报
回复
其实在循环的时候,从第二个数的查找开始,就可以用目的数和前面数字和的差来缩小查找范围
summersky204608 2006-01-17
  • 打赏
  • 举报
回复
好好学习下!
耙子 2006-01-17
  • 打赏
  • 举报
回复
感谢各位参与,事后另开贴送分报答。
耙子 2006-01-17
  • 打赏
  • 举报
回复
谢谢alphaX,明天我试试。
yayx 2006-01-16
  • 打赏
  • 举报
回复
如果用预计算的话,速度肯定很快
我还没仔细看你的程序,我担心的是如果数据很多空间上是否能吃的消

不过我想到个办法,排序之后用动态规划的思想来做,感觉上空间时间都能够降到O(n)级别

我现在很忙,没时间来试.....等下个星期天,呵呵



你的那个算法,最好能试试10000个左右的数据,看看能不能搞定,能的话也不失为好办法~说实话我计算机原理还没学,寄存器什么的基本上也就是知道,没怎么研究过 呵呵
alphax 2006-01-16
  • 打赏
  • 举报
回复
你的原来那个代码大部分时间都消耗在传递参数,调用过程,调整栈和返回指令中,只有小部分是执行加减测试的

但是转成循环,循环体内就比较复杂

象yayx兄的代码(我没认真看,如果做成树搜索我具体实现结构和他的不一样),循环体内的内存写操作、判断和加减法比较多,写内存操作是比较慢的,条件判断会导致intel cpu的指令预取部件命中率下降,并且循环体频繁交替访问数据会导致编译器分配寄存器困难,最后只能是产生大量的内存寄存器传送指令,只是为腾出寄存器和加载操作数来执行实质需要的加减法指令,

单纯从算法角度看,剪枝是有必要的,但是增加了复杂性,就这个拆数问题来说,基本处理就是加减法,本来就是很快的操作,如果搞复杂了,恐怕也不能得到太大的好处,

我的方法虽然是穷举,但简单容易理解,循环内部简单,没有写内存的操作,变量简单,判断只有一个,我把测试函数做出来了

//noresult=true时测试无解的情况
function p4(noresult: boolean): integer;
type
tprec8 = array[byte] of integer;
tbits = record
bytearray: array[0..3] of byte;
end;

var
nums: array[1..24] of integer;
precarr: array[0..3] of tprec8;
b: Byte;
i, j: Integer;
temp0, temp1, temp2, temp3: Integer;
sum: integer;
PInt: PIntegerArray;
i64: int64;
begin
nums[1] := 78539724;
nums[2] := 11989508;
nums[3] := 16924851;
if noresult then
nums[4] := 49903571 //无解
else nums[4] := 49903471; //
nums[5] := 11989508;
nums[6] := 13619134;
nums[7] := 6792615;
nums[8] := 159109415; //
nums[9] := 5457709; //
nums[10] := 10030376;
nums[11] := 9932054; //
nums[12] := 20060777; //
nums[13] := 12329881; //
nums[14] := 23907053; //
nums[15] := 20318391; //
nums[16] := 20318391; //
nums[17] := 55625016; //
nums[18] := 64549082; //
nums[19] := 37358893; //
nums[20] := 39924860; //
nums[21] := 38673204; //
nums[22] := 45567426; //
nums[23] := 140425361; //
nums[24] := 0;

sum := 743460984;


PInt := @nums[1];
for I := 0 to 2 do
begin
for B := 0 to 255 do
begin
temp0 := 0;
for j := 0 to 7 do
begin
if (B and (1 shl j)) <> 0 then
temp0 := temp0 + PInt[j];
end;

precarr[I][B] := temp0;
end;

Inc(PInteger(PInt), 8);
end;

for I := 1 to $00FFFFFF do
begin
temp0 := precarr[0][tbits(I).bytearray[0]];
temp1 := precarr[1][tbits(I).bytearray[1]];
temp2 := precarr[2][tbits(I).bytearray[2]];

temp0 := temp0 + temp1 + temp2;

if abs(temp0 - sum) < 100 then
begin
Result := I;
Exit;
end;
end;

Result := 0;
end;


在我的2GHz的p4 cpu上,无解的情况下,0.6sec完成了预计算和全部穷举,
对于最开始的数据排列,我的代码整个过程只要0.3sec就找到了解

如果预先计算的16位的情况而不是8位的可能情况,那循环的过程就更快,可能要快1/5
yayx 2006-01-16
  • 打赏
  • 举报
回复
说简单一点,这个算法需要不停的入栈,出栈
递归的栈操作效率是很低很低的
耙子 2006-01-16
  • 打赏
  • 举报
回复
讨论基本上已到了尾声。
我想再提一个问题,大家帮我一下。
问题就是 递归的消除,关于递归的消除上学的时候讲过,也仅仅用fibonacci 作了作业,并没有真正测试它的效率,按照我的记忆,尾递归是能直接转为循环(迭代),非尾递归的通常要比如Cooper变换这类的转化为尾递归。

但是这个递归消除真的能提高时间效率吗?
我这个程序可以理解为树的搜索,(而且深度不大,不可能出现溢出)用循环和用递归很难比较出来效率,甚至一个简单调整数据顺序,结果时间都不同。
yayx 2006-01-15
  • 打赏
  • 举报
回复
P.S. to bluekitty(一只Colorful猪)
"递归、枚举、回溯,这个问题也不是在这里的人能解决的"


递归、枚举、回溯,中学里学过OI的都会啊,呵呵
什么叫“这个问题也不是在这里的人能解决的”

用二叉树和**映射算基本上也就是通过堆来在排序基础上进行二分,这个东西有些复杂了,不谈 呵呵
yayx 2006-01-15
  • 打赏
  • 举报
回复
to alphax(弯弯曲曲,和外父老借谷)

和上面的方法没有本质区别。。。只是本来算的是C(n,1)+C(n,2)+...+C(n,n)现在成了C(n,8)+C(n,9)...+C(n,16)

如果数据正好是1个数到n个数都有可能的话,那么不是相当于没有优化吗

还有计算的过程,相当于是空间换时间,也就是先计算出可能的结果去凑目标,而不是一种一种去拆分,这样空间上肯定吃不消的,如果bits.bitsarray不是16而是16000,那么按位计算中,那个precarr数组基本上就是海量(2^n级别大小)...



至于空间换时间的想法,我也想过,首先一定要剪枝,就是用不着把所有sum的可能性都算出来,每次只需要计算最逼近目标的两个数就行了,找到这两个数过程用二分法,应该也是可行的,这样子空间复杂度O(n) 时间大概上O(logn *n)。。。只不过,这样子写出来的程序应该比较复杂,而且很难理解......



我觉得,这个问题其实从算法上讲,计算过程没什么太多好优化的,主要就是把无序变成有序,用栈进行深度优先搜索,通过有序这个条件在搜索运算中不断的剪枝,这样应该足够能满足要求了
耙子 2006-01-15
  • 打赏
  • 举报
回复
楼上的代码我有点没看明白,我再看看。

alphax(弯弯曲曲,和外父老借谷)
你看看汇编,浮点数运算的效率不低于整数,甚至abs这个函数都是单指令的。
alphax 2006-01-15
  • 打赏
  • 举报
回复
为了简化下面的说明,作以下假设

1,目标合计数sum为整数,不允许误差
2,备选数为32个,均为整数,并且随即排列,记为nums[1..32]
3,问题没有解(即要执行完全部计算)

还假设,根据前面兄弟的排序思路,计算得知最多需要要合成sum最多需要24个数,最少需要16个数

那么,按原来的思路,需要执行的加法或减发的次数为

32*31..*(32-16) * (16 + 1)
+
...
+
32*31..*(32-24) * (24 + 1)

没计算结果是多少,应该是很大很大的数字



我的设想是这样:

设一个位数组,用作表示对被选数的选择,因为被选数恰好为32个,那么这个位数组就表示为一个整数,记为bits

比如
31 .................................. 0
bits = 1100 1100 1111 0000 1111 1111 1111 1111
则表示 nums[31] + nums[30] + nums[27] + nums[26] + nums[23] + nums[22] + nums[21] + .... + nums[0]


bits恰好为4个字节,也可以表示为byte[0..3]
bits = record
case boolean of
false: (bitsarray: integer);
true: (bytearray: array[0..3] of byte);
end;

我们先看bytearray[0], 他代表对nums[0], nums[1], nums[2]... , nums[7]的选择

我们预先计算这8个number的各种可能的和,然后存放在一个整数数组中备用,那么就有4个这样的数组,记为
type
tprec8 = array[byte] of integer;

var
precarr: array[0..3] of tprec8;

因为对每一个可能的组合,都要做7次加法(8个数)

那么这个预先计算量为256 * 7 * 4(个数组)

举个例子可能比较形象,
precarr[0][0] = 0
precarr[0][1] = nums[0]
precarr[0][2] = nums[1]
precarr[0][1 + 2] = nums[0] + nums[1]
...
precarr[0][1 + 8 + 16] = nums[0] + nums[2] + nums[3]
...
precarr[0][$ff] = nums[0] + nums[1] .. + nums[7]

...
...
precarr[3][$ff] = nums[24] + nums[25] +...+ nums[31]

那么我们可以很简单地计算,

var
temp0, temp1, temp2, temp3: integer;

for bits.bitsarray := 0 to high(bits.bitsarray) do
begin
temp0 := precarr[0][bits.bytearray[0]];
temp1 := precarr[1][bits.bytearray[1]];
temp2 := precarr[2][bits.bytearray[2]];
temp3 := precarr[3][bits.bytearray[3]];

temp0 := temp0 + temp1;
temp2 := temp2 + temp3;
temp0 := temp0 + temp2;

if temp0 = sum then
begin
ok := true;
exit;
end;
end;


最后一点就是我们已知sum最多可能由24个nums组成,最少16个,因此前面的循环
for bits.bitsarray := 0 to high(bits.bitsarray) do
可以进一步减少成
for bits.bitsarray := $0000FFFF to $FFFFFF00 do

这样计算量大概为 ($FFFFFF00 - $0000FFFF) * (3 + 1) (1次与sum比较时的减法)
如果不考虑最开始确定最多可能数和最小可能数(即bits.bitsarray的值范围)的运算,则没有解的情况下,基本的加减法计算量为

预先计算量 + 实际计算量 =
(256 * 7 * 4)
+
($FFFFFF00 - $0000FFFF) * (3 + 1)


这样,计算量就远远小于原来的方法了,现在的cpu完全能承担得起这样的计算量

还有一个变化就是,

在bits.bitsarray循环时,会出现一些无效的计算,比如循环到$00010000时,因为已知最少16个,
这样在实际计算前可以加插一个位计数判断,但是因为加法是很快的操作,而位计数可能比较慢,所以不做也是可以的,

再一种变化就是,如果无效计算比较多的话(即最多组成个数比较少),则可以避开增量(加1)循环,改为生成有效的位,生成有效位,不过代码不好编

大致就是先生成所有可能的由16个1的组成的bits.bitsarray,逐个计算
然后生成所有可能的由17个1的组成的bits.bitsarray,再逐个计算
,,,
如此一直到生成所有可能的由24个1的组成的bits.bitsarray,再逐个计算




匆忙写下来,可能有错漏的地方,意思就是这样
  • 打赏
  • 举报
回复
mark
我觉得对于组合问题没有什么好办法,也讨论不出什么,无非就是递归、枚举、回溯,这个问题也不是在这里的人能解决的,以前看过一个XX大学的信息数学教授研究用树和XX映射有望对组合问题有重大突破(XX就是忘了),可到现在也没看到,还是那几种算法,除非能跳出组合问题,想了半天最后总是回到组合,只能关注一下了。
alphax 2006-01-15
  • 打赏
  • 举报
回复
单指令不等于一个时钟周期就完成计算,有些说法说一个指令只要一个时钟,说的是流水线调度充满而且各指令之间没有依赖关系时的平均时钟数,比如5级流水线的,N个全程需要5个时钟的指令同时在执行,则平均只要5个时钟就可完成5条指令,即平均1条指令只要1个时钟,这是cpu的情况,fpu不太清楚,反正intel规定了,整数和浮点数混合运算时要等cpu应该等fpu完成才能继续存取操作数,具体这个等要花多少时间就没深入研究了

按照intel手册的说法,一条整数加法指令在p4的加法工作单元上只要半个时钟就完成(不包含取指译码等部分),一条浮点数加法指令则要5个时钟,而且浮点数还要fld送到fpu寄存器,计算以后还要fstp取回来,这个性能上的区别就很大了,而且参数传递时,浮点数是通过栈传递的,这要比整数直接用寄存器传递就慢一点,我以前测试过的,你可以试一下,不过这些优化只能是一个数量级的性能区别,我昨晚帮你想了个算法上的改进,不知道对不对,可能可以极大地降低计算量,也不复杂,我现在整理一下,不行的话也要请吃饭哦,打字很辛苦的

加载更多回复(37)

16,747

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 语言基础/算法/系统设计
社区管理员
  • 语言基础/算法/系统设计社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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