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?
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?
Câu trả lời:
Đ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.
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 n
phầ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à k
luô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 A1
hoặ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ụ cn
bê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 an
2(1/n)(n/2)an = an
cn
∑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)
.
k > length(A) - length(A2)
gì?
A
ra A1
và A2
xung 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
.
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)
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).
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 là 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 :)
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
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;
}
}
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.
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ù.
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 [???]
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]
a1 = [i for i in arr if i > arr[r]]
và a2 = [i for i in arr if i < arr[r]]
sẽ trả về phần tử lớn thứ k .
numpy.sort
cho numpy array
hoặc sorted
cho các danh sách) so với sử dụng thực hiện hướng dẫn này.
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.
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
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.
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/2
và nửa sau khác). Thuật toán này cần thời giancn
ở 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/10
phâ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ạiT(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
Làm thế nào về phương pháp này
Duy trì a buffer of length k
và 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)
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.
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)
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
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;
}
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.
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ó.
Đâ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)
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<k)
{
int result = pq.poll();
}
return result;
}
Đâ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.
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?
Đâ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);
}
}
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);
}
Đi đến cuối của liên kết này: ...........
http://www.geekforgeek.org/kth-smallestlargest-element-unsoped-array-set-3-worst-case-linear-time/
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)
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));
}
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
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).