Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Технологии разработки объектно-ориентированных программ на язык C++. Основы структурного программирования на алгоритмическом языке C++

.pdf
Скачиваний:
4
Добавлен:
12.11.2023
Размер:
3.17 Mб
Скачать

Рис. 15.4. Пример префикс-функции

На примере видно, что после того, как префиксом стали первые три буквы строки, произошло совпадение первой и последней букв. Ввиду этого в префикс-функцию на данном этапе заносится 1. Когда взяли первые четыре буквы строки, совпадение произошло между двумя буквами, поэтому заносится цифра 2. В конце мы получаем префиксную функцию 00120123.

Далее префиксная функция используется следующим образом: начинаем сравнение подстроки и строки. Дана строка abaddababcaba. Первые три символа совпали с началом подстроки, но четвертый не совпал. Как только программа доходит до него, она смотрит, какая цифра записана в префикс-функции на предыдущем элементе. Записана 1, это означает, что следующая проверка начнется не с буквы b, а перескочит одну букву и следующая проверка начнется с буквы a.

Чем больше совпадений в подстроке, тем быстрее будет происходить поиск за счет таких скачков. Это позволяет сократить время поиска по сравнению с прямым поиском [2] (рис. 15.5).

Рис. 15.5. Алгоритм поиска Кнута, Морриса и Пратта

181

Код программы на C++:

int knut_morris_pratt (char *str, char *obr) { // На вход строка и подстрока

int sl, ssl;

 

int res = –1;

 

sl

= strlen(str);

// Считаем длину строки

ssl = strlen(obr);

// И длину подстроки

if

(sl == 0)

// Проверка, есть ли строка

и подстрока cout <<"Неверно задана строка\n";

else if (ssl == 0)

cout <<"Неверно задана подстрока\n"; else // Если все верно

int i, j = 0, k = –1; int *pf;

pf = new int[1000]; // Это префикс-функция pf[0] = –1;

while (j < ssl – 1) {

// Вычисляем префикс-функцию while (k >= 0 && obr[j] != obr[k])

k = pf[k]; // Считаем количество совпадений

j++;

k++;

if (obr[j] == obr[k])

// Заполняем префикс-функцию pf[j] = pf[k];

else

pf[j] = k;

}

// Префикс-функция посчитана, приступаем к поиску i = 0;

j = 0;

while (j < ssl && i < sl) {

// Идем по строке

while (j >= 0 && str[i] != obr[j])

182

// При несовпадении

j = pf[j]; // Получаем номер из префиксфункции

i++; // За счет этого сдвиг j++;

}

delete[] pf; // Поиск окончен, удаляем массив if (j == ssl) res = i – ssl;

// Если поиск успешен, заносим номер else res = –1;

}

return res; // Выводим результат

}

Алгоритм Бойера–Мура

Алгоритм Бойера–Мура (рис. 15.6) считается наиболее быстрым среди алгоритмов, предназначенных для поиска подстроки в строке. Преимущество этого алгоритма заключается в том, что необходимо сделать некоторые предварительные вычисления над подстрокой, чтобы сравнение подстроки с исходной строкой осуществлять не во всех позициях – часть проверок пропускают как заведомо не дающие результата.

Процедура алгоритма следующая:

Сначала строится таблица смещений для каждого символа. Затем исходная строка и шаблон совмещаются по началу, сравнение ведется по последнему символу. Если последние символы совпадают, то сравнение идет по предпоследнему символу и т.д. Если же символы не совпали, то шаблон смещается вправо на число позиций, взятое из таблицы смещений для символа из исходной строки, и тогда снова сравниваются последние символы исходной строки и шаблона. И так, пока шаблон полностью не совпадет с подстрокой исходной строки или не будет достигнут конец строки. Величина сдвига в случае несовпадения последнего символа вычисляется исходя из следующего: сдвиг подстроки должен быть минимальным, таким, чтобы не пропустить вхождение подстроки в строке. Если данный символ строки встречается в подстроке, то смещаем подстроку таким образом, чтобы символ строки совпал с самым правым вхождением этого символа

183

вподстроке. Если же подстрока вообще не содержит этого символа, то сдвигаем подстроку на величину, равную ее длине, так, что первый символ подстроки накладывается на следующий за проверявшимся символом строки.

Величина смещения для каждого символа подстроки зависит только от порядка символов в подстроке, поэтому смещения вычисляют заранее и хранят в виде одномерного массива, где каждому символу подстроки соответствует число смещения.

