遇到的一个关于查找的算法,国外有人控制在30秒内,我却要6分钟,求助算法帝

hd378 2011-10-12 09:30:13
问题大概是这样的,有一个设备,通过串口与主机相连。这个设备内部有一个很大的存储区域,假设可以超过10GB,这个存储区域存储的是一个很大的字符串,格式大概是这样子:
00011111111111122222222222
333333333333
4444444444400000001111111
22222223333344444
也就是由N个连续的0,1,2,3,4组成,这个N是变化的,而不是常量。现在我有一个函数,可以读取这个设备上指定位置的字符串的值。设函数原型为Function GetPosValue(Byval pos As Long) as String
pos是字符串的位置,从1到字符串的长度。
比如 GetPosValue(1)返回0,GetPosValue(4)返回1。这个函数每调用一次都会访问串口设备,大家知道串口是很慢的,所以这个函数会消耗大量时间。
现在,我想把这个设备的所有字符串都读出来保存到一个文件。假设我还有一个函数GetMaxPos()可以用来得到设备上字符串的长度,那么,最简单,也是最慢的算法如下:
open "filename" for append as #1
MaxPos=GetMaxPos()
For i=1 to MaxPos
m=GetPosValue(4)
Print #1,m;
Next

这个办法很好,但却是最糟糕,最慢的办法。

现在已知这个设备字符串还有一些这样的特性:它总是由若干个连续的0,1,2,3,4组成,每次0结束了就跳转到1,1结束了跳转到2,2结束跳转到3,3结束跳转到4,4结束又跳转到0,以此循环。但是每次每种字符连续出现的次数又不是确定的。比如其中的一段为0001111222233333,这段中0连续出现3次,1连续出现4次,2连续出现4次,3连续出现5次。现在又知,每种字符连续出现的次数虽然不确定,但是有一个最大值MaxTime和一个最小值MinTime。比如如果这个设备的MinTime=3,MaxTime=5,则表示某个数值必须出现最少3次,最大5次,也就是000111这样的序列可能出现在设备中,而00111111222333这样的序列不可能出现在设备中,因为这里0只连续出现了2次,小于了MinTime,而1出现了6次,大于MaxTime。
在这种已知条件下,寻求一种算法,能够读出设备中的全部字符串,且调用GetPosValue()函数的次数最少(因为这个函数调用很慢)
在实际情况中,MinTime和MaxTime都是很大的值,比如MinTime可能会超过1000,那么某个数值的连续长度会很大。

我们已经尝试过的算法:

假设MinTime=1000
那么 当GetPosValue(1)="0"的时候,GetPosValue(1000)肯定也等于"0",那么1--1000的长度就全是0,只需要调用两次GetPosValue函数就可以确定这段范围(不需要再调用1000次GetPosValue函数了)。根据这样的思想,我们总是以MinTime跳跃遍历的位置,每次跳跃,调用一次GetPosValue函数检查当前的字符是否等于前一个字符,如果相等,表示还在前一个字符的范围,那么继续跳跃一个MinTime长度。如果不相等,说明已经来到下一个字符的范围,那么我们需要确定这个字符与前一个字符的分界线位置,那么程序会回溯到跳跃前的位置,使用二分法查找分界线。 比如 00000000000111111111111111中,位置第11个字符就是0与1的分界线。我们需要找到这个分界处。那么就可以确定每段的长度,并且减少GetPosValue函数的调用次数。但是以MinTime来跳跃还是比较慢的,为了提高速度,我们使用 MinTime + (MaxTime-MinTime)/2 来跳跃,但是这样的话,可以出现跳过的情况,比如000111222333444000,从开始的0跳过15个字符后,又是0,结果程序会以为还在第一段0的范围,其实已经到了第二段0的范围了。
“跳过”的情况是比较麻烦的,如果要保证完全不跳过,那么精确的算法就会很慢。所以现在我们允许出现跳过,这样会使得到的结果与原始设备的字符串有所误差。现在我们允许这种误差,但是要速度尽可能快(也就是调用GetMaxPos函数的次数最少),误差尽可能小。不知道大家有没有好点的办法?请大家帮帮忙,谢谢啦
...全文
223 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
嗷嗷叫的老马 2011-10-14
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 kitegirl 的回复:]
最后提醒6楼的老马:我至少半个月没上CSDN来逛了,被你这一念叨,昨天莫名其妙就来了。因此,乔布斯烧“五七”之前你千万别在半夜念叨他,你的召唤术潜质太邪门了,容易把他老人家魂儿招来。[/Quote]那我要开始天天召唤500W大奖了.......
KiteGirl 2011-10-14
  • 打赏
  • 举报
