Làm thế nào để tìm phần tử lớn thứ k trong một mảng chưa được sắp xếp có độ dài n trong O (n)?


220

Tôi tin rằng có một cách để tìm phần tử lớn thứ k trong một mảng chưa được sắp xếp có độ dài n trong O (n). Hoặc có lẽ đó là "mong đợi" O (n) hoặc một cái gì đó. Làm thế nào chúng ta có thể làm điều này?


49
Nhân tiện, khá nhiều thuật toán được mô tả ở đây biến thành O (n ^ 2) hoặc O (n log n) khi k == n. Đó là, tôi không nghĩ rằng một trong số chúng là O (n) cho tất cả các giá trị của k. Tôi đã sửa đổi để chỉ ra điều này nhưng nghĩ rằng dù sao bạn cũng nên biết.
Kirk Strauser

19
Các thuật toán lựa chọn có thể là O (n) cho bất kỳ giá trị cố định nào của k. Nghĩa là, bạn có thể có một thuật toán lựa chọn cho k = 25 đó là O (n) cho bất kỳ giá trị nào của n và bạn có thể làm điều này cho bất kỳ giá trị cụ thể nào của k không liên quan đến n. Trường hợp thuật toán không còn O (n) là khi giá trị của k có một số phụ thuộc vào giá trị của n, chẳng hạn như k = n hoặc k = n / 2. Tuy nhiên, điều này không có nghĩa là nếu bạn tình cờ chạy thuật toán k = 25 trong danh sách 25 mục mà nó đột nhiên không còn là O (n) vì ký hiệu O mô tả một thuộc tính của thuật toán, không phải là một đặc tính chạy của nó
Tyler McHenry

1
Tôi đã được hỏi câu hỏi này trong một cuộc phỏng vấn trên amazon như một trường hợp chung về việc tìm kiếm yếu tố lớn thứ hai. Bằng cách người phỏng vấn dẫn dắt cuộc phỏng vấn tôi không hỏi liệu tôi có thể phá hủy mảng ban đầu không (tức là sắp xếp nó), vì vậy tôi đã đưa ra một giải pháp phức tạp.
Sambatyon

4
Đây là Câu hỏi 9 trong Cột 11 (Sắp xếp) Ngọc trai lập trình của Jon Bentley.
Qiang Xu

3
@KirkStrauser: Nếu k == n hoặc k == n-1 thì nó trở nên tầm thường. Chúng ta có thể nhận tối đa hoặc tối đa thứ 2 trong một lần di chuyển ngang. Vì vậy, các thuật toán được cung cấp ở đây sẽ được sử dụng thực tế cho các giá trị của k không thuộc về {1,2, n-1, n}
Aditya Joshee

Câu trả lời:


173

Điều này được gọi là tìm thống kê thứ tự k-th . Có một thuật toán ngẫu nhiên rất đơn giản (được gọi là quickselect ) lấy O(n)thời gian trung bình, O(n^2)thời gian trường hợp xấu nhất và thuật toán không ngẫu nhiên khá phức tạp (gọi là introselect ) lấy O(n)thời gian trong trường hợp xấu nhất. Có một số thông tin trên Wikipedia , nhưng nó không tốt lắm.

Tất cả mọi thứ bạn cần là trong các slide powerpoint . Chỉ để trích xuất thuật toán cơ bản của O(n)thuật toán trường hợp xấu nhất (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)

Nó cũng rất chi tiết trong cuốn sách Giới thiệu về thuật toán của Cormen et al.


6
Cảm ơn bạn cho các slide.
Kshitij Banerjee

5
Tại sao nó phải làm việc trong kích thước 5? Tại sao nó không thể hoạt động với kích thước 3?
Joffrey Baratheon

11
@eladv Liên kết slide bị hỏng :(
Misha Moroshko 17/05/2016

7
@eladv Plese sửa liên kết bị hỏng.
maxx777

1
Liên kết @MishaMoroshko đã được sửa
alfasin

118

Nếu bạn muốn có một O(n)thuật toán thực sự , trái ngược với O(kn)hoặc một cái gì đó tương tự, thì bạn nên sử dụng quickselect (về cơ bản là quicksort nơi bạn ném ra phân vùng mà bạn không quan tâm). Prof của tôi có một bài viết tuyệt vời, với phân tích thời gian chạy: ( tham khảo )

Thuật toán QuickSelect nhanh chóng tìm thấy phần tử nhỏ thứ k của một mảng các nphần tử chưa được sắp xếp . Đây là một Thuật toán ngẫu nhiên , vì vậy chúng tôi tính toán thời gian chạy dự kiến ​​trong trường hợp xấu nhất .

Đây là thuật toán.

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

Thời gian chạy của thuật toán này là gì? Nếu đối thủ lật đồng xu cho chúng ta, chúng ta có thể thấy rằng trục luôn là phần tử lớn nhất và kluôn là 1, cho thời gian hoạt động là

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

Nhưng nếu các lựa chọn thực sự ngẫu nhiên, thời gian chạy dự kiến ​​được đưa ra bởi

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

trong đó chúng ta đang đưa ra giả định không hoàn toàn hợp lý rằng đệ quy luôn rơi vào phần lớn hơn của A1hoặc A2.

Chúng ta hãy đoán điều đó T(n) <= anđối với một số người a. Sau đó, chúng tôi nhận được

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

và bây giờ bằng cách nào đó chúng ta phải có được số tiền khủng khiếp ở bên phải dấu cộng để hấp thụ cnbên trái. Nếu chúng ta chỉ ràng buộc nó như , chúng ta nhận được khoảng . Nhưng cái này quá lớn - không có chỗ để ép thêm . Vì vậy, hãy mở rộng tổng bằng cách sử dụng công thức chuỗi số học: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

trong đó chúng tôi tận dụng n là "đủ lớn" để thay thế các floor(n/2)yếu tố xấu xí bằng chất tẩy rửa nhiều hơn (và nhỏ hơn) n/4. Bây giờ chúng ta có thể tiếp tục với

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

cung cấp a > 16c.

Điều này cho T(n) = O(n). Rõ ràng Omega(n), vì vậy chúng tôi nhận được T(n) = Theta(n).


12
Quickselect chỉ là O (n) trong trường hợp trung bình. Thuật toán trung vị có thể được sử dụng để giải quyết vấn đề trong thời gian O (n) trong trường hợp xấu nhất.
John Kurlak

Ý nghĩa của nó là k > length(A) - length(A2)gì?
WoooHaaaa

đây không phải là O (n), bạn đang gọi lại hàm là đệ quy, T (n). Đã có một O (n) bên trong hàm đệ quy T (n), vì vậy rõ ràng không cần suy nghĩ, độ phức tạp tổng thể sẽ lớn hơn O (n).
dùng1735921

3
@MrROY Cho rằng chúng tôi đã tách Ara A1A2xung quanh trục, chúng tôi biết điều đó length(A) == length(A1)+length(A2)+1. Vì vậy, k > length(A)-length(A2)tương đương với k > length(A1)+1, đó là sự thật khi kở đâu đó trong A2.
Filipe Gonçalves

@ FilipeGonçalves, có nếu không có phần tử trùng lặp trong trục. len (A1) + len (A2) + K-trùng lặp = len (A)
d1val

16

Google nhanh chóng về điều đó ('mảng phần tử lớn thứ k') đã trả về điều này: http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17

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

(nó được dành riêng cho 3d lớn nhất)

và câu trả lời này:

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)

