Как найти k-й по величине элемент в несортированном массиве длины n в O (n)? - PullRequest
214 голосов
/ 31 октября 2008

Я считаю, что есть способ найти k-й по величине элемент в несортированном массиве длины n в O (n). Или, возможно, это «ожидаемый» O (N) или что-то. Как мы можем это сделать?

Ответы [ 31 ]

2 голосов
/ 04 сентября 2015

Согласно этой статье Нахождение K-го по величине элемента в списке из n элементов следующий алгоритм займет O(n) время в худшем случае.

  1. Разделите массив на n / 5 списков по 5 элементов в каждом.
  2. Найдите медиану в каждом подмассиве из 5 элементов.
  3. Рекурсивно найти медиану всех медиан, назовем это M
  4. Разделение массива на два подмассива. 1-й подмассив содержит элементы больше, чем M, допустим, что этот подмассив имеет значение a1, а другой подмассив содержит элементы, меньшие M. a2.
  5. Если k <= | a1 |, вернуть выбор (a1, k). </li>
  6. Если k− 1 = | a1 |, вернуть M.
  7. Если k> | a1 | + 1, возврат выбора (a2, k −a1 - 1).

Анализ: Как указано в оригинальной статье:

Мы используем медиану для разбиения списка на две половины (первая половина, если k <= n/2, а во второй половине иначе). Этот алгоритм занимает время cn на первом уровне рекурсии для некоторой константы c, cn/2 при следующий уровень (поскольку мы повторяем в списке размером n / 2), cn/4 на третий уровень и тд. Общее время заняло cn + cn/2 + cn/4 + .... = 2cn = o(n).

Почему размер раздела взят 5, а не 3?

Как указано в оригинальной бумаге :

Разделение списка на 5 обеспечивает наихудшее разделение на 70 - 30. Atleast половина медиан, превышающих медиану медиан, следовательно, по крайней мере половина блоков n / 5 имеет по крайней мере 3 элемента, и это дает 3n/10 split, что означает, что другой раздел равен 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

2 голосов
/ 18 апреля 2010

Найдите медиану массива за линейное время, затем используйте процедуру разделения точно так же, как в быстрой сортировке, чтобы разделить массив на две части, значения слева от медианы меньше (<), чем медиана, и справа больше, чем ( >) медиана, это тоже можно сделать за линейное время, теперь перейдите к той части массива, где лежит k-й элемент, Теперь повторение становится: T (n) = T (n / 2) + cn что дает мне O (N) в целом.

2 голосов
/ 16 июня 2016

Как насчет такого рода подхода

Поддержание buffer of length k и tmp_max, получение tmp_max равно O (k) и выполняется n раз, что-то вроде O(kn)

enter image description here

Это правильно или я что-то упустил?

Хотя он не превосходит средний случай быстрого выбора и наихудший случай метода медианной статистики, но его довольно легко понять и реализовать.

2 голосов
/ 19 июля 2013

Ниже приведена ссылка на полную реализацию с довольно подробным объяснением того, как работает алгоритм поиска K-го элемента в несортированном алгоритме. Основная идея заключается в разделении массива, как в QuickSort. Но для того, чтобы избежать крайних случаев (например, когда на каждом шаге выбирается наименьший элемент, так что алгоритм вырождается во время выполнения O (n ^ 2)), применяется особый выбор, который называется алгоритмом медианы медиан. Все решение работает в O (n) время в худшем и в среднем случае.

Вот ссылка на полную статью (речь идет о поиске Kth наименьшего элемента, но принцип поиска Kth самый большой такой же:):

Нахождение K-го наименьшего элемента в несортированном массиве

1 голос
/ 28 февраля 2013

Объяснение алгоритма медианы медиан для нахождения 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;
}
1 голос
/ 31 июля 2009

Я хотел бы предложить один ответ

если мы возьмем первые k элементов и отсортируем их в связанный список из k значений

теперь для любого другого значения, даже для наихудшего случая, если мы сделаем вставочную сортировку для остальных значений nk, даже в наихудшем случае число сравнений будет равно k * (nk), а для значений предыдущего k, которые будут отсортированы, оно будет равно k * (k-1), так что получается (nk-k), который равен o (n)

ура

1 голос
/ 31 октября 2008

перебрать список. если текущее значение больше, чем сохраненное наибольшее значение, сохраните его как наибольшее значение и увеличьте 1-4 и 5 выпадет из списка. Если нет, сравните его с номером 2 и сделайте то же самое. Повторите, проверяя все 5 сохраненных значений. это должно сделать это в O (N)

1 голос
/ 24 октября 2016
  1. Создана очередь с приоритетами.
  2. Вставьте все элементы в кучу.
  3. Опрос вызовов () 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&lt;k)
         {
           int result = pq.poll();
         } 
       return result;        
    }
    
1 голос
/ 19 апреля 2015

Вот реализация C ++ Randomized QuickSelect. Идея состоит в том, чтобы случайным образом выбрать элемент поворота. Чтобы реализовать рандомизированное разделение, мы используем случайную функцию rand (), чтобы сгенерировать индекс между l и r, поменять элемент со случайно сгенерированным индексом на последний элемент и, наконец, вызвать стандартный процесс разделения, который использует последний элемент в качестве pivot.

#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). В худшем случае, рандомизированная функция всегда может выбрать угловой элемент. Ожидаемая временная сложность вышеупомянутого рандомизированного быстрого выбора составляет Θ (n)

1 голос
/ 24 января 2015

Решение 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)

Это реализует медиану медианных решений, используя метод withShape, чтобы определить размер раздела без его фактического вычисления.

...