我有一个数组,其中每个元素比前一个元素\ $ \ {x_i = x_ {i-1} \ pm 1 \} \ $少一个或多一个。我希望在不到\ $ O(n)\ $的时间内找到其中的元素。我是这样实现的:

public int searchArray(int[] arr, int i, int elem) {

    if (i > arr.length - 1 || i < 0) {
        return -1;
    }

    if (arr[i] == elem) {
            return i;

    } else {
            int diff = Math.abs(elem - arr[i]);
            int index = searchArray(arr, i + diff, elem);
            if (index == -1) {
                index = searchArray(arr, i - diff, elem);
                if (index == -1) {
                    return -1;
                }
            }
            return index;
    }
}


并这样称呼它:

int[] arr = {2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 6, 5, 4, 3, 4};
int index = searchArray(arr, 0, 3);


它正在工作很好,但是可以改进吗?具体来说,有没有一种方法可以反复进行?并且当前算法小于\ $ O(n)\ $。我想是的,但我不确定。

评论

@DavidHarkness令我惊讶的是,它并没有进行无限递归。实际上,这种迭代方法已中断。内存不足。

那是因为堆栈超出了可用内存。如果内存是无限的,则该方法将永远不会终止。不过,我看不到递归方法如何工作。

当您说“小于O(n)”时,是指最差的情况O(n)是不可接受的,还是O(n-1)很好,尽管在复杂度上等于O(n),因为它小于上)?我会假设是前者,但是如果不比较除一个元素之外的所有元素,似乎就无法在{1,2,1,2,...}中搜索3。我可以看到通过将数组分为升序和降序来减少平均运行时间,但是对于交替元素数组中不匹配的情况,这不会解决最坏的情况。

我想知道短语“小于O(n)”是否具有定义明确的含义。 O(n)已经暗示算法占用的资源上限。请记住,O(n / 2)不小于O(n)。如果问题要求使用O(log(n))算法,那将是一个有意义的问题。

似乎有一些关于O(n)与O(n / 2)的争论。早在计算机运行缓慢且汇编程序员很出色的时候,经常听到“永不忽略k”一词。当我评论时,这想到了:复杂性!=运行时间。对于较大的n,运行时间是k的O倍。 “ O(n / 2)”最简陋,最好的缩写是“ O(n),其中k =某参考算法的1/2”。 O(n / 2)的复杂度并不比O(n)好。 k(n / 2)的运行时间优于k(n)。

#1 楼

虽然其他答案很有意义,但我不得不怀疑您为什么使用递归。这是一个用for循环解决的简单问题。

我假设您不应该从索引0以外的任何索引开始,所以请考虑以下例程:

public int searchArray(int[] arr, int elem) {

    for (int i = 0; i < arr.length; ) {
        if (arr[i] == elem) {
            return i;
        }
        i += Math.abs(elem - arr[i]);
    }
    return -1;
}


(如果需要在数组中途开始搜索,则可以再次添加offset输入参数,然后从中开始输入i)。

The底线是递归过大,此系统为\ $ O(n)\ $,但是每个循环的成本都小于使用递归的同一事物。

我不知道有什么方法用高于\ $ O(n)\ $复杂度的系统解决给定问题。


讨论复杂度-为什么是\ $ O(n)\ $

这个答案引起了很多关于复杂性的讨论,这种方法最多只扫描输入数组的一半成员,因此应该是复杂性\ $ O \ left(\ frac { n} {2} \ right)\ $而不是\ $ O(n)\ $。给定的参数类似于:


考虑最坏情况的数据1,2,1,2,1,2,1,2,1,2,1,2和搜索项3。对于这种情况,该方法将从data[0]开始,然后跳至data[2]data[4],依此类推。它将永远不会检查data[1]和其他奇数索引的数据点。如果搜索词与实际数据的差异更大(例如100),则该方法将在data[0]处进行一次比较,然后返回“未找到” -1


这是一个有趣的观察,该方法最多只需要扫描一半的数据。考虑到“天真的”方法,该方法特别有趣,它只一次扫描一个成员,然后在找到值时返回。这种“天真的”方法肯定具有\ $ O \ left(n \ right)\ $“性能”和复杂性,并且“跳过方法”的速度将是其两倍以上。

不过要注意的重要一点是,算法如何相对于数据量而不是彼此进行缩放!

