You are on page 1of 11

Мажорант

Корените на идеята за разделяне на сложната задача на няколко по-прости, които се


атакуват по-лесно поотделно и чието решаване позволява лесно конструиране на решение на
изходната задача, се крият далеч в Древността. Тя достига своя връх по времето на Римската
империя, която формулира и издига “Разделяй и владей” като основен принцип на външната
си политика по отношение на съседните на Империята враждуващи помежду си племена.
Всъщност римляните съвсем не са откриватели на принципа и той е в основата на експанзия-
та на всяка голяма империя преди и след римската.
Как стоят нещата в програмирането? Приложението на метода протича на три стъпки.
На първата стъпка се извършва разбиване на изходната задача на няколко подзадачи, най-
често две. Следва решаване на всяка от задачите поотделно. На третата стъпка въз основа на
решенията на подзадачите се конструира решение на изходната задача.

Дефиниция: Нека е дадено n-елементно мултимножество (т.е. множество, в което се


допуска повторение на елементи). Ще казваме, че даден елемент на множеството е негов
мажорант, ако се повтаря повече от n/2 пъти.
Така мултимножеството {2,3,3,1,3} има за мажорант 3, а {1,1,2,3} няма мажорант.
Очевидно, съгласно горната дефиниция, мултимножеството не би могло да има два
мажоранта. По-долу ще рагледаме няколко различни начина за намиране на мажорант, като
мултимножеството ще представяме с едномерен масив. Освен ако изрично не сме казали
друго, няма да предполагаме някакъв конкретен тип на елементите, нито съществуването на
наредба (частична или пълна) в този тип, още по-малко пък наредба на елементите в масива.
В програмите по-долу за целта ще използваме макроса CDataType, който ще дефинираме като
char. Читателят би могъл да провери независимостта на алгоритъма от типа char, като
промени макроса, например на int или на float.
Първият алгоритъм, който може би хрумва на читателя, е да се премине през
елементите на масива и за всеки от тях да се провери дали се среща повече от n/2 пъти. В
предложената реализация мажорантът се намира от функцията findMajority(), която
получава като параметър масива и размера му и адрес, в който да запише мажоранта, в
случай, че в масива има такъв (т.е. функцията има страничен ефект). Функцията връща
стойност 1, ако е намерен мажорант и 0 — в противен случай. Надяваме се, че читателят не
се притеснява от това, че използваме типа char като булев. Намирането на броя на
срещанията на даден елемент в масива е отделено във функцията count(), която ще
използваме по-долу и при други алгоритми. В случай на преждевременно намиране на
мажоранта по-нататъшната работа се прекратява незабавно.

#include <stdio.h>
#define CDataType char

unsigned count(CDataType m[], unsigned size, CDataType candidate)


{ unsigned cnt, i;
for (i = cnt = 0; i < size; i++)
if (m[i] == candidate)
cnt++;
return cnt;
}

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, size2 = size/2;
for (i = 0; i < size; i++)
if (count(m,size, m[i]) > size2) {
*majority = m[i];
return 1;
}
return 0;
}
void main() {
CDataType majority;
if (findMajority("AAACCBBCCCBCC", 13, &majority))
printf("Majority: %c\n", majority);
else
printf("No majority element.\n");
}

Очевидно сложността на предложения алгоритъм е O(n2), тъй като всеки от n-те


елемента се тества за мажорант, а тестът изисква още едно преминаване. Бихме могли “да се
изхитрим” и да намалим проверките, без по същество да променяме алгоритъма. За целта е
необходимо да забележим, че при всяко следващо извикване функцията count() върши все
повече излишна работа, преминавайки през целия масив. Не е трудно да се съобрази, че
броенето би могло спокойно да пропусне елементите вляво от текущия. Наистина, така
бихме могли да изпуснем срещания на тествания елемент. В такъв случай обаче той не би
бил мажорант, защото на някоя предишна стъпка, би трябвало вече да сме го разгледали и да
сме го отхвърлили. Същите разсъждения ни позволяват да приключим проверката на n/2-ия
елемент на масива. Наистина, той е последният, вдясно от който има достатъчно други,
потенциално равни на него, които биха могли да го направят мажорант.

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, j, cnt, size2 = size/2;
for (i = 0; i <= size/2; i++) {
for (cnt = 0, j = i; j < size; j++)
if (m[i] == m[j]) cnt++;
if (cnt > size2) {
*majority = m[i];
return 1;
}
}
return 0;
}

