用于确定数字数组中的高值和低值的最佳算法



我在这里使用伪代码,但这是在JavaScript中。用最有效的算法,我试图找到给定正整数数组的高和低。这就是我的想法,但我认为这可能不是最好的,我只是想知道是否有人有其他建议。

var low = 1;
var high = 1;
for ( loop numbers ) {
    if ( number > high ) {
        high = number;
    }
    if ( low == 1 ) {
        low = high;
    }
    if ( number < low ) {
        low = number;
    }
}

初始化high和low作为第一个元素。比任意选择"高"或"低"数字更有意义。

var myArray = [...],
    low = myArray[0],
    high = myArray[0]
;
// start looping at index 1
for (var i = 1, l = myArray.length; i < l; ++i) {
    if (myArray[i] > high) {
        high = myArray[i];
    } else if (myArray[i] < low) {
        low = myArray[i];
    }
}

或者,避免了多次查找阵列的需要:

for (var i = 1, val; (val = myArray[i]) !== undefined; ++i) {
    if (val > high) {
        high = val;
    } else if (val < low) {
        low = val;
    }
}

您必须在O(n)时间内完成,因为您需要循环遍历所有(n)元素来检查它们,因为任何一个元素都可能是最小或最大值(除非它们已经排序。)

换句话说,您需要遍历所有元素,并像现在这样进行最大值和最小值检查。

排序通常最多为O(n*log(n))。因此,它比单个扫过(O(n))慢。

您的示例几乎是最有效的算法,但很明显,当所有的数字都小于1或大于1时,它将不起作用。此代码将在以下情况下工作:

var low = numbers[0]; // first number in array
var high = numbers[0]; // first number in array
for ( loop numbers ) {
    if ( number > high ) {
        high = number;
    }
    if ( number < low ) {
        low = number;
    }
}

如果列表很小(其中"small"小于几千个元素),而你做得不多(其中"many"小于几千次),那也没关系在担心优化最大/最小算法之前,先对代码进行评测以找到真正的瓶颈。

现在回答你提出的问题。

因为无法避免查看列表中的每个元素,所以线性搜索是最有效的算法。它需要N个时间,其中N是列表中元素的数量。在一个循环中完成这一切比调用max()然后调用min()(需要2*N时间)更有效。所以你的代码基本上是正确的,尽管它没有考虑负数。这是用Perl编写的。

# Initialize max & min
my $max = $list[0];
my $min = $list[0];
for my $num (@list) {
     $max = $num if $num > $max;
     $min = $num if $num < $min;
}

排序然后抓取第一个和最后一个元素是最低效的。它采用N*log(N),其中N是列表中元素的数量。

最有效的最小/最大算法是每次从列表中添加或删除元素时重新计算最小/最大值的算法。实际上,缓存结果并避免每次进行线性搜索。花在这上面的时间就是列表更改的次数。它最多需要M次,其中M是更改次数,无论您调用多少次。

要做到这一点,您可以考虑一个搜索树,它可以保持元素的顺序。在该结构中获得最小值/最大值是O(1)或O(log[n]),这取决于您使用的树样式。

尽管它仍然是一个O(n)算法,但通过首先成对比较相邻元素,然后将较小的元素与最小值和较大的元素与最大值进行比较,您可以更快地完成25%(即比例常数为3/2 vs 2)。我不知道javascript,但它在C++中:

std::pair<int, int> minmax(int* a, int n)
{
  int low = std::numeric_limits<int>::max();
  int high = std::numeric_limits<int>::min();
  for (int i = 0; i < n-1; i += 2) {
    if (a[i] < a[i+i]) {
      if (a[i] < low) {
        low = a[i];
      }
      if (a[i+1] > high) {
        high = a[i+1];
      }
    }
    else {
      if (a[i] > high) {
        high = a[i];
      }
      if (a[i+1] < low) {
        low = a[i+1];
      }
    }
  }
  // Handle last element if we've got an odd array size
  if (a[n-1] < low) {
    low = a[n-1];
  }
  if (a[n-1] > high) {
    high = a[n-1];
  }
  return std::make_pair(low, high);
} 
var numbers = [1,2,5,9,16,4,6];
var maxNumber = Math.max.apply(null, numbers);
var minNumber = Math.min.apply(null, numbers);

nickf的算法不是最好的方法。在最坏的情况下,尼克夫的算法对每个数字进行2次比较,总数为2n-2。