因此,请考虑一组假设的最坏情况数据1,2,1,2,1,2,....和搜索项3。这个假设恰好是由跳过方法在4毫秒内搜索的,而由朴素方法在8毫秒内搜索的。现在我们将数据量加倍,会发生什么?两种方法的处理时间将加倍!

在两种情况下,每增加一倍的数据量,算法的性能就会加倍。这就是使这两种算法\ $ O(n)\ $变得复杂的原因。来自Wikipedia:


在计算机科学中,大O表示法用于根据算法对输入大小变化的响应方式(例如,在处理时间或工作空间要求方面)对算法进行分类。 br />

通过建议跳过方法具有\ $ O \ left(\ frac {n} {2} \ right)\ $复杂性来反转参数,然后我希望,如果我将数据加倍,执行时间只会增加一半或50%。对于跳过方法(也不是幼稚方法),这显然是不正确的。

这两种方法都具有\ $ O(n)\ $的复杂性,因为它们都以相同的方式随着体积的增加而缩放数据。

但是,仅仅因为它们以相同的方式缩放,并不意味着一种方法并不比另一种方法好。

评论


\ $ \ begingroup \ $
@ user3011937在我的示例中,解决方案始终会从起始位置找到第一个匹配值(我将其设置为0,但可以通过添加参数轻松地对其进行更改)。由于数据规则(index [n] = index [n-1] +/- 1,因此您始终可以向前移动,而不必向后移动。我认为您应该对各种解决方案进行一些调试,或者工作他们写在纸上。。。确实如此。
\ $ \ endgroup \ $
–rolfl
13年12月3日,12:23

\ $ \ begingroup \ $
“一个好的程序员知道如何使用递归。一个好的程序员知道什么时候不使用递归。” - 匿名
\ $ \ endgroup \ $
–插件
13年12月3日,13:57

\ $ \ begingroup \ $
这是一个很好的算法,但是什么使任何人认为它花费的时间少于O(n)?当然,它不需要检查数组中的所有n个元素,但是说花费的时间少于O(n)则意味着随着n的增长,完成算法的时间比n增长的速度更慢。有任何证据吗? (比方说平均情况,更不用说最坏的情况了。)
\ $ \ endgroup \ $
– LarsH
2013年12月3日14:58



\ $ \ begingroup \ $
@xuwicha-确实,但是O(n / 2)仍然是O(n)
\ $ \ endgroup \ $
–rolfl
2013年12月4日下午3:45

\ $ \ begingroup \ $
@xuwicha-实际上,如果将数据量增加一倍,该算法将花费两倍的时间,因此O(n)...
\ $ \ endgroup \ $
–rolfl
2013年12月4日下午3:59

#2 楼

问题出在\ $ O(n)\ $中。考虑200_success描述的情况。

您有一个1和2交替排列的序列,其中单个1被3代替。

当您要求搜索3时,在检查了第一个元素,它将具有偶数索引。但是,如果每个奇数索引都包含2,那么任何偶数索引都可以包含3,因此您不能保证3不在搜索的最后一个索引中。这意味着您将不得不查看\ $ O(\ dfrac {n} {2})\ $个地方。 \ $ O(\ dfrac {n} {2})\ $ = \ $ O(n)\ $,因此问题是\ $ O(n)\ $。

没有正确的算法可以提供比这更好的最差时间性能。

更有趣的问题是,如果您知道没有数字出现超过某个固定上限,会发生什么。 br />

评论


\ $ \ begingroup \ $
这应该是公认的答案,因为它是迄今为止唯一可以正确解决找到“小于O(n)”算法问题的答案。
\ $ \ endgroup \ $
– LarsH
13年12月3日在15:04

\ $ \ begingroup \ $
@LarsH Well OP还询问如何改进他的算法。从上下文来看,可以公平地假设这是问题中最重要的部分。因此,对于哪个答案被接受,我并不感到很遗憾。 Esp,因为这是codereview而不是cstheory。
\ $ \ endgroup \ $
–塔米尔
2013年12月3日15:49

#3 楼

如果从0开始,就不需要向后看。

通过对算法步骤j的归纳来证明:

对于j = 0,i(j)= 0,您不能

对于j> 1,有两种情况:第一种,我们找到了我们的数字。第二个是diff(j) = abs(elem - array[i(j)])。那么array[i(j),i(j)+diff)中的任何数字都不能包含elem。根据归纳假设,array[i(j-1),i(j)=i(j-1)+diff(j-1))中的任何元素也不包含该数字。因此,i的下一个可能的索引是i(j)+diff(j) (= i(j+1))

,但正如Taemyr已经回答的那样,该算法确实在O(n)中。

评论


\ $ \ begingroup \ $
知道了。非常好。非常感谢您的归纳:)让我澄清自己。如果有的话,只是前进将使我获得要素。如果还有更多内容,我会自动获得第一个元素对吗?
\ $ \ endgroup \ $
–user3011937
2013年12月3日12:40



