Я вважаю, що існує спосіб знайти найбільш великий елемент в несортованому масиві довжиною n в O (n). А може, це "очікуване" O (n) чи щось таке. Як ми можемо це зробити?
Я вважаю, що існує спосіб знайти найбільш великий елемент в несортованому масиві довжиною n в O (n). А може, це "очікуване" O (n) чи щось таке. Як ми можемо це зробити?
Відповіді:
Це називається знаходженням статистики k-го порядку . Існує дуже простий рандомізований алгоритм (який називається швидким вибором ), що займає O(n)
середній час, O(n^2)
найгірший час, і досить складний не рандомізований алгоритм (званий інтроселект ), який займає O(n)
найгірший час. У Вікіпедії є якась інформація , але це не дуже добре.
Все, що вам потрібно, - це у цих слайдах Powerpoint . Просто витягнути базовий алгоритм O(n)
найгіршого алгоритму (інтроселект):
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)
Це також дуже гарно докладно описано у книзі «Вступ до алгоритмів» Кормена та ін.
Якщо ви хочете справжнього O(n)
алгоритму, на відміну від цього O(kn)
чи чогось подібного, тоді вам слід скористатися швидким вибором (це в основному хит-корт, де викинете розділ, який вас не цікавить). У мого професора є чудова реєстрація з аналізом виконання: ( посилання )
Алгоритм QuickSelect швидко знаходить k-й найменший елемент несортованого масиву n
елементів. Це 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 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
де ми скористаємося тим, що 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)
.
k > length(A) - length(A2)
?
A
на стрижень A1
і A2
навколо нього, ми це знаємо length(A) == length(A1)+length(A2)+1
. Отже, k > length(A)-length(A2)
рівнозначно k > length(A1)+1
, що вірно, коли k
десь A2
.
Швидкий Google на цьому ("kth найбільш великий масив елементів") повернув це: 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)
Ви любите кікспорт. Виберіть елемент навмання та зсуньте все вище чи нижче. У цей момент ви дізнаєтеся, який елемент ви насправді вибрали, і якщо це kth елемент, який ви зробили, в іншому випадку ви повторите з біном (вищим або нижчим), щоб kth елемент потрапляв. Статистично кажучи, час потрібно знайти kth елемент, який росте з n, O (n).
Аналіз супроводу алгоритму програміста дає версію, яка є 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). Також переконайтеся, що ближче до O (n), ніж nLog (n). Функція написана на 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;
}
}
Я реалізував пошук kth мінімуму в n несортованих елементах, використовуючи динамічне програмування, зокрема метод турніру. Час виконання - O (n + klog (n)). Використовуваний механізм вказаний як один із методів на сторінці Вікіпедії про Алгоритм Вибору (як зазначено в одній із публікацій вище). Ви можете прочитати про алгоритм, а також знайти код (java) на моїй сторінці блогу Пошук Kth Minimum . Крім того, логіка може зробити часткове впорядкування списку - повернути перші K min (або max) за час O (klog (n)).
Хоча код надає результат kth мінімум, схожа логіка може бути використана для пошуку kth максимуму в O (klog (n)), ігноруючи попередню роботу, зроблену для створення дерева турніру.
Ви можете зробити це в O (n + kn) = O (n) (для постійної k) у часі та O (k) для простору, відслідковуючи k найбільших елементів, які ви бачили.
Для кожного елемента в масиві ви можете сканувати список k найбільших та замінити найменший елемент на новий, якщо він більший.
Однак пріоритетним вирішенням проблеми Уоррена є акуратніше.
O(n log k)
... все-таки вироджується до O (nlogn) у випадку великих k. Я думаю, це буде добре для малих значень k, однак ... можливо, швидше, ніж деякі інші алгоритми, згадані тут [???]
Сексуальний швидкий вибір у 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]]
і a2 = [i for i in arr if i < arr[r]]
, поверне kth найбільший елемент.
numpy.sort
за numpy array
або sorted
для списків), ніж використовувати цю ручну реалізацію.
Знайдіть медіану масиву в лінійному часі, а потім використовуйте процедуру розділення точно так само, як у швидкості, щоб розділити масив на дві частини, значення ліворуч від медіани менше (<), ніж медіана та праворуч більше (>) медіана , що теж можна зробити в лінійний час, тепер перейдіть до тієї частини масиву, де лежить kth елемент, тепер рецидив стає: T (n) = T (n / 2) + cn, що дає мені O (n) overral.
Нижче наводиться посилання на повну реалізацію з досить широким поясненням того, як працює алгоритм пошуку Kth-елемента в несортованому алгоритмі. Основна ідея - розділити масив, як у QuickSort. Але щоб уникнути екстремальних випадків (наприклад, коли найменший елемент вибирається як стрижневий на кожному кроці, щоб алгоритм перероджувався в час виконання O (n ^ 2)), застосовується спеціальний вибір вибору, який називається алгоритмом медіани медіанів. Все рішення працює в O (n) час у гіршому та середньому випадку.
Ось посилання на повну статтю (йдеться про пошук найменшого елемента Kth , але принцип той самий для пошуку найбільшого Kth ):
Знайти найменший елемент Kth у масиві, який не використовується в нормі
Відповідно до цієї статті Для знаходження найбільшого елемента K у списку з n елементів наступний алгоритм потребує O(n)
часу в гіршому випадку.
Аналіз: Як запропоновано в початковій роботі:
Ми використовуємо медіану, щоб розділити список на дві половини (перша половина, якщо
k <= n/2
, а друга половина інакше). Цей алгоритм вимагає часуcn
на першому рівні рекурсії для деякого постійногоc
,cn/2
на наступному рівні (оскільки ми повторюємось у списку розміру n / 2),cn/4
на третьому рівні тощо. Загальний витрачений час - цеcn + cn/2 + cn/4 + .... = 2cn = o(n)
.
Чому розмір розділу береться 5, а не 3?
Як зазначено в оригінальному документі :
Розділення списку на 5 забезпечує найгірший поділ у 70 - 30. Принаймні половина медіани більша за медіану медіани, отже, принаймні половина n / 5 блоків має принаймні 3 елементи, і це дає
3n/10
розкол, який означає, що інший розділ становить 7n / 10 в гіршому випадку. Це даєT(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1
, найгірший час роботиO(n)
.
Тепер я спробував реалізувати зазначений вище алгоритм як:
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];
}
Для покращення роботи інший алгоритм використовує Чергу пріоритетів і потребує часу 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;
}
Обидва ці алгоритми можна перевірити як:
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));
}
Очікуваний вихід:
18
18
Як щодо цього своєрідного підходу
Підтримуйте a buffer of length k
і a tmp_max
, отримуючи tmp_max - це O (k) і робиться n разів так, як щось подібнеO(kn)
Це правильно чи я щось пропускаю?
Хоча він не перемагає середній випадок швидкого вибору та найгірший випадок методу медіанної статистики, але його досить легко зрозуміти та реалізувати.
повторити список. якщо поточне значення більше, ніж найбільше збережене значення, збережіть його як найбільше значення та підкресліть 1-4 вниз та 5 випавши зі списку. Якщо ні, порівняйте його з числом 2 і зробіть те саме. Повторіть, перевіривши його на всіх 5 збережених значеннях. це слід робити в O (n)
Я хотів би запропонувати одну відповідь
якщо ми візьмемо перші k елементів і відсортуємо їх у зв'язаний список значень k
тепер для будь-якого іншого значення, навіть у гіршому випадку, якщо ми робимо вставку сортування для nk значень спокою, навіть у гіршому випадку кількість порівнянь буде k * (nk), а для prev k значення буде відсортовано, нехай це буде k * (k- 1) значить, виходить, що (nk-k) є o (n)
ура
Пояснення алгоритму медіани середніх для знаходження k-го найбільшого цілого числа з n можна знайти тут: http://cs.indstate.edu/~spitla/presentation.pdf
Реалізація в c ++ знаходиться нижче:
#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;
}
Існує також алгоритм вибору Вірта , який має більш просту реалізацію, ніж QuickSelect. Алгоритм вибору Вірта повільніше, ніж QuickSelect, але з деякими вдосконаленнями він стає швидшим.
Більш детально. Використовуючи оптимізацію MODIFIND Володимира Забродського та вибір медіани середніх 3-х та приділяючи деяку увагу кінцевим крокам частини алгоритму, що розділяється, я придумав такий алгоритм (уявно названий "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];
}
У тестах, які я тут робив , LefSelect на 20-30% швидше, ніж QuickSelect.
Haskell Solution:
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)
Це реалізує медіану медіанних рішень за допомогою методу withShape для виявлення розміру розділу без фактичного обчислення.
Ось C ++ реалізація Randomized QuickSelect. Ідея полягає у тому, щоб випадковим чином підібрати зведений елемент. Для реалізації рандомізованого розділу ми використовуємо випадкову функцію, rand () для генерування індексу між l та r, поміняємо елемент на випадково генерований індекс останнім елементом і, нарешті, викликаємо стандартний процес розділу, який використовує останній елемент як опорний.
#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;
}
Найгірша часова складність вищезгаданого рішення все ще є O (n2). У гіршому випадку, рандомізована функція завжди може вибрати кутовий елемент. Очікувана часова складність вище рандомізованого QuickSelect становить Θ (n)
Виклик опитування () k разів.
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;
}
Це реалізація в Javascript.
Якщо ви випустите обмеження, що ви не можете змінити масив, ви можете запобігти використанню додаткової пам'яті за допомогою двох індексів для ідентифікації "поточного розділу" (у класичному стилі quicksort - http://www.nczonline.net/blog/2012/ 11/27 / інформатика-в-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;
}
}
}
Якщо ви хочете перевірити його ефективність, ви можете скористатися цією варіацією:
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};
}
Решта коду - просто створити деякий майданчик:
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);
Тепер запустіть тести кілька разів. Через Math.random () він буде отримувати кожен раз різні результати:
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);
Якщо ви тестуєте його кілька разів, ви навіть емпірично бачите, що кількість ітерацій в середньому становить O (n) ~ = константа * n, а значення k не впливає на алгоритм.
Я придумав цей алгоритм і, здається, O (n):
Скажімо, k = 3, і ми хочемо знайти 3-й за величиною елемент у масиві. Я створив би три змінні і порівняв кожен елемент масиву з мінімальними з цих трьох змінних. Якщо елемент масиву більший за наш мінімум, ми замінимо змінну min на значення. Продовжуємо те ж саме до кінця масиву. Мінімум наших трьох змінних - це 3-й за величиною елемент у масиві.
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
І, щоб знайти найбільш великий елемент Kth, нам потрібні K змінні.
Приклад: (k = 3)
[1,2,4,1,7,3,9,5,6,2,9,8]
Final variable values:
a=7 (answer)
b=8
c=9
Може хтось, будь ласка, перегляне це і дасть мені знати, чого я пропускаю?
Ось запропонована реалізація алгоритму eladv (я також ставлю сюди реалізацію з випадковим стрижнем):
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);
}
}
це схоже на стратегію quickSort, де ми вибираємо довільну точку зіткнення і підводимо менші елементи ліворуч, а великі праворуч
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);
}
Перейдіть до кінця цього посилання: ...........
Ви можете знайти k-й найменший елемент за O (n) часу та постійного простору. Якщо ми вважаємо, масив призначений лише для цілих чисел.
Підхід полягає у виконанні двійкового пошуку за діапазоном значень масиву. Якщо ми маємо мінімальне значення та максимальне значення, обидва в цілому діапазоні, ми можемо виконати двійковий пошук у цьому діапазоні. Ми можемо написати функцію порівняння, яка підкаже, чи є яке-небудь значення kth-найменшим або меншим, ніж kth-найменшим або більшим, ніж kth-найменшим. Робіть двійковий пошук, поки не досягнете найменшого числа kth
Ось код для цього
клас Рішення:
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)
Існує також один алгоритм, який перевершує алгоритм швидкого вибору. Це називається алгоритмом Floyd-Rivets (FR) .
Оригінальна стаття: https://doi.org/10.1145/360680.360694
Версія для завантаження: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.309.7108&rep=rep1&type=pdf
Стаття у Вікіпедії https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm
Я спробував реалізувати алгоритм швидкого вибору та FR в C ++. Також я порівняв їх зі стандартними реалізаціями бібліотек C ++ std :: nth_element (який в основному є інтроселективним гібридом швидкого вибору та heapselect). Результатом було швидкий вибір, і nth_element в середньому працював порівняно, але алгоритм FR виконував приблизно вдвічі швидше порівняно з ними.
Приклад коду, який я використовував для алгоритму 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));
}
Що я б робив, це:
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
Ви можете просто зберігати покажчики на перший і останній елемент у пов'язаному списку. Вони змінюються лише після внесення оновлень до списку.
Оновлення:
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
По-перше, ми можемо побудувати BST з несортованого масиву, який займає час O (n), а з BST ми можемо знайти k-й найменший елемент в O (log (n)), який по всіх рахунках до порядку O (n).