增加前置和追加百万次计算的速度



这段代码被用来进行一百万次计算,我不太明白为了提高速度需要做些什么。我得到了包含一个新数组(tempIndex,目前未使用)的提示,但不知道如何利用它,也不知道在get()和set()方法中需要更改什么。

public class IAList {
private int[] a;
private int length;
private int[] tempIndex;
public IAList() {
length = 0;
a = new int[4];
tempIndex = new int[4];
}
public int get(int i) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException(i+"");
}
return a[i];
}
public int size() {
return length;
}
public void set(int i, int x) {
if (i < 0 || i >= length) {
throw new IndexOutOfBoundsException(i+"");
}
a[i] = x;
}
public void add(int x) {
if (length >= a.length) {
int[] b = new int[a.length * 2];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
a = b;
}
a[length] = x;
length = length + 1;
}
public void addBefore(int x) {
if (length >= a.length) {
int[] b = new int [a.length * 2];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
a = b;
}
int i;
for (i = a.length - 1; i > 0; i--) {
a[i] = a[i - 1];
}
a[0] = x;
length++;
}
}

如图所示,计算完成,但需要几分钟。我该怎么办?

Now starting tests. You'll see an 'Everything looks OK!' message if everything is ok!
Creating two IALists, a and b.
Adding the numbers 1 through 10 to a by calling a.add(1); a.add(2); etc.
Initial functionality looks OK. Now testing addBefore with a single value.
OK. Now addBefore-ing 90 more values.
OK. Now adding 200 values.
OK. Now alternating addBefore and append with a bunch of values.
OK. Now running a speed test addBefore-ing and adding a million values.
OK. Now running a speed test alternating addBefore and append with 100,000 zeros.
OK, but you took 192675 milliseconds, which seems too long (I'm expecting 1000 or less)```

您似乎编写了一个类似列表的数据类型,支持添加添加'在后面'或添加'在前面'。

添加'at The back'的代码并不是真正的问题。它只设置单个数组索引,除非后备数组被"填满",在这种情况下,它会复制数组。是的,System.arraycopy在理论上更快,但在实践中通常不会有特别明显的差异。请随意阅读System.arraycopy并使用它,但它不会提供显着的速度提升。

另一方面,在前面加上'at The front'的代码,哦,天哪。这将在每次时在整个数组中运行一个完整的循环调用它是为了将所有元素上移一个槽以腾出空间。

这就是问题所在。随着列表越来越大,这将花费越来越长的时间。System.arraycopy不会解决这个问题。

但是有一个解决方案:RINGS

.不要把你的int[]想象成一个有开始和结束的东西,就好像它是一条线,把那条线弯曲,使两端接触。这是一个戒指。考虑new int[100]。想象一下,我们声明这件事既没有开始也没有结束,就像一个戒指。它不是无限大的(它是"100大"),但是,在56号元素之后是57号元素。元素99之后是元素0。要做到这一点,请想象0或99没有什么特别之处。它只是另一个索引,并且所有索引都有next和previous。您需要在代码中使用一些非常简单的数学来模拟这个环(从0开始"向下"应该从length -1开始,从length -1插槽开始"向上"应该到0,但这就是您真正需要的)。

在当前的实现中,假设有一个大小为150的后备数组,但数据结构的长度只有100。在这种情况下,该数组索引0到99处的元素表示列表中的实际元素,其余50个槽仅供将来使用。

在环中,只使用环的一个弧。在您当前的实现中,包含实际数据的后台数组中的子范围的"开始"固定为0,并且您使用length字段跟踪子范围的"结束"。

这里的巫毒魔法是,用一个环,通过浮动开始,你现在可以从前面和后面快速生长.

要在后面添加元素,请在正确的位置添加元素并增加"子列表结束"标记。要将元素添加到前面,请将它们添加到正确的位置并减去"子列表的前面"标记。请记住,这是一个环,所以如果当前后备数组容量为100,那么"向结束标记添加1"可能涉及将99转换为0。类似地,"将起始段递减1,然后将该数字写入空格"可能涉及"递减0",这将意味着索引变为99。

您仍然需要编写代码来创建一个新的更大的数组,并在后备数组容量耗尽时复制所有元素。

get(int idx)变得有点复杂。例如,如果后台数组有150个元素,'start'当前位于140,'end'位于88,那么.get(55)意味着您需要返回backingArray[45]。幸运的是,如果你知道模算子(%),数学就很简单。模块操作符类似于除法,只是它返回余数并丢弃实际结果。所以,start + index % backingArray.length是你想要的:(140 + 45) % 150是45,正如你想要的。

您需要使用一些概念,并尝试使用模来以简洁的方式编写代码。

请记住,目标是而不是在后面的前面的add-to- front操作中复制整个数组,除非后面的数组被填满。环形解决方案是你如何到达那里。

最新更新