\ $ \ begingroup \ $
@ user3011937不仅如此,而且由于您始终朝一个方向前进,因此消除了无限循环的可能性。
\ $ \ endgroup \ $
– David Harkness
2013年12月3日23:19

#4 楼

以下是一些通用建议:


避免使用单字母参数名称。对于小循环,i很好,但是它不应泄漏到方法签名中,尤其是当index仅是四个字母时。

if-else一致。您有两个均从该方法返回的if块,或者同时使用else或都不使用。否则,乍一看似乎没有退出。

if (x)
    return foo;
else if (y)
    return bar;
else
    baz


if (x)
    return foo;
if (y)
    return bar;
baz


if (index == -1) return -1;是多余的,因为它紧随其后紧跟着return index;
Math.abs是不必要的,因为先添加然后减去diff。这里的顺序无关紧要。
我将颠倒ielem参数,并添加一个忽略i的重载形式,并简单地用i = 0调用另一个。这为调用者提供了更好的API。如果仅在外部需要后一种形式,则三参数版本也可以专用于包含这两种形式的类。

关于您的时间复杂性问题,我的直觉说,对于最糟糕的情况是导致无限循环,但我还没有深入研究实际算法。

要迭代地重写此算法,您需要使用堆栈来允许回溯,因为有时您需要双向分支。这是伪代码中的一个快速刺探:

search(int[] arr, int find)
    return search(arr, find, 0)

search(int[] arr, int find, int index)
    stack = [-1]
    while (index != -1)
        if (0 <= index && index < arr.length)
            elem = arr[index]
            if (elem == find)
                return index
            else
                diff = find - elem
                stack.push(index + diff)
                stack.push(index - diff)
        index = stack.pop();
    return -1


评论


\ $ \ begingroup \ $
大卫,你好感谢您的宝贵提示。根据建议,我已经在原始代码中进行了修改。但是迭代方法不起作用。它总是返回-1。
\ $ \ endgroup \ $
–user3011937
2013年12月3日在6:25

\ $ \ begingroup \ $
好吧,如果差异为-1,则您的代码将失败。这就是目前正在发生的事情。如果我搜索3,并且第一个元素是2,它将中断。我正在尝试修复它。
\ $ \ endgroup \ $
–user3011937
2013年12月3日在6:27

\ $ \ begingroup \ $
修复它。循环前无需将任何东西压入堆栈。我将外部while循环更改为while(true);,并删除了它后面的return语句。非常感谢。如果您有更好的方法来解决此问题,除了(true)以外,请分享。
\ $ \ endgroup \ $
–user3011937
2013年12月3日6:29



\ $ \ begingroup \ $
@ user3011937堆栈中的初始-1值表示何时我们用完要检查的索引,因此找不到该元素。您可以使用stack.isEmpty检查执行相同的操作,但是我发现使用毒药值更简单。请注意,我使用缩进而不是花括号,因为它是psuedocode:两个推调用必须仅在带有diff的else块中发生。
\ $ \ endgroup \ $
– David Harkness
2013年12月3日6:36



\ $ \ begingroup \ $
@ChrisWue我在解释自己方面做得很差。希望我对第二个项目符号的编辑更加清晰。
\ $ \ endgroup \ $
– David Harkness
2013年12月3日,下午6:52

#5 楼

如果可以使用O(n)时间只建立一次查找表,则可以在O(1)时间完成。如果数组保持不变,并且您要搜索多次,那么这可能是一个好方法。以下伪代码假定您要搜索整个数组(从索引0开始),并返回找到该元素的第一个索引。可以很容易地对其进行修改以从索引> 0开始搜索,并且还告诉您所有索引元素所在的位置。

