Кіріспе
Кіші мәселелерді рекурсивті түрде шешетін алгоритмдер. Компьютер ғылымында, "бөліп талқандау" – алгоритмдік жобалау парадигмасы болып табылады. "Бөліп талқандау" алгоритмі бір мәселені екі немесе одан көп, бір-бірімен ұқсас немесе байланысты кіші мәселелерге рекурсивті түрде бөледі, олар тікелей шешілетіндей қарапайым болғанша. Содан кейін кіші мәселелердің шешімдері бастапқы мәселенің шешімін алу үшін біріктіріледі. "Бөліп талқандау" әдісі көптеген мәселелер үшін тиімді алгоритмдердің негізі болып табылады, мысалы, сұрыптау (мысалы, жылдам сұрыптау, біріктіру сұрыптау), үлкен сандарды көбейту (мысалы, Каратсуба алгоритмі), ең жақын нүктелер жұбын табу, синтаксистік талдау (мысалы, жоғарыдан төменге қарай талдаушылар) және дискретті Фурье түрлендіруін (FFT) есептеу. Тиімді "бөліп талқандау" алгоритмдерін жобалау қиын болуы мүмкін. Математикалық индукциядағыдай, рекурсивті шешімге қолдануға ыңғайлы ету үшін мәселені жалпылау қажет болуы мүмкін. "Бөліп талқандау" алгоритмінің дұрыстығы әдетте математикалық индукция арқылы дәлелденеді, ал оның есептеулік шығындары жиі рекурренттік қатынастарды шешу арқылы анықталады.
In computer science, divide and conquer is an algorithm design paradigm. A divide and conquer algorithm recursively breaks down a problem into two or more sub problems of the same or related type, until these become simple enough to be solved directly. The solutions to the sub problems are then combined to give a solution to the original problem. The divide and conquer technique is the basis of efficient algorithms for many problems, such as sorting (e. g., quicksort, merge sort), multiplying large numbers (e. g., the Karatsuba algorithm), finding the closest pair of points, syntactic analysis (e. g., top down parsers), and computing the discrete Fourier transform (FFT). Designing efficient divide and conquer algorithms can be difficult. As in mathematical induction, it is often necessary to generalize the problem to make it amenable to a recursive solution. The correctness of a divide and conquer algorithm is usually proved by mathematical induction, and its computational cost is often determined by solving recurrence relations.
Бөлініп , билік ет
"Бөліп ал да билік ет" парадигмасы көбінесе мәселенің оңтайлы шешімін табу үшін қолданылады. Оның негізгі идеясы – берілген мәселені екі немесе одан да көп ұқсас, бірақ қарапайым, кіші мәселелерге бөлу, оларды кезекпен шешу және берілген мәселені шешу үшін олардың шешімдерін біріктіру. Жетілді деңгейдегі қарапайым мәселелер тікелей шешіледі. Мысалы, n табиғи сандардың берілген тізімін сұрыптау үшін, оны әрқайсысы шамамен n/2 саннан тұратын екі тізімге бөліп, олардың әрқайсысын кезекпен сұрыптап, берілген тізімнің сұрыпталған нұсқасын алу үшін екі нәтижені де тиісті түрде араластыру керек (суретті қараңыз). Бұл тәсіл біріктіру сұрыптау алгоритмі деп аталады. "Бөліп, жеңіп алу" атауы кейде әрбір мәселені тек бір ғана кіші мәселеге дейін азайтатын алгоритмдерге қолданылады, мысалы, реттелген тізімдегі жазбаны табу үшін екілік іздеу алгоритмі (немесе сандық есептеудегі оның аналогы, тамырды табу үшін екіге бөлу алгоритмі). Бұл алгоритмдер жалпы "бөліп-жеңу" алгоритмдерінен тиімдірек іске асырылуы мүмкін; әсіресе, егер олар құйрық рекурсиясын қолданса, оларды қарапайым циклдерге айналдыруға болады. Дегенмен, осы кең анықтама бойынша рекурсияны немесе циклдарды пайдаланатын кез келген алгоритмді "бөліп жеңу алгоритмі" деп қарастыруға болады. Сондықтан кейбір авторлар "бөлу және билеу" деген атауды тек әрбір мәселе екі немесе одан да көп кіші мәселелерді тудыратын жағдайда ғана қолдану керек деп санайды. Бір ғана кіші мәселе класы үшін "төмендету және жеңу" атауы ұсынылған. "Бөлу мен билеудің" маңызды қолданылуы – оптимизацияда, егер іздеу кеңістігі әр қадамда тұрақты фактормен қысқартылса ("талшықталса"), онда жалпы алгоритмнің асимптотикалық күрделілігі кесу қадамының күрделілігімен бірдей болады, ал тұрақты кесу факторы кесу факторына байланысты (геометриялық қатарды қосу арқылы); бұл "талшықтау және іздеу" деп аталады.
Ертедегі тарихи мысалдар
Бұл алгоритмдердің алғашқы мысалдары негізінен «азайту және жеңу» тәсіліне жатады – бастапқы мәселе біртіндеп жекеше кіші мәселелерге бөлінеді және іс жүзінде итеративті түрде шешілуі мүмкін. Бинарлық іздеу – кіші мәселелер бастапқы мөлшердің шамамен жартысына тең болатын «азайту және жеңу» алгоритмі, ұзақ тарихқа ие. Алгоритмнің компьютерлердегі нақты сипаттамасы 1946 жылы Джон Мочлидің мақаласында жарияланған, бірақ іздеуді жеңілдету үшін сұрыпталған элементтер тізімін пайдалану идеясы кем дегенде б.з.д. 200 жылға дейін сонау Вавилонға дейін барып жетеді. Ол FFT операцияларының сандық тұрғыдағы тиімділігін талдамаса да, олар бір ғасырдан астам уақыттан кейін қайта ашылғанға дейін кеңінен таралмады. Компьютерлер үшін арнайы әзірленген және дұрыс талданған алғашқы екі кіші мәселелі «азайту және жеңу» алгоритмі – Джон фон Нейманның 1945 жылы ойлап тапқан біріктіру сұрыптау алгоритмі. Тағы бір маңызды мысал – Анатолий А. Каратсубаның 1960 жылы құрастырған алгоритмі, ол екі n цифрлы санды (Big O белгісінде) операциялар арқылы көбейтуге мүмкіндік береді. Бұл алгоритм Андрей Колмогоровтың 1956 жылғы осы тапсырманы орындау үшін операциялар қажет деген болжамын жоққа шығарды. Компьютерлерді бастапқыда пайдаланбаған «бөліп-жеңу» алгоритмінің тағы бір мысалы ретінде Дональд Кнут пошта бөлімшелерінің хаттарды бағыттау үшін қолданатын әдісін келтіреді: хаттар әртүрлі географиялық аймақтар үшін бөлек қаптарға сұрыпталады, осы қаптардың әрқайсысы кішігірім аймақтар үшін топтамаларға жіктеледі және олар мекенжайсына жеткізілгенге дейін. Бұл 1929 жылы перфокарталарды сұрыптау машиналары үшін сипатталған радикс сұрыптаумен байланысты. Сонымен қатар, «азайту және жеңу» алгоритмдерін маңызды алгоритмдер үшін (мысалы, сұрыптау, FFT және матрица көбейту) оптималды кэшті ескермейтін алгоритмдер ретінде құруға болады – олар кэштің мөлшеріне қарамастан, асимптотикалық тұрғыдан мүмкін ең тиімді жолмен пайдаланады. Керісінше, кэшті пайдаланудың дәстүрлі тәсілі – бұғаттау, мысалы, циклдарды оңтайландыруда, онда мәселе тиісті мөлшердегі бөліктерге бөлінеді. Бұл кэшті тиімді пайдалана алады, бірақ алгоритм белгілі бір машинаның нақты кэш мөлшеріне қалыптасқанда ғана. Басқа иерархиялық сақтау жүйелері, мысалы NUMA немесе виртуалды жад, сондай-ақ кэштің бірнеше деңгейлері үшін де осы артықшылық сақталады: кіші мәселе жеткілікті кішкентай болған кезде, оны иерархияның белгілі бір деңгейінде, жоғары (баяу) деңгейлерге жүгінбестен шешуге болады.
Айналдыруды басқару
Түзетілген арифметикамен есептеулерде, мысалы, қозғалатын нүктелі сандармен, «бөліп талқандау» алгоритмі сырттай ұқсас итерациялық әдіске қарағанда дәлірек нәтижелер бере алады. Мысалы, N санды әрбір мәліметті бір айнымалыға қосатын қарапайым цикл арқылы немесе мәліметтер жиынын екіге бөлетін, әр бөлігінің қосындысын рекурсивті есептейтін және содан кейін екі қосындыны қосатын «жұптық қосу» деп аталатын «бөліп талқандау» алгоритмі арқылы қосуға болады. Екінші әдіс біріншісімен бірдей санда қосу операцияларын орындайды, бірақ рекурсивті шақырулардың қосымша шығындарына түседі, әдетте дәлірек болады.
Қайталану
Бөліп талқандау алгоритмдері табиғи түрде рекурсивті процедуралар ретінде іске асырылады. Ондай жағдайда, қазіргі шешіліп жатқан мәселеге алып келетін ішінара мәселелер процедура шақыру стегінде автоматты түрде сақталады. Рекурсивті функция – өзінің анықтамасы ішінде өзін өзі шақыратын функция.
Айқын стек
Бөлу және билеу алгоритмдерін рекурсиялық емес бағдарлама арқылы да іске асыруға болады, ол ішінара ішкі проблемаларды стек, кезек немесе басымдық кезегі сияқты нақты дерек құрылымдарында сақтайды. Бұл тәсіл келесі шешілетін ішкі проблеманы таңдауда көбірек еркіндік береді, бұл кейбір қолдануларда маңызды – мысалы, ендік бойынша іздеу рекурсиясында және функцияларды оңтайландыру үшін тармақталу және шектеу әдісінде. Сондай-ақ, бұл тәсіл рекурсивті процедураларды қолдамайтын бағдарламалау тілдеріндегі стандартты шешім болып табылады.
Қорытынды өлшемі
D&C алгоритмдерінің рекурсивті іске асырылуында рекурсиялық стекке жеткілікті жад бөлінгеніне көз жеткізу қажет, әйтпесе стек асып кетуіне байланысты орындалу сәтсіз аяқталуы мүмкін. Уақыт бойынша тиімді D&C алгоритмдерінің рекурсия тереңдігі көбінесе шағын болады. Мысалы, жылдам сұрыптау алгоритмін элементтерді сұрыптау үшін ең көп дегенде ұялы рекурсивті шақырулармен іске асыруға болады. Рекурсивті процедураларды қолданғанда стек асып кетуінен аулақ болу қиын болуы мүмкін, себебі көптеген компиляторлар рекурсиялық стек жадтың үздіксіз бөлігі деп санайды, ал кейбіреулері оған белгілі бір көлемде жад бөледі. Компиляторлар рекурсиялық стекке қажетінен артық ақпаратты, мысалы, қайтару мекенжайын, өзгермейтін параметрлерді және процедураның ішкі айнымалыларын сақтауы мүмкін. Осылайша, стек асып кету қаупін рекурсивті процедураның параметрлері мен ішкі айнымалыларын азайту арқылы немесе нақты стек құрылымын пайдалану арқылы төмендетуге болады.
Базалық жағдайларды таңдау
Кез келген рекурсивті алгоритмде базалық жағдайларды таңдауда үлкен еркіндік бар, олар рекурсияны тоқтату үшін тікелей шешілетін кішігірім мәселелер. Ең кішкентай немесе ең қарапайым базалық жағдайларды таңдау көбінесе қарапайым бағдарламаларға әкеледі, себебі қарастыруға аз жағдай болады және оларды шешу оңай. Мысалы, FFT алгоритмі кіріс бір үлгі болғанда рекурсияны тоқтатуы мүмкін, ал жылдам сұрыптау алгоритмі кіріс бос тізім болғанда тоқтауы мүмкін; екі жағдайда да қарастырылатын бір ғана базалық жағдай бар және ол қосымша өңдеуді қажет етпейді. Екінші жағынан, рекурсия салыстырмалы түрде үлкен базалық жағдайларда тоқтатылса, тиімділік көбінесе жақсарады, және олар рекурсиясыз шешіледі, нәтижесінде гибридті алгоритм пайда болады. Бұл стратегия рекурсивті шақырулардың артық жұмыс істемейтін немесе аз жұмыс істейтін жағдайларын болдырмайды, сондай-ақ осы базалық жағдайларда нақты рекурсиядан гөрі тиімдірек болатын арнайы рекурсиясыз алгоритмдерді пайдалануға мүмкіндік береді. Қарапайым гибридті рекурсивті алгоритмді жүзеге асырудың жалпы тәсілі – базалық жағдайды жылдам тексеру, сонымен қатар «қол ұзындығындағы» рекурсия деп те аталады. Бұл жағдайда, келесі қадамның базалық жағдайға жеткізетінін-жеткізбейтіні функцияны шақыру алдында тексеріледі, артық функция шақыруларын болдырмау үшін. Мысалы, ағашта, балалық түйінге рекурсия жасаудың орнына және оның бос екенін тексеру, рекурсия жасау алдында бос екенін тексеру, кейбір екілік ағаштардағы алгоритмдердегі функция шақырулардың жартысын болдырмайды. D&C алгоритмі әрбір мәселені немесе кіші мәселені соңында көптеген базалық жағдайларға дейін азайтады, сондықтан олар көбінесе алгоритмнің жалпы құнында басым болады, әсіресе бөлу/қосу операцияларының құны төмен болғанда. Бұл ескертулер рекурсияны компилятор немесе нақты стек жүзеге асырғандығына қарамастан қолданылады. Мысалы, жылдам сұрыптаудың көптеген кітапханалық нұсқалары, сұрыпталатын элементтердің саны жеткілікті түрде аз болғанда, қарапайым циклға негізделген енгізу сұрыптау (немесе ұқсас) алгоритміне ауысады. Егер бос тізім жалғыз базалық жағдай болса, жазбалары бар тізімді сұрыптау үшін тек жылдам сұрыптау шақырылады, ол бірден қайтарылады. Базалық жағдайларды 2 немесе одан аз өлшемді тізімдерге ұлғайту ештеңе жасамайтын шақырулардың көп бөлігін жояды, және жалпы алғанда, 2-ден үлкен базалық жағдай функция шақыруына немесе стекпен жұмыс істеуге жұмсалатын уақытты азайту үшін қолданылады. Балама ретінде, әлі де бөліп-жарып алу алгоритмін қолданатын үлкен базалық жағдайларды қолдануға болады, бірақ алгоритмді алдын ала анықталған өлшемдер жиынтығы үшін жүзеге асыруға болады, онда алгоритм рекурсиясыз, циклдарсыз немесе шарттылықтарсыз кодқа толық ашылады (бөлшек бағалау әдісімен байланысты). Мысалы, бұл тәсіл кейбір тиімді FFT нұсқаларында қолданылады, онда базалық жағдайлар FFT алгоритмдерін бөліп-жарып алудың белгілі бір өлшемдер жиынтығы үшін ашылған нұсқалары болып табылады. Бұл стратегияны тиімді жүзеге асыру үшін қажетті көптеген жеке базалық жағдайларды жасау үшін бастапқы кодты жасау әдістерін пайдалануға болады.
Бір-біріне ауыспалы кіші проблемаларды динамикалық бағдарламалау
Кейбір проблемалар үшін тармақталған рекурсия бірдей кіші проблеманы көп рет есептеуге алып келуі мүмкін. Мұндай жағдайларда, осы қайталап келетін кіші проблемалардың шешімдерін анықтап, сақтау пайдалы болуы мүмкін, бұл техника мемоизация деп аталады. Бұл әдістің соңғы шегіне жеткенде, динамикалық бағдарламалау сияқты, төменнен жоғарыға қарай бөліп-жеңу алгоритмдеріне қол жеткізіледі.