回复
上面写得有点乱,下面总结一下它究竟是一种什么思路:

1、为了减少读端口次数,所以需要将数字段模数化。这个模数M是1/2^n的Mmin(Mmin是与MinTime最接近的2^n,你可以用对数取)。在计算中不取精确的字段长度,而是取模数精度下的模数长度。一个段被描述成M的若干倍。

2、每一个模数段与后一个模数段有M个数据是不确定区域。这个不确定区域你就不能再深究分界线究竟在哪里,只能近似地认为是1/2M。

3、模数化前段的已知性决定后续段有一定范围的可预测性。由于前一个模数段与后一个模数段的开始间隔M个数据。由于M是近似1/2^n倍的MinTime,所以必然后面有至少((MinTime - M) \ M) - 1个M是可确定的数字段。

4、当你取得了一个可预测范围内的模数段之后,再向后按模数M的合适的2^n倍为步长试探(具体初次步长取多少个M由MaxTime-MinTime决定,大致就是二分法的思路,但精确度控制在M的二分法不需要多少步),直到取得下一个段的数字、并且将当前模数段精确到M为止。
KiteGirl 2011-10-14
  • 打赏
  • 举报
回复
这个问题我昨天就看到了,最初大致想法和10楼说的差不多。

接下来说说我的想法:

1、做一件事之前首先要考虑它有没有可能。首先你应该用串口速率去估算一下那个30秒之内的算法每帧读了多少次串口。如果次数远远比你估计的最理想的次数要少,那么说明他们可能不是用了更快的算法,而是用了另一种方法来利用这些数据。

2、MinTime和MaxTime的取值、以及你想用这些数据从事的应用都影响算法。
我也没有万能的算法,只能假设MinTime和MaxTime相差不多的前提下,你的应用是利用数字段的长度来获取一些数据。针对这种用途来考虑一种算法。

这个算法的目的不是取一个完整的连续数字段,而是确定一个MinTime长度3/4倍的完整数据段的存在位置(精度在MinTime长度1/4范围内。)。以下管一个1倍MinTime长度的数字段简称为“段”,我的要的东西是“3/4段”的大致位置。

(1)首先你先从第一个字节取一个值,按照你的规律必然是0(或者其他数字)。

(2)向后1/2MinTime取一个值,它必然也是0。你得到一个“1/2段”

(3)接着向后1/4MinTime取一个值,它必然还是0。至此,算法的第一个阶段结束,你得到一个“3/4段”的开始结束地址。从此以后这些数据就不会这么有规律了。

(4)从这个“3/4段”尾部向后取(MaxTime-MinTime)+1/4MinTime+1(如果MaxTime差别很小,也可以取1/4MinTime+1),如果MaxTime-MinTime在“算法允许的范围”内。得到的就必然是1。这个“算法允许的范围”究竟是多少,我暂时没时间给你细算。总之这是该算法成立的关键条件。

(5)从这个1的位置向后取“1/2段”,然后从“1/2段”尾部尝试向后再取“1/4段”,你可能有两种结局:一个是取到2,另一个是取到1。如果取到1那么你又获得一个“3/4段”,如果是2,那么从“1/2段”向前取“1/4段”。然后你的程序保留这个向前(或者向后)的优先取“1/4段”方向的状态,因为下一个方向相同的概率比较大。你可以从前面已知“段”的长度估算出前面还有多大的空间,而后面的空间则是不可能知道的。