15
tốt, nó thực sự là O (n) + O (k log n) không giảm cho các giá trị quan trọng của K
Jimmy

2
Nhưng việc tìm điểm chèn trong danh sách liên kết đôi đó là O (k).
Kirk Strauser

1
Và nếu k cố định, O (k) = O (1)
Tyler McHenry

1
@warren: Big-O gần đúng, nhưng bạn luôn gần đúng. Quicksort thực sự là O (n ^ 2), ví dụ, vì đó là trường hợp xấu nhất. cái này là O (n + k log n).
Claudiu

1
bạn không thể coi k là hằng số. Có thể k = n trong trường hợp độ phức tạp thời gian là O (nlogn)
sabbir

11

Bạn thích quicksort. Chọn một yếu tố ngẫu nhiên và đẩy mọi thứ cao hơn hoặc thấp hơn. Tại thời điểm này, bạn sẽ biết phần tử nào bạn thực sự đã chọn và nếu đó là phần tử thứ k bạn đã hoàn thành, nếu không, bạn lặp lại với thùng (cao hơn hoặc thấp hơn), phần tử thứ k sẽ rơi vào. Nói theo thống kê, thời gian phải tìm phần tử thứ k phát triển với n, O (n).


2
Đây là những gì quickselect là, FWIW.
rogerdpack

6

Phân tích thuật toán đồng hành của lập trình viên đưa ra một phiên bản O (n), mặc dù tác giả nói rằng hệ số không đổi rất cao, có lẽ bạn thích phương pháp sắp xếp danh sách ngây thơ trong danh sách.

Tôi đã trả lời thư của câu hỏi của bạn :)


2
Không thực sự đúng trong mọi trường hợp. Tôi đã triển khai các giá trị trung bình và so sánh nó với phương pháp Sắp xếp tích hợp trong .NET và giải pháp tùy chỉnh thực sự chạy nhanh hơn theo thứ tự cường độ. Bây giờ câu hỏi thực sự là: điều đó có quan trọng với bạn trong hoàn cảnh nhất định không. Viết và gỡ lỗi 100 dòng mã so với một lớp chỉ trả hết nếu mã đó sẽ được thực thi nhiều lần đến nỗi người dùng bắt đầu nhận thấy sự khác biệt về thời gian chạy và cảm thấy khó chịu khi chờ thao tác hoàn tất.
Zoran Horvat

5

Thư viện chuẩn C ++ có chức năng gọi gần như chính xác nth_element, mặc dù nó sửa đổi dữ liệu của bạn. Nó đã dự kiến ​​thời gian chạy tuyến tính, O (N), và nó cũng thực hiện một phần sắp xếp.

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

1
Không, nó có thời gian chạy O (n) trung bình dự kiến . Ví dụ, quicksort trung bình là O (nlogn) với trường hợp xấu nhất là O (n ^ 2). Wow, một cái gì đó thẳng lên thực tế sai!
Kirk Strauser

5
Không, thực tế không có gì sai với câu trả lời này. Nó hoạt động và tiêu chuẩn C ++ yêu cầu thời gian chạy tuyến tính dự kiến.
David Nehme

Tôi đã được hỏi trong cuộc phỏng vấn để cho rằng không gian trống của O (k) và 'n' là rất lớn. Tôi không thể nói với anh ấy giải pháp O (n) vì tôi nghĩ nth_element sẽ cần không gian o (n). Tôi có sai không? Thuật toán cơ bản không phải là quicksort dựa trên nth_element?
Manish Baphna

4

Mặc dù không chắc chắn lắm về độ phức tạp của O (n), nhưng nó sẽ chắc chắn nằm giữa O (n) và nLog (n). Cũng chắc chắn là gần với O (n) hơn nLog (n). Hàm được viết bằng Java

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;
    }
}

Mã hóa đẹp, +1. Nhưng không cần phải sử dụng thêm không gian.
Hengameh 24/07/2015

4

Tôi đã triển khai tìm tối thiểu thứ k trong n phần tử chưa được sắp xếp bằng cách sử dụng lập trình động, cụ thể là phương thức giải đấu. Thời gian thực hiện là O (n + klog (n)). Cơ chế được sử dụng được liệt kê là một trong những phương pháp trên trang Wikipedia về Thuật toán lựa chọn (như được chỉ ra trong một trong các bài đăng ở trên). Bạn có thể đọc về thuật toán và cũng có thể tìm mã (java) trên trang blog của tôi Tìm Kth Minimal . Ngoài ra, logic có thể thực hiện thứ tự một phần của danh sách - trả về K min (hoặc max) đầu tiên trong thời gian O (klog (n)).

