Поиск и сортировка

advertisement
LOGO
Поиск и сортировка
10 класс, углублённый уровень
по учебнику Калинина И.А. и Самылкиной Н.Н.
Задача поиска
Крупная задача = алгоритм 1+
алгоритм 2+ … + алгоритм n
Каждый алгоритм решает
определённую подзадачу.
Задача поиска: в наборе элементов a1,
a2, …, an требуется найти элементы,
соответствующие некоторому условию
(например, ai=b), или выяснить, что
таких элементов нет.
Прямой поиск
Перебираются все элементы
последовательно.
Если элемент удовлетворяет условию,
то элемент искомый найден.
Если дошли до конца, но
переобозначений не делали, такого
элемента нет.
Сложность алгоритма: О(n) – в
плохом случае, в лучшем – О(1),
средний случай – О(n/2).
Минусы прямого поиска:
За один проход ищется один элемент.
Для поиска m элементов в n сложность
алгоритма О(nm).
Постоянно занята оперативная память
или неудобно постоянно обращаться к
внешнему устройству.
Задача: в массиве из десяти случайных
целых чисел найдите:
А) 5
Б) 5, 7, -3
Метод половинного деления
Когда последовательность
отсортирована, то поиск производится
быстрей.
Общая идея метода: отрезок делится
на две половины. Искомый элемент
сравнивают с «серединой» и выбирают
часть, в которой продолжается поиск.
Метод половинного деления
 Найдём число b и определим его место в массиве
(индекс k):
left = 1
right = n
While (left < right)
k = left + (right – left) div 2
if (a[k] > b)
left = k + 1
else
if (b <> a[k])
right = k – 1
else
break
Задание: постройте блок-схему
алгоритма.
Важно! Мы делаем середину либо левой, либо правой границей следующего
отрезка.
Сложность алгоритма: каждый шаг отбрасывает половину имеющихся
вариантов, поэтому для a циклов сложность составит n=2a, т.е. a=log2n.
Следовательно, О (log2n).
Хэширование
 Рассмотрим хэшированные таблицы (от англ. hash –
мешанина, мелкая нарезка).
 Основная идея этой группы алгоритмов состоит в вычислении
некоторого характеристического значения, которое будет
удовлетворять следующим требованиям:
1. легко вычисляться,
2. иметь существенно меньшее количество значений, чем ключи
поиска,
3. большинство встречающихся в задаче ключей поиска будут
иметь примерно равно распределённые значения хэшфункции.
Коллизия – ситуация, когда разные ключи имеют одно и то же
значение функции.
Хэширование
Допустим, функция есть, тогда:
1. Строится таблица (чаще - массив) соответствий
значений функции и ключа поиска. Можно строить
постепенно (дополнять).
2. При получении значения для поиска, вычисляется
хэш и проверяется соответствующая строка
таблицы. Если искомый элемент есть, то строка
обязательно присутствует.
3. Если хэш ранее не встречался, его заносят в
таблицу (процесс достраивания).
Хэш-функции
 Выбор зависит от входных данных.
 Для символьных данных лучше, когда выполняется накопление
к хэшу символов как однобайтовых натуральных чисел, с
домножением предыдущего результата на число 31 или 37.
 Количество значений будет зависеть от длины массива. Обычно
длину выбирают как простое число.
 Для символьных данных хэш-функция даёт 256 значений.
Пример:
function hashBKDR(S)
h=0
for i = 1 to length(s)
h = h*31 + s[i]
return h div NumHashArray
Хэш-функции
Хэш-функции
Работа с
элементом
Возможны коллизии.
Поиск адреса
элемента
Позволяет
коллизий.
избежать
Во втором случае таблица может быть больше, чем количество
элементов в наборе данных. Однако, использование подобной
функции позволяет находить значение за О(1) шагов.
Сортировка
 Сортировка (англ. sorting — классификация,
упорядочение) – последовательное расположение
элементов (по убыванию или по невозрастанию и
т.п.). Будем рассматривать по неубыванию.
 На общий вид алгоритма конкретный вид сортировки
не влияет.
 Замена знака сравнения = инверсия прохода
массива.
 Выбор вида зависит от ряда критериев, включая
возможность выполнения тех или иных операций на
магнитной ленте (вспомните машину Тьюринга).
Виды сортировок: пузырьковым методом, методом вставок,
сортировка выбором, сортировка Quicksort, Heapsort, сортировка
перечислением.
«Алгоритм пузырька»
 Он же – сортировка простыми обменами.
 Все элементы находятся в оперативной памяти (внутренняя
сортировка), не потребуется постоянно растущий объём памяти.
 В обратную сторону этот вид иногда называют «методом камня».
