在比较次数最少的情况下,找到数组中的第二大元素


70

对于大小为N的数组,需要进行多少次比较?


2
您允许多少临时存储空间?
迈克尔·迈尔斯

2
@Sachin,它将是n * log(n)个比较。排序不能更快。
riwalk

2
@ Stargazer712:除非数组是整数。然后,您可以进行基数排序,而无需进行任何比较;-)
Steve Jessop 2010年

1
@Steve:即使这样,也假设您知道整数:)的上下限。基数排序非常有用,但受到极大限制。如果在问题中未给出此类约束,则最好假设n * log(n)
riwalk 2010年

2
@ Stargazer712:无需限制:en.wikipedia.org/wiki/…。想到这一点,基数排序仍然涉及对输入数据的循环,而循环则必须对终止条件进行比较。不过,它不必是顺序比较,而只是相等性比较。但是您是对的,这个问题没有说明数据类型,因此正确的答案必须假设不透明数据和比较器功能。如果访调员错误地提出了一个int特例(或实际按下字符串),
则为

Answers:


119

最佳算法使用n + log n-2个比较。将元素视为竞争对手,比赛将对其进行排名。

首先,比较树中的元素

   |
  / \
 |   |
/ \ / \
x x x x

这需要进行n-1个比较,并且每个元素最多要进行n次比较。您将找到最大的元素作为获胜者。

第二大元素必定输给了获胜者(他不能输掉与另一个要素的比赛),因此他是获胜者所对抗的log n元素之一。您可以使用log n-1比较来查找其中的哪个。

通过对抗性论证证明了最优性。请参阅/math/1601http://compgeom.cs.uiuc.edu/~jeffe/teaching/497/02-selection.pdfhttp://www.imada.sdu。 dk /〜jbj / DM19 / lb06.pdfhttps://www.utdallas.edu/~chandra/documents/6363/lbd.pdf


2
@Jatin:否,总共需要N + log N-2:N-1个比较才能找到最大值,log N-1个比较才能找到丢失最大元素的log N中最大的元素。
sdcvvc 2012年

4
Jatin:创建一个二叉树,然后从底部开始填充它。叶子是数组的元素。每个内部顶点最多为其两个子顶点。您需要的比较数是内部顶点数,即n-1。然后,查看最大元素的“对手”,即对数N元素。
sdcvvc 2012年

1
因此,这涉及到一些空间上的复杂性,并回答了我的所有问题:)
Sreekar

1
@POOJA GUPTA:首先,找到最大和第二大元素(如我的回答);给出一个登录期限。第三大元素必须输给第一个或第二个元素,因此您需要检查丢失与第一大元素(即第二个logn项)和第二大元素(即第三个logn项)比较的元素。
sdcvvc 2014年

3
@Hengameh,老实说,我看不出8的原因。需要进行(n + log n-2)个比较才能找到最大和第二大元素,将它们称为L_1和L_2。接下来,我们找到丢失到L_1的最大元素,不是L_2,为此有log n-1个候选,这意味着对数n-2比较,接下来,我们找到丢失到L_2的最大元素,有log n-2个候选者,因此对数n-3进行比较,取这两个值中的最大值,得出n + log n-2 +对数n-2 +对数n-3 +对数n + 1 + 1 = n + 3对数n-6。我可能会丢失一些东西,对数应该在上限之下
。– sdcvvc 2015年

12

您可以找到最多具有2·(N -1)个比较的第二个最大值,以及两个具有最大值和第二个最大值的变量:

largest := numbers[0];
secondLargest := null
for i=1 to numbers.length-1 do
    number := numbers[i];
    if number > largest then
        secondLargest := largest;
        largest := number;
    else
        if number > secondLargest then
            secondLargest := number;
        end;
    end;
end;

如何使用2个比较在3个元素集中找到2个最大元素?
Maciej Hehl 2010年

@sdcvvc:是的,但是我想他的目标是复杂性。那就是O(N)。
Gumbo 2010年

3
您的算法不适用于{1,3,2}。它将返回1而不是
2。– x4u

