Найти доминирующий режим несортированного массива

Обратите внимание, это домашнее задание.

Мне нужно найти режим массива (положительные значения) и вторично вернуть это значение, если режим больше, чем sizeof(array)/2, доминирующее значение. Некоторые массивы не будут иметь ни того, ни другого. Это достаточно просто, но есть ограничение, что массив НЕ должен быть отсортирован до определения, кроме того, сложность должна быть порядка O(nlogn).

Используя это второе ограничение и основную теорему, мы можем определить, что временная сложность 'T(n) = A*T(n/B) + n^D', где A=B и log_B(A)=D для истинности O(nlogn). Таким образом, А=В=D=2. Это также удобно, поскольку доминирующее значение должно быть доминирующим в 1-й, 2-й или обеих половинах массива.

Используя «T (n) = A * T (n / B) + n ^ D», мы знаем, что функция поиска будет вызывать себя дважды на каждом уровне (A), делит набор задач на 2 на каждом уровне (B). Я застрял, пытаясь понять, как заставить мой алгоритм учитывать n ^ 2 на каждом уровне.

Чтобы сделать код этого:

int search(a,b) {
    search(a, a+(b-a)/2);
    search(a+(b-a)/2+1, b);
}

«Клей», которого мне здесь не хватает, заключается в том, как объединить эти разделенные функции, и я думаю, что это реализует сложность n ^ 2. Здесь есть некоторая хитрость, когда доминанта должна быть доминантой в 1-й или 2-й половине или в обеих, не совсем уверен, как это помогает мне прямо сейчас с ограничением сложности.

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

На уровне 0 функция должна вызывать себя для поиска в первой и второй половине массива. Это должно рекурсировать и вызывать себя. Затем на каждом уровне нужно выполнить n^2 операций. Таким образом, в массиве [2,0,2,0,2] он разделит его на поиск в [2,0] и поиск в [2,0,2] И выполнит 25 операций. Поиск по [2,0] вызовет поиск по [2] и поиск по [0] И выполнит 4 операции. Я предполагаю, что это должен быть поиск самого пространства массива. Я планировал использовать C++ и использовать что-то из STL для перебора и подсчета значений. Я мог бы создать большой массив и просто обновить счетчики по их индексу.