Mặc dù mã được cung cấp kết quả tối thiểu thứ k, logic tương tự có thể được sử dụng để tìm mức tối đa thứ k trong O (klog (n)), bỏ qua công việc trước được thực hiện để tạo cây giải đấu.


3

Bạn có thể làm điều đó trong O (n + kn) = O (n) (không đổi k) theo thời gian và O (k) cho không gian, bằng cách theo dõi các yếu tố k lớn nhất bạn đã thấy.

Đối với mỗi phần tử trong mảng, bạn có thể quét danh sách k lớn nhất và thay thế phần tử nhỏ nhất bằng phần tử mới nếu nó lớn hơn.

Giải pháp heap ưu tiên của Warren là gọn gàng hơn mặc dù.


3
Điều này sẽ có trường hợp xấu nhất là O (n ^ 2) khi bạn được yêu cầu vật phẩm nhỏ nhất.
Elie

2
"Mục nhỏ nhất" có nghĩa là k = n, vì vậy k không còn là hằng số.
Tyler McHenry

Hoặc có thể giữ một đống (hoặc đống đảo ngược, hoặc cây cân bằng) của k lớn nhất bạn từng thấy cho đến nay O(n log k)... vẫn thoái hóa thành O (nlogn) trong trường hợp k lớn. Tôi nghĩ rằng nó sẽ hoạt động tốt với các giá trị nhỏ của k tuy nhiên ... có thể nhanh hơn một số thuật toán khác được đề cập ở đây [???]
rogerdpack

3

Sexy quickselect trong Python

def quickselect(arr, k):
    '''
     k = 1 returns first element in ascending order.
     can be easily modified to return first element in descending order
    '''

    r = random.randrange(0, len(arr))

    a1 = [i for i in arr if i < arr[r]] '''partition'''
    a2 = [i for i in arr if i > arr[r]]

    if k <= len(a1):
        return quickselect(a1, k)
    elif k > len(arr)-len(a2):
        return quickselect(a2, k - (len(arr) - len(a2)))
    else:
        return arr[r]

Giải pháp hay, ngoại trừ việc điều này trả về phần tử nhỏ thứ k trong danh sách chưa sắp xếp. Đảo ngược các toán tử so sánh trong phần hiểu danh sách a1 = [i for i in arr if i > arr[r]]a2 = [i for i in arr if i < arr[r]]sẽ trả về phần tử lớn thứ k .
cách giao thiệp

Từ một chuẩn mực nhỏ, thậm chí trên mảng lớn, nó là nhanh hơn để sắp xếp (với numpy.sortcho numpy arrayhoặc sortedcho các danh sách) so với sử dụng thực hiện hướng dẫn này.
Nreen

2

Tìm trung vị của mảng trong thời gian tuyến tính, sau đó sử dụng thủ tục phân vùng chính xác như trong quicksort để chia mảng thành hai phần, các giá trị ở bên trái của trung vị nhỏ hơn (<) so với trung vị và bên phải lớn hơn (>) trung bình , điều đó cũng có thể được thực hiện trong thời gian lineat, bây giờ, đi đến phần đó của mảng có phần tử thứ k, Bây giờ tái phát trở thành: T (n) = T (n / 2) + cn mang lại cho tôi O (n) overal.


Không cần phải tìm trung bình. không có trung bình cách tiếp cận của bạn vẫn tốt.
Hengameh

2
Và làm thế nào để bạn tìm thấy trung vị trong thời gian tuyến tính, tôi dám hỏi? ... :)
rogerdpack

2

Dưới đây là liên kết đến việc thực hiện đầy đủ với một lời giải thích sâu rộng về cách thuật toán tìm phần tử Kth trong thuật toán chưa sắp xếp hoạt động. Ý tưởng cơ bản là phân vùng mảng như trong QuickSort. Nhưng để tránh các trường hợp cực đoan (ví dụ: khi phần tử nhỏ nhất được chọn làm trục trong mỗi bước, để thuật toán suy biến thành thời gian chạy O (n ^ 2)), lựa chọn trục đặc biệt được áp dụng, được gọi là thuật toán trung vị. Toàn bộ giải pháp chạy trong thời gian O (n) trong trường hợp xấu nhất và trong trường hợp trung bình.

Đây là liên kết đến toàn bộ bài viết (đó là về việc tìm phần tử nhỏ nhất của Kth , nhưng nguyên tắc là giống nhau để tìm Kth lớn nhất ):

Tìm phần tử nhỏ nhất của Kth trong một mảng chưa được sắp xếp


2

Theo bài viết này Tìm mục lớn nhất thứ K trong danh sách n mục , thuật toán sau sẽ mất O(n)thời gian trong trường hợp xấu nhất.

  1. Chia mảng thành n / 5 danh sách gồm 5 phần tử mỗi phần.
  2. Tìm trung vị trong mỗi mảng phụ gồm 5 phần tử.
  3. Đệ quy, trung bình của tất cả các trung vị, hãy gọi nó là M
  4. Phân vùng mảng thành hai mảng phụ mảng phụ thứ nhất chứa các phần tử lớn hơn M, giả sử mảng phụ này là a1, trong khi mảng phụ khác chứa các phần tử nhỏ hơn M., hãy gọi mảng phụ này là a2.
  5. Nếu k <= | a1 |, trả về lựa chọn (a1, k).
  6. Nếu k− 1 = | a1 |, trả về M.
  7. Nếu k> | a1 | + 1, trả về lựa chọn (a2, k a1 - 1).

Phân tích: Theo đề xuất trong bài báo gốc:

Chúng tôi sử dụng trung bình để phân vùng danh sách thành hai nửa (nửa đầu, nếu k <= n/2và nửa sau khác). Thuật toán này cần thời gian cnở cấp đệ quy đầu tiên cho một số hằng số c, cn/2ở cấp độ tiếp theo (vì chúng tôi lặp lại trong một danh sách kích thước n / 2), cn/4ở cấp độ thứ ba, v.v. Tổng thời gian thực hiện là cn + cn/2 + cn/4 + .... = 2cn = o(n).

