我的导师提供了伪代码:
Q <- empty
for i from 1 to n-1 do:
x <- smallest of { head(F), head(Q) }
dequeue(x)
y <- smallest of { head(F), head(Q) }
dequeue(y)
enqueue(x+y) -> Q
return head(Q)
其中F [n]是一个有序数组,Q是我们需要使用堆栈的队列。
因此,我们可以使用push for enqueue,但我们需要另一个寄存器来跟踪出队位置
据我所知,如果我们采取F[5] = {3, 5, 7, 9, 12}
。首先,3+5=8
加入队列。然后,我们将8
与7
和9
进行比较。由于8
和7
最低,我们将8+7=15
排入队列。同样,9+12=21
已排队,但现在我们必须将其添加到队列中的上一个值15
,以获得总计36
。最后一部分让我困惑,因为我的导师告诉我,队列应该很容易拥有总价值。
编辑:我认为我的导师意味着要添加两个最小的浮点数,然后添加两个最小的等等。
答案 0 :(得分:0)
对于F[5] = {3, 5, 7, 9, 12}
,数据将如下所示:
creating empty Q queue, new Q {}
i = 1 (for i: 1..4)
F: {3, 5, 7, 9, 12}
Q: {}
x = 3 from F, new F {5, 7, 9, 12}
y = 5 from F, new F {7, 9, 12}
Q.add(3+5), new Q { 8 }
// next loop
i = 2 (for i: 1..4)
F: {7, 9, 12}
Q: {8}
x = 7 from F, new F {9, 12}
y = 8 from Q, new Q {}
Q.add(7+8), new Q { 15 }
// next loop
i = 3 (for i: 1..4)
F: {9, 12}
Q: {15}
x = 9 from F, new F {12}
y = 12 from F, new F {}
Q.add(9+12), new Q { 15, 21 }
// next loop (last one)
i = 4 (for i: 1..4)
F: {}
Q: {15, 21}
x = 15 from Q, new Q {21}
y = 21 from Q, new Q {}
Q.add(15+21), new Q { 36 }
return Q.head() = 36 (which is sum of original F)
该算法以这样的方式设计,即Q队列的生成内容也将保持排序,就像F输入一样,因此最小可用值始终是F或Q队列头之一,即&#39;为什么选择x
和y
的规则只检查F或Q的头部并选择较小的一个。
因此,在实际代码中,您必须为队列方法.head(), .pop_head(), .push_back()
编写逻辑,并将其用于两个独立队列(F和Q)。
如果你想编写最小的内存使用量,并且F已经在堆栈中(底部地址的值最小!):你知道F只会在计算期间(没有增长)和F的总大小清空。 size + Q.size总是小于n
,所以你可能实际上在内存中的F下开始新的Q,然后向上增长,覆盖F的原始最小成员,重用Q内存的堆栈内存,然后汇编代码不需要任何额外的内存来处理(它只会破坏原始的F数组内容......呃,在这种情况下F输入就像数组一样)。
即。来自底部地址的堆栈内存看起来像(在每个for循环的开头/结尾):
i = 1 (for i: 1..4) beginning:
stack content (starting at "base" address, dword values, thus *4 in pointers):
{3, 5, 7, 9, 12}
F[head, end] = [stack_base_adr, stack_base_adr+5*4] ; 5x dword value
Q[head, end] = [stack_base_adr, stack_base_adr] ; empty
^^ I mean head/end pointers pairs
i = 1 (for i: 1..4) end:
stack: {8, 5, 7, 9, 12}
F[head, end] = [stack_base_adr+2*4, stack_base_adr+5*4] ; starts at 7
Q[head, end] = [stack_base_adr, stack_base_adr+1*4] ; starts at 8
i = 2 (for i: 1..4) end:
stack: {8, 15, 7, 9, 12}
F[head, end] = [stack_base_adr+3*4, stack_base_adr+5*4] ; starts at 9
Q[head, end] = [stack_base_adr+1*4, stack_base_adr+2*4] ; starts at 15
i = 3 (for i: 1..4) end:
stack: {8, 15, 21, 9, 12}
F[head, end] = [stack_base_adr+5*4, stack_base_adr+5*4] ; empty
Q[head, end] = [stack_base_adr+1*4, stack_base_adr+3*4] ; starts at 15
i = 4 (for i: 1..4) end:
stack: {8, 15, 21, 36, 12}
F[head, end] = [stack_base_adr+5*4, stack_base_adr+5*4] ; empty
Q[head, end] = [stack_base_adr+3*4, stack_base_adr+4*4] ; starts at 36
所以你需要4个指针来跟踪F / Q开始/结束,并且......好吧..简单地写它,对我来说看起来不会让人感到困惑或复杂,其中大部分会翻译1: 1到汇编指令,如mov
从队列加载/存储值,add
添加值和更新队列指针。哦,你有float
个值,所以加载/存储/添加值本身不是mov/add
整数指令,而是FP(旧x87或现代SSE2,无论你想学习哪个+使用)。
尝试写一些东西,慢慢地,例如用空for
循环代码开始,学会使用调试器来检查它是否按预期循环并正确退出,然后添加F队列输入,再次检查调试器内部循环堆栈是否包含预期的值(也决定如何给出F队列的子例程n
大小)。
然后尝试选择&#34; pop&#34;单个队列中的值仅使用F头/结束指针,并返回最后一个弹出值(带有样本值的12)。然后尝试在临时中弹出+ sum并返回(结果是正确的36,但是内部循环实现不正确,#34;作弊&#34;它)。
此时你应该很好地调试和编写基本的asm代码,所以添加Q队列代码应该是可行的。
必不可少的是学会使用调试器,并以小步骤添加asm代码,通过单步执行每条指令来观察结果,并推断你写的是什么,为什么,以及它是否真的做了你想要的(具有真正的目的)当我编写上面的子步骤时,目标算法或子步骤目标,避免只是在这里和那里移动数据/指针而不清楚地识别每条指令的目的,因为这通常会导致臃肿,难以调试的汇编代码,没有多大意义)。
另外,为了清晰和简洁,以及针对性能的手写组装,手写组件之间存在巨大差异。听起来你应该瞄准第一个。我上面的评论旨在向您展示&#34;设计内存内容在运行期间将如何发展,这是DDD和数据驱动设计的方式。思维。这种思维方式特别适合汇编编程,但即使在高级语言中也不要犹豫,因为纯OOP思维方式有时会导致过于复杂和愚蠢的设计,用数据结构重新检查架构记住,通常可以通过重新组织数据结构以更好地适应目标机器架构,为您提供如何简化任务的新想法,或者至少获得巨大的性能优势。
......不,我不会为你写任何asm代码。