我们可以做得更好。当你比较两个元素a和b时,如果a>b,我们知道a不是最小值,b不是最大值。通过这种方式,我们使用所有可用的信息来消除尽可能多的元素。为了简单起见,假设我们有偶数个元素。

将它们分成两对:(a1,a2),(a3,a4)等

比较它们,把它们分成一组赢家和输家——这需要n/2的比较,给我们两组n/2的大小。现在找出赢家的最大值和输家的最小值。

根据以上内容,找到n个元素的最小值或最大值需要n-1个比较。因此,运行时是:n/2(对于初始比较)+n/2-1(最大赢家)+n/2-1(最小输家)=n/2+n/2+n/2-2=3n/2-2。如果n是奇数,我们在每个集合中都多了一个元素,所以运行时将是3n/2

事实上,我们可以证明,这是任何算法都可能解决这个问题的最快速度。

一个例子:

假设我们的数组是1,5,2,3,1,8,4分成两对:(1,5),(2,3)(1,8),(4,-)。比较获胜者是:(5,3,8,4)。失败者是(1,2,1,4)。

扫描获胜者得到8分。扫描失败者得到1。

在V8上真实地尝试这些片段,Drew-Hall的算法运行时间是nickf的2/3,正如预测的那样。使循环递减而不是递增将其减少到大约59%的时间(尽管这更依赖于实现)。仅轻微测试:

var A = [ /* 100,000 random integers */];
function minmax() {
    var low = A[A.length-1];
    var high = A[A.length-1];
    var i, x, y;
    for (i = A.length - 3; 0 <= i; i -= 2) {
        y = A[i+1];
        x = A[i];
        if (x < y) {
            if (x < low) {
                low = x;
            }
            if (high < y) {
                high = y;
            }
        } else {
            if (y < low) {
                low = y;
            }
            if (high < x) {
                high = x;
            }
        }
    }
    if (i === -1) {
        x = A[0];
        if (high < x) {
            high = x;
        } else if (x < low) {
            low = x;
        }
    }
    return [low, high];
}
for (var i = 0; i < 1000; ++i) { minmax(); }

但是,伙计,它相当丑陋。

Javascript数组有一个本机排序函数,该函数接受用于比较的函数。你可以对数字进行排序,只需取头和尾就可以得到最小值和最大值。

var sorted = arrayOfNumbers.sort(function(a, b) { return a - b; }),
    ,min = sorted[0], max = sorted[sorted.length -1];

默认情况下,sort方法按字典顺序进行排序,所以这就是为什么必须传入一个函数,让它用来进行数字排序。传入的函数需要返回1、-1或0来确定排序顺序。

// standard sort function
function sorter(a, b) {
  if (/* some check */)
    return -1; // a should be left of b
  if (/*some other check*/)
    return 1; // a should be to the right of b
  return 0; // a is equal to b (no movement)
}

对于数字,您只需从第一个参数中减去第二个参数即可确定顺序。

var numbers = [5,8,123,1,7,77,3.14,-5];
// default lexicographical sort
numbers.sort() // -5,1,123,3.14,5,7,77,8
// numerical sort
numbers.sort(function(a, b) { return a - b; }) // -5,1,123,3.14,5,7,77,8

我建议的唯一进一步优化是优化循环本身。在JavaScript中倒计时比倒计时快。

使用排列语法以ES6方式执行:

var arrNums = [1, 2, 3, 4, 5];
Math.max(...arrNums) // 5
Math.min(...arrNums) // 1

假设列表还没有排序,这是你能做的最好的事情

low = +INFINITY
high = -INFINITY
for each n in numbers:
    if n < low:
        low = n
    if n > high:
        high = n

这是一个O(n)运算,基本上是你能做的最好的运算。

这个算法适用于O(n),不需要额外的内存来存储元素。。。

enter code here
int l=0,h=1,index,i=3;
    if(a[l]>a[h])
                 swap(&a[l],&a[h]);
    for(i=2;i<9;i++)
    {
                                if(a[i]<a[l])
                                {
                                      swap(&a[i],&a[l]);  
                                }
                                if(a[i]>a[h])
                                {
                                             swap(&a[i],&a[h]);
                                }
    }
    printf("Low:  %d  High:  %d",a[0],a[1]);

在python中:

>>> seq = [1, 2, 3, 4, 5, 6, 7]
>>> max(seq)
7
>>> min(seq)
1

最新更新