优化的javascript代码,在数组中找到3个最大的元素及其索引?

时间:2012-08-03 08:34:18

标签: javascript algorithm sorting

我需要更优化的javascript代码才能找到数组中最大的3个元素。 我试过这段代码。

var maxIndex = new Array();
var maxPoints = new Array();
var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);

function findLargest3(){ 

      maxPoints[0]=0;
      maxPoints[1]=0;
      maxPoints[2]=0;
    for(i=0;i < scoreByPattern.length; i++){        
        if( scoreByPattern[i] > maxPoints[0]){                               
             maxPoints[0] = scoreByPattern[i];
             maxIndex[0]=i;   
        }


    }

    for(i=0;i < scoreByPattern.length; i++){        
        if( scoreByPattern[i] > maxPoints[1] && scoreByPattern[i]< maxPoints[0] ){                               
             maxPoints[1] = scoreByPattern[i];
             maxIndex[1]=i;   
        }            
    }

    for(i=0;i < scoreByPattern.length; i++){        
        if( scoreByPattern[i] > maxPoints[2] && scoreByPattern[i]< maxPoints[1] ){                               
             maxPoints[2] = scoreByPattern[i];
             maxIndex[2]=i;   
        }            
    }

alert(scoreByPattern+"/******/"+maxPoints[0]+"/"+maxPoints[1]+"/"+maxPoints[2]);
//alert(maxIndex);
}

如何优化以上? (我需要最大数字的索引)是否有其他简单的方法来解决这个问题?

13 个答案:

答案 0 :(得分:9)

修改后的版本

我修改了我的答案,使其更通用。它搜索数组中n个最大元素数的索引:

var scoreByPattern = [93,255,17,56,91,98,33,9,38,55,78,29,81,60];

function findIndicesOfMax(inp, count) {
    var outp = [];
    for (var i = 0; i < inp.length; i++) {
        outp.push(i); // add index to output array
        if (outp.length > count) {
            outp.sort(function(a, b) { return inp[b] - inp[a]; }); // descending sort the output array
            outp.pop(); // remove the last index (index of smallest element in output array)
        }
    }
    return outp;
}

// show original array
console.log(scoreByPattern);

// get indices of 3 greatest elements
var indices = findIndicesOfMax(scoreByPattern, 3);
console.log(indices);

// show 3 greatest scores
for (var i = 0; i < indices.length; i++)
    console.log(scoreByPattern[indices[i]]);

这是jsFiddle

答案 1 :(得分:6)

不对大数组进行排序:考虑到大数组,在O(n)中运行。返回带有[x,y]的数组数组,其中x是大数组中的索引值y

var ar = [3,172,56,91,98,33,9,38,55,78,291,81,60];
console.log(`input is: ${ar}`);

function getMax(ar){
    if (ar.length < 3) return ar;
    var max = [[ar[0],0],[ar[1],1],[ar[2],2]],
        i,j;
    
    for (i = 3;i<ar.length;i++){
        for (j = 0;j<max.length;j++){
            if (ar[i] > max[j][0]){
                max[j] = [ar[i],i];
                if (j<2){
                    max.sort(function(a,b) { return a[0]-b[0]; });
                }
               break;            
            }
        }
    }
    return max;
}
result = getMax(ar);

console.log('output [number,index] is:');
console.log(result);

答案 2 :(得分:5)

对数组进行降序排序,然后获取它的前三个元素:

var maxPoints = new Array();
var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);

findLargest3();

function findLargest3(){
    // sort descending
    scoreByPattern.sort(function(a,b) {
        if (a < b) { return 1; }
        else if (a == b) { return 0; }
        else { return -1; }
    });

    alert(scoreByPattern+"/******/"+scoreByPattern[0]+"/"+scoreByPattern[1]+"/"+scoreByPattern[2]);
}

Example fiddle

答案 3 :(得分:3)

默认的javascript排序回调效果不佳,因为它在lexicographical order中排序。 10将在5之前(因为1)

我不相信,但是:

my_array.sort(function(a,b) {
    return a-b;
});

答案 4 :(得分:2)

假设分布相当正常,这应该是相当优化的:

var max_three, numbers = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);