假设您的输入数组称为arr,arr的长度为n,而arr [0]是k。我们知道arr中的值在[k-n + 1,k + n-1]范围内,总共有2n-1个不同的值。对于范围内的每个可能的整数,我们在查找表中为其输入一个条目:

// Initialization
for i = 0 to 2n-2 
    lookup[i] = -1

k = arr[0]

// Build lookup-table
for i = 0 to n-1
    index = arr[i]-k+n-1
    if lookup[index] == -1
        lookup[index] = i // We only store the position in arr of the first occurrence


// Search for, say, s (assuming s is in the valid range, no check for it here)
lookup[s-k+n-1] // A result >= 0 is a hit, giving the (first) position of s in arr


#6 楼

public static int search(int[] array, int start, int end, int target)
{

    if(array[start] == target)return start;
    if(array[end] == target)return end;
    if(Math.abs(array[start]-target) + Math.abs(array[end]-target) >= end-start+1)
        return -1;


    int middle = (start+end) / 2;

    int val = search(array, start, middle, target);
    if(val != -1)return val;

    val = search(array, middle+1, end, target);
    if(val != -1)return val;

    return -1;
}


这就是我想出的。它实际上将列表拆分到中间。每次拆分后,它都会检查目标是否可能在此数组中。 Math.abs(array[start]-target) + Math.abs(array[end]-target) >= end-start+1涵盖了这一点(它使用了一个事实,即您必须从第一个数字获取目标,然后再向下返回到数组中的最后一个数字)。如果有可能,我们将继续进行拆分,直到目标是范围的开始或结束。

有关如何剪切的示例,请考虑以1开始和结束的数组,它的长度仅为5。要求您在其中找到4。您知道这是不可能的,因为您需要花费3个插槽来增加到4个,然后再将3个插槽减少到1个。这意味着长度必须至少为7。因此我们可以立即返回-1。

甚至在12121212121212 ..... 321212的情况下甚至确实有所帮助,因为您经常会得到121的子列表,不能包含3。

就是这样。在我看来,最糟糕的情况仍然是O(n)。尽管如果具有次线性平均情况,我不会感到惊讶。

评论


\ $ \ begingroup \ $
这是一个很好的建议... OP的问题未指定结果是否必须为“第一”索引。如果问题是是否受到打击,您的解决方案将给出O(log(n))。但是,即使那样,使用正确的深度优先方法也可以很好地工作。简单循环的最佳情况会更快,但最坏的情况比简单循环的最坏情况要好得多。
\ $ \ endgroup \ $
–rolfl
13年12月3日在17:14

\ $ \ begingroup \ $
实际上,我已经研究了您的解决方案,我认为最坏的情况要比简单循环选项的最坏情况慢。对于数据1,2,1,2,1,2,1,2,1,2,1,2,1,2,3,2,1,它会执行与简单循环一样多的操作,并带有递归开销。您的解决方案在性能上与循环非常相似,循环也会“跳过”无法获得值的块。
\ $ \ endgroup \ $
–rolfl
13年12月3日在17:27

\ $ \ begingroup \ $
@rolfl此解决方案确实找到了第一个索引。因为我先递归上半年。当上半部分未返回时,我仅查看下半部分。但是,是的,它仍然存在最坏的情况。我只是认为我会提出一种稍微不同的解决问题的方法:)
\ $ \ endgroup \ $
– Cruncher
13年12月3日在17:37

\ $ \ begingroup \ $
我一直在研究您的解决方案,这引起了我的兴趣。但是,在我的(几乎)最终分析中,迭代解决方案将始终在递归解决方案之前找到第一个匹配解决方案,并且将始终需要较少的比较。尽管起初我虽然认为您的解决方案具有log(n)复杂性,但令人惊讶的是,我现在确信它也是纯O(n)。仅当目标是返回第一个比赛时,这才成立。
\ $ \ endgroup \ $
–rolfl
2013年12月3日21:04



\ $ \ begingroup \ $
@rolfl很高兴我吸引了您xD。实际上,可以证明O(n)是最优的最坏情况。我从一开始就以为。我更感兴趣的是平均情况,我在分析时很糟糕(更不用说此解决方案的分析难度了)。您是否认为平均可以达到log(n)的复杂性?
\ $ \ endgroup \ $
– Cruncher
2013年12月3日21:20