Общая идея: элементы сравниваются попарно и меняются местами,
если они не удовлетворяют условиям сортировки. Наверх «всплывает»
первое наибольшее значение, далее выполняем для n-1 элемента и т.д.,
пока не закончится одна пара.
for i=1 to n–1
for j=1 to n–i
if (a[j] > a[j+1])
b = a[j]
a[j] = a[j+1]
a[j+1] = b
Дополнительно:
http://www.youtub
e.com/watch?v=N
s4TPTC8whw
Сложность алгоритма: пропорциональна квадрату количества
элементов, т.е. О(n2).
«Метод пузырька» с флагом
lim = n–1
do
k=0
for i = 1 to lim
if (a[i] > a[i+1])
b = a[i]
a[i] = a[i+1]
a[i+1] = b
k=i
lim = k–1
while (k <> 0)
Сложность алгоритма: один проход теперь имеет сложность О(n), однако
средний показатель и сложность всего алгоритма не изменятся.
Быстрая сортировка (Quick Sort)
 Общий принцип: массив разбивается на две части, что позволяет
уменьшить количество повторов. Каждый раз массив разбивается
снова на две части (не обязательно равных) и выполняется обмен
(пока не упрёмся в границу), после которого все элементы в левой
части меньше элементов в правой. Потом сортируем внутри левой и
правой части.
Partion (A,len,r)
x = A[l+(r–len) div 2]
i = len–1
j = r+1
do
do
j = j–1
while (A[j] > x)
do
i=i+1
while (A[i] < x)
if (i < j)
b = a[i]
a[i] = a[j]
a[j] = b
while (i <= j)
return j
Вид процедуры:
QuickSort(A,l,r)
If (r < l)
q = Partion(A,l,r)
QuickSort(a,l,q)
QuickSort(a,q+1,r)
Первый вызов:
QuickSort(A,1,length(A))
Быстрая сортировка (Quick Sort)
 Видео: https://youtu.be/ywWBy6J5gz8
Максимально возможная оценка
времени: О(n2). Но таких
последовательностей очень мало,
поэтому чаще имеет среднюю оценку –
O(n logn).
1. В таком виде алгоритм неприменим для больших массивов, т.к.
рекурсивный вызов функции требует выделения памяти в стеке вызовов
для большого количества переменных, а он (стек) конечен. На самом деле
алгоритм требует еще O(log n) памяти.
2. Разбиение массива выполнено простым, но не самым быстрым
способом.
3. Для некоторых массивов (коротких или уже отсортированных) этот
метод сортировки менее эффективен, чем другие.
Практическая часть
 1. С клавиатуры вводят слова (не менее 3 и не более 15 букв).
Предложите способ сортировки, который сразу после окончания
ввода выдаст слова в алфавитном порядке.
 2. Из файла читают слова, которых значительно больше, чем
есть в наличии памяти. Но многие слова повторяются.
Предложите метод, который позволит сформировать файл со
всеми словами в алфавитном порядке.
Свой выбор обоснуйте!
Практическая часть
 3. В файле перечислены слова и названия документов, в
которых они встречаются (в формате – слово: название 1,
название 2 и т.д.). Напишите программу, которая будет
максимально быстро выдавать список документов по
введённому слову.
 Примечания: текстовый файл назовите dictionary.txt и
заполните приведённым ниже контентом.
информатика: учебник.pdf
математика:учебник1.doc,учебник2.doc,задачник.doc
Код для задания 3
program hashedSearch;
const
NumHashLen = 577;
//Возьмём тот же список, который изучали на предудущем занятии:
type
pHashNode = ^HashNode;
HashNode = record
val: string[40];
docList: string;
next: ^HashNode;
end;
function hashBKDR(S: string): word;//Хэш-функция
var
i: byte;
h: uint64;
begin
h := 0;
for i := 1 to length(s) do
h := h * 61 + ord(s[i]);// где 61 - ближайшее простое
hashBKDR := h mod NumHashLen;
end;
//Добавление записи
function addNode(wrd: string; head: pHashNode; list: string ): pHashNode;
begin
new(result);
Result^.val := wrd;
Result^.docList := list;
Result^.next := head;
end;
function searchword(wrd: string; head: pHashNode): pHashNode;
begin
result := head;
while (result <> nil) and (result^.val <> wrd) do
result := result^.next;
end;
var
SearchArray: array[0..NumHashLen]
of pHashNode;
txt: PABCSystem.Text;
hs: word;
n: pHashNode;
s, wrd: string;
begin
for hs := 0 to NumHashLen do // Инициализация пустого массива
SearchArray[hs] := nil;
Assign(txt, 'dictionary.txt');
Reset(txt);
while not Eof(txt) do
begin
ReadLn(txt, s);// Читаем по одному слову
if length(s) > 1 then
begin
wrd := s.Split(':')[0];
hs := hashBKDR(wrd); // Вычисляем хэш
SearchArray[hs] := addNode(wrd,
SearchArray[hs], s.Split(':')[1] );
end;
end;
close(txt);
repeat
write('Введите слово для поиска: ');
readln(wrd);
if length(wrd) > 0 then
begin
hs := hashBKDR(wrd); // Вычисляем хэш
n := searchword(wrd, SearchArray[hs]);
if n <> nil then
writeln('Список документов: ', n^.docList)
else
if wrd<>'отмена' then
writeln('Такого слова в списке нет')
else
writeln('Поиск завершён.');
end;
until wrd = 'отмена';
end.
Практическая часть
 4*. Усовершенствуйте программу так, чтобы она могла выдать