person mike_b    schedule 07.02.2013    source источник
comment
Вы имеете в виду найти значение в массиве, которое встречается большую часть времени. и если это произойдет sizeof(array)/2 или больше, вернуть его?   -  person songlj    schedule 07.02.2013
comment
Я не понимаю, что вы ищете или почему вы рекурсивно.   -  person Gabe    schedule 07.02.2013
comment
@Gabe, я предполагаю, что он ищет режим массива .... # который чаще всего встречается на основе комментария выше. Рекурсия типа «разделяй и властвуй» делает это быстрее.   -  person thang    schedule 07.02.2013
comment
@mike_b: вы можете перебирать массив, увеличивая map<Value,Count>, затем повторять map, чтобы найти наибольшее количество... будет O(nlogn), так как это стоимость вставки карты, а итераций в любом случае меньше.   -  person Tony Delroy    schedule 07.02.2013
comment
Я думаю, что он не должен использовать больше памяти. то, что вы предлагаете (вы оба), в основном сначала сортируете по системе счисления, а затем определяете режим оттуда. существует ограничение, согласно которому массив НЕ ДОЛЖЕН быть отсортирован до определения.   -  person thang    schedule 07.02.2013
comment
Хорошо, еще один совет: используйте этот факт, если режим больше, чем sizeof(array)/2   -  person thang    schedule 07.02.2013
comment
@songlj - Да, доминирующее значение означает, что более половины элементов в массиве имеют это значение.   -  person mike_b    schedule 07.02.2013
comment
@Gabe - это задание, аналогичное сложности сортировки слиянием. Вы должны рекурсивно получить O (nlogn)   -  person mike_b    schedule 07.02.2013
comment
@thang - да, это основано на основной теореме и является частью алгоритмов разделяй и властвуй. Кроме того, я не думаю, что поиск режима как функции мне здесь поможет.   -  person mike_b    schedule 07.02.2013
comment
@mike_b, так что я хочу сказать, что если вы уберете условие sizeof(array)/2, это станет намного сложнее...   -  person thang    schedule 07.02.2013
comment
вот все подсказки, которые я дал. это действительно очень просто. 1. что вы заметили: доминанта должна быть доминантой в 1-й или 2-й половине или в обеих, 2. чтобы определить, является ли преобладающее значение a или b преобладающим, является линейное время, 3. линейное время * логарифмический шаг = n log n, 4. вам нужно, чтобы оно было доминирующим значением. если бы это был просто мод, то задача намного сложнее, и 5. базовый случай состоит в том, что доминирующим значением массива размера 1 является это одно число.   -  person thang    schedule 07.02.2013
comment
Я думаю, определить доминанту путем подсчета было бы легко и да, линейно по времени. Но если вы выполняете линейный поиск на каждом шаге журнала, это, конечно, не nlogn. Опять же, что касается «разделяй и властвуй», это означает, что мы делим проблему пополам до тех пор, пока не сможем больше делить. Следовательно, мы получаем массив размера n=1, то есть линейный поиск. Так что на самом деле, логарифмическая база 2 n шагов и 1 * n линейных поисков, когда n = 1 на шаге k = логарифмическая база 2 из n. Итак, сначала разделите и рекурсивно получите часть журнала.   -  person mike_b    schedule 07.02.2013
comment
давайте продолжим это обсуждение в чате   -  person mike_b    schedule 07.02.2013
comment
@mike_b, если вы делаете линейный каждый шаг журнала, то это nlogn. на самом деле, каждый раз, когда вы делаете линейный, это k ‹ n, тогда это действительно ‹ n log n в зависимости от того, насколько k далеко от n.   -  person thang    schedule 07.02.2013
comment
нет, это nlogn на каждом шагу. На каждом шаге имеется 2^i наборов. Имеется n/(2^i) элементов. Следовательно, есть (2^i)(n/(2^i)) шагов или просто n шагов на уровне поиска. Посмотрите на ряд, просуммируйте n от i=0 до logn. Это n + nlogn или O(nlogn) само по себе. Это с logn шагов O (nlog ^ 2 (n)). Я допустил ошибку выше, я сказал A=B=D, но логарифмическое основание 2 равно 1, следовательно, A*T(n/B) + n^D -> A=B=2, D=1. Т(п) = 2Т(п/2) + п. Я до сих пор не знаю, как доказать мой алгоритм для n-части, кроме как сказать, что сумма ряда n/(2^i) от i до n равна O(n). но я не уверен, как туда добавить часть n^d.   -  person mike_b    schedule 07.02.2013
comment
кроме как на каждом шаге, ищется n/2 [T(n)=2T(n/2)], но также возвращается и сравнивается n/2^i результатов, так что я предполагаю, что это n удовлетворяет основной теореме .   -  person mike_b    schedule 07.02.2013
comment
Вы правы насчет nlogn, но оно не соответствует основной теореме, для этого вам также понадобится 2T(n/2), который выполняет линейный поиск на каждом шаге log^2(n) в сочетании с 2T(n/ 2).   -  person mike_b    schedule 07.02.2013


Ответы (2)


если какое-то число встречается больше половины, это можно сделать с временной сложностью O (n) и пространственной сложностью O (1) следующим образом:

int num = a[0], occ = 1;
for (int i=1; i<n; i++) {
    if (a[i] == num) occ++;
    else {
        occ--;
        if (occ < 0) {
            num = a[i];
            occ = 1;
        }
    }
}

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