Tại sao kích thước phân vùng được lấy 5 mà không phải 3?

Như đã đề cập trong bài báo gốc :

Chia danh sách cho 5 đảm bảo sự phân chia trường hợp xấu nhất là 70 - 30. Một nửa số trung vị lớn hơn trung bình, do đó ít nhất một nửa trong số 5 khối có ít nhất 3 phần tử và điều này tạo ra sự 3n/10phân chia, trong đó có nghĩa là phân vùng khác là 7n / 10 trong trường hợp xấu nhất. Điều đó mang lại T(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1, thời gian chạy trường hợp xấu nhất là O(n).

Bây giờ tôi đã cố gắng thực hiện các thuật toán trên như:

public static int findKthLargestUsingMedian(Integer[] array, int k) {
        // Step 1: Divide the list into n/5 lists of 5 element each.
        int noOfRequiredLists = (int) Math.ceil(array.length / 5.0);
        // Step 2: Find pivotal element aka median of medians.
        int medianOfMedian =  findMedianOfMedians(array, noOfRequiredLists);
        //Now we need two lists split using medianOfMedian as pivot. All elements in list listOne will be grater than medianOfMedian and listTwo will have elements lesser than medianOfMedian.
        List<Integer> listWithGreaterNumbers = new ArrayList<>(); // elements greater than medianOfMedian
        List<Integer> listWithSmallerNumbers = new ArrayList<>(); // elements less than medianOfMedian
        for (Integer element : array) {
            if (element < medianOfMedian) {
                listWithSmallerNumbers.add(element);
            } else if (element > medianOfMedian) {
                listWithGreaterNumbers.add(element);
            }
        }
        // Next step.
        if (k <= listWithGreaterNumbers.size()) return findKthLargestUsingMedian((Integer[]) listWithGreaterNumbers.toArray(new Integer[listWithGreaterNumbers.size()]), k);
        else if ((k - 1) == listWithGreaterNumbers.size()) return medianOfMedian;
        else if (k > (listWithGreaterNumbers.size() + 1)) return findKthLargestUsingMedian((Integer[]) listWithSmallerNumbers.toArray(new Integer[listWithSmallerNumbers.size()]), k-listWithGreaterNumbers.size()-1);
        return -1;
    }

    public static int findMedianOfMedians(Integer[] mainList, int noOfRequiredLists) {
        int[] medians = new int[noOfRequiredLists];
        for (int count = 0; count < noOfRequiredLists; count++) {
            int startOfPartialArray = 5 * count;
            int endOfPartialArray = startOfPartialArray + 5;
            Integer[] partialArray = Arrays.copyOfRange((Integer[]) mainList, startOfPartialArray, endOfPartialArray);
            // Step 2: Find median of each of these sublists.
            int medianIndex = partialArray.length/2;
            medians[count] = partialArray[medianIndex];
        }
        // Step 3: Find median of the medians.
        return medians[medians.length / 2];
    }

Chỉ cần hoàn thành, một thuật toán khác sử dụng Hàng đợi ưu tiên và mất thời gian O(nlogn).

public static int findKthLargestUsingPriorityQueue(Integer[] nums, int k) {
        int p = 0;
        int numElements = nums.length;
        // create priority queue where all the elements of nums will be stored
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();

        // place all the elements of the array to this priority queue
        for (int n : nums) {
            pq.add(n);
        }

        // extract the kth largest element
        while (numElements - k + 1 > 0) {
            p = pq.poll();
            k++;
        }

        return p;
    }

Cả hai thuật toán này đều có thể được kiểm tra như:

public static void main(String[] args) throws IOException {
        Integer[] numbers = new Integer[]{2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};
        System.out.println(findKthLargestUsingMedian(numbers, 8));
        System.out.println(findKthLargestUsingPriorityQueue(numbers, 8));
    }

Như sản lượng dự kiến ​​là: 18 18


@rogerdpack Tôi đã cung cấp liên kết tôi đã theo dõi.
akhil_mittal

2

Làm thế nào về phương pháp này

Duy trì a buffer of length kvà a tmp_max, nhận tmp_max là O (k) và được thực hiện n lần nên một số thứ nhưO(kn)

nhập mô tả hình ảnh ở đây

Là nó đúng hay tôi đang thiếu một cái gì đó?

Mặc dù nó không đánh bại trường hợp trung bình của quickselect và trường hợp xấu nhất của phương pháp thống kê trung bình nhưng nó khá dễ hiểu và dễ thực hiện.


1
Tôi thích nó, dễ hiểu hơn. Mặc dù độ phức tạp là O (nk) như bạn đã chỉ ra.
Hajjat

1

lặp qua danh sách nếu giá trị hiện tại lớn hơn giá trị lớn nhất được lưu trữ, hãy lưu trữ dưới dạng giá trị lớn nhất và giảm 1-4 xuống và giảm 5 danh sách. Nếu không, hãy so sánh nó với số 2 và làm điều tương tự. Lặp lại, kiểm tra nó với tất cả 5 giá trị được lưu trữ. điều này sẽ làm điều đó trong O (n)


"Cú va chạm" đó là O (n) nếu bạn đang sử dụng một mảng hoặc xuống O (log n) (tôi nghĩ) nếu bạn sử dụng cấu trúc tốt hơn.
Kirk Strauser

Không cần phải là O (log k) - nếu danh sách là danh sách được liên kết thì thêm phần tử mới vào đầu và thả phần tử cuối cùng giống như O (2)
Alnitak

Cú va chạm sẽ là O (k) cho danh sách được hỗ trợ mảng, O (1) cho danh sách được liên kết phù hợp. Dù bằng cách nào, loại câu hỏi này thường cho rằng nó có tác động tối thiểu so với n và nó không giới thiệu thêm yếu tố nào của n.
bobince

nó cũng sẽ là O (1) nếu vết sưng sử dụng bộ đệm vòng
Alnitak

1
Dù sao, thuật toán của nhận xét là không đầy đủ, nó không xem xét một yếu tố n đến trong đó là phần tử mới (ví dụ) lớn thứ hai. Hành vi xấu nhất, trong đó mỗi yếu tố trong n phải được so sánh với từng yếu tố trong bảng điểm cao, là O (kn) - nhưng điều đó có lẽ vẫn có nghĩa là O (n) về câu hỏi.
bobince

1

tôi muốn đề nghị một câu trả lời

nếu chúng ta lấy các phần tử k đầu tiên và sắp xếp chúng vào một danh sách các giá trị k được liên kết

bây giờ cho mọi giá trị khác ngay cả trong trường hợp xấu nhất nếu chúng ta sắp xếp chèn cho các giá trị nk còn lại ngay cả trong trường hợp so sánh xấu nhất sẽ là k * (nk) và đối với các giá trị k được sắp xếp, hãy để nó là k * (k- 1) vì vậy nó trở thành (nk-k) là o (n)

chúc mừng


1
sắp xếp mất thời gian nlogn ... thuật toán sẽ chạy trong thời gian tuyến tính
MrDatabase

1

Có thể tìm thấy giải thích về thuật toán trung bình - của - để tìm số nguyên lớn thứ k trong số n ở đây: http://cs.indstate.edu/~spitla/presentation.pdf

Thực hiện trong c ++ dưới đây:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int findMedian(vector<int> vec){
//    Find median of a vector
    int median;
    size_t size = vec.size();
    median = vec[(size/2)];
    return median;
}

int findMedianOfMedians(vector<vector<int> > values){
    vector<int> medians;

    for (int i = 0; i < values.size(); i++) {
        int m = findMedian(values[i]);
        medians.push_back(m);
    }

    return findMedian(medians);
}

void selectionByMedianOfMedians(const vector<int> values, int k){
//    Divide the list into n/5 lists of 5 elements each
    vector<vector<int> > vec2D;

    int count = 0;
    while (count != values.size()) {
        int countRow = 0;
        vector<int> row;

        while ((countRow < 5) && (count < values.size())) {
            row.push_back(values[count]);
            count++;
            countRow++;
        }
        vec2D.push_back(row);
    }

    cout<<endl<<endl<<"Printing 2D vector : "<<endl;
    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            cout<<vec2D[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;

//    Calculating a new pivot for making splits
    int m = findMedianOfMedians(vec2D);
    cout<<"Median of medians is : "<<m<<endl;

//    Partition the list into unique elements larger than 'm' (call this sublist L1) and
//    those smaller them 'm' (call this sublist L2)
    vector<int> L1, L2;

    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            if (vec2D[i][j] > m) {
                L1.push_back(vec2D[i][j]);
            }else if (vec2D[i][j] < m){
                L2.push_back(vec2D[i][j]);
            }
        }
    }

//    Checking the splits as per the new pivot 'm'
    cout<<endl<<"Printing L1 : "<<endl;
    for (int i = 0; i < L1.size(); i++) {
        cout<<L1[i]<<" ";
    }

    cout<<endl<<endl<<"Printing L2 : "<<endl;
    for (int i = 0; i < L2.size(); i++) {
        cout<<L2[i]<<" ";
    }

//    Recursive calls
    if ((k - 1) == L1.size()) {
        cout<<endl<<endl<<"Answer :"<<m;
    }else if (k <= L1.size()) {
        return selectionByMedianOfMedians(L1, k);
    }else if (k > (L1.size() + 1)){
        return selectionByMedianOfMedians(L2, k-((int)L1.size())-1);
    }

}