max_three = (function (numbers) {
    var i, one, two, three;
    one = -9999;
    two = -9999;
    three = -9999;

    for (i = 0; i < numbers.length; i += 1) {
        num = numbers[i];
        if (num > three) {
            if (num >= two) {
                three = two;
                if (num >= one) {
                    two = one;
                    one = num;
                }
                else {
                    two = num;
                }
            }
            else {
                three = num;
            }
        }
    }

    return [one, two, three]

}(numbers))



document.write(max_three)​​​​​​​

98,93,91

答案 5 :(得分:1)

为什么不对它进行排序并采用第一个(或最后一个按升序排序)三个元素。

var maxPoints = new Array();
var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);
scoreByPattern.sort();
maxPoints[0] = scoreByPattern[scoreByPattern.length - 1];
maxPoints[1] = scoreByPattern[scoreByPattern.length - 2];
maxPoints[2] = scoreByPattern[scoreByPattern.length - 3];

修改
如果你需要最大数组的indeces,你可以制作一个你排序的副本,然后找到原始数组中的索引:

var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);

// Make a copy of the original array.
var maxPoints = scoreByPattern.slice();

// Sort in descending order.
maxPoints.sort(function(a, b) {
    if (a < b) { return 1; }
    else if (a == b) { return 0; }
    else { return -1; }

});

// Find the indices of the three largest elements in the original array.
var maxPointsIndices = new Array();
maxPointsIndices[0] = scoreByPattern.indexOf(maxPoints[0]);
maxPointsIndices[1] = scoreByPattern.indexOf(maxPoints[1]);
maxPointsIndices[2] = scoreByPattern.indexOf(maxPoints[2]);

找到没有排序的索引的另一种方法是:

var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);
var maxIndices = new Array(Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);

for (var i = 0; i < scoreByPattern.length; i++) {
  if (maxIndices[0] < scoreByPattern[i]) {
    maxIndices[2] = maxIndices[1];
    maxIndices[1] = maxIndices[0];
    maxIndices[0] = scoreByPattern[i];
  }
  else if (maxIndices[1] < scoreByPattern[i]) {
    maxIndices[2] = maxIndices[1];
    maxIndices[1] = scoreByPattern[i];
  }
  else if (maxIndices[2] < scoreByPattern[i]) maxIndices[2] = scoreByPattern[i];
}

答案 6 :(得分:1)

最好的方法是使用sortslice的组合:

这个简单的衬垫将解决您的问题。

[1, -5, 2, 8, 17, 0, -2].sort(function(a, b){return b - a}).slice(0, 3)

所以,如果你有一个数组,并希望找到N个最大值:

arr.sort(function(a, b){return b - a}).slice(0, n)

对于N个最小值:

arr.sort(function(a, b){return a - b}).slice(0, n)

答案 7 :(得分:1)

这是针对您的问题的优化解决方案,无需使用排序或其他复杂的数组方法:

var maxIndex = new Array();
var maxPoints = new Array();
var scoreByPattern = new Array(93,17,56,91,98,33,9,38,55,78,29,81,60);
function findTop3(n) {
	for (var i = 0; i < n.length; i ++) {
		if (i === 0) {
			maxPoints.push(n[i]);
			maxIndex.push(i);
		} else if (i === 1) {
			if (n[i] > maxPoints[0]) {
				maxPoints.push(maxPoints[0]);				
				maxPoints[0] = n[i];
				maxIndex.push(maxIndex[0]);
				maxIndex[0] = i;
			} else {
				maxPoints.push(n[i]);
				maxIndex.push(i);
			}
		} else if (i === 2) {
			if (n[i] > maxPoints[0]) {
				maxPoints.push(maxPoints[0]);
				maxPoints[1] = maxPoints[0];
				maxPoints[0] = n[i];
				maxIndex.push(maxIndex[0]);
				maxIndex[1] = maxIndex[0];
				maxIndex[0] = i;
				
			} else {
				if (n[i] > maxPoints[1]) {
					maxPoints.push(maxPoints[1]);
					maxPoints[1] = n[i];
					maxIndex.push(maxIndex[1]);
					maxIndex[1] = i;
				} else {
					maxPoints.push(n[i]);
					maxIndex.push(i);
				}
			}
		} else {
			if (n[i] > maxPoints[0]) {
				maxPoints[2] = maxPoints[1];
				maxPoints[1] = maxPoints[0];
				maxPoints[0] = n[i];
				maxIndex[2] = maxIndex[1];
				maxIndex[1] = maxIndex[0];
				maxIndex[0] = i;
			} else {
				if (n[i] > maxPoints[1]) {
					maxPoints[2] = maxPoints[1];
					maxPoints[1] = n[i];
					maxIndex[2] = maxIndex[1];
					maxIndex[1] = i;
				} else if(n[i] > maxPoints[2]) {
					maxPoints[2] = n[i];
					maxIndex[2] = i;
				}
			}
		}
	}
}
findTop3(scoreByPattern);
console.log('Top Elements: ', maxPoints);
console.log('With Index: ', maxIndex);

