[performance] O (n)에서 길이 n의 정렬되지 않은 배열에서 k 번째로 큰 요소를 찾는 방법은 무엇입니까?

O (n)에서 길이 n의 정렬되지 않은 배열에서 k 번째로 큰 요소를 찾는 방법이 있다고 생각합니다. 또는 아마도 “예상 된”O (n) 또는 무언가 일 것입니다. 우리는 어떻게 이것을 할 수 있습니까?



답변

이것을 k 차 통계량 찾기라고 합니다 . 평균 시간, 최악의 경우 시간을 취하는 매우 간단한 무작위 알고리즘 ( quickselect ) O(n)O(n^2)최악의 경우를 취하는 매우 복잡한 비 랜덤 화 알고리즘 ( introselect )이 O(n)있습니다. Wikipedia에 대한 정보가 있지만 그다지 좋지 않습니다.

필요한 것은 이 파워 포인트 슬라이드에 있습니다 . O(n)최악의 알고리즘 (introselect) 의 기본 알고리즘을 추출하기 만하면됩니다 .

Select(A,n,i):
    Divide input into ⌈n/5⌉ groups of size 5.

    /* Partition on median-of-medians */
    medians = array of each group’s median.
    pivot = Select(medians, ⌈n/5⌉, ⌈n/10⌉)
    Left Array L and Right Array G = partition(A, pivot)

    /* Find ith element in L, pivot, or G */
    k = |L| + 1
    If i = k, return pivot
    If i < k, return Select(L, k-1, i)
    If i > k, return Select(G, n-k, i-k)

Cormen et al.의 알고리즘 소개 책에도 매우 자세하게 설명되어 있습니다.


답변

당신이 진실을 원한다면 O(n) 알고리즘O(kn) 이와는 반대로 quickselect를 사용해야합니다 (기본적으로 관심없는 파티션을 버리는 곳은 quicksort입니다). 내 교수님은 런타임 분석을 통해 훌륭한 글을 작성했습니다 : ( reference )

QuickSelect 알고리즘은 정렬되지 않은 n요소 배열에서 k 번째로 작은 요소를 빠르게 찾습니다 . 이것은 RandomizedAlgorithm 그래서 우리는 최악의 경우는 계산, 예상 시간을 실행할 수 있습니다.

알고리즘은 다음과 같습니다.