int main()
{
    int values[] = {2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};

    vector<int> vec(values, values + 25);

    cout<<"The given array is : "<<endl;
    for (int i = 0; i < vec.size(); i++) {
        cout<<vec[i]<<" ";
    }

    selectionByMedianOfMedians(vec, 8);

    return 0;
}

Giải pháp này không hoạt động. Bạn cần sắp xếp mảng trước khi trả về trung vị cho trường hợp 5 phần tử.
Agamelom Hayopadhyay

1

Ngoài ra còn có thuật toán lựa chọn của Wirth , có cách triển khai đơn giản hơn QuickSelect. Thuật toán lựa chọn của Wirth chậm hơn QuickSelect, nhưng với một số cải tiến, nó trở nên nhanh hơn.

Chi tiết hơn. Sử dụng tối ưu hóa MODIFIND của Vladimir Zabrodsky và lựa chọn trục trung bình 3 và chú ý đến các bước cuối cùng của phần phân vùng của thuật toán, tôi đã đưa ra thuật toán sau (có thể gọi là "LefSelect"):

#define F_SWAP(a,b) { float temp=(a);(a)=(b);(b)=temp; }

# Note: The code needs more than 2 elements to work
float lefselect(float a[], const int n, const int k) {
    int l=0, m = n-1, i=l, j=m;
    float x;

    while (l<m) {
        if( a[k] < a[i] ) F_SWAP(a[i],a[k]);
        if( a[j] < a[i] ) F_SWAP(a[i],a[j]);
        if( a[j] < a[k] ) F_SWAP(a[k],a[j]);

        x=a[k];
        while (j>k & i<k) {
            do i++; while (a[i]<x);
            do j--; while (a[j]>x);

            F_SWAP(a[i],a[j]);
        }
        i++; j--;

        if (j<k) {
            while (a[i]<x) i++;
            l=i; j=m;
        }
        if (k<i) {
            while (x<a[j]) j--;
            m=j; i=l;
        }
    }
    return a[k];
}

Trong các điểm chuẩn mà tôi đã làm ở đây , LefSelect nhanh hơn 20-30% so với QuickSelect.


1

Giải pháp Haskell:

kthElem index list = sort list !! index

withShape ~[]     []     = []
withShape ~(x:xs) (y:ys) = x : withShape xs ys