答案 8 :(得分:0)

http://jsfiddle.net/GGkSt/

var maxPoints = [];
var scoreByPattern = [93,17,56,91,98,33,9,38,55,78,29,81,60];

function cloneArray(array) {
    return array.map(function(i){ return i; });
}    
function max3(array) {
    return cloneArray(array).sort(function(a,b) { return b-a; }).slice(0,3);
}
function min3(array) {
     return cloneArray(array).sort(function(a,b) { return a-b; }).slice(0,3);
}

var array=scoreByPattern;
alert("Max:"+ max3(array)[0] +' '+max3(array)[1] +' '+max3(array)[2]);
alert("Min:"+ min3(array)[0] +' '+min3(array)[1] +' '+min3(array)[2]);

答案 9 :(得分:0)

function get3TopItems(arr) {
  return arr.sort((a, b) => b - a).slice(0, 3);
}

答案 10 :(得分:0)

以下函数返回一个数组,其元素是一个固定的双元素数组,第一个元素作为索引,第二个元素作为其值。

const searchCriteria = {
  startTime: new Date("08/12/2020"),
  endTime: new Date("09/12/2020"),
  LiveTime: new Date("10/12/2020"),
  schedAction_Active: "Active",
  siteId: 1825,
  scheduleId: 3792
};

const res = new URLSearchParams(searchCriteria).toString();
console.log(res);

答案 11 :(得分:0)

我不敢相信这些答案中有多少比 OP 提供的解决方案更糟糕。大多数都更简洁,但要么:

  • 不要回答这个问题。通常只提供前 3 个值,当问题明确还要求提供前 3 个值的索引
  • 建议“排序”,但甚至没有提及性能、副作用或大 O 符号。

这对 SO 来说可能有点太长了,但我觉得这是相关信息。


<块引用>
  1. 我需要此 javascript 代码的更优化版本来查找数组中的 3 个最大值。
  2. 我需要获取最大数的索引。
  3. 有没有其他更简单的方法来解决这个问题?

我认为 (3) 表示“我希望方法更简洁/可读”,(2) 表示“索引可直接在输出中使用”,以及 (1) 表示“我希望代码能够尽可能高效”。

从 (3) 开始,因为它对人们最有用:您的原始算法非常有效,但您基本上是在 3 个循环中的每个循环中复制代码;每个循环中唯一的区别实际上是索引发生了变化。如果您只是想减少字符数,您可以将您的算法(有几个小警告1)重写为:

function findLargest3(arry) {
  const max = {
    indices: new Array(0, 0, 0),
    points : new Array(0, 0, 0)
  }
  
  // Inner private function to remove duplicate code
  // Modifies "max" as a side-effect
  function setLargest (idx) {
    const lastMax = max.points[idx - 1] || Number.MAX_VALUE;
    for (let i = 0; i < arry.length; i++) {
      if (arry[i] > max.points[idx] && arry[i] < lastMax) {
        max.points[idx] = arry[i];
        max.indices[idx] = i;
      }
    }
  }
  
  setLargest(0); // Get's the 0'th largest item
  setLargest(1); // etc. 
  setLargest(2);
  
  return max;
}