QuickSelect(A, k)
  let r be chosen uniformly at random in the range 1 to length(A)
  let pivot = A[r]
  let A1, A2 be new arrays
  # split into a pile A1 of small elements and A2 of big elements
  for i = 1 to n
    if A[i] < pivot then
      append A[i] to A1
    else if A[i] > pivot then
      append A[i] to A2
    else
      # do nothing
  end for
  if k <= length(A1):
    # it's in the pile of small elements
    return QuickSelect(A1, k)
  else if k > length(A) - length(A2)
    # it's in the pile of big elements
    return QuickSelect(A2, k - (length(A) - length(A2))
  else
    # it's equal to the pivot
    return pivot

이 알고리즘의 실행 시간은 무엇입니까? 적이 우리를 위해 동전을 뒤집 으면 피벗이 항상 가장 큰 요소이며k 항상 1이며

T(n) = Theta(n) + T(n-1) = Theta(n2)

그러나 선택 사항이 실제로 임의 인 경우 예상 실행 시간은 다음과 같습니다.

T(n) <= Theta(n) + (1/n) ∑i=1 to nT(max(i, n-i-1))

우리는 재귀가 항상 더 큰 영역에 도달한다고 전적으로 합리적이지 않다고 가정하고 있습니다. A1 또는A2 .

T(n) <= an일부를 추측 해 봅시다a . 그럼 우리는 얻는다

T(n)
 <= cn + (1/n) ∑i=1 to nT(max(i-1, n-i))
 = cn + (1/n) ∑i=1 to floor(n/2) T(n-i) + (1/n) ∑i=floor(n/2)+1 to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n ai

이제 어떻게 든 우리는 왼쪽에있는 +를 흡수하기 위해 더하기 부호의 오른쪽에 끔찍한 합계를 가져와야합니다 cn. 우리가 방금 묶으면 대략적으로 얻을 수 있습니다 . 그러나 이것은 너무 큽니다-여분으로 짜낼 공간이 없습니다.2(1/n) ∑i=n/2 to n an2(1/n)(n/2)an = ancn . 산술 시리즈 공식을 사용하여 합계를 확장 해 봅시다.

i=floor(n/2) to n i
 = ∑i=1 to n i - ∑i=1 to floor(n/2) i
 = n(n+1)/2 - floor(n/2)(floor(n/2)+1)/2
 <= n2/2 - (n/4)2/2
 = (15/32)n2

여기서 우리는 n이 “충분히 크다”는 것을 이용하여 못생긴 floor(n/2)요소를 훨씬 더 깨끗하고 작은 것으로 대체합니다 n/4. 이제 우리는 계속할 수 있습니다

cn + 2 (1/n) ∑i=floor(n/2) to n ai,
 <= cn + (2a/n) (15/32) n2
 = n (c + (15/16)a)
 <= an

제공 a > 16c.

이 제공합니다 T(n) = O(n). 분명히 Omega(n)그렇습니다 T(n) = Theta(n).


답변

그 ( ‘kth 가장 큰 요소 배열’)에 대한 빠른 Google은 이것을 반환했습니다 : http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17

"Make one pass through tracking the three largest values so far."

(특히 3d 최대)

그리고이 답변 :

Build a heap/priority queue.  O(n)
Pop top element.  O(log n)
Pop top element.  O(log n)
Pop top element.  O(log n)

Total = O(n) + 3 O(log n) = O(n)


답변

당신은 퀵 정렬을 좋아합니다. 무작위로 원소를 고르고 모든 것을 높이거나 낮추십시오. 이 시점에서 실제로 선택한 요소를 알 수 있으며 수행 한 k 번째 요소 인 경우 bin (높거나 낮은)으로 반복하여 k 번째 요소가 빠지게됩니다. 통계적으로 말하면, 시간 k 번째 원소가 n, O (n)으로 커지는 것을 찾는 데 필요합니다.


답변

프로그래머의 알고리즘 분석 도우미 (Companion to Algorithm Analysis ) O (n) 버전을 제공 하지만 저자는 상수 인자가 너무 높지만 순진한 정렬 목록과 선택 방법을 선호 할 것입니다.

나는 당신의 질문의 편지에 대답했습니다 🙂


답변

C ++ 표준 라이브러리는 데이터를 수정하더라도 거의 정확하게 함수 호출을 갖 nth_element습니다. 선형 런타임 O (N)을 예상했으며 부분 정렬도 수행합니다.

const int N = ...;
double a[N];
// ...
const int m = ...; // m < N
nth_element (a, a + m, a + N);
// a[m] contains the mth element in a


답변

O (n) 복잡성에 대해서는 잘 모르지만 O (n)과 nLog (n) 사이에 있어야합니다. 또한 nLog (n)보다 O (n)에 더 가깝습니다. 함수는 자바로 작성

public int quickSelect(ArrayList<Integer>list, int nthSmallest){
    //Choose random number in range of 0 to array length
    Random random =  new Random();
    //This will give random number which is not greater than length - 1
    int pivotIndex = random.nextInt(list.size() - 1);

    int pivot = list.get(pivotIndex);

    ArrayList<Integer> smallerNumberList = new ArrayList<Integer>();
    ArrayList<Integer> greaterNumberList = new ArrayList<Integer>();

    //Split list into two.
    //Value smaller than pivot should go to smallerNumberList
    //Value greater than pivot should go to greaterNumberList
    //Do nothing for value which is equal to pivot
    for(int i=0; i<list.size(); i++){
        if(list.get(i)<pivot){
            smallerNumberList.add(list.get(i));
        }
        else if(list.get(i)>pivot){
            greaterNumberList.add(list.get(i));
        }
        else{
            //Do nothing
        }
    }

    //If smallerNumberList size is greater than nthSmallest value, nthSmallest number must be in this list
    if(nthSmallest < smallerNumberList.size()){
        return quickSelect(smallerNumberList, nthSmallest);
    }
    //If nthSmallest is greater than [ list.size() - greaterNumberList.size() ], nthSmallest number must be in this list
    //The step is bit tricky. If confusing, please see the above loop once again for clarification.
    else if(nthSmallest > (list.size() - greaterNumberList.size())){
        //nthSmallest will have to be changed here. [ list.size() - greaterNumberList.size() ] elements are already in
        //smallerNumberList
        nthSmallest = nthSmallest - (list.size() - greaterNumberList.size());
        return quickSelect(greaterNumberList,nthSmallest);
    }
    else{
        return pivot;
    }
}