список документов, в которых встречается несколько слов.
//Ошибка стека, из-за переполнения. Добавляет в список пустые значения (судя по выводу ассемблера).
program hashedSearchWords;
const
NumHashLen = 577;
type
pHashNode = ^HashNode;
HashNode = record
val: string;
docList: string[40];
next: ^HashNode;
end;
pWordNode = ^WordNode;
WordNode = record
vl: string;
next: ^WordNode;
mark: boolean;
end;
iterate = function(item: pWordNode): pWordNode;
function hashBKDR(S: string): word;// Хэш-функция
var
i: byte;
h: uint64;
begin
h := 0;
for i := 1 to length(s) do
h := h * 61 + ord(s[i]);
hashBKDR := h mod NumHashLen;
end;
function addNode(wrd: string; head: pHashNode; list: string ): pHashNode;
begin
new(result);
Result^.val := wrd;
Result^.docList := list;
Result^.next := head;
end;
function searchword(wrd: string; head: pHashNode): pHashNode;
begin
result := head;
while (result <> nil) and (result^.val <> wrd) do
result := result^.next;
end;
function iterateWordList(start: pWordNode; iter: iterate): pWordNode;
var
current: pWordNode;
begin
current := start;
while current <> nil do
current := iter(current);
end;
function doPrint(nod: pWordNode): pWordNode;
begin
writeln(nod^.vl );
result := nod^.next;
end;
function doMarcFalse(nod: pWordNode): pWordNode;
begin
nod^.mark := false;
result := nod^.next;
end;
function Intersection(head: pWordNode;docs: string ): pWordNode; //Пересечение списков документов
var
current, prev: pWordNode;
wrd: string;
begin
iterateWordList(head, doMarcFalse);
foreach wrd in docs.Split(',') do //Помечаем все документы, которые есть в списке
begin
current := head;
while current <> nil do
begin
if current^.vl = wrd then
current^.mark := true;
current := current^.next;
end;
end;
current := head;
prev := nil;
while current <> nil do
begin
if not current^.mark then //Удаляем документы, которых не оказалось в новом списке
begin
if prev <> nil then
begin
prev^.next := current^.next;
Dispose(current);
current := prev^.next;
end
else
begin
prev := current^.next;
Dispose(current);
current := prev;
result := current;
prev := nil;
end;
end
else
begin
prev := current;
current := current^.next;
end;
end;
end;
function SplitList(docs: string): pWordNode;
var
wrd: string;
current: pWordNode;
begin
foreach wrd in docs.Split(',') do
begin
if result = nil then
begin
new(current);
result := current
end
else
begin
new(current^.next);
current := current^.next;
end;
current^.next := nil;
current^.vl := wrd;
end;
end;
var
SearchArray: array[0..NumHashLen]
of pHashNode;
txt: PABCSystem.Text;
hs: word;
n: pHashNode;
s, wrd: string;
resSearch: pWordNode;
begin
for hs := 0 to NumHashLen do //Инициализация пустого массива
SearchArray[hs] := nil;
Assign(txt, 'dictionary.txt');
Reset(txt);
while not Eof(txt) do
begin
ReadLn(txt, s);// Читаем по одному слову
if length(s) > 1 then
begin
wrd := s.Split(':')[0];
hs := hashBKDR(wrd); // Вычисляем хэш
SearchArray[hs] := addNode(wrd,
SearchArray[hs], s.Split(':')[1]);
end;
end;
close(txt);
repeat
writeln('Введите слова для поиска:');readln(s);
if length(s) > 0 then
begin
foreach wrd in s.Split(' ') do
begin
hs := hashBKDR(wrd); // Вычисляем хэш
n := searchword(wrd, SearchArray[hs]);
if n <> nil then
begin
if resSearch <> nil then resSearch := Intersection(resSearch, n^.docList)
else
resSearch := SplitList(n^.docList);
end
else
break;
end;
if resSearch <> nil then
iterateWordList(resSearch, doPrint)
else
WriteLn('Извините, таких документов в списке нет.');
end;
until wrd = 'отмена';
end.
Download