За съжаление, въпреки направените подобрения, както в средния, така и в най-лошия


случай, при който първите n/2+1 елемента са различни помежду си, сложността си остава
O(n2). Бихме могли да опитаме да ограничим още повече разглежданите в процеса на броене
елементи. Идеята е да прекратяваме броенето в момента, в който стане ясно, че броят на
оставащите елементи е недостатъчен да направи разглеждания елемент мажорант, дори ако
всички те съвпадат с него.

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, j, cnt, size2 = size/2;
for (i = 0; i <= size/2; i++) {
for (cnt = 0, j = i; j < size; j++)
if (cnt + size - j <= size2) break;
else if (m[i] == m[j]) cnt++;
if (cnt > size2) {
*majority = m[i];
return 1;
}
}
return 0;
}

Ползата от това “подобрение” в общия случай обаче е доста съмнителна, тъй като то
изисква допълнително: едно събиране, едно изваждане и едно сравнение на всяка стъпка от
броенето. Т.е. в най-лошия случай това решение е по-лошо от предходното и сложността му
отново е O(n2).
Няма да се задълбочаваме повече в тази посока, тъй като съществуват по-ефективни
алгоритми. Да видим какво става, ако отслабим строгите ограничения, наложени в началото,
и допуснем тип на елементите, за който е дефинирана пълна наредба. В такъв случай бихме
могли да сортираме елементите на масива и да проверим дали средният му елемент е
мажорант. Получаваме следния алгоритъм (Доказателството, че ако сортираме масива,
средният му елемент се заема от мажоранта, ако има такъв, оставяме на читателя като леко
упражнение.):

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{
heapSort(m,size); // или mergeSort(m,size);
if (count(m,size,m[size/2]) > size2) {
*majority = m[size/2];
return 1;
}
return 0;
}

Тъй като проверката дали елементът е мажорант е линейна, сложността на алгоритъма


се доминира от сложността на сортирането. Ако използваме подходящ алгоритъм като
пирамидално сортиране или сортиране чрез сливане, които имат гарантирана сложност
O(nlog2n) в най-лошия случай, получаваме обща сложност на алгоритъма O(n.log2n). Ще
отбележим, че бързото сортиране на Хоор не би ни свършило работа. Макар в средния
случай да има сложност O(n.log2n) и да бие пирамидалното от 2 до 3 пъти, в най-лошия
случай сложността му е O(n2). [Кнут-1976, Уирт-1980]
За да намерим средния (и изобщо k-ия по големина) елемент на масив обаче не е
необходимо да го сортираме. Алгоритъмът на Rivest-Tarjan намира средния елемент с
линейна сложност O(n), извършвайки 5,43n–163 сравнения, n > 32. [Кнут-1976]

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ CDataType med;
med = findMedian(m,size);
if (count(m,size,med) > size2) {
*majority = med;
return 1;
}
return 0;
}

Така получихме линеен алгоритъм за намиране на мажорант, но при допълнителното


предположение, че между елементите съществува наредба, т.е. че за всеки два елемента с
различни стойности a и b е в сила точно едно от двете отношения: a<b или a>b.
Да видим какво ще получим, ако се откажем от наредбата, но поискаме броя на
възможните различни стойности на елементите на масива да е предварително известен и да е
достатъчно малко число. В такъв случай отново можем да получим линеен алгоритъм. По
същество се използва идеята на алгоритъма за сортиране чрез броене. С едно преминаване
през елементите на масива се намира броят на срещанията на всички кандидати за мажорант.
Следва преминаване през възможните стойности на кандидатите и проверка за всеки един
дали се среща строго повече от n/2 пъти. Това става за време O(k), независещо от броя на
елементите в масива, а само от броя на различните стойности k, които могат да приемат. Така
за общата сложност на алгоритъма получаваме O(n+k). При достатъчно малки стойности на k
(например k < n) имаме O(n).