#7 楼

该函数应为

public static int searchArray(int[] array, int value) {
    // Call private static int searchArray(int[] array, int index, int value)
    return searchArray(array, 0, value);
}


…因为调用者可以选择任何起始索引,则结果可能是错误的。 (在您的示例中考虑searchArray(arr, 12, 6)。)这些函数应为static,因为它们不依赖任何实例变量。

我认为最坏的情况至少是O(n),如以下示例所示:

int[] arr = {1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3};
int index = searchArray(arr, 0, 3);


评论


\ $ \ begingroup \ $
您有更好的方法吗?那么最坏的情况是小于O(n)吗?如果我们从中间开始怎么办?还是一些随机的位置?
\ $ \ endgroup \ $
–user3011937
2013年12月3日,7:18

\ $ \ begingroup \ $
从中间开始最多可以将时间缩短一半。 O(n / 4)仍然是O(n)。同样的论点也适用于其他任何地方。
\ $ \ endgroup \ $
– 200_success
2013年12月3日,7:25

#8 楼

这里有很多很好的答案,但我想对这个问题taking之以鼻。

如果您的任务是使用\ $ O(n)\ $或更小的复杂度来搜索数组,我最自鸣得意的建议:

int search(int[] data, int value) {
    for (int i = 0 ; i < data.length ; i++) {
        if (data[i] == value) {
            return i;
        }
    }
    return -1;
}


所以我怀疑是这样;要么原始的提问者不正确地使用大\ $ O \ $来表示运行时间,否则您实际上使搜索变得过于复杂。

假设它们意味着运行时间\ $ R
您似乎了解到可以通过跳过两个值\ $ x \ $和\ $ y \来节省时间$分别位于索引\ $ i \ $和\ $ j \ $中,其中\ $ i
不建议使用递归,因为它通过占用堆栈扩展了算法的存储要求,使算法的复杂性更高难以评估,通常会增加\ $ k \ $。完全可以使用一个循环来搜索,循环很容易显示为\ $ O(n)\ $时间和\ $ O(k)\ $空间复杂度。

int searchWeirdlyOrganizedArray(int[] data, int forValue) {
    int i = 0;
    while (i < data.length) {
        if (data[i] == forValue) {
            return i;
        } else {
            i += abs(forValue - data[i]);
        }
    }
    return -1;
}


最后的注释:我的评论有误,要搜索\ $ \ lbrace1、2、1、2,\ dots \ rbrace \ $需要3,就需要进行\ $ N \ $比较。它需要\ $ \ frac {N} {2} \ $比较,因为所有2都被跳过了。如果2和1颠倒了(\ $ \ lbrace2、1、2、1,\ dots \ rbrace \ $),则需要\ $ 1 + \ frac {N-1} {2} \ $比较,这只是初始比较使搜索前进1,但此后每2跳过一次。但是,我是对的,它仍然是\ $ O(n)\ $复杂性。

评论


\ $ \ begingroup \ $
有点困惑。我试图查看您的建议代码和接受答案的代码之间是否有任何显着不同(除了while-vs-for循环之外)。我想念什么吗?
\ $ \ endgroup \ $
–rolfl
2013年12月24日17:11

\ $ \ begingroup \ $
@rolfl代码看起来没有什么不同。我不喜欢for在循环体内修改i。对OP的代码的讨论和比较有些不同。另外,如果按字面解释,我也提供了如前所述的轻松解决方案。我可能应该同意同意的答案,是吗?
\ $ \ endgroup \ $
–狡猾地
2013年12月24日23:27

#9 楼

我认为您不会找到比\ $ O(n)\ $更好的最坏情况。但是,如果您要对一个数组进行大量查询(即检查单个数组中的多个数字),则可以使用计数排序。

这里可以做一个改进,可以在\每个查询$ O(n)\ $预处理和\ $ O(1)\ $时间。也就是说,您可以在\ $ O(t + n)\ $的时间内执行\ $ t \ $查询,而不是在\ $ O(nt)\ $的时间内进行查询,就像使用当前方法一样。

一种明显的方法是在\ $ O(nlogn)\ $中对数组进行排序,并在\ $ O(logn)\ $中进行二进制搜索。对于\ $ t \ $查询,时间为\ $ O(tlogn + nlogn)\ $。但这仍然可以改善。

