Найти доминирующий елемент в масиве (алгоритм)
Есть одномерный масив. Извесно что в нем есть доминирующий елемент (тоесть елемент который встречается больше чем в половине ячеек масива). Нужно его найти за минимальную сложность.
Пример: Масив - 1 2 2 2 3 2 Ответ - 2 (потому что 2 встеречаеется более 3-х раз).
Сравнивать попарно элементы массива, начиная с первого, если они не равны друг другу, то удалять оба. После удаления сравнивать начиная с первого. В результате получается массив хотя бы из одного элемента, который и является доминирующим.
aabbbbbbaaabbbbbbbaa
---поехали---
aabbbbbbaaabbbbbbbaa
abbbbbaaabbbbbbbaa
bbbbaaabbbbbbbaa
bbbaabbbbbbbaa
bbabbbbbbbaa
bbbbbbbbaa
bbbbbbba
---ответ---
bbbbbb
Единственное, что смущает, так это сложность. Не оценивал.:)
Так вот разработан алгоритм для поиска i порядковой статистики. Оценка его в
наихудщем случии N.
Алгоритм сейчас попробую написать. И распишу что к чему.
Сравнивать попарно элементы массива, начиная с первого, если они не равны друг другу, то удалять оба. После удаления сравнивать начиная с первого. В результате получается массив хотя бы из одного элемента, который и является доминирующим.
aabbbbbbaaabbbbbbbaa
---поехали---
aabbbbbbaaabbbbbbbaa
abbbbbaaabbbbbbbaa
bbbbaaabbbbbbbaa
bbbaabbbbbbbaa
bbabbbbbbbaa
bbbbbbbbaa
bbbbbbba
---ответ---
bbbbbb
Единственное, что смущает, так это сложность. Не оценивал.:)
Красивое решение! Сложность равна N.
Только после удаления стоит начинать сравнивать не с первого, а с левого от места удаления элемента (если такого нет, то с первого).
P.S. Решено при помощи анализа на бумаге и курсора в аське, ни единой строки кода не писал.:) На бумаге обнаружилось, что совпадения вида ab не делают погоды, а в аське - уж не помню, как я к этому пришёл - проявился работающий алгоритм, ну и там же проделал десяток тестовых прогонов на разных случаях.
Но могу и ошибаться.
Сравнивать попарно элементы массива, начиная с первого, если они не равны друг другу, то удалять оба.
То есть главная идея вот в чем: если из этого массива удалить два неодинаковых элемента, то в полученном массиве доминирующий элемент останется доминирующим.
Далее я бы сделал по другому. Но это уже технические подробности :)
{
vector<int> p(sz), pa(a, a + sz);
int m = 0;
while(sz)
{
m = 0;
int i;
for(i = 0; i < sz; i += 2)
{
if(pa == pa[i + 1])
{
p[m] = pa;
++m;
}
}
if(!m) p[0] = pa.back(); // если нечетное количество элементов
if(m <= 3) break;
sz = m;
pa = p;
}
if(m == 3) // если нечетное количество элементов
if(p[0] != p[2])
p[0] = p[1];
p[0];
return p[0];
}
int main()
{
const int sz = 13;
int mass[sz] = {2,2,2,2,2,2,1,1,1,1,1,1,2};
cout << testArray(mass, sz) << endl;
return 0;
}
public static T GetDominant<T>(T[] source)
{
T[] items = (T[]) source.Clone();
if (ProcessPairs(items, items.Length) == 0)
throw new Exception("No dominant");
return items[0];
}
// Рабочий метод
static int ProcessPairs<T>(T[] items, int count)
{
while (count > 1)
{
int newcount = 0;
for (int i = 0; i < count - 1; i += 2)
{
T left = items;
T right = items[i + 1];
if (left.Equals(right)) // same as left == right
{
items[newcount] = left;
newcount++;
}
}
if (count % 2 != 0 && newcount % 2 == 0)
{
items[newcount] = items[count - 1];
newcount++;
}
count = newcount;
}
return count;
}
{
Random rnd = new Random(Environment.TickCount);
for (int testid = 0; testid < 100000; testid++)
{
int count = rnd.Next(200) + 2; // Общее количество элементов
// Разность между общим количеством элементов
// и количеством вхождений доминирующего элемента
int difference = rnd.Next(count / 2 - 1);
// Количество вхождений доминирующего элемента
int dominantcount = count - difference;
// Сам доминирующий элемент
int dominant = rnd.Next(20);
List<int> positions = new List<int>();
for (int i = 0; i < count; i++)
positions.Add(i);
int[] source = new int[count];
// Размещаем доминирующий элемент случайным образом dominantcount раз
for (int i = 0; i < dominantcount; i++)
{
int index = rnd.Next(positions.Count);
int position = positions[index];
source[position] = dominant;
positions.RemoveAt(index);
}
// В остальные позиции кладём случайные элементы
foreach (int position in positions)
source[position] = rnd.Next(20);
// Тестируем
int d = GetDominant(source);
if (d != dominant)
{
string message = "{ ";
foreach (int n in source)
message += n.ToString() + " ";
message += "}\n";
message += string.Format("expected {0} got {1}", dominant, d);
Console.WriteLine(message);
}
}
}
:)
Проверь для 2 2 2 2 8 8 8 8 2 и 2 2 2 2 2 8 8 8 8
:)
Проверь для 2 2 2 2 8 8 8 8 2 и 2 2 2 2 2 8 8 8 8
И? У меня двойку выдает (по крайней мере в программе предпоследнего моего сообщения).
Просьба: писать массив через запяятые: лень их вставлять :)
Но могу и ошибаться.
Я даже рискну сказать, что сложность меньше N, в зависимости от содержимого.
Конечно при условии, что начинаем не с первого, а с "левого".
Я прогнал (в уме :)) крайние варианты: одинаковые элементы подряд (ааааа), разные элементы подряд (ababa)
aaaaа
---сравнения---
aaaaа
ааааа
ааааа
ааааа
---результат---
4
ababа
---сравнения---
ababа
aba
a
---результат---
2
Аналогично
Тогда всё тип-топ, поздравляю :)
Под сложностью понимаем O-большое?
Тогда сложность меньше N подразумевает, что это делается за время меньше линейно зависящего от кол-ва элементов. А это не так.
Нам необходимо просмотреть ВСЕ ячейки массива, чтоб решить эту задачу, а сл-но сложность не может быть меньше N.
Что касается примеров, то там линейная зависимость, - ровно N обращений к ячейкам массива.
Можно привести примеры, где кол-во это будет больше N, например: aabbaabbaa.
Что-то вроде: выпускаем 2 указателя на эл-ты массива. Изначально на 1ый и второй эл-т соответственно. Если эл-ты по указателям равны, первый указатель принимает значение второго, а второй переходит к следующему элементу, если не равны - первый откатывается назад (если есть куда) а второй продвигается вперед.. Главное чтобы второй указатель назад по массиву не вернулся в одном из шагов, и первый в случае "удаления" самого левого элемента встал на корректную позицию..
Вот розпросят по аське, а потом продадут.
Короче все правильно. Выбрасывать парами надо и тогда ответ останется невыброшеным. По сути задачу решыли, осталось только написать хорошую реализацыю, но ето уже дело техники. А один проход или несколько - неважно. Главное O(N)
В любом случае, при проверке парами результат непредсказуем, если в массиве нет элемента, поподающего под определение "доминирующий". Я вот всё надеюсь, что есть метод, позволяющий и проверку предусловия заодно проводить.
Уфф.
Это значит, что коллективный разум участников форума смог бы работать в "одной более чем извесной фирме", если бы думал в десятки раз быстрее :)
Что-то вроде: выпускаем 2 указателя на эл-ты массива. Изначально на 1ый и второй эл-т соответственно. Если эл-ты по указателям равны, первый указатель принимает значение второго, а второй переходит к следующему элементу, если не равны - первый откатывается назад (если есть куда) а второй продвигается вперед.. Главное чтобы второй указатель назад по массиву не вернулся в одном из шагов, и первый в случае "удаления" самого левого элемента встал на корректную позицию..
Красота :)
{
int *p1 = a, *p2 = a + 1;
while(p2 < a + sz - 1)
{
if(*p1 == *p2)
{
p1++;
}
else
{
if(p1 - a > 0)p1--;
}
p2++;
}
return *p1;
}
int main()
{
const int sz = 9;
//int mass[sz] = {2, 2, 2, 2, 2, 8, 8, 8, 8};
int mass[sz] = {2, 8, 2, 8, 2, 8, 2, 8, 2};
cout << testArray(mass, sz) << endl;
return 0;
}
Правда, особо не тестировал.
Ну, я-то решал задачу, в которой по условию такой элемент был. Мой алгоритм, кстати, возвращает NULL, если доминирующий элемент не найден. :)
Тогда сложность меньше N подразумевает, что это делается за время меньше линейно зависящего от кол-ва элементов. А это не так.
Нам необходимо просмотреть ВСЕ ячейки массива, чтоб решить эту задачу, а сл-но сложность не может быть меньше N...
Сложность алгоритма определяется не количеством обращений к единице массива (это справедливо только для поиска), имхо. При поиске каждый элемент сравнивается с искомым выражением, следовательно основная часть алгоритма будет работать с каждым элементом. В нашем случае основная операция сравнение двух элементов - их я и считал.
Относительно того может ли быть сложность меньше N (для сортировки - нет, но мы ведь и не сортируем ;)).
Правда О большое это абстрагирование от лишних слагаемых: N-1 => O(n). Так что сложность все таки O(N).
P.S. а вообще классная задача :) "известная фирма" была наверное не прочь заполучить коллективный разум :)
forum.codenet.ru не продается! :D
...
Правда, особо не тестировал.
Потестировал идею от Phodopus. Не работает :(
Придется наворачивать что-то типа:
int testArray(int *a, int sz)
{
int *p1 = a, *p2 = a + 1, *p3 = 0;
while(p2 < a + sz - 1)
{
if(*p1 == *p2)
{
p1 = p2;
p3 = p1; // Точка возврата
}
else
{
if(p3 && (p1 != p3))
p1 = p3; // Возвращаемся
else
{
p3 = 0; // Больше некуда возвращаться
p1 += 2;
p2++;
}
}
p2++;
}
return *p1;
}
int main()
{
const int sz = 9;
int mass[sz] = {2, 8, 2, 8, 2, 8, 2, 8, 2};
std::cout << testArray(mass, sz) << std::endl;
return 0;
}
- Наивные... - пробормотал, воровато озираясь, mike, и скрылся во мгле ночного переулка. В его руке был чемоданчик, набитый новенькими хрустящими купюрками :D
// bool Ascending = sort dictionary
private Dictionary<char, int> CalcChars(string Text, bool Ascending)
{
// Creating a Dictionary
Dictionary<char, int> charsDic = new Dictionary<char, int>();
int i = 0;
CharEnumerator charEnum = Text.GetEnumerator();
while (charEnum.MoveNext())
{
char ch = Text;
if (charsDic.ContainsKey(ch))
{
charsDic[ch]++;
}
else
{
charsDic.Add(ch, 0);
}
i++;
}
// Sorting a Dictionary
List<KeyValuePair<char, int>> rList = new List<KeyValuePair<char, int>>(charsDic);
if (Ascending)
{
rList.Sort(delegate(KeyValuePair<char, int> first, KeyValuePair<char, int> second)
{
return first.Value.CompareTo(second.Value);
}
);
}
else
{
rList.Sort(delegate(KeyValuePair<char, int> first, KeyValuePair<char, int> second)
{
return second.Value.CompareTo(first.Value);
}
);
}
// Clear Dictionary
charsDic.Clear();
foreach (KeyValuePair<char, int> np in rList)
{
charsDic.Add(np.Key, np.Value);
}
return charsDic;
}
Идея понятна, но чет слишком многословно. Вот кратко и обобщенно (да и без ненужной сортировки):
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1 {
class Program {
public static bool TryFindDominator<T>(T[] array, out T dominator) {
int bound_value = (array.Length / 2);
int current_dominator_count = 0;
T current_dominator = default(T);
Dictionary<T, int> item_count_table = new Dictionary<T, int>(array.Length);
foreach (T item in array) {
int count_value;
if (item_count_table.TryGetValue(item, out count_value)) {
count_value += 1;
} else {
count_value = 1;
}
item_count_table[item] = count_value;
if (count_value > current_dominator_count) {
current_dominator_count = count_value;
current_dominator = item;
if (current_dominator_count > bound_value)
break;
}
}
dominator = current_dominator;
return (current_dominator_count > bound_value);
}
static void Main(string[] args) {
int[] test = new int[] { 1, 2, 3, 3, 3, 3, 3, 5 };
int possible_dominator;
bool dominator_found = TryFindDominator<int>(test, out possible_dominator);
Console.WriteLine("Found: {0}", dominator_found);
Console.WriteLine("Possible dominator: {0}", possible_dominator);
Console.ReadLine();
}
}
}
UPD: добавил прерывание цикла при нахождении доминирующего элемента (больше половины длины массива). Т.о. сложность алгоритма: от O(N /2) в лучшем случае до O(N), где N - длина массива.