#define MAX_NUM 127


CDataType cnt[MAX_NUM+1];

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, j, size2 = size/2;
// Инициализация
for (i = 0; i < MAX_NUM; i++)
cnt[i] = 0;
// Броене
for (j = 0; j < size; j++)
cnt[m[j]]++;
// Проверка за мажорант
for (i = 0; i < MAX_NUM; i++)
if (cnt[i] > size2) {
*majority = i;
return 1;
}
return 0;
}

Да се върнем обаче към изходното условие, при което не можем да разчитаме нито на
наредба, нито на предварително известен малък брой възможни стойности на елементите, и
да опитаме да приложим стратегията “разделяй и владей”. Да разделим масива на две почти
равни части (в случай на нечетен брой елементи, едната част ще има един елемент повече).
Основната идея е, че ако x е мажорант на изходния масив, той ще бъде мажорант на поне
една от двете части. (Доказателството е тривиално и го оставяме на читателя.) Оттук лесно
получаваме съответен рекурсивен алгоритъм. Разделяме масива на две почти равни части и
намираме мажоранта на всеки от тях. Налице са три случая:
1) И двата подмасива нямат мажорант. Тогава и изходният масив няма да има.
2) Единият подмасив има мажорант x, а другият — няма. Проверява се, дали x е
мажорант на изходния масив.
3) И двата подмасива имат мажоранти x и y, евентуално различни. Проверяват се
както x, така и y. (Разбира се, ако x се окаже мажорант, проверката за y може да се
пропусне.)
За намирането на мажоранта на всеки от подмасивите се прилага рекурсивно същият
алгоритъм, като процесът приключва при достигане на масив с един елемент, в който случай
този елемент се явява мажорант. Процесът би могъл да приключи и по-рано (например при
два елемента) от съображения за ефективност, но няма да се спираме на разсъждения в тази
посока. Ще отбележим, че поддържането на лява и дясна граница изисква малка промяна в
параметрите и на двете функции, като началното извикване става с лява граница на
разглеждания масив 0, и дясна — size–1.

unsigned count(CDataType m[], unsigned left, unsigned right, CDataType candidate)


{ unsigned cnt;
for (cnt = 0; left <= right; left++)
if (m[left] == candidate)
cnt++;
return cnt;
}

char findMajority(CDataType m[], unsigned left, unsigned right, CDataType *majority)


{ unsigned mid;

if (left == right) {
*majority = m[left];
return 1;
}

mid = (left + right) / 2;


if (findMajority(m,left,mid,majority))
if (count(m,left,right,*majority) > (right-left+1)/2)
return 1;
if (findMajority(m,mid+1,right,majority))
if (count(m,left,right,*majority) > (right-left+1)/2)
return 1;

return 0;
}

Какво постигнахме? В общия случай на всяко ниво на рекурсията имаме по две


рекурсивни обръщения с масиви с наполовина по-малко елементи. Това е частта “разделяй”.
Обединението на резултатите на двете рекурсивни обръщения (частта “владей”) изисква 0, n
или 2n сравнения, съответно за случаите 1), 2) и 3). В граничния случай на един елемент
имаме константна сложност, без сравнения. Ако означим броя на извършваните сравнения за
n-елементен масив с T(n), в най-лошия случай получаваме следните рекурентни зависимости:
T(1) = 0
T(n) = T(n/2) + 2n
След решаване на тази зависимост получаваме, че T(n)  O(n.log2n).