person songlj    schedule 07.02.2013
comment
@thang, почему ты сказал, что я шучу? что-то не так с моим кодом? - person songlj; 07.02.2013
comment
целью было найти решение со сложностью O(nlogn). для этого требуется рекурсия с log n шагов. - person mike_b; 07.02.2013
comment
@songlj Я думаю, что в некоторых случаях алгоритм не работает, попробуйте, например, 1, 2, 1, 2, 1, 2, 1, 3, 3, 1 . извините, я добавляю это как ответ, потому что пока не могу комментировать. РЕДАКТИРОВАТЬ: Если мы знаем, что режим появляется в массиве не менее N/2 раз, это алгоритм большинства Бойера-Мура. Этот stackoverflow.com /questions/3740371/ обсуждает этот конкретный пример поиска режима. - person argsv; 26.07.2014

Если вы хотите найти только доминирующий режим массива и сделать это рекурсивно, вот псевдокод:

def DominantMode(array):
    # if there is only one element, that's the dominant mode
    if len(array) == 1: return array[0]
    # otherwise, find the dominant mode of the left and right halves
    left = DominantMode(array[0:len(array)/2])
    right = DominantMode(array[len(array)/2:len(array)])
    # if both sides have the same dominant mode, the whole array has that mode
    if left == right: return left
    # otherwise, we have to scan the whole array to determine which one wins
    leftCount = sum(element == left for element in array)
    rightCount = sum(element == right for element in array)
    if leftCount > len(array) / 2: return left
    if rightCount > len(array) / 2: return right
    # if neither wins, just return None
    return None

Приведенный выше алгоритм занимает время O(nlogn), но только пространство O(logn).

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

После того, как гистограмма будет рассчитана, вы можете выполнить итерацию по ней (посещая каждый элемент не более одного раза), чтобы найти самую высокую частоту. Как только вы найдете частоту, превышающую половину размера массива, вы можете немедленно вернуться и проигнорировать остальную часть гистограммы. Поскольку размер гистограммы не может быть больше размера исходного массива, этот шаг также составляет O(n) по времени (и O(n) по пространству).

Поскольку оба шага занимают время O(n), результирующая алгоритмическая сложность составляет время O(n).

person Gabe    schedule 07.02.2013
comment
@mike_b: обозначение Big-O просто дает верхнюю границу; все функции O(n) также равны O(nlogn). Другими словами, алгоритм, который я дал, также имеет сложность O(nlogn). - person Gabe; 07.02.2013
comment
почти. 1. вам нужно проверить случай, когда left==none или right==none 2. вы можете запустить это на месте, поэтому O (1) в пространстве. - person thang; 07.02.2013
comment
@thang: Что мне нужно сделать по-другому, если left==None или right==None? И я не понимаю, как вы можете глубоко рекурсировать log(n) без использования пространства стека k*log(n). - person Gabe; 07.02.2013
comment
Преподаватель подразумевает, что нам нужно сделать это без простого линейного поиска. Он не хочет чего-то O(n). Ему нужна рекурсивно вызываемая функция, похожая на сортировку слиянием. то, что у вас есть сейчас, похоже на то, что я разработал. ваша правая сторона также имеет перекрытие, она должна быть len(array)/2+1. Вы подсчитываете только этот экземпляр функции? если это так, бывают случаи, когда это не сработает, и счетчик значения необходимо поддерживать при рекурсивных вызовах. - person mike_b; 07.02.2013
comment
@ Гейб, ты действительно можешь это исправить, но ты прав. Я допустил ошибку. В этом коде вам нужно log n в пространстве стека. Что касается левого и правого, я проверил еще раз, и вы неявно обработали их. Довольно круто. +1. - person thang; 07.02.2013
comment
@mike_b: Моя нотация [x:y] включает только элементы x .. y-1, поэтому совпадений нет. Что вы имеете в виду, когда говорите, что количество значений должно поддерживаться при рекурсивных вызовах? - person Gabe; 07.02.2013