Построение таблицы смещения: строится таблица, содержащая все буквы подстроки. Для каждой неповторяющейся буквы число

втаблице будет равно длине подстроки минус 1, кроме последнего

вподстроке символа – его число равно длине всей подстроки. Если же символ повторяется, то ему присваивается 1. Если наблюдается, что повторение происходит лишь один раз или постоянно через определенное число символов, то присваивается длина этого промежутка [2].

Рис. 15.6. Алгоритм Бойера–Мура

Код программы на C++:

int boyer_moor (char *str, char *obr) { // На вход строка

и подстрока

 

int sl, ssl;

 

int res = –1;

 

sl = strlen(str);

// Считаем длину строки

ssl = strlen(obr);

// И длину подстроки

if (sl == 0)

// Проверка, есть ли строка и

 

подстрока

cout <<"Неверно задана строка\n";

184

else if

(ssl == 0)

 

cout <<"Неверно задана подстрока\n";

else

 

// Если все верно

int

i, Pos;

 

int

bmt[256];

// Массив смещения

for

(i = 0; i < 256; i++)

 

BMT[i] = ssl;

/* Сначала каждому присвоить

 

 

длину подстроки */

for

(i = ssl – 1; i >= 0; i--)

//Корректируем массив

//Для символов подстроки находим их ячейку в массиве

if (bmt[(short)(obr[i])] == ssl)

//И присваиваем необходимое значение

BMT[(short)(obr[i])] = ssl – i – 1; pos = ssl – 1;

while (pos < sl) // Пока строка не закончилась if (obr[ssl – 1] != str[Pos])

// Если символы не совпали

pos = pos + bmt[(short)(str[pos])];

// Сдвиг

else // Если совпали

for (i = ssl – 2; i >= 0; i--) { // Проверяем по очереди каждый // Как только не совпадут

if (obr[i] != str[pos – ssl + i + 1])

{

// Сдвиг

Pos += BMT[(short)(str[pos – ssl + i + + 1])] – 1;

break;

// И прервать текущую проверку

}

else

if (i == 0)

// Если поиск окончен return pos – ssl + 1;

// Вернуть позицию

185

cout <<"Позиция "<< i << endl;

}

}

return res;

}

15.5. Методы быстрой сортировки

Массивы часто используют для хранения и быстрого поиска данных в них. Но чтобы затрачивать на поиск наименьшее время, необходимо, чтобы массив был отсортирован.

Существует множество методов сортировки, но далее будут подробно рассмотрены три из них.

Сортировка методом прямого обмена (метод пузырька)

Такое название этот метод получил из-за аналогии с легкими пузырьками воды. При использовании этого метода наименьшие числа «всплывают» в один конец массива, а наибольшие опускаются в другой.

Алгоритм:

Текущий элемент массива сравнивается со следующим: если данный элемент больше следующего элемента, то они меняются местами.

Проверка повторяется, пока не закончатся элементы в мас-

сиве.

После прохождения всего массива максимальный элемент будет «вытолкнут», т.е. будет стоять самым последним. Таким образом, максимальный из всех элементов массива окажется на своем месте.

Данная операция повторяется столько раз, сколько есть элементов в массиве, минус 1. Последний элемент встанет на свое место автоматически, если остальные стоят уже на своих местах.

Дано: массив с элементами 7 5 1 4. Отсортировать его методом пузырька. Сравниваем элементы 7 и 5, элемент 7 больше, поэтому меняем их местами. Получаем массив 5 7 1 4. Теперь сравниваем элементы 7 и 1. Снова 7 больше, поэтому меняем их местами. Полу-

186

чаем 5 1 7 4. Сравниваем элементы 7 и 4. Меняем местами, так как 7 больше 4. После первого прохода получаем массив 5 1 4 7, причем самый большой элемент уже точно на своем месте. Поскольку элементов четыре, необходимо повторить алгоритм три раза.

Второй проход: сравниваем 5 и 1, так как 5 больше, то меняем их местами. Сравниваем 5 и 4, меняем местами. Так как известно, что последний элемент стоит на своем месте, дальше проверять нет смысла. Получаем массив 1 4 5 7.

Третий проход: сравниваем элементы 1 и 4, так как 1 меньше 4, то оставляем как есть. Далее сравнивать нет смысла, так как оставшиеся элементы на своих местах. Получаем отсортированный мас-