sort []     = []
sort (x:xs) = (sort ls `withShape` ls) ++ [x] ++ (sort rs `withShape` rs)
  where
   ls = filter (<  x)
   rs = filter (>= x)

Điều này thực hiện trung bình của các giải pháp trung bình bằng cách sử dụng phương thức withShape để khám phá kích thước của một phân vùng mà không thực sự tính toán nó.


1

Đây là một triển khai C ++ của QuickSelect ngẫu nhiên. Ý tưởng là chọn ngẫu nhiên một yếu tố trục. Để thực hiện phân vùng ngẫu nhiên, chúng tôi sử dụng hàm ngẫu nhiên, rand () để tạo chỉ mục giữa l và r, hoán đổi phần tử ở chỉ mục được tạo ngẫu nhiên với phần tử cuối cùng và cuối cùng gọi quy trình phân vùng tiêu chuẩn sử dụng phần tử cuối cùng làm trục.

#include<iostream>
#include<climits>
#include<cstdlib>
using namespace std;

int randomPartition(int arr[], int l, int r);

// This function returns k'th smallest element in arr[l..r] using
// QuickSort based method.  ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT
int kthSmallest(int arr[], int l, int r, int k)
{
    // If k is smaller than number of elements in array
    if (k > 0 && k <= r - l + 1)
    {
        // Partition the array around a random element and
        // get position of pivot element in sorted array
        int pos = randomPartition(arr, l, r);

        // If position is same as k
        if (pos-l == k-1)
            return arr[pos];
        if (pos-l > k-1)  // If position is more, recur for left subarray
            return kthSmallest(arr, l, pos-1, k);

        // Else recur for right subarray
        return kthSmallest(arr, pos+1, r, k-pos+l-1);
    }

    // If k is more than number of elements in array
    return INT_MAX;
}

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Standard partition process of QuickSort().  It considers the last
// element as pivot and moves all smaller element to left of it and
// greater elements to right. This function is used by randomPartition()
int partition(int arr[], int l, int r)
{
    int x = arr[r], i = l;
    for (int j = l; j <= r - 1; j++)
    {
        if (arr[j] <= x) //arr[i] is bigger than arr[j] so swap them
        {
            swap(&arr[i], &arr[j]);
            i++;
        }
    }
    swap(&arr[i], &arr[r]); // swap the pivot
    return i;
}

// Picks a random pivot element between l and r and partitions
// arr[l..r] around the randomly picked element using partition()
int randomPartition(int arr[], int l, int r)
{
    int n = r-l+1;
    int pivot = rand() % n;
    swap(&arr[l + pivot], &arr[r]);
    return partition(arr, l, r);
}

// Driver program to test above methods
int main()
{
    int arr[] = {12, 3, 5, 7, 4, 19, 26};
    int n = sizeof(arr)/sizeof(arr[0]), k = 3;
    cout << "K'th smallest element is " << kthSmallest(arr, 0, n-1, k);
    return 0;
}

Độ phức tạp thời gian trường hợp xấu nhất của giải pháp trên vẫn là O (n2). Trong trường hợp xấu nhất, hàm ngẫu nhiên có thể luôn chọn một phần tử góc. Độ phức tạp thời gian dự kiến ​​của QuickSelect ngẫu nhiên ở trên là (n)


Mã tốt đẹp. Cảm ơn bạn đã chia sẻ, +1
Hengameh 24/07/2015

1
  1. Có hàng ưu tiên được tạo.
  2. Chèn tất cả các yếu tố vào đống.
  3. Gọi thăm dò ý kiến ​​() k lần.

    public static int getKthLargestElements(int[] arr)
    {
        PriorityQueue<Integer> pq =  new PriorityQueue<>((x , y) -> (y-x));
        //insert all the elements into heap
        for(int ele : arr)
           pq.offer(ele);
        // call poll() k times
        int i=0;
        while(i&lt;k)
         {
           int result = pq.poll();
         } 
       return result;        
    }
    

0

Đây là một triển khai trong Javascript.

Nếu bạn giải phóng ràng buộc mà bạn không thể sửa đổi mảng, bạn có thể ngăn việc sử dụng bộ nhớ thêm bằng cách sử dụng hai chỉ mục để xác định "phân vùng hiện tại" (theo kiểu quicksort cổ điển - http://www.nczonline.net/blog/2012/ 11/27 / khoa học máy tính trong javascript-quicksort / ).

function kthMax(a, k){
    var size = a.length;

    var pivot = a[ parseInt(Math.random()*size) ]; //Another choice could have been (size / 2) 

    //Create an array with all element lower than the pivot and an array with all element higher than the pivot
    var i, lowerArray = [], upperArray = [];
    for (i = 0; i  < size; i++){
        var current = a[i];

        if (current < pivot) {
            lowerArray.push(current);
        } else if (current > pivot) {
            upperArray.push(current);
        }
    }

    //Which one should I continue with?
    if(k <= upperArray.length) {
        //Upper
        return kthMax(upperArray, k);
    } else {
        var newK = k - (size - lowerArray.length);

        if (newK > 0) {
            ///Lower
            return kthMax(lowerArray, newK);
        } else {
            //None ... it's the current pivot!
            return pivot;
        }   
    }
}  

Nếu bạn muốn kiểm tra cách thức hoạt động, bạn có thể sử dụng biến thể này:

    function kthMax (a, k, logging) {
         var comparisonCount = 0; //Number of comparison that the algorithm uses
         var memoryCount = 0;     //Number of integers in memory that the algorithm uses
         var _log = logging;

         if(k < 0 || k >= a.length) {
            if (_log) console.log ("k is out of range"); 
            return false;
         }      

         function _kthmax(a, k){
             var size = a.length;
             var pivot = a[parseInt(Math.random()*size)];
             if(_log) console.log("Inputs:", a,  "size="+size, "k="+k, "pivot="+pivot);

             // This should never happen. Just a nice check in this exercise
             // if you are playing with the code to avoid never ending recursion            
             if(typeof pivot === "undefined") {
                 if (_log) console.log ("Ops..."); 
                 return false;
             }

             var i, lowerArray = [], upperArray = [];
             for (i = 0; i  < size; i++){
                 var current = a[i];
                 if (current < pivot) {
                     comparisonCount += 1;
                     memoryCount++;
                     lowerArray.push(current);
                 } else if (current > pivot) {
                     comparisonCount += 2;
                     memoryCount++;
                     upperArray.push(current);
                 }
             }
             if(_log) console.log("Pivoting:",lowerArray, "*"+pivot+"*", upperArray);

             if(k <= upperArray.length) {
                 comparisonCount += 1;
                 return _kthmax(upperArray, k);
             } else if (k > size - lowerArray.length) {
                 comparisonCount += 2;
                 return _kthmax(lowerArray, k - (size - lowerArray.length));
             } else {
                 comparisonCount += 2;
                 return pivot;
             }
     /* 
      * BTW, this is the logic for kthMin if we want to implement that... ;-)
      * 

             if(k <= lowerArray.length) {
                 return kthMin(lowerArray, k);
             } else if (k > size - upperArray.length) {
                 return kthMin(upperArray, k - (size - upperArray.length));
             } else 
                 return pivot;
     */            
         }

         var result = _kthmax(a, k);
         return {result: result, iterations: comparisonCount, memory: memoryCount};
     }

Phần còn lại của mã chỉ là để tạo ra một sân chơi:

    function getRandomArray (n){
        var ar = [];
        for (var i = 0, l = n; i < l; i++) {
            ar.push(Math.round(Math.random() * l))
        }

        return ar;
    }

    //Create a random array of 50 numbers
    var ar = getRandomArray (50);   

Bây giờ, chạy bạn kiểm tra một vài lần. Do Math.random () nó sẽ tạo ra mỗi kết quả khác nhau:

    kthMax(ar, 2, true);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 34, true);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);