(6)将上述算法继续下去,你会得到所有3/4长度的数字字符串的位置,精度在1/4MinTime范围内。对于一些应用来讲,你足以利用这些数据做点什么了。

该算法最理想的状态对一种数字段要读3次,最坏状态是读4次(如果是1/8精度,则需要4~5次)。根据MaxTime、MinTime的不同取值,还可能出现“3/4段”和“1倍段”共存的情况。

总之上面给你的是一种大致思路。


最后提醒6楼的老马:我至少半个月没上CSDN来逛了,被你这一念叨,昨天莫名其妙就来了。因此,乔布斯烧“五七”之前你千万别在半夜念叨他,你的召唤术潜质太邪门了,容易把他老人家魂儿招来。
kuye133 2011-10-14
  • 打赏
  • 举报
回复
求一名师傅!本人长期在线
隐身 2011-10-14
  • 打赏
  • 举报
回复
大家看这里,解释的比较详细。
http://bbs.pediy.com/showthread.php?t=141314
隐身 2011-10-14
  • 打赏
  • 举报
回复
大家看这里,,解释的比较详细
http://bbs.pediy.com/showthread.php?t=141314
用户 昵称 2011-10-13
  • 打赏
  • 举报
回复
串口通讯速度是多少?
zdingyun 2011-10-13
  • 打赏
  • 举报
回复
路过
波导终结者 2011-10-13
  • 打赏
  • 举报
回复
这么有规律的东西不进行数据压缩是很囧的做法

另外,你的算法描述好像也不太精确吧

假设MinTime=1000
那么 当GetPosValue(1)="0"的时候,GetPosValue(1000)肯定也等于"0",那么1--1000的长度就全是0,只需要调用两次GetPosValue函数就可以确定这段范围

这个明显只需要一次吧,当你知道(1)=0、min=1000的时候,(1000)不用取了吧,不是听得很明白啊……

为了提高速度,我们使用 MinTime + (MaxTime-MinTime)/2 来跳跃,但是这样的话,可以出现跳过的情况,比如000111222333444000,从开始的0跳过15个字符后,又是0

你之前说min=3,max=5,那这个怎么可能跳过15个?

你这个min和max值是只有一个,全部通用的吗?

如果不能做数据压缩的话,建议你这么做:

需要根据min和max的值来设计算法,比如min为1000,而max大于4000,就可以以4000为跨度来跳跃(这样不会出现跳过),确定了(4000)的值后,再决定(1001)到(4000)怎么处理。但如果max只有1000多不到2000,那就只能以2000为跨度来进行跳跃。将各个关键点的值确定之后,再往下走,具体怎么做取决于这个min和max的大小关系了。
king06 2011-10-13
  • 打赏
  • 举报
回复
最后要达到什么目的不是太明朗。。
大体看了下,顺便提下自己的拙见供参考:
1) 取这个跳跃的长度前可以先判断MinTime和MaxTime之间的关系,再行取值。
2)可以正向取值也可以考虑逆向取值,或者同时取。
3)如果知道固定出现一系列0,1,2,3,4 可想办法按这样的一大部分一大部分地处理。
lixiaolongxuexi 2011-10-13
  • 打赏
  • 举报
回复
额,看这个就头疼,学习中...
王二.麻子 2011-10-13
  • 打赏
  • 举报
回复
这不是一个实际问题,这是一个为了鼓捣算法而想出来的问题.实际应用谁都知道在设备那边随便压缩下数据,象这样重复度高的数据,压缩调99%没问题的.

LZ说可以运行误差,不知道误差100%可以不,嘿嘿