1
-1。您的算法无效。在输入“ 3,5,4”上尝试一下。
riwalk

1
@ x4u,@ Stargazer712:已修复该问题。
Gumbo 2010年

10

使用冒泡排序或选择排序算法对数组进行降序排序。不要对数组进行完全排序。只有两关。第一遍给出最大元素,第二遍给出第二大元素。

第一次通过的比较数:n-1

首次通过比较的次数:n-2

总数 寻找第二大的比较值:2n-3

可能是您可以推广此算法。如果您需要第三大,则可以进行3次通过。

通过上面的策略,您不需要任何临时变量,因为Bubble排序和Selection排序是就地排序算法。


2
这确实是一个聪明的解决方案
Akhil Jain

我对此解决方案的时间复杂度感到好奇,因为一个循环只会执行两次。
Vikram

我的解决方案也是2n-3
拉贾特·萨克塞纳

2

以下是一些可能不是最佳代码,但至少实际上找到了第二大元素的代码:

if( val[ 0 ] > val[ 1 ] )
{
    largest = val[ 0 ]
    secondLargest = val[ 1 ];
}
else
{
    largest = val[ 1 ]
    secondLargest = val[ 0 ];
}

for( i = 2; i < N; ++i )
{
    if( val[ i ] > secondLargest )
    {
        if( val[ i ] > largest )
        {
            secondLargest = largest;
            largest = val[ i ];
        }
        else
        {
            secondLargest = val[ i ];
        }
    }
}

如果最大的2个元素位于数组的开头,则至少需要N-1个比较,而在最坏的情况下(前2个元素之一在数组中最小),它至少需要2N-3个比较。


1

情况1-> 9 8 7 6 5 4 3 2 1
情况2-> 50 10 8 25 ........
情况3-> 50 50 10 8 25 .........
案例4-> 50 50 10 8 50 25 .......

public void second element()  
{
      int a[10],i,max1,max2;  
      max1=a[0],max2=a[1];  
      for(i=1;i<a.length();i++)  
      {  
         if(a[i]>max1)  
          {
             max2=max1;  
             max1=a[i];  
          }  
         else if(a[i]>max2 &&a[i]!=max1)  
           max2=a[i];  
         else if(max1==max2)  
           max2=a[i];  
      }  
}

1

抱歉,JS代码...

经过两个输入的测试:

a = [55,11,66,77,72];
a = [ 0, 12, 13, 4, 5, 32, 8 ];

var first = Number.MIN_VALUE;
var second = Number.MIN_VALUE;
for (var i = -1, len = a.length; ++i < len;) {
    var dist = a[i];
    // get the largest 2
    if (dist > first) {
        second = first;
        first = dist;
    } else if (dist > second) { // && dist < first) { // this is actually not needed, I believe
        second = dist;
    }
}

console.log('largest, second largest',first,second);
largest, second largest 32 13

最多应有a.length * 2个比较,并且只能通过列表一次。


1

我知道这是一个古老的问题,但这是我尝试使用锦标赛算法来解决的问题。它类似于@sdcvvc使用的解决方案,但我使用的是二维数组来存储元素。

为了使事情起作用,有两个假设:
1)数组中元素的数量为2的幂
2)数组中没有重复项

整个过程包括两个步骤: 2.我们有一个2D数组,其中最后一行仅包含一个元素:最大的元素。我们继续从下至上,在每个数组中找到被最大“击败”的元素,并将其与当前“第二大”值进行比较。为了找到被最大元素击败的元素,并避免O(n)比较,我们必须在前一行中存储最大元素的索引。这样,我们可以轻松地检查相邻的元素。在任何级别(高于根级别)下,相邻元素的获取方式为:
1.通过比较两个两个元素来构建2D数组。2D数组中的第一行将是整个输入数组。下一行包含上一行的比较结果。我们将继续对新建阵列进行比较,并继续构建2D阵列,直到到达只有一个元素(最大的一个)的阵列。

leftAdjacent = rootIndex*2
rightAdjacent = rootIndex*2+1,

其中,rootIndex是上一级的largest(root)元素的索引。