Nếu bạn kiểm tra nó một vài lần, bạn thậm chí có thể thấy rằng số lần lặp là trung bình, O (n) ~ = hằng * n và giá trị của k không ảnh hưởng đến thuật toán.


0

Tôi đã đưa ra thuật toán này và dường như là O (n):

Giả sử k = 3 và chúng tôi muốn tìm mục lớn thứ 3 trong mảng. Tôi sẽ tạo ba biến và so sánh từng mục của mảng với mức tối thiểu của ba biến này. Nếu mục mảng lớn hơn mức tối thiểu của chúng tôi, chúng tôi sẽ thay thế biến tối thiểu bằng giá trị mục. Chúng tôi tiếp tục điều tương tự cho đến khi kết thúc mảng. Tối thiểu trong ba biến của chúng tôi là mục lớn thứ 3 trong mảng.

define variables a=0, b=0, c=0
iterate through the array items
    find minimum a,b,c
    if item > min then replace the min variable with item value
    continue until end of array
the minimum of a,b,c is our answer

Và, để tìm mục lớn nhất Kth, chúng ta cần có biến K.

Ví dụ: (k = 3)

[1,2,4,1,7,3,9,5,6,2,9,8]

Final variable values:

a=7 (answer)
b=8
c=9

Ai đó có thể vui lòng xem lại điều này và cho tôi biết những gì tôi đang thiếu?


0

Đây là cách triển khai thuật toán eladv được đề xuất (tôi cũng đặt ở đây cách thực hiện với trục ngẫu nhiên):

public class Median {

    public static void main(String[] s) {

        int[] test = {4,18,20,3,7,13,5,8,2,1,15,17,25,30,16};
        System.out.println(selectK(test,8));

        /*
        int n = 100000000;
        int[] test = new int[n];
        for(int i=0; i<test.length; i++)
            test[i] = (int)(Math.random()*test.length);

        long start = System.currentTimeMillis();
        random_selectK(test, test.length/2);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        */
    }

    public static int random_selectK(int[] a, int k) {
        if(a.length <= 1)
            return a[0];

        int r = (int)(Math.random() * a.length);
        int p = a[r];

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return random_selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return random_selectK(temp,k-small-equal);
        }
    }

    public static int selectK(int[] a, int k) {
        if(a.length <= 5) {
            Arrays.sort(a);
            return a[k-1];
        }

        int p = median_of_medians(a);

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return selectK(temp,k-small-equal);
        }
    }

    private static int median_of_medians(int[] a) {
        int[] b = new int[a.length/5];
        int[] temp = new int[5];
        for(int i=0; i<b.length; i++) {
            for(int j=0; j<5; j++)
                temp[j] = a[5*i + j];
            Arrays.sort(temp);
            b[i] = temp[2];
        }

        return selectK(b, b.length/2 + 1);
    }
}

0

nó tương tự như chiến lược quickSort, trong đó chúng ta chọn một trục tùy ý và đưa các phần tử nhỏ hơn sang trái và lớn hơn ở bên phải

    public static int kthElInUnsortedList(List<int> list, int k)
    {
        if (list.Count == 1)
            return list[0];

        List<int> left = new List<int>();
        List<int> right = new List<int>();

        int pivotIndex = list.Count / 2;
        int pivot = list[pivotIndex]; //arbitrary

        for (int i = 0; i < list.Count && i != pivotIndex; i++)
        {
            int currentEl = list[i];
            if (currentEl < pivot)
                left.Add(currentEl);
            else
                right.Add(currentEl);
        }

        if (k == left.Count + 1)
            return pivot;

        if (left.Count < k)
            return kthElInUnsortedList(right, k - left.Count - 1);
        else
            return kthElInUnsortedList(left, k);
    }


0

Bạn có thể tìm thấy phần tử nhỏ thứ k trong thời gian O (n) và không gian không đổi. Nếu chúng ta xem xét mảng chỉ dành cho số nguyên.