Дали не бихме могли да получим още по-добри резултати? Оказва се, че лесно можем
да получим линейна сложност на алгоритъма забелязвайки, че ако масивът има мажорант са
в сила следните твърдения:
Твърдение 1: Ако елементите m[i] и m[j] са различни и ги премахнем, мажорантът на
новия масив ще съвпада с мажоранта на стария.
Твърдение 2: Ако всички елементи в масива се срещат по двойки, то ако запазим
единия представител и изхвърлим другия за всяка двойка, то мажорантът на новия масив ще
съвпада с мажоранта на стария.
Доказателството на горните твърдения е леко и го оставяме на читателя.
Нека отново опитаме да опростим условието, като поискаме: 1) масивът гарантирано
да съдържа мажорант, и 2) позволява се промяна на масива така, че след приключване
работата на програмата той почти сигурно да се различава от изходния. Нека за момент
преположим, че масивът има четен брой елементи. Да опитаме отново да приложим
римската стратегия, разделяйки масива на две равни части. Вместо да търсим мажорант във
всяка от тях обаче, този път ще сравняваме елементите по двойки: единият елемент е от
едната част, а другият — от другата. Разглеждаме една конкретна двойка (последователни
елементи). Ако двата елемента са различни, изключваме и двата от по-нататъшни
разглеждания, а ако са равни — запазваме единия. Прилагайки това за всички двойки,
получаваме нов масив с най-много n/2 елемента. За него се прилага същата стратегия, като
процесът продължава до достигане на масив с един елемент: мажорантът (сигурни сме, че
има такъв).
Проблем възниква при нечетен брой елементи, при което един от елементите няма да
може да бъде включен в двойка. Тъй като този елемент може да бъде критичен и да определя
мажоранта, не бихме искали да го изхвърлим. (Например: AA BB A). От друга страна, не
бихме искали и винаги да го пазим, защото така можем да разрушим мажоранта (Например:
AA B).
Едно възможно решение е да го запазим, давайки му по-малко тегло и да го вземем
предвид, само ако в новия масив без него не може да се определи мажорантът. Получаваме
алгоритъм с линейна сложност, тъй като на всяка стъпка броят на елементите намаляват поне
наполовина. Наистина, ако означим броя на извършваните сравнения за n-елементен масив с
T(n), в най-лошия случай получаваме следните рекурентни зависимости:
T(1) = 0
T(n) = T(n/2) + n/2
След решаване на тази зависимост получаваме, че T(n)  O(n). Предложената
програмна реализация не използва допълнителен масив, а на всяка стъпка последователно
копира по един представител на елементите от двойките с еднакви елементи в началото на
същия масив. Лесно се вижда, че това не е опасно, тъй като копирането става “зад гърба ни”.
За съжаление така се разрушава оригиналният масив, което ни лишава и от възможността за
извършване на горната проста проверка дали намереният елемент е наистина мажорант.

void findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, curCnt;
char part = 0;
do {
for (curCnt = 0, i = 1; i < size; i += 2)
if (m[i-1] == m[i])
m[curCnt++] = m[i];
if (i == size) {
m[curCnt++] = m[i-1];
part = 1;
}
else
if (part)
m[curCnt] = m[size-2];
else
if (m[size-2] == m[size-1])
m[curCnt] = m[size-2];
else
curCnt--;
size = curCnt;
} while (size > 1);
*majority = m[0];
}

Друга възможност е да запазим последния елемент на масива при нечетен брой


елементи в специална отделна променлива. Ако на някоя следваща стъпка масивът отново се
окаже с нечетен брой елементи, ще я унищожим, записвайки отгоре й последния елемент на
масива, за който няма “другарче”. В един момент масивът остава с нула елемента, а
кандидатът за мажорант ще се намира в специалната променлива. Дали това работи?
Преминавайки към следващата стъпка, ще загубим мажоранта, само ако в новия масив точно
половината елементи са равни на мажоранта, при което нашият запазен елемент по
необходимост ще трябва да бъде равен на мажоранта (знаем, че в масива със сигурност има
мажорант). По-нататък на всяка следваща стъпка поне половината елементи ще бъдат равни
на мажоранта, така че ако в един момент се окажем с нечетен брой елементи, то оставащият
елемент отново ще трябва да бъде равен на мажоранта, което означава, че спокойно можем
да премахнем старата стойност. След изчерпване елементите на масива, мажорантът ще се
намира в специалната променлива. Броят на елементите отново намалява поне наполовина на
всяка стъпка, т.е. имаме същата линейна сложност.

void findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, curCnt;
do {
for (curCnt = 0, i = 1; i < size; i += 2)
if (m[i-1] == m[i])
m[curCnt++] = m[i];
if (size & 1)
*majority = m[size-1];
size = curCnt;
} while (size > 0);
}

Дали не бихме могли да се освободим от нуждата от допълнителен елемент? Нека


помислим, кога трябва да вземем предвид последния елемент. Не е трудно да се съобрази, че
той няма да е нужен, ако броят на елементите в новия масив е нечетен, защото в такъв случай
не може да има два кандидата за мажорант. Ако обаче този брой е четен, то е възможно да не
може да се определи мажорант (т.е. елементите да съдържат две стойности, разпределени по
равно), поради което той ще трябва да се вземе предвид. И така: в случай на нечетен брой
елементи последният елемент се прибавя към края на новия масив, само ако иначе броят на
елементите му би станал четен. Така веднъж достигнали до масив с нечетен брой елементи,
ще съхраняваме нечетен брой, докато в него не остане единствен елемент: мажорантът.
Описаният алгоритъм е проста вариация на предходния и сложността му отново е линейна.

void findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, curCnt;
do {
for (curCnt = 0, i = 1; i < size; i += 2)
if (m[i-1] == m[i])
m[curCnt++] = m[i];
if (!(curCnt & 1)) m[curCnt++] = m[size-1];
size = curCnt;
} while (size > 1);
*majority = m[0];
}

Друг възможен (но излишно по-сложен) вариант на решаване на проблема с нечетния


брой елементи е с всеки елемент да асоциираме брояч, показващ колко елемента
представлява той в действителност. В началото броячите на всички елементи се
инициализират с 1. При сравняване на два елемента m[i-1] и m[i], с броячи съответно cnt[i-
1] и cnt[i], имаме две възможности:
1. m[i-1] = m[i]
Запазваме едно копие на елемента, с брояч cnt[i-1] + cnt[i].
2. m[i-1]  m[i]
2.1. cnt[i-1] = cnt[i]
Премахваме и двата елемента.
2.2. cnt[i-1] < cnt[i]
Запазваме m[i] с брояч cnt[i]-cnt[i-1].
2.3. cnt[i-1] > cnt[i]
Запазваме m[i-1] с брояч cnt[i-1]-cnt[i].
Сега можем винаги да запазваме последния елемент в случай на нечетен брой
елементи. В случай на четен брой, разбира се, няма да го пазим, защото той ще бъде вкл’чен
в двойка. Лесно се вижда, че полученият алгоритъм отново е линеен и на всяка стъпка
мажорантът се запазва. Следва примерна реализация:

void findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, curCnt;
unsigned *cnt = (unsigned *) malloc(size*sizeof(*cnt));
for (i = 0; i < size; i++)
cnt[i] = 1;
do {
for (curCnt = 0, i = 1; i < size; i += 2)
if (m[i-1] == m[i]) {
cnt[curCnt] = cnt[i-1] + cnt[i];
m[curCnt++] = m[i];
}
else if (cnt[i] > cnt[i-1]) {
cnt[curCnt] = cnt[i] - cnt[i-1];
m[curCnt++] = m[i];
}
else if (cnt[i] < cnt[i-1]) {
cnt[curCnt] = cnt[i-1] - cnt[i];
m[curCnt++] = m[i-1];
}
if (size & 1) {
cnt[curCnt] = cnt[i-1];
m[curCnt++] = m[i-1];
}
size = curCnt;
} while (size > 1);
free(cnt);
*majority = m[0];
}

Алтернативен вариант за решаване на задачата за време O(n) е свързан с използването