let scoreByPattern = new Array(93, 17, 56, 91, 98, 33, 9, 38, 55, 78, 29, 81, 60);
let max = findLargest3(scoreByPattern);
console.log(scoreByPattern + "/******/" + max.points.join("/"));

除了添加函数之外,我唯一真正需要添加的是:const lastMax = max.points[idx - 1] || Number.MAX_VALUE;。这仅适用于 idx == 0 的情况。由于没有 max.points[-1],我们希望第二次检查始终返回 true,因此如果 max.points[idx - 1] 不存在,请通过 || 将其设置为可能的最高值。

这实际上比您的实现慢了大约 4 倍。这种缓慢是没有直接修改全局状态(更快但更难维护)和包含内部函数而不是代码重复的结果。用原始的重复代码替换内部函数并保持其他所有更改相同,最终执行速度提高了 40%,因为它完全避免了函数的创建和调用;但再次更容易阅读。它还使得将 findLargest3 扩展为 findLargest5findLargestN 变得更加简单。

此外,它应该与您的原始解决方案相同(请参阅我对 1 的回答),这与涉及 sort 的解决方案不同;这意味着它将与您的原始解决方案一样扩展,同时更易于阅读和维护。如果您真的需要性能,那么您的原始解决方案几乎没有问题,我什至认为您的原始解决方案比某些已发布的解决方案更好。 (例如,从粗略测试来看,将数组的大小增加到约 100 个元素会使我的解决方案运行速度降低约 3 倍,但使使用 sort 的解决方案运行速度降低 6 倍以上。


1注意:您的算法有一个主要问题,那就是 i 是“全局范围的”。如果您编写以下内容,它将运行 i==0,然后永远循环使用 i==scoreByPattern.length & 永远不会完成:

// Outer loop
for (var i = 0; i <= scoreByPattern.length + 1; i++) {
    findLargest3()
}

那是因为您的 for (i = 0 ... 语句没有引用 i 的“本地”版本,并且会回退到先前范围中定义的范围(如果找不到,则返回全局范围) )。您的循环在完成时始终将 i 设置为 scoreByPattern.length,这会更改此外部循环中的值。如果您尝试在循环期间修改迭代器,某些语言和语言结构(例如 C# 中的 foreach)实际上不会编译。修复实际上真的很简单,只需在循环声明中说 var ilet i 然后 i 将只引用局部于功能。

还有一个小问题,就是你的原始算法忽略了数组中的重复项。如果我将另一个 93 添加到原始数组的末尾,它仍将顶部值返回为 [98, 93, 91]。我没有在我的解决方案中改变它,因为它可能是故意的(例如,前三个“值”是 98、93 和 91,但 93 恰好出现了两次)。要修复,您只需检查 arry[i] < max.points[... 是否不在 i 中,而不是检查 max.indices。根据重复的数量,这可能会减慢算法的速度(无需重写),但假设 duplicates << array.length 那么它不会明显变慢。

虽然本身不​​是“问题”,但拥有“全局状态”(即 scoreByPattern, maxPoints, maxIndex)然后让它们被函数改变通常并不理想。它可以在计算上更快,但更难维护。这就是为什么我将 maxPoints / maxIndex 的声明移到函数体中并让它请求 scoreByPattern 作为参数的原因。有很多场景都可以(例如对象成员变量和增变器方法)。但在这里,findLargest3 看起来真的像是一个辅助函数/函数,因此理想情况下不应该有副作用(即修改外部状态/传递给它的参数)。


简单提一下 (2):如果索引本身很重要,那么对数组进行排序并没有真正的意义,除非在某些非常特殊的情况下(请参阅我对第 1 点的回答)。排序是直接在数组上使用 Javascript 的 sort 方法的“就地”操作。这意味着它将在大多数这些答案中修改原始数组;所以原始索引实际上丢失了并且对程序的其余部分也有其他后果。为了使用排序来获取前 N 个值,您必须先复制数组,对副本进行排序,取前 3 个值,然后遍历原始数组 ~3 次尝试使用 indexOf 进行匹配或类似(并处理重复项)。

这显然是一种资源浪费,因为其中一些答案比原始答案慢了 30 倍之多。您可以尝试生成“索引”而不是值的列表,根据原始数组对它们进行排序,这样会稍微快一些。但是在这种特定情况下,除非我疯了,否则我们只想要“前 3 名”(再次参见我对第 1 点的回答),排序几乎总是会变慢


最后,看 (1) 在分析算法时应用称为“Big-O 表示法”的概念很有用。我建议谷歌搜索并阅读,但要广泛:我们想看看算法的效率,随着输入长度的增加。例如。在您的应用程序中,我们有数组 scoreByPattern,我们会说它的长度为 N,以及您的问题强加的显式“3 个索引/值”,但它对能够说“返回顶部 M 索引/值”。如果 scoreByPatterntop M 非常短,那么算法的复杂度并不重要,但如果它们有成百上千的元素,那么它们的计算复杂度就变得非常重要。

另外,重要的是要说JS中的数组,如果你知道索引,“查找”实际上是一个几乎瞬时的操作~Big O(1)。此外,在 Big-O 中,我们倾向于只关注分析的“主要”项而忽略任何恒定时间因素。例如。如果解决方案是 10x O(1) + 2x O(N)(10 个恒定时间操作 + 长度为 N 的列表中的每个项目的 2 个操作),我们说解决方案的 Big-O 是 O(N)。这是因为,对于相当长的列表,如果我们将 N 的值加倍,所花费的时间将大致加倍。如果它受到 O(N2) 操作的限制并且列表长度加倍,则需要 22 倍(即慢 4 倍)。

所以你原来的解决方案是:

  • 一些恒定的时间步
  • 遍历列表一次,做两次布尔检查和两次赋值
  • 再做一次
  • 第三次这样做

意味着它会像 kO(1) + 3O(cN) 这样的东西,其中 kc 是一些常量,一般将其保留为:O(N)。如果我们想要“Top M 索引”,您最终会得到:MO(N) 最终是 O(NM)(即 N * M 的 Big-O)。为什么这很重要?

sortO(N Log2(N)) 的 Big-O 这意味着,例如,大约 64 个项目的列表,排序大约需要 64 * Log2(64) == 64 * 6 == ~384ish operations。因此,将您的解决方案与涉及 sort 的解决方案进行比较,除非 M > Log2(N),否则最好不要排序。

从正确的角度来看:如果您的列表有 100 个项目,您需要尝试获得 6 个以上的“最大”项目以提高排序效率。如果是 100,000 个项目,您需要尝试获得超过 16 个“最大”项目。在这个用例中,特别是因为您明确地说了“3”,所以几乎总是排序更有意义。特别是,因为您正在尝试获取索引;如果您想要值,那么排序解决方案可能更可取,因为它们很简单。

您可能可以稍微调整一下您的算法以进一步加快速度,但从粗略的测试来看:我认为您的解决方案已经是这里最快的解决方案。如果你想让维护/阅读/扩展更容易,那绝对是可能的;但这真的不应该以实际增加“时间复杂度”为代价(即使解决方案比 O(N) 更糟糕),除非它大大简化了实现。

答案 12 :(得分:0)

我创建了一个函数,该函数返回 N 最大元素的 N 索引(在值相等的情况下,它返回找到的第一个索引)。 在代码中,NqtyMax。 函数的成本为 O(n),在最坏的情况下为 n * qtyMax

function maxIdxs(arr, qtyMax) {
    var maxIndex = new Array();
    for(var i=0; i<arr.length; i++) {
        var m=0;
        while(m<qtyMax && arr[i]<=arr[maxIndex[m]])
            m++;
        if(m<qtyMax)
            maxIndex.splice(m, 0, i);
    }
    return maxIndex.slice(0, qtyMax);
}

console.log(maxIdxs( [1,23,46,35,20] , 0 )); // []
console.log(maxIdxs( []              , 3 )); // []
console.log(maxIdxs( [1,23]          , 3 )); // [1,0]
console.log(maxIdxs( [1,23,46,35,20] , 3 )); // [2,3,1]
console.log(maxIdxs( [1,40,40,40,40] , 3 )); // [1,2,3]

要获得相应的函数minIdxs(找到最小的N索引),您只需要将{{1}的条件中的<=替换为>= }.