Cách tiếp cận là thực hiện tìm kiếm nhị phân trên phạm vi giá trị Array. Nếu chúng ta có cả min_value và max_value trong phạm vi số nguyên, chúng ta có thể thực hiện tìm kiếm nhị phân trên phạm vi đó. Chúng ta có thể viết một hàm so sánh sẽ cho chúng ta biết nếu có bất kỳ giá trị nào là nhỏ nhất thứ k hoặc nhỏ hơn kth nhỏ nhất hoặc lớn hơn kth nhỏ nhất. Thực hiện tìm kiếm nhị phân cho đến khi bạn đạt đến số nhỏ nhất thứ k

Đây là mã cho điều đó

Giải pháp lớp:

def _iskthsmallest(self, A, val, k):
    less_count, equal_count = 0, 0
    for i in range(len(A)):
        if A[i] == val: equal_count += 1
        if A[i] < val: less_count += 1

    if less_count >= k: return 1
    if less_count + equal_count < k: return -1
    return 0

def kthsmallest_binary(self, A, min_val, max_val, k):
    if min_val == max_val:
        return min_val
    mid = (min_val + max_val)/2
    iskthsmallest = self._iskthsmallest(A, mid, k)
    if iskthsmallest == 0: return mid
    if iskthsmallest > 0: return self.kthsmallest_binary(A, min_val, mid, k)
    return self.kthsmallest_binary(A, mid+1, max_val, k)

# @param A : tuple of integers
# @param B : integer
# @return an integer
def kthsmallest(self, A, k):
    if not A: return 0
    if k > len(A): return 0
    min_val, max_val = min(A), max(A)
    return self.kthsmallest_binary(A, min_val, max_val, k)

0

Ngoài ra còn có một thuật toán, vượt trội hơn thuật toán quickselect. Nó được gọi là thuật toán Floyd-Rivets (FR) .

Bài viết gốc: https://doi.org/10.1145/360680.360694

Phiên bản có thể tải xuống: http://citeseerx.ist.psu.edu/viewdoc/doad?doi=10.1.1.309.7108&rep=rep1&type=pdf

Bài viết trên Wikipedia https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_alacticm

Tôi đã cố gắng thực hiện thuật toán quickselect và FR trong C ++. Ngoài ra, tôi đã so sánh chúng với các triển khai thư viện C ++ tiêu chuẩn std :: nth_element (về cơ bản là introselect hybrid của quickselect và heapselect). Kết quả là quickselect và nth_element chạy tương đối trung bình, nhưng thuật toán FR chạy xấp xỉ. nhanh gấp đôi so với họ.

Mã mẫu mà tôi đã sử dụng cho thuật toán FR:

template <typename T>
T FRselect(std::vector<T>& data, const size_t& n)
{
    if (n == 0)
        return *(std::min_element(data.begin(), data.end()));
    else if (n == data.size() - 1)
        return *(std::max_element(data.begin(), data.end()));
    else
        return _FRselect(data, 0, data.size() - 1, n);
}

template <typename T>
T _FRselect(std::vector<T>& data, const size_t& left, const size_t& right, const size_t& n)
{
    size_t leftIdx = left;
    size_t rightIdx = right;

    while (rightIdx > leftIdx)
    {
        if (rightIdx - leftIdx > 600)
        {
            size_t range = rightIdx - leftIdx + 1;
            long long i = n - (long long)leftIdx + 1;
            long long z = log(range);
            long long s = 0.5 * exp(2 * z / 3);
            long long sd = 0.5 * sqrt(z * s * (range - s) / range) * sgn(i - (long long)range / 2);

            size_t newLeft = fmax(leftIdx, n - i * s / range + sd);
            size_t newRight = fmin(rightIdx, n + (range - i) * s / range + sd);

            _FRselect(data, newLeft, newRight, n);
        }
        T t = data[n];
        size_t i = leftIdx;
        size_t j = rightIdx;
        // arrange pivot and right index
        std::swap(data[leftIdx], data[n]);
        if (data[rightIdx] > t)
            std::swap(data[rightIdx], data[leftIdx]);

        while (i < j)
        {
            std::swap(data[i], data[j]);
            ++i; --j;
            while (data[i] < t) ++i;
            while (data[j] > t) --j;
        }

        if (data[leftIdx] == t)
            std::swap(data[leftIdx], data[j]);
        else
        {
            ++j;
            std::swap(data[j], data[rightIdx]);
        }
        // adjust left and right towards the boundaries of the subset
        // containing the (k - left + 1)th smallest element
        if (j <= n)
            leftIdx = j + 1;
        if (n <= j)
            rightIdx = j - 1;
    }

    return data[leftIdx];
}

template <typename T>
int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

-1

Những gì tôi sẽ làm là đây:

initialize empty doubly linked list l
for each element e in array
    if e larger than head(l)
        make e the new head of l
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

Bạn chỉ có thể lưu trữ con trỏ đến phần tử đầu tiên và cuối cùng trong danh sách được liên kết. Họ chỉ thay đổi khi cập nhật vào danh sách được thực hiện.

Cập nhật:

initialize empty sorted tree l
for each element e in array
    if e between head(l) and tail(l)
        insert e into l // O(log k)
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

Nếu e nhỏ hơn đầu (l) thì sao? Nó vẫn có thể lớn hơn phần tử lớn thứ k, nhưng sẽ không bao giờ được thêm vào danh sách đó. Bạn sẽ cần sắp xếp danh sách các mục để làm việc này, theo thứ tự tăng dần.
Elie

Bạn nói đúng, tôi sẽ cần suy nghĩ thêm về điều này. :-)
Jasper Bekkers

Giải pháp sẽ là kiểm tra xem e có nằm giữa đầu (l) và đuôi (l) không và đặt nó vào đúng vị trí nếu có. Làm điều này O (kn). Bạn có thể làm cho nó O (n log k) khi sử dụng cây nhị phân theo dõi các phần tử tối thiểu và tối đa.
Jasper Bekkers

-1

Trước tiên, chúng ta có thể xây dựng một BST từ mảng chưa sắp xếp, mất thời gian O (n) và từ BST, chúng ta có thể tìm thấy phần tử nhỏ thứ k trong O (log (n)), qua tất cả các số theo thứ tự O (n).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.