我知道这个问题需要C ++,但是这是我尝试用Java解决它的尝试。(我使用列表而不是数组,以避免混乱地更改数组大小和/或不必要的数组大小计算)

public static Integer findSecondLargest(List<Integer> list) {
        if (list == null) {
            return null;
        }
        if (list.size() == 1) {
            return list.get(0);
        }
        List<List<Integer>> structure = buildUpStructure(list);
        System.out.println(structure);
        return secondLargest(structure);

    }

    public static List<List<Integer>> buildUpStructure(List<Integer> list) {
        List<List<Integer>> newList = new ArrayList<List<Integer>>();
        List<Integer> tmpList = new ArrayList<Integer>(list);
        newList.add(tmpList);
        int n = list.size();
        while (n>1) {
            tmpList = new ArrayList<Integer>();
            for (int i = 0; i<n; i=i+2) {
                Integer i1 = list.get(i);
                Integer i2 = list.get(i+1);
                tmpList.add(Math.max(i1, i2));
            }
            n/= 2;
            newList.add(tmpList);   
            list = tmpList;
        }
        return newList;
    }

    public static Integer secondLargest(List<List<Integer>> structure) {
        int n = structure.size();
        int rootIndex = 0;
        Integer largest = structure.get(n-1).get(rootIndex);
        List<Integer> tmpList = structure.get(n-2);
        Integer secondLargest = Integer.MIN_VALUE;
        Integer leftAdjacent = -1;
        Integer rightAdjacent = -1;
        for (int i = n-2; i>=0; i--) {
            rootIndex*=2;
            tmpList = structure.get(i);
            leftAdjacent = tmpList.get(rootIndex);
            rightAdjacent = tmpList.get(rootIndex+1); 
            if (leftAdjacent.equals(largest)) {
                if (rightAdjacent > secondLargest) {
                    secondLargest = rightAdjacent;
                }
            }
            if (rightAdjacent.equals(largest)) {
                if (leftAdjacent > secondLargest) {
                    secondLargest = leftAdjacent;
                }
                rootIndex=rootIndex+1;
            }
        }

        return secondLargest;
    }

在此处借用您的解决方案-k2code.blogspot.in/2014/03/…。谢谢。
kinshuk4

1

假设提供的数组为inPutArray = [1,2,5,8,7,3]预期的O / P-> 7(第二大)

 take temp array 
      temp = [0,0], int dummmy=0;
    for (no in inPutArray) {
    if(temp[1]<no)
     temp[1] = no
     if(temp[0]<temp[1]){
    dummmy = temp[0]
    temp[0] = temp[1]
    temp[1] = temp
      }
    }

    print("Second largest no is %d",temp[1])


0

假设空间无关紧要,这是我能得到的最小空间。在最坏的情况下需要2 * n个比较,在最坏的情况下需要n个比较:

arr = [ 0, 12, 13, 4, 5, 32, 8 ]
max = [ -1, -1 ]

for i in range(len(arr)):
     if( arr[i] > max[0] ):
        max.insert(0,arr[i])
     elif( arr[i] > max[1] ):
        max.insert(1,arr[i])

print max[1]

0

尝试这个。

max1 = a[0].
max2.
for i = 0, until length:
  if a[i] > max:
     max2 = max1.
     max1 = a[i].
     #end IF
  #end FOR
return min2.

它应该像一种魅力。低复杂度。

这是一个Java代码。

int secondlLargestValue(int[] secondMax){
int max1 = secondMax[0]; // assign the first element of the array, no matter what, sorted or not.
int max2 = 0; // anything really work, but zero is just fundamental.
   for(int n = 0; n < secondMax.length; n++){ // start at zero, end when larger than length, grow by 1. 
        if(secondMax[n] > max1){ // nth element of the array is larger than max1, if so.
           max2 = max1; // largest in now second largest,
           max1 = secondMax[n]; // and this nth element is now max.
        }//end IF
    }//end FOR
    return max2;
}//end secondLargestValue()

比较次数效率不高!您扫描整个阵列,我认为比较的数量是n!
Hengameh 2015年