на стек. Този път ще се освободим от изискването масивът задължително да съдържа
мажорант. Започваме с празен стек, в който в началото постъпва първият елемент на масива.
След това на всяка стъпка от масива се извлича следващият елемент и се сравнява с елемента
на върха на стека. Ако елементите са еднакви, новият елемент постъпва в стека. В противен
случай, се изключва елементът на върха на стека, което практически означава отхвърляне и
на двата елемента. В случай на празен стек следващият елемент постъпва в него. Процесът
продължава до изчерпване елементите на масива. Ако има мажорант, той е на върха на стека.
Наистина, елементите се унищожават едновременно по двойки, при това само ако са
различни. Така мажорантът не може да бъде унищожен, защото няма достатъчно
немажорантни елементи в масива. Наистина, по определение мажорантът е равен на повече
от половината от елементите.
Възможно е обаче масивът да не съдържа мажорант и все пак на върха на стека да има
елемент, например:
“ABC” (стек = )
“BC” (стек = А)
“C” (стек = )
“” (стек = C)
Това налага едно допълнително преминаване през масива, за да се провери дали
елементът наистина е мажорант. Забележете, че в този случай това е възможно, защото
оригиналният масив не се разрушава. Ще отбележим, че стекът може да съдържа и повече от
един елемент. В този случай всички елементи в него трябва да имат една и съща стойност,
съгласно разсъжденията по-горе. Следва примерна реализация на алгоритъма:

// Stack functions
#define STACK_SIZE 100
CDataType stack[STACK_SIZE];
unsigned stIndex;
void stackInit() { stIndex = 0; }
void stackPush(CDataType elem) { stack[stIndex++] = elem; }
CDataType stackPop() { return stack[--stIndex]; }
CDataType stackTop() { return stack[stIndex-1]; }
char stackIsEmpty() { return 0 == stIndex; }

char findMajority(CDataType m[], unsigned size, CDataType *majority)


{ unsigned i, cnt;

stackInit();
for (stackPush(m[0]), i = 1; i < size; i++) {
if (stackIsEmpty())
stackPush(m[i]);
else if (stackTop() == m[i])
stackPush(m[i]);
else
stackPop();
}

if (stackIsEmpty())
return 0;

for (*majority = stackPop(), i = cnt = 0; i < size; i++)


if (m[i] == *majority)
cnt++;
return (cnt > size/2);
}

Дали имаме наистина нужда от стек? Не е трудно да се види, че в предложения


алгоритъм стекът винаги съдържа елементи с еднаква стойност. Наистина, ако текущият
елемент е различен този на върха на стека (предполагаме, че стекът не е празен), той не може
да се добави, а вместо това взаимно се унищожава с елемента на върха му. Но тогава можем
да се овободим от стека и да го заместим с двойка променливи от вида:
текущ_елемент:брояч. В началото кандидатът има неопределена стойност, а броячът има
стойност 0. Алгоритъмът преминава последователно през елементите на масива и на всяка
стъпка се извършват следните проверки:
1) Ако броячът е 0, кандидат става текущият елемент, а броячът става 1.
2) Ако броячът е различен от 0:
2.1) Ако кандидатът съвпада с текущия елемент, броячът се увеличава с 1.
2.2) Ако са различни, броячът се намалява с 1.
Процесът продължава до изчерпване елементите на масива. Ако накрая броячът се
окаже 0, масивът със сигурност не съдържа мажорант. Ако обаче е различен от 0, оттук не
следва, че кандидатът непременно е мажорант и следва да се направи съответна проверка. От
друга страна, ако масивът има мажорант, то това задължително е кандидатът.
Следва пример за приложението на алгоритъма върху една конкретна редица,
съдържаща мажорант.

A A A C C B B C C C B C C
^
?:0

A A A C C B B C C C B C C
^
A:1

A A A C C B B C C C B C C
^
A:2

A A A C C B B C C C B C C
^
A:3

A A A C C B B C C C B C C
^
A:2

A A A C C B B C C C B C C
^
A:1

A A A C C B B C C C B C C
^
?:0

A A A C C B B C C C B C C
^
B:1

A A A C C B B C C C B C C
^
?:0

A A A C C B B C C C B C C
^
C:1

A A A C C B B C C C B C C
^
C:2

A A A C C B B C C C B C C
^
C:1