您可以使用计数排序以\ $ O(n)\ $时间对数字进行排序,但是这种方法存在一个权衡。也就是说,如果数组中的最大数字为\ $ k \ $,则RAM中需要一个大小为\ $ k \ $的数组,这在极少数情况下会引起问题。

这代码仅适用于非负整数。稍作更改,该算法也适用于负整数。

#10 楼

这是一个不同的解决方案:

public static void radixSort(int[] data) {
    boolean flag = true;
    int divisor = 1;
    Queue [ ] buckets = new Queue[10];
    for (int i = 0; i < 10; i++)
            buckets[i] = new LinkedList();

    while (flag) {
            flag = false;
            // first copy the values into buckets
            for (int i = 0; i < data.length; i++) {
                    int hashIndex = (data[i] / divisor) % 10;
                    if (hashIndex > 0) flag = true;
                    buckets[hashIndex].add(data[i]);
            }
                    // then copy the values back into vector
            divisor *= 10;
            int i = 0;
            for (int j = 0; j < 10; j++) {
                    while (! buckets[j].isEmpty()) {
                            Integer ival = (Integer) buckets[j].element();
                            buckets[j].remove();
                            data[i++] = ival.intValue();
                    }
            }
    }
}

public static int searchArray(int[] arr,int offset,int elm) {
    radixSort(arr);
    return Arrays.binarySearch(arr, elm);
}


第一部分radixSort是\ $ O(kn)\ $,第二部分binarySearch是\ $ O( log(n))\ $。除了编写radixSort之外,您还可以使用顺序为\ $ O(n log(n))\ $的标准Java Arrays.sort,因为它是快速排序的实现。

指出的解决方案仍然是\ $ O(n)\ $:

解决方案非常简单。您可以将其优化到\ $ O(\ frac {n} {2})\ $,仅因为查看项目的值时,您知道该项目前后该项目的价值是什么。

简单的功能是:

public int searchArray(int[] arr,int offset,int elm) {          
     if(arr != null && arr.length > (offset+1)) {
            for(int i = (offset+1); i < arr.length; i+=2) {                
                int absVal = Math.abs(elm - arr[i]);
                if(absVal == 1) {
                    int behind = i - 1;
                    int infront = i + 1;
                    if(arr[behind] == elm) {
                        return behind;
                    } else if(arr[infront] == elm) {
                        return infront;
                    }
                } else if(absVal == 0) {
                    //we are the value
                    return i;
                }
            }
        } else if(arr != null && arr.length > offset && arr[offset] == elm) {
            return offset;
        }                    
        return -1;              
    }


这里是一个完整的示例程序,该程序还将对每个示例的整个代码示例进行回归测试数组中的元素以证明其有效。我还包括一个计数器,以显示实际执行的周期数。由于在任何情况下我们都将数组计数器增加2,因此算法的最大顺序将始终低于\ $ O(n)\ $。

完整的代码示例:

package cstura;

import javax.xml.ws.Holder;

/**
 *
 * @author cstura
 */
public class Test {

    public static void main(String[] args) throws Throwable {
        int[] arr = {2, 1, 2, 3, 4, 3, 2, 3, 4, 5, 6, 5, 4, 3, 4};        
        for(int i = 0; i < arr.length; ++i) {
            Holder<Integer> numItrs = new Holder(0);
            int idx = searchArray(arr,0,arr[i],numItrs);
            System.out.println(String.format("first position of %d in the array is: %d total itrs were: %d",arr[i],idx,numItrs.value));
        }
    }

    public static int searchArray(int[] arr,int offset,int elm,Holder<Integer> numItrs) {
        int itrs = 0;
        try {            
            if(arr != null && arr.length > (offset+1)) {
                for(int i = (offset+1); i < arr.length; i+=2) {                
                    itrs++;
                    int absVal = Math.abs(elm - arr[i]);
                    if(absVal == 1) {
                        //the value if infront or behind us (maybe)
                        int behind = i - 1;
                        int infront = i + 1;
                        if(arr[behind] == elm) {
                            return behind;
                        } else if(arr[infront] == elm) { //if it's not behind then it must be infront.
                            return infront;
                        }
                    } else if(absVal == 0) {
                        //we are the value
                        return i;
                    }
                }
            } else if(arr != null && arr.length > offset && arr[offset] == elm) {
                return offset;
            }

            return -1;  
        }finally {
            numItrs.value = itrs;
        }
    }        
}