而且您的代码也不正确!如果“ secondmax [n]> max2”怎么办?您没有检查这种可能性!
亨伽美

0

使用计数排序,然后找到第二大元素,从索引0开始到结尾。最多应该有至少1个比较n-1(当只有一个元素时!)。


这不能回答有关比较次数的问题。问题是分析算法,而不仅仅是分析算法本身。
eh9 2012年

0
#include<stdio.h>
main()
{
        int a[5] = {55,11,66,77,72};
        int max,min,i;
        int smax,smin;
        max = min = a[0];
        smax = smin = a[0];
        for(i=0;i<=4;i++)
        {
                if(a[i]>max)
                {
                        smax = max;
                        max = a[i];
                }
                if(max>a[i]&&smax<a[i])
                {
                        smax = a[i];
                }
        }
        printf("the first max element z %d\n",max);
        printf("the second max element z %d\n",smax);
}

0

C ++ 11中sdcvvc接受的解决方案。

#include <algorithm>
#include <iostream>
#include <vector>
#include <cassert>
#include <climits>

using std::vector;
using std::cout;
using std::endl;
using std::random_shuffle;
using std::min;
using std::max;

vector<int> create_tournament(const vector<int>& input) {
  // make sure we have at least two elements, so the problem is interesting
  if (input.size() <= 1) {
    return input;
  }

  vector<int> result(2 * input.size() - 1, -1);

  int i = 0;
  for (const auto& el : input) {
    result[input.size() - 1 + i] = el;
    ++i;
  }

  for (uint j = input.size() / 2; j > 0; j >>= 1) {
    for (uint k = 0; k < 2 * j; k += 2) {
      result[j - 1 + k / 2] = min(result[2 * j - 1 + k], result[2 * j + k]);
    }
  }

  return result;
}

int second_smaller(const vector<int>& tournament) {
  const auto& minimum = tournament[0];
  int second = INT_MAX;

  for (uint j = 0; j < tournament.size() / 2; ) {
    if (tournament[2 * j + 1] == minimum) {
      second = min(second, tournament[2 * j + 2]);
      j = 2 * j + 1;
    }
    else {
      second = min(second, tournament[2 * j + 1]);
      j = 2 * j + 2;
    }
  }

  return second;
}

void print_vector(const vector<int>& v) {
  for (const auto& el : v) {
    cout << el << " ";
  }
  cout << endl;
}

int main() {

  vector<int> a;
  for (int i = 1; i <= 2048; ++i)
    a.push_back(i);

  for (int i = 0; i < 1000; i++) {
    random_shuffle(a.begin(), a.end());
    const auto& v = create_tournament(a);
    assert (second_smaller(v) == 2);
  }

  return 0;
}

0

我已经看完了以上所有文章,但是我坚信Tournament算法的实现是最好的方法。让我们考虑@Gumbo发布的以下算法

largest := numbers[0];
secondLargest := null
for i=1 to numbers.length-1 do
    number := numbers[i];
    if number > largest then
        secondLargest := largest;
        largest := number;
    else
        if number > secondLargest then
            secondLargest := number;
        end;
    end;
end;

万一我们要在数组中找到第二大数字,这非常好。它具有(2n-1)个比较数。但是,如果要计算第三大数或第k大数怎么办。上面的算法不起作用。您需要执行另一个过程。

因此,我相信锦标赛算法方法是最好的,这是该链接


0

以下解决方案将进行2(N-1)个比较:

arr  #array with 'n' elements
first=arr[0]
second=-999999  #large negative no
i=1
while i is less than length(arr):
    if arr[i] greater than first:
        second=first
        first=arr[i]
    else:
        if arr[i] is greater than second and arr[i] less than first:
            second=arr[i]
    i=i+1
print second

0

可以通过n + ceil(log n)-2比较来完成。

解决方案:需要n-1个比较才能得出最小值。

但是为了达到最低限度,我们将构建一个锦标赛,其中每个元素将成对分组。就像网球比赛一样,任何回合的胜利者都会前进。

这棵树的高度将为log n,因为我们每回合一半。