A A A C C B B C C C B C C
^
C:2

A A A C C B B C C C B C C
^
C:3

В нашия случай мажорантът е C и алгоритъмът правилно го посочи като кандидат.


Забележете, че ако заменим например първото C с A, алгоритъмът отново ще посочи C като
кандидат за мажорант, но в този случай мажорант просто няма. Т.е. необходима е
стандартната финална проверка, за да се уверим, че посоченият кандидат действително е
мажорант. Следва примерна реализация:
char FindMajority(CDataType m[], unsigned size, CDataType *majority)
{ unsigned cnt, i;
for (i = cnt = 0; i < size; i++) {
if (0 == cnt) {
*majority = m[i];
cnt = 1;
}
else if (m[i] == *majority)
cnt++;
else
cnt--;
}

if (cnt > 0) {
for (i = cnt = 0; i < size; i++)
if (m[i] == *majority)
cnt++;
return (cnt > size/2);
}
return 0;
}

Забележете, че този алгоритъм, за разлика от някои предишни, използва константна


допълнителна памет (за две променливи) и не променя масива. Въпреки, че този алгоритъм
може да се разглежда като вариант на предишния, ще се опитаме да го обясним и обосновем
от още една гледна точка.
Да предположим, че алгоритъмът е започнал работа и е достигнал до някаква позиция.
Можем, да считаме, че е разделил прегледаните елементи на масива на две области. Едната
област съдържа елементи, групирани по двойки, така че членовете на всяка двойка са
различни. Останалите елементи са равни помежду си и равни на кандидата за мажорант.
Лесно се вижда, че ако масивът съдържа мажорант, негов представител непременно ще
попадне във втората група. Наистина, противното не е възможно, тъй като няма достатъчно
елементи, различни от мажоранта, които да влязат в двойка с него, така че всички срещания
на мажоранта да попаднат в първата група. Ако масивът няма мажорант обаче, е възможно
във втората група все пак да попадне елемент-кандидат (например: ABC, C ще бъде
кандидат). Ето защо и тук се налага познатото ни допълнително преминаване през масива.
Ако втората група се окаже празна, то в масива гарантирано няма мажорант.
Ще отбележим, че макар да бяха разгледани редица линейни алгоритми, не трябва да
се забравят скритите константи. Така например настоящият алгоритъм е по-добър от
алгоритъма на Rivest-Tarjan за намиране на медиана, който работеше при по-строги
ограничения. Наистина тук броят на сравненията е 2n, докато при алгоритъма на Rivest-
Tarjan е 5,43n–163, n>32. Към това трябва да добавим още n–1 сравнения за проверка дали
медианата е мажорант, получавайки 6,43n–164, n>32.

Задачи:
1. Докажете, че ако сортираме даден масив, съдържащ мажорант, средният му елемент се
заема от мажоранта.
2. Докажете, че ако x е мажорант на даден масив и разделим масива на две почти равни
части, то x е мажорант на поне едната от тях.
3. Докажете, че ако масивът m[] има мажорант, то ако елементите m[i] и m[j] са различни и
ги премахнем, мажорантът на новия масив ще съвпада с мажоранта на стария.
4. Докажете, че ако масивът m[] има мажорант, то ако всички елементи в масива се срещат
по двойки, то ако запазим единия представител и изхвърлим другия за всяка двойка, то
мажорантът на новия масив ще съвпада с мажоранта на стария.
5. Да се реализира програмно алгоритъмът за запълване на област с шаблони, описан в 5.9.

Литература:
[Кнут-1976] Кнут Д., “Искусство программирования для ЭВМ” том 3, “Мир”, Москва, 1976.

[Уирт-1980] Уирт Н., “Алгоритми + структури от данни = програми”, “Техника”, София,


1980.
[Швертнер – 1995] Швертнер Й., “Структури от данни и алгоритми”, Университетско из-
дателство “Св. Климент Охридски”, София, 1995.
[Brassard,Bratley–1987] Brassard G., Bratley P., Fundamentals of Algorithmics, Prentice-Hall,
1996.

You might also like