评论


\ $ \ begingroup \ $
O(n / 2)不小于O(n),它与O(n)完全相同。
\ $ \ endgroup \ $
– svick
2013年12月4日12:50

\ $ \ begingroup \ $
这句话没有多大意义。因为在最坏的情况下,n / 2的时钟周期是O(n)的一半,这意味着它不同,速度更快。精确地说,算法总是快两倍。
\ $ \ endgroup \ $
–cstura
2013年12月4日14:08

\ $ \ begingroup \ $
然后,您很可能不了解大O表示法的实际含义。比较算法时,很难比较“ n / 2个慢迭代”和“ n个快迭代”。因此,您可以使用大O表示法来表示两者大致相同,并且真正的区别在于O(log n),O(n)和O(n ^ 2)之间。
\ $ \ endgroup \ $
– svick
2013年12月4日14:16



\ $ \ begingroup \ $
欢迎使用代码审查。答案应包含对原始代码的一些批评。如果您提供自己的代码,请说明为什么更好。 (更短?更快?更容易理解?……)
\ $ \ endgroup \ $
– 200_success
13年12月4日在17:29

#11 楼

是否有限制要求阵列必须(或不能以特定顺序)?如果保留排序的数组,则可以执行二进制搜索,该搜索将在O(log(n))中运行。既有递归实现又有迭代实现。当然,缺点是必须对数组进行排序,但优点是您可以在不到10次迭代/递归的情况下搜索数百万条记录。

来自Wikipedia的二进制搜索算法文章:

int binary_search(int A[], int key, int imin, int imax)
{
  // test if array is empty
  if (imax < imin)
    // set is empty, so return value showing not found
    return KEY_NOT_FOUND;
  else
    {
      // calculate midpoint to cut set in half
      int imid = midpoint(imin, imax);

      // three-way comparison
      if (A[imid] > key)
        // key is in lower subset
        return binary_search(A, key, imin, imid-1);
      else if (A[imid] < key)
        // key is in upper subset
        return binary_search(A, key, imid+1, imax);
      else
        // key has been found
        return imid;
    }
}


评论


\ $ \ begingroup \ $
拥有一个预定数组将无法解决OP的问题(实际上会破坏它...)。原始问题要求数据按预定义(无序)顺序进行
\ $ \ endgroup \ $
–rolfl
13年12月3日在16:54

\ $ \ begingroup \ $
@rolfl根据OP的要求,有序列表不是不可能的,但肯定不是假定的。这只是在解决您所说的有序数组将打破OP的问题的情况,而这并不一定。
\ $ \ endgroup \ $
– Cruncher
2013年12月3日18:04



\ $ \ begingroup \ $
@Cruncher-当然,您在我的声明中指出不一致之处是正确的,但是,强行下令肯定会破坏OP的要求。
\ $ \ endgroup \ $
–rolfl
2013年12月3日在18:17

#12 楼

我敢肯定,平均而言,有算法要比\ $ O(n)\ $更好。例如,如果搜索算法搜索数组source,长度length,并通过索引使用变量index搜索target,然后如果source[index] != target,则index的下一个值可能是index + abs(source[index]-target),这可能会跳过许多元素,甚至跑出数组末尾并终止。

,即,您只需要检查数组中的几个元素,因为它们的值受到限制。

比我更好的数学家将可以告诉您上限是多少,但我的猜测是\ $ O (1)\ $。

评论


\ $ \ begingroup \ $
在最坏的情况下,abs(source [index] -target)始终小于或等于2,因此此算法仅跳过一半的元素,使其为O(n / 2),即与O(n)相同。
\ $ \ endgroup \ $
– Tanner Swett
2014年2月24日22:05

\ $ \ begingroup \ $
@TannerSwett:我不相信abs(source [index] -target)始终小于或等于2。
\ $ \ endgroup \ $
– Qumrana
2014年2月25日在11:03

\ $ \ begingroup \ $
我并不是说abs(source [index] -target)始终小于或等于2。我是说abs(source [index] -target)小于或等于2在整个数组中,没有算法能够比O(n)更快地成功搜索该数组。
\ $ \ endgroup \ $
– Tanner Swett
2014年2月27日在1:09