我在这里使用伪代码,但这是在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