сив 1 4 5 7.

Реализуем этот алгоритм в программном коде:

#include <iostream> using namespace std; int main() {

setlocale(LC_ALL, "Russian"); // Для подключения

 

 

русского языка

int

size = 0;

// Это размерность массива

cout << "Введите размер массива " << endl;

cin

>> size;

 

// Пользователь вводит количество элементов int *mas = new int [size]; // Создаем массив cout << "Вводите элементы массива: " << endl; for (int i = 0; i < size; i++) {

// В цикле по очереди cin >> mas[i];

// Пользователь вводит элементы массива

}

cout << "Введенный массив: "; for (int i = 0; i < size; i++) {

//В цикле по очереди cout << mas[i] << " ";

//Вводим элементы массива через пробел

}

for (int i = 0; i < size – 1; i++) {

187

//Цикл сортировки по количеству проходов

//Далее цикл отдельно для каждого прохода for (int j = 0; j < (size – i) – 1; j++) {

/*

Не обязательно каждый раз доходить до конца строки, только до стоящих на своих местах чисел

*/

if (mass[j] > mas[j + 1]) { //Если первый больше второго

int a = mas[j]; // Меняем местами mas[j] = mas[j + 1];

mas[j + 1] = a; }

}

}

cout << endl; // Чтобы вывод шел с новой строки for (int i = 0; i < size; i++) {

// В цикле по очереди

cout << mas[i] << " "; } /* Вводим элементы массива через пробел */

system("pause");

// Пауза программы, чтобы посмотреть на консоль return 0;

}

На рис. 15.7 приведен пример сортировки методом пузырька.

Рис. 15.7. Результат сортировки методом пузырька

Сортировка методом прямого включения (метод вставки)

Это самый простой метод, но он эффективен только на коротких последовательностях или на последовательностях с некоторым количеством повторяющихся элементов.

188

Алгоритм:

Массив делится на две части: отсортированная и неотсортированная. На первом шаге в первую часть входит один элемент массива (первый).

Элементы из неотсортированной части выбираются поочередно и вставляются в отсортированную часть так, чтобы не нарушить ее порядок.

Алгоритм повторяется на один раз меньше, чем всего элементов в массиве (ибо первый элемент заранее отсортирован).

На каждом шаге элемент из неотсортированной части записываем в дополнительную ячейку (переменную). Затем сравниваем его с крайним правым элементом в отсортированной части. Если элемент отсортированной части больше сохраненного, то сдвигаем отсортированный элемент вправо, образуя пустую ячейку. Повторяем сравнение, пока сохраненный элемент не станет меньше текущего из отсортированной части. Тогда вставляем сохраненный элемент в пустую ячейку.

Реализуем этот алгоритм в программном коде:

#include <iostream> #include <ctime>

// Другая директива для генератора случайных чисел using namespace std;

int main() { setlocale(LC_ALL, "Rus");

cout << "Введите количество элементов массива: "; int size = 0;

do {

// Это пример проверки на правильность ввода cin >> size;

if (size == 0)

cout << "Пустая размерность, введите снова: "; if (size < 0)

cout << "Отрицательная размерность, введите снова: ";

} while (size <= 0);

int *mas = new int[n]; // Создание массива

189

srand(time(NULL)); // Это случайный выбор числа for (int i = 0; i < n; i++) {

// Случайное заполнение числами до 100 mas[i] = rand() % 100 + 1;}

for (int i = 0; i < size; i++) // Вывод массива на экран cout << mas[i] << " ";

for (int i = 1; i < size; i++) {

// Сортировка начиная со второго элемента (его номер 1) int y = mas[i]; // Сохраняем элемент

for (int j = i-1; j >= 0; j--) {

// В отсортированной части ищем для него место

if (mas[j] > y) { // Сдвигаем элементы, ес-

ли необходимо

mas[j + 1] = mas[j]; mas[j] = y; }

}

}

cout << endl; // Переходим на следующую строку

for (int i = 0; i < size; i++) // Вывод отсортированного

массива

cout << mas[i] << " "; system("pause");

return 0;

}

На рис. 15.8 приведен пример сортировки методом вставки.

Рис. 15.8. Результат сортировки методом вставки

Сортировка методом выбора

Этот метод легче всего реализовать в программном коде, но он требует самого большого количества времени на выполнение.

Анализ:

В массиве находим элемент с минимальным значением и меняем его местами с первым.

190

Соседние файлы в папке книги