Professional Documents
Culture Documents
NP пълнота
NP пълнота
NP пълнота
ПРИРОДО-МАТЕМАТИЧЕСКИ ФАКУЛТЕТ
ГР. БЛАГОЕВГРАД
РЕФЕРАТ
По „Теория на алгоритмите“
На тема:
„NP-пълнота. Полиномиално време.“
Благоевград, 2019 г.
1. Изчислителна сложност на алгоритмите. Същност на теорията на
NP- пълните задачи
Когато разглеждаме даден компютърен алгоритъм се интересуваме
най-общо от три негови свойства:
простота и елегантност
коректност
бързодействие
Докато първото от тях всеки може да оцени интуитивно, то за
последните две е необходим много по-задълбочен анализ. Изучавайки
теорията на NP-пълните задачи ще придобием знания и умения, които ще
ни позволяват лесно и точно да определяме ефективността на даден
алгоритъм, да сравняваме алгоритми, да повишаваме бързодействието им
чрез премерени и точни промени.
Теорията на NP- пълните задачи възниква в началото на 70-те години
на XX век във връзка с необходимостта от теоретична оценка на
изчислителната сложност на алгоритмите. Компютризацията на различни
области на обществената практика води до възникване на широк клас
задачи, които трябва да бъдат решени в условията на многоразмерност и
времекритичност. Стремежът да се разработят достатъчно общи и
ефективни алгоритми за решение на голям клас дискретни и комбинаторни
задачи се сблъсква с определени трудности при практическата им
реализация, свързани с голям разход на памет и машинно време. В същото
време общата теория на алгоритмите изучава потенциалната възможност
за алгоритмично решение на даден клас задачи, без да се отчитат
необходимите за решението ресурси. Но наличието на алгоритъм за
получаване на решение все още не значи, че решението може да бъде
получено на практика. Възниква необходимост от по-задълбочено
изследване на алгоритмите във връзка с практическата им реализуемост,
която довежда до създаването на теорията на NP – пълните задачи. Тя
1
предоставя теоретична база за сравнение на алгоритмите, за оценка на
тяхната ефективност и анализ на необходимите за решението на дадена
задача изчислителни ресурси.
Пълно изложение на теорията на NP - пълните задачи е дадено в
книгата на M. Garey и D. Jonson (Гэри М., Джонсон Д. Вычислителные
машины и труднорешаемые задачи. Москва, Мир 1982), която и досега
остава една от най-изчерпателните и достъпни книги по въпроса.
Нека разгледаме следния програмен фрагмент:
1) n = 100;
2) sum = 0;
3) for (int i=0; i<n; i++)
4) for (int j=0; j<n; j++)
5) sum++;
Интересува ни колко бързо ще работи горната програма, т.е. какви са
критериите по-които се определя бързината й? Това, което можем да
направим експериментално е да проверим за колко време ще се изпълни и
ще завърши работата си. За да изследваме по-общо нейното поведение е
възможно да я изпълним за други стойности на n - нека резултатите от
последното са обобщени в следната таблица:
n=10 0.000001 сек.
n=100 0.0001 сек.
n=1000 0.01 сек.
n=10000 1.071 сек.
n=100000 106.543 сек.
n=100000 10663.6 сек.
0
Лесно се забелязва от таблицата, че когато увеличаваме n 10 пъти,
времето, необходимо на програмата, се увеличава с фактор 100.
2
Нека изследваме по-задълбочено горния фрагмент. На редове 1) и 2)
има статична инициализация, тя отнема константно време, ще го означим с
a. За операциите i=0, проверката i<n, i++ отново е необходимо константно
време (всяка от тях представлява константен брой инструкции за
процесора), ще го означим съответно с b, c, d. На ред 4) се времето
необходимо за операциите j=0; j<n; j++ означаваме с e, f, g. Последно, нека
операцията на ред 5) изисква константно време h.
С така въведените означения не е трудно да се пресметне общото
време, необходимо за завършване на програмата, за произволна стойност
на n:
a + b + n.c + n.d + n.(e + n.f + n.g + n.h) =
= a + b + n.c + n.d + n.e + n.n.f + n.n.g + n.n.h =
= n2.(f + g + h) + n.(c + d + e) + a + b
Тъй като a, b, c, d, e, f, g, h са константи, нека означим:
i=f+g+h
j=c+d+e
k=a+b
Така алгоритъмът се изпълнява за време:
i.n2 + j.n + k
Константите i, j и k са важни за бързодействието на алгоритъма, но
не са определящи. На практика когато изследваме ефективността на даден
алгоритъм, ние не се интересуваме от тях. Тези константи зависят
предимно от машинното представяне на нашата програма, както и от
бързината на хардуера, на който тя се изпълнява.
Нещо повече, когато изследваме поведението на нашия алгоритъм
можем да игнорираме дори и едночлените j.n и k и да оставим единствено
този, в който n участва в най-висока степен. Целта при подобно
„опростяване“ е да оставим само най-значимата характеристика за даден
алгоритъм, функцията, от която в най-голяма степен зависи времето на
3
изпълнение - и това е тази, която нараства най-бързо, когато се увеличава
размерът на входните данни.
Нека разгледаме две функции (които показват времето за изпълнение
на два дадени алгоритъма А1 и A2, в зависимост от n):
f = 2.n2 и
g = 200.n.
Лесно се забелязва, че въпреки, че коефициентът на g е много по-
голям от този на f, когато n премине някаква фиксирана стойност (в случая
n>100), алгоритъмът A2 ще решава задачата по-бързо от А1. Нещо повече,
колкото повече се увеличава n, толкова повече отношението между
времето за изпълнение на двата алгоритъма се увеличава в полза на A2.
Асимптотично алгоритъмът A2 е по-бърз и неговата сложност е линейна,
докато тази на A1 е квадратична.
Тези наблюдения могат да бъдат формализирани чрез апарата на
теорията на NP-пълните задачи.
2. Класове на сложност NP и P
Класовете NP и P играят основна роля в количествената теория на
алгоритмите и дават възможност за оценка на тяхната ефективност и
практическа реализируемост.
Класът NP включва всички изброителни задачи, които могат да
бъдат решени чрез метода на изчерпващото търсене, а класът P
съдържа изброителните задачи с полиномиална временна сложност.
Част от задачите в NP служат като еталон на сложност и се наричат
универсални или NP-пълни. Към тях могат полиномиално да се сведат
всички задачи от класа NP.
Една NP-задача A се нарича NP-пълна, ако за всяка друга задача B от
класа NP следва, че BA.
4
Въпросът за съотношението между класовете NP и P има голямо
теоретично и практическо значение. Графично им представяне е дадено на
фиг. 1. Очевидно P NP. Съвпадението на двата класа P = NP означава
елиминация на изчерпващото търсене и насочване на усилията към
разработка на ефективни полиномиални алгоритми (които в момента
липсват, но може би откриването им е въпрос на време). Ако Р NP
трябва да приемем наличието на „труднорешаеми” задачи, за които не
съществуват ефективни алгоритми. Ако дадена задача П NP \ P, то тя е
„труднорешаема”. Американският математически институт Clay предлага
1 000 000$ награда за отговор на въпроса P = NP? [Стойчев САА].
По-прецизна картина за предполагаемото съотношение на класовете
на сложност е дадена на следната фигура:
P-задачи
Това са задачи, за които съществува решение с полиномиална
сложност.
NP-задачи
NP - полиномиално проверими задачи. Дадена задача принадлежи
към класа NP, ако е възможно с полиномиална сложност да се провери
дали даден кандидат за решение действително е решение на задачата.
5
NP-пълни задачи
Универсални или NP-пълни задачи, към които полиномиално се
свеждат всички задачи от класа NP.
еxp-time задачи
Това са задачи, които могат да се решат с експоненциална сложност.
Този клас съдържа предишните три, но не е всеобхватен — има задачи, за
които най-добрите алгоритми са със сложност по-висока от
експоненциалната.
Space задачи. Това са задачи, при които критична е паметта, която
използват (като функция от големината на входните данни). При тях не се
интересуваме от сложността на алгоритъма за решаването им. Този клас
може да се разпадне до няколко подкласа: P-space (задачи за които е
необходима полиномиална памет), exp-space (експоненциална памет) и др.
Ясно е, че изчислителната сложност на алгоритъма е винаги поне
толкова, колкото е сложността по памет.
Нерешими задачи
Съществуват алгоритмични задачи, за които може да се докаже, че
не могат да бъдат решени, независимо от това с колко време и памет
разполагаме.
6
Очевидно, съществените ресурси, влияещи на практическата
реализируемост на алгоритмите и тяхната ефективност са времето за
работа на алгоритъма и необходимата памет за съхранение на данните.
При голям размер на задачата може да се окаже, че тя не може да
бъде решена за „разумно” време или изисква памет за съхранение на
данни, която не може да се предостави. Възниква необходимост от
предварителна оценка на изчислителната сложност (временна сложност,
сложност по памет) на алгоритмите в зависимост от размера на решаваните
задачи. Обикновено ефективността на алгоритмите се свързва с временната
им сложност, тъй като времето е доминиращ фактор при оценка на
възможността за практическата им реализуемост.
Временна сложност на алгоритмите. Временната сложност на
алгоритмите е количествена оценка на времето за работа на алгоритъма в
зависимост от размера на задачата. Ясно е, че времето за решение зависи
от избраната схема за кодиране и модела на изчислителното устройство.
Без ограничение на общността ги приемаме за фиксирани. Най-често за
модел на изчислително устройство при оценка на изчислителната
сложност на алгоритмите се приема машината на Тюринг или машина с
произволен достъп до паметта.
Временната сложност на алгоритмите се представя чрез
функция, която на всяка входна дължина n поставя в съответствие
максималното време за решение на индивидуалните задачи с тази
дължина.
max
T ( n) = T(I)
I, |a( I )|= n
7
сложност при големи стойности на n (n). Използват се следните
класове асимптотична сложност:
T(n) = O(f(n)), ако c>0 n0>0, такива че T(n) cf(n) за n n0. В този
случай казваме, че f(n) е горна асимптотична граница на T(n). Времето
за решение на дадена задача T(n) расте със скорост по-малка или равна на
скоростта на растеж на функцията f(n).
T(n) = (f(n)), ако c>0 n0>0, такива че T(n) cf(n) за n n0. В този
случай казваме, че f(n) е долна асимптотична граница на T(n).
Временната сложност T(n) расте със скорост по-голяма или равна на
скоростта на растеж на функцията f(n).
8
Временната сложност на експоненциалните алгоритми се оценява с
O(2n), O(3n), O(nn), O(n!) и т.н. NP е съкращение от non-deterministic
polynomial time, т.е. полиномиално проверими задачи. Дадена задача
принадлежи към класа NP, ако е възможно с полиномиална сложност да се
провери дали даден кандидат за решение действително е решение, без да
се интересуваме колко време ще отнеме намирането на този кандидат. Така
за NP-задачите е характерно, че ако веднъж успеем да "налучкаме"
правилното решение, то лесно можем да го проверим.
Описаният в достъпната литература практически опит в решението
на многоразмерни реални задачи показва, че двата класа силно се
различават по трудоемкост. Това различие се проявява особено ясно при
теоретична оценка на времето за решение на задачи с голяма рамерност.
Следващата таблица показва скоростта на нарастване на времето за
решение при различна временна сложност и входен размер.
Временна
сложност
Полиномиална Експоненциална
Размер
O(n2) O(n5) O(n10) O(2n) O(3n) O(n!) O(nn)
9
От таблицата се вижда, че увеличението на входния размер на
задачата влияе по различен начин върху времето за решение при
полиномиалните и експоненциални алгоритми. Поради значително по-
бавното нарастване на времето при полиномиалните алгоритми, те се
приемат за ефективни, практически използваеми, „добри” алгоритми.
Бързото нарастване на времето при експоненциалните ги прави
практически неприложими при голям размер на задачите, т.е. „лоши”
алгоритми. Липсата на полиномиални алгоритми за решение на дадена
задача я прави алгоритмично нерешима в приложен смисъл.
Съществената разлика между полиномиалните и експоненциалните
алгоритми се състои в следното. Анализът на дискретните задачи показва,
че те могат да бъдат решени чрез изчерпващо търсене в множеството от
потенциални решения, което включва две стъпки:
1. Пораждане на решения без пропуски и повторения;
2. Проверка за допустимост.
Типични примери са задачата за търговския пътник, задачата за
раницата и много други, наричани изброителни, при които решението се
търси между краен в теоретично отношение брой варианти. На практика
обаче, броят на вариантите, а съответно и времето за решение, расте
експоненциално при увеличение на размера. При големи размерности
такива задачи не могат да бъдат решени за обозримо време, т.е. те са
практически неразрешими. В това се състои и основния проблем на
дискретната математика: съществува ли принципна възможност за
намиране на решение без изчерпване на експоненциален брой варианти,
т.е. елиминиране на изчерпващото търсене. На този въпрос досега не е
намерен задоволителен отговор.
10
Сводимост на задачите в класа Р.
Казваме, че задачата A е сводима към задачата B и пишем AB, ако е
възможно да се намери алгоритъм за решението на А, използващ
полиномиален брой обръщения към функция, решаваща B, и всички
изчисления извън тези обръщения са с полиномиална сложност.
Сводимостта формално означава следното: ако П 1, П2 NP, то П1 е
сводима към П2, ако методът за решение на П 2 може да бъде преобразуван
в метод за решение на П1. Сводимостта е полиномиална, ако такова
преобразуване може да се извърши за полиномиално време.
Тогава, ако една задача B е от класа P, и AB, то следва, че А също е в
класа P. Да илюстрираме казаното дотук с един пример: Нека имаме
задачата за намиране на Хамилтонов цикъл в граф - т.е. дали в графа
съществува цикъл, в който всеки връх участва точно по веднъж. Тази
задача може да се реши, като се използва следния пример:
for ( всяко ребро (i,j) от графа) {
if (съществува прост път с дължина n-1 върха от i до j) {
return yes; /* Съществува Хамилтонов цикъл – това е
намереният път + реброто (i,j)*/
}
}
return no;
Алгоритъмът извършва m обръщения към задачата за намиране на
най-дълъг път в граф и сложността извън тази задача е О(m). Прилагайки
дефиницията по-горе това означава, че “Хамилтонов цикъл” ”Най-дълъг
прост път”. Тъй като обаче не е известен полиномиален алгоритъм за
решението на “най-дълъг прост път”, то не можем да твърдим, че
Хамилтоновият цикъл е полиномиално решима задача.
1 0=0 1 0=1
0 1=0 0 1=1
0 0=0 0 0=0
12
че стойността на израза да бъде „истина”. Задачата се състои в да се
провери дали даден израз е удовлетворим.
Например, изразът ( X̄ 2 X2 X1) X̄ 3 е удовлетворим за X1=1, X2=0,
X3=0, или X1=1, X2=1, X3=0 (където с 1 сме означили, че променливата има
стойност „истина”, а с 0 - „лъжа”). Проверката, дали един израз е
удовлетворим, е NP-задача: ако имаме някакво присвояване на стойности
на променливите, можем (както ще се види след малко) с полиномиална
сложност да проверим, дали то е решение, но за самото намиране на това
присвояване не е известен полиномиален алгоритъм.
Преди да преминем към решението на задачата, ще припомним
някои елементи от математическата логика. Дизюнкт се нарича булев
израз, в който участва само операцията „или“. Булев израз в конюнктивна
нормална форма е израз, в който участват само дизюнкти, обединени с
операцията „и“. С многократно прилагане на тъждеството
A (B C) = (A B) (A C)
произволен булев израз може да се представи в конюнктивна
нормална форма. Ето пример за такъв израз:
(X1 X4) ( X̄ 1 X2) (X1 X̄ 3 ) ( X̄ 2 X3 X̄ 4) ( X̄ 1 X̄ 2 X̄ 3 )
13
присвояваме на променливата стойност „лъжа” и отново опитваме да
продължим.
2)Граничен случай е налице, когато на всички променливи е
присвоена някаква стойност - тогава се изчислява стойността на целия
израз и се проверява дали тя е „истина”.
В реализацията на описания алгоритъм булевият израз (приведен в
конюнктивна нормална форма) ще представим с двумерен масив - всеки
дизюнкт се асоциира с един ред от масива. Булевите променливите от един
ред (дизюнкт) ще представяме с целите числа от 1 до n, а за отрицание на
променливата i ще използваме числото – i. В масива values[] се намират
стойностите (0 или 1), които сме присвоили на всяка една променлива.
Функцията true() проверява стойността на булевият израз въз основа на
направените във values[] присвоявания и връща 1, ако изразът е
удовлетворим. Тази проверка се извършва лесно: За да има целият израз
стойност „истина” трябва всеки един от дизюнктите да има стойност
„истина” (тъй като операцията между дизюнктите е „и“). За да има един
дизюнкт стойност „истина”, то поне една от променливите в него трябва да
бъде със стойност „истина” или отрицание на променлива да бъде със
стойност „лъжа” - това се дължи на факта, че всички променливи в един
дизюнкт са обединени с операцията „или“.
Присвояване на стойностите на променливите в алгоритъма по-горе,
извършва функцията assign(int i), като в граничния случай (i>n) се извиква
функцията true(), за да провери дали изразът е удовлетворен (т.е. да
провери дали присвояването е решение на задачата). Следва и пълната
реализация:
bool.cpp
#include <stdio.h>
14
{-1, 2},
{ 1, -3},
{-2, 3,-4},
{-1,-2,-3}};
int values[N+1];
void printAssign(){
printf("Изразът е удовлетворим за: ");
for (int i=1; i<=N; i++) printf("X%d=%d ",i,values[i]); printf("\n");
}
//във всеки от K-те дизюнкта трябва поне една променлива да бъде със стойност
“истина”
int true() {
for (int i=0; i<K; i++) {
int j=0;
int ok=0;
while (expr[i][j]!=0){
int p=expr[i][j];
if ((p>0)&&(values[p]==1)) {ok=1; break; }
if ((p<0)&&(values[-p]==0)) {ok=1; break; }
j++;
}
if (!ok) return 0;
}
return 1;
}
void main() {
assign(1);
}
15
При NP-пълните задачи (и в частност задачата за удовлетворимост),
от всички кандидати за решение по неявен начин се формира дърво (или
граф), което алгоритъмът за търсене с връщане обхожда. Често обаче е
излишно изследването на голяма част от това дърво. Например, за израза
X1(X2X3) при присвояване X1=0 веднага се забелязва, че няма да
достигнем до решение, независимо от стойностите, които ще получат
останалите две променливи X2 и X3. Когато променливите са повече и
булевият израз е по-сложен, подобно „изрязване“ на изследваното дърво
може да доведе до значително по-бърза програма.
Реализацията на това изрязване ще осъществим като извършим
следните модификации в горната програма:
1)На функцията true() ще добавим нов аргумент t означаващ, че са
присвоени стойности само на първите t променливи. Така, ако един
дизюнкт е съставен само от променливи с номера по-малки или равни на t
и нито една от тях няма стойност „истина”, то целия булев израз е „лъжа”
и няма смисъл да се присвояват стойности на останалите n-t променливи.
Ако в дизюнкта има променливи, на които още не е присвоена стойност, то
неговата стойност не може да се определи и продължаваме разглеждането.
2)При генерирането се извиква функцията true() в началото на всяка
стъпка. Така прекъсваме по-нататъшното генериране веднага, щом то стане
безсмислено - стойността на булевия израз ще бъде „лъжа”, независимо от
това как ще се извършат останалите присвоявания.
boolcut.cpp
boolcut.cpp
#include <stdio.h>
16
{-1, 2},
{ 1, -3},
{-2, 3,-4},
{-1,-2,-3}};
int values[n+1];
void printAssign(){
printf("Изразът е удовлетворим за: ");
for (int i=1; i<=n; i++) printf("X%d=%d ",i,values[i]); printf("\n");
}
//във всеки от K-те дизюнкта трябва поне една променлива да бъде със
стойност “истина”
int true(int t) {
for (int i=0; i<K; i++) {
int j=0;
int ok=0;
while (expr[i][j]!=0){
int p=expr[i][j];
if ((p>t)||(-p>t)) {ok=1; break; }
if ((p>0)&&(values[p]==1)) {ok=1; break; }
if ((p<0)&&(values[-p]==0)) {ok=1; break; }
j++;
}
if (!ok) return 0;
}
return 1;
}
values[i]=1;
assign(i+1);
values[i]=0;
assign(i+1);
}
void main() {
assign(1);
}
17
2.Обратно, ако някоя задача от NP е „труднорешаема”, то такава е и
задачата за изпълнимост.
3.Всички въпроси, свързани с анализ на изчислителната сложност на
алгоритмите се свеждат до един: Труднорешаеми ли са задачите от класа
NP?
Работата на Кук дава тласък на изследванията в тази област. Доказва
се NP-пълнотата на много известни задачи. В учебника на Джонсън са
описани над 300 такива задачи, разпределени в 12 категории (теория на
графите, мрежи, множества, съхранение и търсене на данни,
математическо програмиране, алгебра, теория на числата, логика и др.).
Ако само за една от тях бъде доказано, че може, или че не може да бъде
решена с полиномиален алгоритъм, то това ще следва и за всички останали
Доказателството на NP-пълнотата на дадена задача П се извършва по
следния начин:
1. доказва се, че П NP;
2. избор на известна NP-пълна задача П’;
3. свеждане на П’ към П;
4. доказателство, че сводимостта е полиномиална.
ЗАКЛЮЧЕНИЕ
Теорията на NP-пълните задачи ни предоставя математическия
апарат, необходим за пълноценното изследване на всеки компютърен
алгоритъм. Темата за оценка и сложност на алгоритмите е свързана с
оценката на времето за работа и оценка на ефективността на алгоритмите,
което има несъмнено значение за практическата им реализация.
18
Използвана литература:
1. Миланов, П. и кол., Теория на алгоритмите и оптимизация, Благоевград,
ЮЗУ „Неофит Рилски“, http://bioinfo.swu.bg/project/kniga%20algoritmi.pdf
2. Наков, П., П. Добриков, Програмиране = ++ Алгоритми,
http://www.programirane.org/
3. Марков, М., Теория на алгоритмите,
https://learn.fmi.uni-sofia.bg/pluginfile.php/99103/mod_resource/content/1/lec-
algs.pdf
4. Марков, М., Лекции по алгоритми,
https://learn.fmi.uni-sofia.bg/pluginfile.php/135158/mod_resource/content/10/
lec-algs.pdf
19