获得第二个最低要求的想法是,它将在上一轮中被最低候选人击败。因此,我们需要找到最少的潜在候选人(被击败)。

潜在候选者将为log n =树的高度

所以不行。使用锦标赛树查找最小值的比较值是n-1,第二个最小值是log n -1的总和= n + ceil(log n)-2

这是C ++代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <vector>

using namespace std;

typedef pair<int,int> ii;

bool isPowerOfTwo (int x)
{
  /* First x in the below expression is for the case when x is 0 */
  return x && (!(x&(x-1)));
}
// modified
int log_2(unsigned int n) {
    int bits = 0;
    if (!isPowerOfTwo(n))
        bits++;
    if (n > 32767) {
        n >>= 16;
        bits += 16;
    }
    if (n > 127) {
        n >>= 8;
        bits += 8;
    }
    if (n > 7) {
        n >>= 4;
        bits += 4;
    }
    if (n > 1) {
        n >>= 2;
        bits += 2;
    }
    if (n > 0) {
        bits++;
    }
    return bits;
}

int second_minima(int a[], unsigned int n) {

    // build a tree of size of log2n in the form of 2d array
    // 1st row represents all elements which fights for min
    // candidate pairwise. winner of each pair moves to 2nd
    // row and so on
    int log_2n = log_2(n);
    long comparison_count = 0;
    // pair of ints : first element stores value and second
    //                stores index of its first row
    ii **p = new ii*[log_2n];
    int i, j, k;
    for (i = 0, j = n; i < log_2n; i++) {
        p[i] = new ii[j];
        j = j&1 ? j/2+1 : j/2;
    }
    for (i = 0; i < n; i++)
        p[0][i] = make_pair(a[i], i);



    // find minima using pair wise fighting
    for (i = 1, j = n; i < log_2n; i++) {
        // for each pair
        for (k = 0; k+1 < j; k += 2) {
            // find its winner
            if (++comparison_count && p[i-1][k].first < p[i-1][k+1].first) {
                p[i][k/2].first = p[i-1][k].first;
                p[i][k/2].second = p[i-1][k].second;
            }
            else {
                p[i][k/2].first = p[i-1][k+1].first;
                p[i][k/2].second = p[i-1][k+1].second;
            }

        }
        // if no. of elements in row is odd the last element
        // directly moves to next round (row)
        if (j&1) {
            p[i][j/2].first = p[i-1][j-1].first;
            p[i][j/2].second = p[i-1][j-1].second;
        }
        j = j&1 ? j/2+1 : j/2;
    }



    int minima, second_minima;
    int index;
    minima = p[log_2n-1][0].first;
    // initialize second minima by its final (last 2nd row)
    // potential candidate with which its final took place
    second_minima = minima == p[log_2n-2][0].first ? p[log_2n-2][1].first : p[log_2n-2][0].first;
    // minima original index
    index = p[log_2n-1][0].second;
    for (i = 0, j = n; i <= log_2n - 3; i++) {
        // if its last candidate in any round then there is
        // no potential candidate
        if (j&1 && index == j-1) {
            index /= 2;
            j = j/2+1;
            continue;
        }
        // if minima index is odd, then it fighted with its index - 1
        // else its index + 1
        // this is a potential candidate for second minima, so check it
        if (index&1) {
            if (++comparison_count && second_minima > p[i][index-1].first)
                second_minima = p[i][index-1].first;
        }
        else {
            if (++comparison_count && second_minima > p[i][index+1].first)
                second_minima = p[i][index+1].first;
        }
        index/=2;
        j = j&1 ? j/2+1 : j/2;
    }


    printf("-------------------------------------------------------------------------------\n");
    printf("Minimum          : %d\n", minima);
    printf("Second Minimum   : %d\n", second_minima);
    printf("comparison count : %ld\n", comparison_count);
    printf("Least No. Of Comparisons (");
    printf("n+ceil(log2_n)-2) : %d\n", (int)(n+ceil(log(n)/log(2))-2));
    return 0;
}

