资讯中心

紧凑数组删除对象时为什么会改变另一对象的位置:独立观察第04例

2026-11-10

为了让活动对象连续存放,可以在删除某项时用最后一项补上空位。这避免搬动整段数组,但会让被搬来的对象改变位置,所以必须同时维护编号到位置的对应表。

对象编号为零至十一。紧凑数组按当前存放顺序保存编号,索引表按对象编号保存它的位置,不存在记为空。插入已有对象不做事;删除存在对象时把末项移到它的位置,再更新两个编号对应的索引。

输入与计算记录

add表示加入,remove表示移除;表中None表示不存在或本步没有搬移对象。

事件 | 紧凑数组 | 编号零至十一的索引 | 搬来的编号 ['add', 5] | [5] | [None, None, None, None, None, 0, None, None, None, None, None, None] | None ['add', 2] | [5, 2] | [None, None, 1, None, None, 0, None, None, None, None, None, None] | None ['add', 4] | [5, 2, 4] | [None, None, 1, None, 2, 0, None, None, None, None, None, None] | None ['add', 1] | [5, 2, 4, 1] | [None, 3, 1, None, 2, 0, None, None, None, None, None, None] | None ['add', 8] | [5, 2, 4, 1, 8] | [None, 3, 1, None, 2, 0, None, None, 4, None, None, None] | None ['remove', 6] | [5, 2, 4, 1, 8] | [None, 3, 1, None, 2, 0, None, None, 4, None, None, None] | None ['add', 7] | [5, 2, 4, 1, 8, 7] | [None, 3, 1, None, 2, 0, None, 5, 4, None, None, None] | None ['add', 6] | [5, 2, 4, 1, 8, 7, 6] | [None, 3, 1, None, 2, 0, 6, 5, 4, None, None, None] | None ['add', 10] | [5, 2, 4, 1, 8, 7, 6, 10] | [None, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['add', 4] | [5, 2, 4, 1, 8, 7, 6, 10] | [None, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['add', 0] | [5, 2, 4, 1, 8, 7, 6, 10, 0] | [8, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['add', 10] | [5, 2, 4, 1, 8, 7, 6, 10, 0] | [8, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['remove', 11] | [5, 2, 4, 1, 8, 7, 6, 10, 0] | [8, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['add', 8] | [5, 2, 4, 1, 8, 7, 6, 10, 0] | [8, 3, 1, None, 2, 0, 6, 5, 4, None, 7, None] | None ['remove', 10] | [5, 2, 4, 1, 8, 7, 6, 0] | [7, 3, 1, None, 2, 0, 6, 5, 4, None, None, None] | 0 ['remove', 9] | [5, 2, 4, 1, 8, 7, 6, 0] | [7, 3, 1, None, 2, 0, 6, 5, 4, None, None, None] | None ['remove', 1] | [5, 2, 4, 0, 8, 7, 6] | [3, None, 1, None, 2, 0, 6, 5, 4, None, None, None] | 0 ['remove', 9] | [5, 2, 4, 0, 8, 7, 6] | [3, None, 1, None, 2, 0, 6, 5, 4, None, None, None] | None ['add', 9] | [5, 2, 4, 0, 8, 7, 6, 9] | [3, None, 1, None, 2, 0, 6, 5, 4, 7, None, None] | None ['remove', 1] | [5, 2, 4, 0, 8, 7, 6, 9] | [3, None, 1, None, 2, 0, 6, 5, 4, 7, None, None] | None ['add', 7] | [5, 2, 4, 0, 8, 7, 6, 9] | [3, None, 1, None, 2, 0, 6, 5, 4, 7, None, None] | None ['add', 1] | [5, 2, 4, 0, 8, 7, 6, 9, 1] | [3, 8, 1, None, 2, 0, 6, 5, 4, 7, None, None] | None

阅读记录时先确认编号代表的是格子、对象、时间还是数组位置,再逐项对应输入与输出。中间状态提供了定位差异的依据,最后的一个合计数字不能替代全部步骤;没有对象的集合与数值零也应区别看待。

怎样检查这个结果

每个紧凑位置i的编号x必须满足索引表x等于i,所有非空索引都要指回正确编号。对象身份由编号决定,不等于当前数组下标;删除后继续使用旧下标可能指向另一个对象。

这是单线程的集合存储模型,未处理对象编号复用、并发迭代或外部长期持有引用。若遍历时删除元素,也要注意补来的末项是否还需要在当前轮被处理,不能机械地跳到下一个下标。

可以遮住结果表先按规则复算第一步,再选择一个边界输入重复操作。若更换规则中的任一前提,应重新推导结果,不要只调整最后答案。这样既能理解本例为何成立,也能看清它在哪些情况下不再适用。