search

Линейный поиск

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


#include <iostream>
using namespace std;
int LinearSearch (int array[], int size, int key){
 for(int i=0;i < size;i++)
 if(array[i] == key)
 return i;
 return -1;
}
void main()
{
 const int arraySize=100;
 int a[arraySize], searchKey, element;
 for(int x=0;x < arraySize;x++)
 a[x]=2*x;
 // Следующая строка выводит на экран сообщение
 // Введите ключ поиска:
 cout<<"Please, enter the key: ";
 cin>>searchKey;
 element=LinearSearch(a, arraySize, searchKey);
 if(element!=-1)
 // Следующая строка выводит на экран сообщение
 // Найдено значение в элементе
 cout<<"\nThe key was found in element "<<
 element<<'\n';
 // Следующая строка выводит на экран сообщение
 // Значение не найдено
 else
 cout<<"\nValue not found ";
}

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

Двоичный поиск

Предположим, что переменные Lb и Ub содержат, соответственно, левую и правую границы отрезка массива, где находится нужный нам элемент. Поиск мы всегда будем начинать с анализа среднего элемента отрезка массива. Если искомое значение меньше среднего элемента, мы переходим к поиску в верхней половине отрезка, где все элементы меньше только что проверенного. Другими словами, значением Ub становится (M (средний элемент) –1) и на следующей итерации мы работаем с половиной массива. Таким образом, в результате каждой проверки мы вдвое сужаем область поиска. Так, в нашем примере, после первой итерации область поиска — всего лишь три элемента, после второй остается всего лишь один элемент. Таким образом, если длина массива равна 6, нам достаточно трех итераций, чтобы найти нужное число.


#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