如果把一组0(若干个)1(若干个)2(若干)3(若干)4(若干) 组成的一个字符串叫做1帧,每个帧的最小长度是4*MinTime,最大长度是4*MaxTime.

一个帧的开始位置为A,用二分法跳跃的时候,现在从B位置开始跳跃,跳跃长度是C,如果B+C>A+4*MaxTime,这个跳跃就是失败的,要忽略,用更小的跳跃长度.这个判断在对后面数字的检测会有效.

另外跳跃长度C用长度的平方根比跳跃长度C用长度的一半会减少跳跃.

知识有限,..呵呵
咸清 2011-10-13
  • 打赏
  • 举报
回复
没看懂⋯⋯
split(ss,“40”)来分串吗?
无·法 2011-10-13
  • 打赏
  • 举报
回复
好难啊,要不你用c或者更底层的语言做中间件然后调用?

重金之下必有苦力
贝隆 2011-10-13
  • 打赏
  • 举报
回复
我想通常这样的运用都是用在航天飞机或永动机上的吧?
hd378 2011-10-13
  • 打赏
  • 举报
回复
非常感谢 spt_petrolor 和 alifriend 的回答.还有我想知道马哥的小仙妹妹来了没有哇?

spt_petrolor对问题的解释非常到位。不过,这不是一个纯粹的算法游戏。现在我们确实遇到了这样的设备。为了简化说明,我对这个问题描述的稍微抽象了,实际情况可能比这个还要复杂。

其实我们可以把这个存储字符串的串口设备看做一个黑盒,外界只能通过调用GetMaxPos()函数获得黑盒内字符串的总个数,通过GetPosValue()函数获得指定位置处的单个字符串的值,这个函数一次只能获取一个位置处的字符串值。然后我们已知这个黑盒内的字符串数量高达数GB,现在我们想采用一种高效的办法快速的把黑盒中数GB大小的字符串读出来。已知每调用一次GetPosValue()函数需要耗费大量的时间,所以问题就变成了:如何让调用GetPosValue()函数的次数最少?如果黑盒中的字符串杂乱无章,毫无规律,也就是拥有很大的煽值,那么显然没有太好的算法。幸运的是我们得知黑盒中的字符串存在一些规律,所以可以证明存在一种这样的算法,能够完整读出黑盒的内容,并且调用GetPosValue()函数的次数最少。

“如果把一组0(若干个)1(若干个)2(若干)3(若干)4(若干) 组成的一个字符串叫做1帧,每个帧的最小长度是4*MinTime,最大长度是4*MaxTime.”
非常正确,黑盒中的字符串可以认为是由很多个这样的帧组成的,而且因为存在MinTime和MaxTime,所以我们可以据此判断某个帧可能的大小,和帧内部某个连续字符串可能的长度,这样就能减少GetPosValue()函数的调用次数。其实现在比较麻烦的问题就是,如何确定分界处,也就是比如00000000000011111111111,确定每一帧里面的每一个小段,0和1的分界处在什么地方,这个应该是整个问题的关键了。

嗷嗷叫的老马 2011-10-13
  • 打赏
  • 举报
回复
汗,你总是遇到些BT问题啊,哈哈.

顶顶............

看看小仙妹来不来
现在还是人类 2011-10-12
  • 打赏
  • 举报
回复
没看懂具体想实现什么?具备什么条件?
有时候就是因为这种问题影响到架构,而提问的人还觉得架构是可行的,可能给出个不可能实现的虚拟问题。这种不明朗的问题,还是自己吞了吧。
libralibra 2011-10-12
  • 打赏
  • 举报
回复
是什么acm题吗?把原题贴出来吧
hd378 2011-10-12
  • 打赏
  • 举报
回复
不允许,只能使用GetPosValue函数一次读一个指定位置的字符。现在不考虑语言环境等因素,只考虑算法,有什么好办法吗?
加载更多回复(2)

7,763

社区成员

发帖
与我相关
我的任务
社区描述
VB 基础类
社区管理员
  • VB基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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