12. Сортиране. Търсене. Хеширане
Сортиране
- общи положения; сортиране чрез сравнение
** Класификации на алгоритмите за сортиране [Глава 3, стр.187]
* В зависимост от местонахождението на данните:
- вътрешно (директен достъп), например бързо сортиране и
- външно (последователен достъп), например сливане.
* В зависимост от операцията:
- чрез сравнение (<,
> и ==) на двойки елементи и
- чрез трансформация, напр. сортиране чрез броене.
* Свойство на алгоритъма за сортиране:
- устойчиви - относителният ред на елементите с равни ключове
остава непроменен и
- неустойчиви - разместване на елементи с равни ключове
(сортиране с 2 ключа)
* Ефективност на алгоритмите за сортиране - брой на извършени сравнения
и размени (присвоявания).
* Използване на допълнителна памет.
** Дърво на сравненията; сортиране на 3 числа.
** Класически универсални "елементарни" методи за сортиране чрез
сравнение O(n2):
- пряко вмъкване - намираме елемент, който "не е сортиран" и го
поставяме на мястото му в сортираната част;
- пряка селекция (избор) - намираме най-малкия елемент и го
поставяме на мястото му в окончателно сортираната част;
- мехурчето - последователно се разглеждат двойки елементи и
евентуално се разменят.
** Бързо сортиране на Хоор - O(n log2n) средно и O(n2)
в
най-лошия
случай.
** Пирамидално сортиране, сортиране чрез сливане: O(n
log2n) и
тази оценка не може да се
подобри при сортиране чрез сравняване.
** Сортиране
чрез
трансформация
[3.2]
** Сортиране чрез множество [3.2.1]
Дадено е множество M
от числа в затворения интервал [a, b]
и
инективна
функция
за нареждане f:
M -> [a, b], т.е. ако x1 и x2 са
различни, то са различни и f (x1) и f (x2).
Построяваме нулев масив S с индекси от a до b и
с
едно
минаване
през множеството M поставяме
стойности
1
на
S[f (x)] за всяко x от M. След това минаваме през
масива S за да
подредим елементите на M.
Сложност O(m+n), където n е броят на елементите на M, а m = b - a + 1.
** Сортиране чрез броене [3.2.2]
** Побитово сортиране [3.2.3]
** Метод на бройните системи [3.2.4]
** Сортиране чрез пермутация [3.2.5]
Дадено е множество M
от n елементи. Означаваме с S множеството {1, 2, 3, ..., n}.
Функцията
за
нареждане
е f: S ->
S е сюрективна, т.е.
ако x1
и x2 са
различни, то са различни и f (x1) и f (x2)
и
за
всяко
y от S съществува x от S такова, че y = f (x).
Разменяме m[1]
с m[m[1]] докато на 1-во място не дойде 1. После по същия начин с
втория елемент и т.н.
позиции 1234567
4375612
5374612
6374512
1374562
1734562
1234567
Броят
на
размените
не
недвишава n,
а броят на сравненията - 2n.
Бързо сортиране
А. Разделяне на дялове:
1. Избираме случаен елемент x от
масива a
2. Преглеждаме масива отляво (от началото), докато достигнем
до елемент > x
3. Преглеждаме масива отдясно (от края), докато достигнем до
елемент <
x
4. Разменяме местата на двата елемента
vector<int> a(n);
void partition(int x)
{
int i=1, j=n;
do
{
while (a[i] < x) i++;
while (a[j] > x) j--;
if (i<=j) { swap(a[i], a[j]); i++;
j--; }
}
while (i<=j);
}
Б. Сортиране - след като масивът се раздели, двата
му дяла се
подлагат
на същата
обработка и това продължава, докато се получат дялове само с по един
елемент.
//
qsort.cpp
void quicksort(int left, int right)
{
int i=left, j=right;
int x=a[(i + j)/2];
do
{
while (a[i] < x) i++;
while (a[j] > x) j--;
if (i<=j)
{ swap(a[i], a[j]); i++; j--; }
}
while (i<=j);
if (left<j) quicksort(left, j);
if (i<right) quicksort(i, right);
}
qsort от STL
Сортиране чрез сливане
mergesort.cpp
Последователно
търсене [4, стр. 231]
** Човешката дейност търсене
** Опростен модел на търсене
** Фундаментални операции над елемнтите на множество (правилна оценка
на ефективността на алгоритмите за търсене)
-- инициализиране
-- търсене
-- вмъкване
-- изтриване
-- обединяване на множества
-- сортиране.
** Ключ, повтарящи се ключове
-- има ли елемент с даден ключ? [bool
exists(unsigned
key)]
-- индекс на елемент (обект) с даден ключ? [unsigned find_one(unsigned key)]
-- брой елементи с даден ключ? [unsigned count(unsigned key)]
-- индекси на всички елементи (обекти) с даден ключ? [vector<unsigned>
find_all(unsigned key)]
** Последователно (линейно) търсене
Проверяваме последователно
елементите на множеството (което е линейно наредено), докато или
намерим търсения елемент или стигнем
до края на редицата.
Ефективност на алгоритъма за линейно
търсене:
Броят на обръщенията към елементите на масива зависи
от търсеното число, но в най-лошия случай, когато числото не се среща в
масива, е равен на дължината на масива. Следователно сложността на
алгоритъма
е O(n).
// lsearch.cpp
int
linear_search(vector<int> v,
int
a)
{
int i;
for (i = 0; i < v.size();
i++)
if
(v[i] == a) return i;
return -1;
}
** Последователно търсене в сортиран списък с поддържане на
сортирания
списък при вмъкване на нов елемент.
** Последователно търсене с преподреждане
Двоично търсене (разделай и владей) [4.3, стр. 239]
// bsearch.cpp
int
binary_search(vector<int> v,
int
from, int to, int a)
{
if (from > to)
return
-1;
int mid = (from + to) / 2;
int diff = v[mid] - a;
if (diff == 0) return
mid;
/* v[mid] == a */
else if (diff <
0)
/*
v[mid]
<
a */
return
binary_search(v, mid + 1, to, a);
else
return
binary_search(v, from, mid - 1, a);
}
Търсене и
сортиране на реални данни
// esearch.cpp
int
binary_search(vector<Employee> v,
int from, int to, string n)
{
if (from > to) return -1;
int mid = (from + to) / 2;
if (v[mid].get_name() ==
n) return mid;
else if (v[mid].get_name()
< n)
return
binary_search(v, mid + 1, to, n);
else
return
binary_search(v, from, mid - 1, n);
}
** Структура от данни речник (dictionary): ключ-елемент
** Търсене в речник
** Хеш-функция h: key -> [0, N-1], пример h(x)
= x mod N