int BinarySearch (int A[], int Lb, int Ub, int Key)
{
 int M;
 while(1){
 M = (Lb + Ub)/2;
 if (Key < A[M])
 Ub = M - 1;
 else if (Key > A[M])
 Lb = M + 1;
 else
 return M;
 if (Lb > Ub)
 return -1;
 }
}
void main(){
 srand(time(NULL));
 const long SIZE=10;
 int ar[SIZE];
 int key,ind;
 // до сортировки
 for(int i=0;i < SIZE;i++){
 ar[i]=rand()%100;
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
 cout<<"Enter any digit:";
 cin>>key;
 ind=BinarySearch(ar,0,SIZE,key);
 cout<<"Index - "<< ind<<"\t";
 cout<<"\n\n";
}

Двоичный поиск — очень мощный метод. Посудите сами: например, длина массива равна 1023, после первого сравнения область сужается до 11 элементов, а после второй — до 255. Легко посчитать, что для поиска в массиве из 1023 элементов достаточно 10 сравнений.

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

Идея данного метода состоит в том, чтобы создавать отсортированную последовательность путем присоединения к ней одного элемента за другим в правильном порядке. Сейчас, мы с вами попробуем построить готовую последовательность, начиная с левого конца массива. Алгоритм состоит из n последовательных шагов, начиная от нулевого и заканчивая (n–1). На i-м шаге выбираем наименьший из элементов a[i] ... a[n] и меняем его местами с a[i]. Последовательность шагов при n=5 изображена на рисунке ниже.


Sort

Вне зависимости от номера текущего шага i, последовательность a[0]...a[i] является упорядоченной. Таким образом, на шаге (n-1) вся последовательность, кроме a[n] оказывается отсортированной, а a[n] стоит на последнем месте по праву: все меньшие элементы уже ушли влево. Рассмотрим пример, реализующий данный метод:


#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

template <class T> // Шаблоны смотрите в меню
void selectSort(T a[], long size) {
 long i, j, k;
 T x;
 for(i = 0; i < size; i++) { // i - номер текущего шага
 k=i;
 x=a[i];
 // цикл выбора наименьшего элемента
 for(j = i+1; j < size; j++)
 if(a[j] < x){
 k=j;
 x=a[j];
 // k - индекс наименьшего элемента
 }
 a[k]=a[i];
 a[i]=x; // меняем местами наименьший с a[i]
 }
}
void main(){
 srand(time(NULL));
 const long SIZE=10;
 int ar[SIZE];
 // до сортировки
 for(int i=0;i < SIZE;i++){
 ar[i]=rand()%100;
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
 selectSort(ar,SIZE);
 // после сортировки
 for(int i=0;i < SIZE;i++){
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
}

Для нахождения наименьшего элемента из n+1 рассматриваемый алгоритм совершает n сравнений. Таким образом, так как число обменов всегда будет меньше числа сравнений, время сортировки возрастает относительно количества элементов.

Алгоритм не использует дополнительной памяти: все операции происходят «на месте».

Пузырьковая сортировка

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


Sort

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


Sort

Пример:


#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

template <class T> // Шаблоны смотрите в меню
void bubbleSort(T a[], long size){
 long i, j;
 T x;
 for(i=0;i<size;i++){ // i - номер прохода
 for(j=size-1;j>i;j--){ // внутренний цикл
 // прохода
 if(a[j-1]>a[j]){
 x=a[j-1];
 a[j-1]=a[j];
 a[j]=x;
 }
 }
 }
}
void main(){
 srand(time(NULL));
 const long SIZE=10;
 int ar[SIZE];

 // до сортировки

 for(int i=0; i < SIZE; i++){
 ar[i]=rand()%100;
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
 bubbleSort(ar,SIZE);
 // после сортировки
 for(int i=0; i < SIZE; i++){
 cout << ar[i] << "\t";
 }
 cout<<"\n\n";
}

Среднее число сравнений и обменов имеют квадратичный порядок роста, отсюда можно заключить, что алгоритм пузырька очень медлителен и малоэффективен. Тем не менее, у него есть громадный плюс: он прост и его можно по-всякому улучшать.

Сортировка вставками

Сортировка простыми вставками в чем-то похожа на методы изложенные в предыдущих разделах урока. Аналогичным образом делаются проходы по части массива, и аналогичным же образом в его начале «вырастает» отсортированная последовательность.

Однако в сортировке пузырьком или выбором можно было четко заявить, что на i-м шаге элементы a[0]...a[i] стоят на правильных местах и никуда более не переместятся. Здесь же подобное утверждение будет более слабым: последовательность a[0]...a[i] упорядочена. При этом по ходу алгоритма в нее будут вставляться все новые элементы.

Будем разбирать алгоритм, рассматривая его действия на i-м шаге. Как говорилось выше, последовательность к этому моменту разделена на две части: готовую a[0]...a[i] и неупорядоченную a[i+1]...a[n]. На следующем, (i+1)-м каждом шаге алгоритма берем a[i+1] и вставляем на нужное место в готовую часть массива. Поиск подходящего места для очередного элемента входной последовательности осуществляется путем последовательных сравнений с элементом, стоящим перед ним. В зависимости от результата сравнения элемент либо остается на текущем месте(вставка завершена), либо они меняются местами и процесс повторяется.


Sort

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


#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

template <class T> // Шаблоны смотрите в меню
void insertSort(T a[], long size) {
 T x;
 long i, j;
 // цикл проходов, i - номер прохода
 for(i=0;i<size;i++){
 x=a[i];

 // поиск места элемента в готовой
 // последовательности
 for(j=i-1;j>=0&&a[j]>x;j--)
 // сдвигаем элемент направо,
 //пока не дошли
 a[j+1]=a[j];
 // место найдено, вставить элемент
 a[j+1] = x;
 }
}
void main(){
 srand(time(NULL));
 const long SIZE=10;
 int ar[SIZE];
 // до сортировки
 for(int i=0; i < SIZE; i++){
 ar[i]=rand()%100;
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
 shakerSort(ar,SIZE);
 // после сортировки
 for(int i=0; i < SIZE; i++){
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
}

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

Быстрая сортировка

Быстрая сортировка — была разработана около 40 лет назад и является наиболее широко применяемым и в принципе самым эффективным алгоритмом. Метод основан на разделении массива на части. Общая схема такова:

1. Из массива выбирается некоторый опорный элемент a[i].
2. Запускается функция разделения массива, которая перемещает все ключи, меньшие, либо равные a[i], слева от него, а все ключи, большие, либо равные a[i] — справа, теперь массив состоит из двух частей, причем элементы левой меньше элементов правой.
3. Если в подмассиве более двух элементов, рекурсивно запускаем для них ту же функцию.
4. В конце получится полностью отсортированная последовательность.
Рассмотрим алгоритм более детально.

Делим массив пополам

Входные данные: массив a[0]...a[N] и элемент p, по которому будет производиться разделение.

Введем два указателя: i и j. В начале алгоритма они указывают, соответственно, на левый и правый конец последовательности.

Будем двигать указатель i с шагом в 1 элемент по направлению к концу массива, пока не будет найден элемент a[i] >= p.

Затем аналогичным образом начнем двигать указатель j от конца массива к началу, пока не будет найден a[j] <= p.

Далее, если i <= j, меняем a[i] и a[j] местами и продолжаем двигать i, j по тем же правилам.

Повторяем шаг 3, пока i <= j.

Рассмотрим рисунок, где опорный элемент p = a[3].


Sort

Массив разделился на две части: все элементы левой меньше либо равны p, все элементы правой — больше, либо равны p.

Пример программы:


#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;

template <class T> // Шаблоны смотрите в меню
void quickSortR(T a[], long N) {
 // На входе - массив a[], a[N] - его последний элемент.
 // поставить указатели на исходные места
 long i = 0, j = N;
 T temp, p;

 p = a[ N/2 ]; // центральный элемент
 // процедура разделения
 do {
 while ( a[i] < p ) i++;
 while ( a[j] > p ) j--;
 if (i <= j){
 temp = a[i];
 a[i] = a[j];
 a[j] = temp;
 i++;
 j--;
 }
 }
 while ( i<=j );
 // рекурсивные вызовы, если есть, что сортировать
 if ( j > 0 ) quickSortR(a, j);
 if ( N > i ) quickSortR(a+i, N-i);
}
void main(){
 srand(time(NULL));
 const long SIZE=10;
 int ar[SIZE];

 // до сортировки
 for(int i=0; i < SIZE; i++){
 ar[i]=rand()%100;
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
 quickSortR(ar,SIZE-1);
 // после сортировки
 for(int i=0; i < SIZE; i++){
 
 cout<< ar[i]<<"\t";
 }
 cout<<"\n\n";
}

Алгоритм рекурсии
1. Выбрать опорный элемент p — середину массива.
2. Разделить массив по этому элементу.
3. Если подмассив слева от p содержит более одного элемента, вызвать quickSortR для него.
4. Если подмассив справа от p содержит более одного элемента, вызвать quickSortR для него.