int main()
{
    unsigned int n;
    scanf("%u", &n);
    int a[n];
    int i;
    for (i = 0; i < n; i++)
        scanf("%d", &a[i]);
    second_minima(a,n);
    return 0;
}


0

O(1)时间复杂度的一种好方法是使用最大堆。两次调用heapify,您就会得到答案。


0
    int[] int_array = {4, 6, 2, 9, 1, 7, 4, 2, 9, 0, 3, 6, 1, 6, 8};
    int largst=int_array[0];
    int second=int_array[0];
    for (int i=0; i<int_array.length; i++){        
        if(int_array[i]>largst) { 
            second=largst;
            largst=int_array[i];
        }  
        else if(int_array[i]>second  &&  int_array[i]<largst) { 
            second=int_array[i];
        } 
    }

每个人在其他部分都缺少第二个条件。如果数组中的数字大于second_largest且还小于当前的largest_number,则在此比较数字。
Usman

0

我想,按照上面的“最优算法使用n + log n-2比较”的方式,我想出的不使用二进制树存储值的代码如下:

在每次递归调用期间,数组大小都会减半。

因此,比较数为:

第一次迭代:n / 2个比较

第二次迭代:n / 4个比较

第三次迭代:n / 8个比较

...最多记录n次迭代?

因此,总数=> n-1个比较?

function findSecondLargestInArray(array) {
    let winner = [];
    if (array.length === 2) {
        if (array[0] < array[1]) {
            return array[0];
        } else {
            return array[1];
        }
    }
    for (let i = 1; i <= Math.floor(array.length / 2); i++) {
        if (array[2 * i - 1] > array[2 * i - 2]) {
            winner.push(array[2 * i - 1]);
        } else {
            winner.push(array[2 * i - 2]);
        }
    }
    return findSecondLargestInArray(winner);
}

假设数组包含2 ^ n个数字。

如果有6个数字,那么3个数字将移至下一个级别,这是不正确的。

需要8个数字=> 4个数字=> 2个数字=> 1个数字=> 2 ^ n个数字


-2
class solution:
def SecondLargestNumber (self,l):
    Largest = 0
    secondLargest = 0
    for i in l:
        if i> Largest:
            secondLargest = Largest
        Largest = max(Largest, i)
    return Largest, secondLargest

1
问题不在于实现,而在于所需的比较次数。此解决方案需要O(n)比较。能有一个更有效的方法吗?
sapanoia

-2
package com.array.orderstatistics;

import java.util.Arrays;
import java.util.Collections;

public class SecondLargestElement {

    /**
     *  Total Time Complexity will be n log n + O(1)
     * @param str
     */
    public static void main(String str[]) {
        Integer[] integerArr = new Integer[] { 5, 1, 2, 6, 4 };



        // Step1 : Time Complexity will be n log(n)
        Arrays.sort(integerArr, Collections.reverseOrder());

        // Step2 : Array.get Second largestElement
        int secondLargestElement = integerArr[1];

        System.out.println(secondLargestElement);
    }
}

您忘了计算比较次数的位置。(提示:的调用中隐藏了很多内容Arrays.sort())。
Toby Speight,

感谢您的评论。我已经更正了代码。最近的JDK中的Arrays.sort(int [] a)是通过Dual-pivot Quicksort算法实现的,该算法的平均复杂度为O(n log n),并且可以就地执行(例如,不需要额外的空间)。因此,该程序的总时间复杂度将为O(n log n)+ O(1)。
Yuvaraj Ram

请在您的代码中添加一些说明!当您这样做时,将会更容易理解。
Arnav Borborah,

-3

将数组按升序排序,然后将变量分配给第(n-1)个项。


2
效率很低。根据定义需要n * log(n)比较。
riwalk

使用蛮力或锦标赛方法,在蛮力中,第一个比较将花费n-1并在删除bigsT时,第二个将进行n-2 = n-1 + n-2 = 2n-3与O(1)和T( 1)。在锦标赛中,您需要确保有2的幂的元素,否则将其他数字添加到数组中,并且比较将减少为n + logn-2(登录时创建锦标赛,n-1用于查找在锦标赛中输给最大者中的最大者)
AJ先生
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.