* Принципи на "разделяй и владей": разбиване на изходната задача на няколко подзадачи (разделяй), решаване на подзадачите и конструиране на решение на изходната задача (владей).
* Пример с двоично търсене.
* Намиране на максимален елемент (първия по големина) - n - 1 сравнения, n присвоявания в най-лошия случай, 1 присвояване в най-добрия случай, средния брой присвоявания е от порядък O(log n).
* Едновременно намиране на минимален и максимален елемент (първия и последния по големина) - 3n/2 сравнения ! Сравняваме 2 съседни елемента, по-големия го сравняване за max, а по-малкия - за min.
* Пълно сортиране - най-добрите алгоритми са O(n log n).
* Бързо сортиране с разделяне на дялове (Хоор, "разделяй и владей") - O(n2) в най-лошия случай, O(n) в най-добрия случай, O(n log n) средно. Сложността зависи от избора на елемент за разделяне на дялове.
* Когато търсим k-тия по големина елемент няма нужда да обработваме и двата дяла - отново O(n2) в най-лошия случай.
* Метод на Блум, Флойд, Прат, Ривест и Тарджан.
- Нека имаме метод M за намиране на k-тия
по големина елемент с 28n сравнения.
- За n
<= 55 прилагаме метода на мехурчето, 28n > n(n-1)/2
- Медиана - елемент на масив, който се намира на
позиция n/2 в сортирания масив.
- Търсене на медиана на медианите:
1. Разделяме масива
на групи по 7 елемента.
2. Сортираме всяка
група и намираме медианата й (третия елемент) чрез сортиране по
метода на мехурчето (или друг квадратичен метод).
3. Намираме медианата
на медианите по метод M (рекурсивно!).
- Разделянато на дялове извършваме с така
намерената медиана на медианите.
- Оценка на метода (7(7-1)/2)(n/7)
+ 28(n/7) + 28(5n/7) = 3n + 4n +
20n = 27n (при всяко рекурсивно извикване
областта намалява с поне 2n/7 елемента), т.е.
предположението за 28n сравнения е вярно.
* Метод на Дур и Цвик [1995] - 2.95n сравнения, [2001] - (2+epsilon).n сравнения.
Допълнителна литература: Лендерт Амерал, 3.12.
* Класическият алгоритъм за умножение на цели числа има сложност O(n2).
* Алгоритъм за умножение на две n-цифрени числа X и Y, като n е степен на 2.
* Тази формула изисква 3 умножения, 6 събирания и 2 премествания вляво (умножения на степени на 2). От рекурентната формула
* Пример 1: 15 x 8 = 120, X
= 15 = 11112, Y = 8 = 10002, n = 4 = 22
A = 11, B = 11, C = 10, D =
00
AC = 110, (A
- B)(D - C) = 0.10 = 0, BD = 0
XY = 110 << 4 + 110
<< 2 = 1100000 + 11000 = 11110002 =
8+16+32+64 = 12010
* Когато числата са с различен брой цифри и/или този брой не е степен на 2, допълваме отляво с незначещи нули и използваме горния алгоритъм.
* Пример 2. 50 x 25 = 1250, X
= 50 = 1100102, Y = 25 = 110012, n = 8 = 24
с допълване до най-близката степен на 2.
A = 0011 (3), B = 0010 (2), C =
0001 (1), D = 1001 (9)
AC = 0011 (3), (A
- B)(D - C) = 0001.1000 = 1000 (8), BD = 10010
(18)
XY = 3.256 + (8 + 3 + 18).16
+ 18 =
11
<<
8
+
11101
<<
4
= 11 0000 0000 + 1 1101 0000 + 10010 = 100111000102
=
2+32+64+128+1024 = 125010
* Подзадачи:
- представяне на дълги числа (низ или вътрешно);
- брой на битовете на цяло число;
- разделяне на десните от левите битове на числото;
- сума и разлика на две числа.
* Пример за циклично преместване на k позиции
елементите на масив m от n елемента.
n = 6, k = 2 0 1 2 3 4
5 -> 2 3 4 5 0 1
* Алгоритъм 1:
- копираме първите k елемента на m в друг масив
x;
- преместваме n-k елемента на m на k
позиции вляво;
- копираме елементите на x обратно в m на
последните k позиции.
Сложност O(n), допълнителна памет k
елемента.
* Алгоритъм 2:
Описания алгоритъм за k = 1. За да осъществим циклично
преместване с k елемента, е необходимо да приложим този
алгоритъм k пъти.
Сложност O(n2), допълнителна памет 1
елемент.
* Алгоритъм 3:
- копираме m[0] в x;
- преместваме m[k] в m[0], преместваме
m[2k%n] в m[k], преместваме m[3k%n]
в m[2k%n] и т.н. докато стигнем последния
непреместен елемент, който го заменяме с x
(Пример А). Ако достигнем до преместен елемент, спираме и
започваме отначало по същия начин, като заменяме k
с k+1 (Пример Б).
Пример А n = 11, k = 3:
0 1 2 3 4 5 6 7 8 9 10 -> 3 4 5 6 7 8 9 10 0 1 2 m01: 0 1 2 3 4 5 6 7 8 9 10 | x: m02: 0 1 2 3 4 5 6 7 8 9 10 | x: 0 m03: 3 1 2 3 4 5 6 7 8 9 10 | x: 0; m04: 3 1 2 6 4 5 6 7 8 9 10 | x: 0; m05: 3 1 2 6 4 5 9 7 8 9 10 | x: 0; m06: 3 1 2 6 4 5 9 7 8 1 10 | x: 0; m07: 3 4 2 6 4 5 9 7 8 1 10 | x: 0; m08: 3 4 2 6 7 5 9 7 8 1 10 | x: 0; m09: 3 4 2 6 7 5 9 10 8 1 10 | x: 0; m10: 3 4 2 6 7 5 9 10 8 1 2 | x: 0; m11: 3 4 5 6 7 5 9 10 8 1 2 | x: 0; m12: 3 4 5 6 7 8 9 10 8 1 2 | x: 0; m13: 3 4 5 6 7 8 9 10 0 1 2 | x: 0; |
Пример Б n = 9, k
= 3: 0 1 2 3 4 5 6 7 8 -> 3 4 5 6 7 8 0 1 2 m01: 0 1 2 3 4 5 6 7 8 | x: m02: 3 1 2 3 4 5 6 7 8 | x: 0; m03: 3 1 2 6 4 5 6 7 8 | x: 0; m04: 3 1 2 6 4 5 0 7 8 | x: 0; m05: 3 1 2 6 4 5 0 7 8 | x: 1; m06: 3 4 2 6 4 5 0 7 8 | x: 1; m07: 3 4 2 6 7 5 0 7 8 | x: 1; m08: 3 4 2 6 7 5 0 1 8 | x: 1; m09: 3 4 2 6 7 5 0 1 8 | x: 2; m10: 3 4 5 6 7 5 0 1 8 | x: 2; m11: 3 4 5 6 7 8 0 1 8 | x: 2; m12: 3 4 5 6 7 8 0 1 2 | x: 2; |
Сложност O(n), допълнителна памет 1 елемент.
* Алгоритъм 4 (разделяй и владей):
- разделяме масива на 2 части: A - първите k елемента и
B - последните n - k елемента;
- разделяме масива B на 2 части, Br - последните k
елемента, Bl - останалите елементи;
- разменяме местата на елементите на A и Br;
- прилагаме същия алгоритъм за масива B.
Пример.
n = 11, k = 3:
0 1 2 3 4 5 6 7 8 9 10 -> 3 4 5
6 7 8 9 10 0 1 2
[0 1 2 3 4 5 6 7 8 9 10]
A = [0 1 2], Bl = [3 4 5 6 7], Br = [8 9 10]
[8 9 10 3 4 5 6 7 0 1 2 ]
[8 9 10 3 4 5 6 7 ] [0 1 2]
A = [8 9 10], Bl =[3 4], Br = [5 6 7]
[5 6 7 3 4 8 9 10]
[5 6 7 3 4] [8 9 10 0 1 2]
A = [5 6 7], Bl = [], Br = [3 4 5]
[ 3 4 5 6 7]
[3 4 5 6 7 8 9 10 0 1 2]
Сложност O(n), допълнителна памет - константа.
* Алгоритъм 5 (Кърниган и Плоджер): BA = (AR BR)R
AR е масив, получен от елементите на A в обратен ред. С 3 извиквания на функция reverse решаваме задачата за циклично преместване на елементите на масив.Пример.
n = 11, k = 3:
0 1 2 3 4 5 6 7 8 9 10 -> 3 4 5 6 7 8 9 10 0 1 2
A = [0 1 2], B = [3 4 5 6 7 8 9 10], AB
= [0 1 2 3 4 5 6 7 8 9 10]
AR = [2 1 0], BR =
[10 9 8 7 6 5 4 3], AR BR =
[2 1 0 10 9 8 7 6 5 4 3]
(AR BR)R = [3 4
5 6 7 8 9 10 0 1 2]
//
shift3.c
#include <stdio.h>
#define N 11
int m[N];
void
reverse(unsigned a, unsigned b)
{ unsigned i, j, k, c;
int tmp;
for (c=(b-a)/2, k=a, j=b, i=0;
i<c; i++, j--, k++)
{ tmp = m[k];
m[k] = m[j];
m[j] = tmp;
}
}
void
shift3(unsigned k)
{ reverse(0, k-1);
reverse(k, N-1);
reverse(0, N-1);
}
int
main()
{
int
i;
for
(i=0;
i<N; i++) m[i]=i;
shift3(3);
for
(i=0;
i<N; i++) printf("%d ", m[i]);
printf("\n");
return
0;
}
Сложност O(n), допълнителна памет - константа.