Предположим, у нас есть вектор / массив на C++, и мы хотим подсчитать, какой из этих N элементов имеет максимальное количество повторяющихся вхождений, и вывести наибольшее количество. Какой алгоритм лучше всего подходит для этой работы.
пример:
int a = { 2, 456, 34, 3456, 2, 435, 2, 456, 2}
выход 4, потому что 2 встречается 4 раза. Это максимальное количество раз, которое встречается 2 раза.
Если вопрос «какой номер», ответ должен быть 2, а не 4 ;-).
Это пахнет домашним заданием.
скорость - это не домашнее задание! это больше о конкуренции, если хорошенько подумать
@Gamecat вопрос, к сожалению, какая частота максимальная
На самом деле, это был первый вопрос в нескольких интервью.
ой это ??? так они вообще недоуменно спрашивают в интервью? я понятия не имел о том, что происходит вокруг !!
это должно быть "int a [] = ..."?





Отсортируйте массив, а затем выполните быстрый проход для подсчета каждого числа. Алгоритм имеет сложность O (N * logN).
В качестве альтернативы создайте хеш-таблицу, используя номер в качестве ключа. Сохраните в хеш-таблице счетчик для каждого введенного вами элемента. Вы сможете подсчитать все элементы за один проход; однако сложность алгоритма теперь зависит от сложности вашей хэш-функции.
да, вот о чем я думал
Эээ, да. Здесь 3 часа ночи, а у меня трехнедельный ребенок, если это можно считать оправданием. :-)
Не нужно оправдываться - в конце концов, SO - это совместная работа :)
Поскольку после сортировки вам не нужен внешний счетчик для каждого числа, вы можете сохранить только один счетчик для текущего числа и один для максимального числа.
Хорошо, я предполагаю, что массив достаточно мал, в конце концов, он находится в памяти для начала.
Единственное, что вы можете сделать, это то, что массив достаточно мал, чтобы уместиться в доступной памяти. Вам не обязательно иметь достаточно памяти, чтобы уместить еще один массив того же размера. Даже если у вас достаточно виртуальной памяти, вы все равно можете закончить подкачку и потерять прирост скорости от вашего алгоритма.
Не говоря уже о том, что у вас может даже не быть виртуальной памяти и чего-то порядка 64 КБ физической памяти. Да, некоторые люди до сих пор пишут код для таких устройств. По крайней мере, с этой частью жизни я покончил. :-)
Если массив достаточно велик, вы можете итеративно читать с диска, тогда как я не уверен, что вы можете сортировать с диска? (открытый вопрос)
Если массив достаточно большой, чтобы не умещаться в памяти, это совсем другая история. :-) Я не спорю с предположением, что он умещается в памяти. Однако вы не можете ничего предположить о доступной памяти после чтения массива. Вам может не хватить счетчиков на все элементы.
Почему набор хешей, а не набор деревьев? Это числа, которые с большей вероятностью получат O (logN) при поиске.
Ну, потому что была поздняя ночь, и я не продумала свой ответ до конца. :-) Более серьезно - хеш-таблица может иметь сложность O (1) (и, конечно, с огромным объемом памяти) в зависимости от хеш-функции.
А хеш по определению не гарантирует уникальности, поэтому второй подход не сработает.
Хеширование обычно считается O (1), предполагая, что слова / числа, которые нужно хешировать, не масштабируются с размером проблемы. Что касается столкновений; в хорошей хеш-функции они тоже будут амортизироваться до O (1) раз. У деревьев будет O (log (n)) r / w. Ответ хорош как есть.
немного псевдокода:
//split string into array firts
strsplit(numbers) //PHP function name to split a string into it's components
i=0
while( i < count(array))
{
if (isset(list[array[i]]))
{
list[array[i]]['count'] = list + 1
}
else
{
list[i]['count'] = 1
list[i]['number']
}
i=i+1
}
usort(list) //usort is a php function that sorts an array by its value not its key, Im assuming that you have something in c++ that does this
print list[0]['number'] //Should contain the most used number
Оптимизирован под пространство:
Быстрая сортировка (например) затем перебирает элементы, отслеживая только наибольшее количество. В лучшем случае O (N log N).
Оптимизирован по скорости:
Обходите все элементы, отслеживая отдельные подсчеты. Этот алгоритм всегда будет O (n).
При сортировке вам нужно сохранить длину самой длинной последовательности из одного числа. Если вы не сортируете, вам нужно хранить счетчики всех чисел в ассоциативном контейнере.
Если вы отслеживаете количество каждого элемента, в худшем случае потребуется N счетчиков. Вы почти вдвое увеличили объем необходимой памяти. Конечно, для машины с памятью 4 ГБ это не будет большой проблемой. Однако для 64-килобайтной памяти, совместно используемой с ОС, вы, возможно, захотите отсортировать.
@Franci Penov: все дело в том, что в вопросе написано «лучше всего», а ответ зависит от чувства «лучше всего».
Ага, согласен. Поэтому я предложил два альтернативных решения - сортировку или хеш-таблицу счетчиков. :-) Сразу хотел указать на недостаток потребления памяти у второго алгоритма. Память тоже важна, а не только скорость.
Разве не самая большая проблема с версией "оптимизированной для скорости" состоит в том, что вам нужен массив размером, равным максимально возможному числу, чтобы сохранить O (n)? В противном случае вам нужно дерево для O (n * log n) или хеш для O (кто знает)?
Если у вас есть ОЗУ и ваши значения не слишком велики, используйте счетная сортировка.
Возможная реализация C++, использующая STL, может быть следующей:
#include <iostream>
#include <algorithm>
#include <map>
// functor
struct maxoccur
{
int _M_val;
int _M_rep;
maxoccur()
: _M_val(0),
_M_rep(0)
{}
void operator()(const std::pair<int,int> &e)
{
std::cout << "pair: " << e.first << " " << e.second << std::endl;
if ( _M_rep < e.second ) {
_M_val = e.first;
_M_rep = e.second;
}
}
};
int
main(int argc, char *argv[])
{
int a[] = {2,456,34,3456,2,435,2,456,2};
std::map<int,int> m;
// load the map
for(unsigned int i=0; i< sizeof(a)/sizeof(a[0]); i++)
m [a[i]]++;
// find the max occurence...
maxoccur ret = std::for_each(m.begin(), m.end(), maxoccur());
std::cout << "value:" << ret._M_val << " max repetition:" << ret._M_rep << std::endl;
return 0;
}
Если диапазон элементов велик по сравнению с количеством элементов, я бы, как говорили другие, просто сортировал и просматривал. Это время n * log n и без дополнительного места (возможно, log n дополнительно).
Проблема со счетной сортировкой заключается в том, что, если диапазон значений велик, для инициализации счетного массива может потребоваться больше времени, чем для сортировки.
Алгоритм хеширования (build count [i] = #occurrences (i) в основном за линейное время) очень практичен, но теоретически не является строго O (n), потому что во время процесса могут возникать конфликты хешей.
Интересным частным случаем этого вопроса является алгоритм большинства, в котором вы хотите найти элемент, который присутствует как минимум в n / 2 записей массива, если такой элемент существует.
Вот быстрое объяснение и более подробное объяснение того, как это сделать в линейное время, без каких-либо ухищрений.
Вот моя полная, протестированная версия с использованием std::tr1::unordered_map.
Я делаю это примерно за O (n). Сначала он выполняет итерацию по n входным значениям для вставки / обновления счетчиков в unordered_map, затем выполняет partial_sort_copy, который равен O (n). 2 * O (n) ~ = O (n).
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
namespace {
// Only used in most_frequent but can't be a local class because of the member template
struct second_greater {
// Need to compare two (slightly) different types of pairs
template <typename PairA, typename PairB>
bool operator() (const PairA& a, const PairB& b) const
{ return a.second > b.second; }
};
}
template <typename Iter>
std::pair<typename std::iterator_traits<Iter>::value_type, unsigned int>
most_frequent(Iter begin, Iter end)
{
typedef typename std::iterator_traits<Iter>::value_type value_type;
typedef std::pair<value_type, unsigned int> result_type;
std::tr1::unordered_map<value_type, unsigned int> counts;
for(; begin != end; ++begin)
// This is safe because new entries in the map are defined to be initialized to 0 for
// built-in numeric types - no need to initialize them first
++ counts[*begin];
// Only need the top one at this point (could easily expand to top-n)
std::vector<result_type> top(1);
std::partial_sort_copy(counts.begin(), counts.end(),
top.begin(), top.end(), second_greater());
return top.front();
}
int main(int argc, char* argv[])
{
int a[] = { 2, 456, 34, 3456, 2, 435, 2, 456, 2 };
std::pair<int, unsigned int> m = most_frequent(a, a + (sizeof(a) / sizeof(a[0])));
std::cout << "most common = " << m.first << " (" << m.second << " instances)" << std::endl;
assert(m.first == 2);
assert(m.second == 4);
return 0;
}
Это будет в O (n) ............ но дело в том, что нет. of array может принимать другой массив того же размера ............
для (i = 0; i
мар = счет [о]; index = o;
для (i = 0; i
тогда вывод будет ......... элемент показатель происходит для Максимум no. раз в этом массиве ........
здесь [] - это массив данных, в котором нам нужно найти максимальное количество определенных номеров. в массиве .......
count [], имеющий количество каждого элемента .......... Примечание: мы уже знаем, что диапазон данных будет в массиве .. скажем, например. данные в этом массиве варьируются от 1 до 100 ....... затем имейте массив счетчиков из 100 элементов для отслеживания, если произошло увеличение индексированного значения на единицу ........
Я использую карту STL для заполнения частот и сортировки с помощью sort (map.begin (), map.end ()), чтобы получить больше скорости?