Сортировка слиянием ( merge sort)
Ещё один сравнительно быстрый и рекурсивный алгоритм сортировки наряду с быстрой сортировкой, которую мы выдали ещё в 1-м разделе (или также quick sort, сортировка Хоара)
Сортировка слияния похожа на быструю сортировку. Здесь массив делится на две примерно равные половины, каждая половина сортируется этой же функцией (происходит рекурсия), а потом отсортированные массивы соединяются.
Если сравнивать с быстрой сортировкой, то там тоже происходит рекурсивное деление, но принцип сортировки другой и больше похож на пузырьковый. Только в первую очередь происходят перестановки элементов на наибольшем возможном расстоянии.
Что у быстрой сортировки, то и у сортировки слиянием- скорость алгоритма O(n*log(n)), как и у пирамидальной.
Теперь сам код сортировки слиянием- тут представлен метод нисходящего слияния.
void mergesort(int a[], int l, int r) {
if (l == r) return;
int mid = (l+r) / 2;
mergesort(a, l, mid);
mergesort(a, mid + 1, r);
int i = l;
int j = mid + 1;
int *tmp = new int [r];
for (int step = 0; step < r - l + 1; step++) {
if ((j > r) || ((i <= mid) && (a[i] < a[j]))) { tmp[step] = a[i]; i++; }
else { tmp[step] = a[j]; j++; }
}
for (int step = 0; step < r - l + 1; step++) a[l + step] = tmp[step];
}
Используется сортировка массива из 11 элементов так: mergeSort(mass,0,10); т.е. указывается левый и правый элемент. Это было нужно для рекурсии. Можно сделать перегрузку чтобы можно было указывать как обычно- добавить ниже ещё 1 доп.функцию:
void mergesort(int a[], int size) {
mergesort(a,0,size-1);
}
И теперь можно использовать как обычно: mergesort(mass,11);
|
|
Ниже другой код сортировки слиянием, он организован без рекурсии. Этот метод называется методом восходящего слияния.
void mergesort(int a[], int size) {
int step = 1; int *temp= new int [size];
while (step<size) {
int index= 0; int l= 0; int m= l+step; int r= l+step*2;
do {
m= m<size ? m : size;
r= r<size ? r : size;
int i1= l, i2= m;
for (; i1 < m && i2 < r; ) {
if (a[i1]<a[i2]) temp[index++]= a[i1++];
else temp[index++]= a[i2++];
}
while (i1 < m) temp[index++]= a[i1++];
while (i2 < r) temp[index++]= a[i2++];
l+= step*2; m+= step*2; r+= step*2;
} while (l<size);
for (int i = 0; i < size; i++) a[i]= temp[i];
step*= 2;
}
}
Глупая сортировка (stupid sort, случайная сортировка)
Особенность этой сортировки в том, что она самая медленная и неразумная из всех, которые только можно было придумать. На практике она не используется и нужна лишь для теории и сравнения с более эффективными практичными алгоритмами. Скорость такой сортировки O(n*n!)
Суть в том, что массив перемешивается случайным образом. И если обнаружилось, что массив отсортированный- заканчиваем сортировку.
Ниже пример такого алгоритма:
// проверка на отсортированность
bool correct (int arr[], int size) {
while (--size > 0) if (arr[size-1] > arr[size]) return false;
return true;
}
// перемешивание
void shuffle (int arr[], int size) {
for (int i=0; i<size; i++) swap(arr[i], arr[rand()%size]);
}
// сама функция сортировки
|
|
void stupidsort (int arr[], int size) {
while (!correct(arr, size)) shuffle(arr, size);
}
Этой функцией можно сортировать массив до 10 элементов. С большими размерами алгоритм уже не справляется, т.к. приходится перебирать всё больше вариантов: время работы растёт по факториальному закону с домножением: 1, 4, 18, 96, 600, 4320 и т.д. Например, мой компьютер сортировал такой функцией массив из 10 целых чисел в пределах значений от 0 до 999 несколько секунд.
Двоичный поиск элемента в массиве
Бывает что нам нужно найти какой-то элемент в массиве или проверить его наличие. Мы уже это делали, но только через алгоритм линейного поиска. Линейный поиск значит что мы просто по очереди перебираем варианты и смотрим- он это или не он. Ниже функции, которые работают через линейный поиск и позволяют нам найти элемент в массиве:
// есть ли элемент в массиве
template <typename Type>
bool isInArray (Type m[], int size, Type checker) {
bool res= false;
for(int i=0; i<size; i++) {
if (m[i]==checker) res= true;
}
return res;
}
// поиск позиции элемента в массиве
template <typename Type>
int PozitionInArray (Type m[], int size, Type checker) {
if(!isInArray(m,size,checker)) return -1;
else {
int poz;
for(int i=0; i<size; i++) {
|
|
if (m[i]==checker) poz= i;
}
return poz;
}
}
// количество таких элементов в массиве
template <typename Type>
int QuantityInArray (Type m[], int size, Type checker) {
int num= 0;
for(int i=0; i<size; i++) {
if(m[i]==checker) num++;
}
return num;
}
Но загвоздка в том, что есть более эффективный способ поиска- двоичный поиск. В нём при каждой итерации область поиска сужается в 2 раза, поэтому такой поиск быстрее. Т.е. условно нужно не N итераций, а log(N). Только важный нюанс: двоичный поиск можно применять только в отсортированном массиве, поэтому сначала массив нужно отсортировать- это можно сделать быстрой сортировкой qsort или любой другой.
Рассмотрим принцип двоичного поиска на примере. Пусть у нас есть массив из 6 элементов. Будем рассматривать уже отсортированный по возрастанию массив.
(2) (3) (5) (7) (9) (15)
В функции для линейного поиска в качестве аргументов мы вбиваем массив, размер и элемент- функция выводит позицию этого элемента в массиве. Если этого элемента нет, то выводит -1. Нам нужно написать аналогичную функцию, но для двоичного поиска.
|
|
Пусть мы хотим найти 15. Глянем в середину, например (7). Т.к. 15 > 7, то идём вправо. Теперь ищем середину там. И так далее.
Ниже представлен алгоритм бинарного поиска элемента в массиве:
int FindPozition (int m[], int size, int x) {
int left= 0, right= size, mid= (left+right)/2;
while ( (m[mid]!=x) && (right-left>1) ) {
if (m[mid]<x) left= mid;
else right= mid;
mid= (left+right)/2;
}
return mid;
}
Данная функция ищет позицию элемента в отсортированном по возрастанию массиве. Но только если этот элемент есть.
Можно протестировать:
const int n= 10;
int m[n]= {0,1,3,5,6,6,7,7,9,15};
int x; cin >> x;
cout << FindPozition(m,n,x) << endl;
Как можно усовершенствовать программу? Ну во-первых пусть в этой функции он сначала и сортирует массив, используем для этого сортировку скоростью O(n*log(n))- пирамидальную, быструю или слиянием. Я буду использовать быструю сортировку, как и раньше:
template <typename Type>
void qsort(Type m[], int size) {
Type terek = 0;
int left= 0, right= 0;
if(size>1) {
Type *Left = new Type[size];
Type *Right = new Type[size];
terek = m[size/2];
for(int i=0;i<size;i++) {
if (i!=size/2) {
if (m[i]<terek) {
Left[left] = m[i]; left++;
}
else {
Right[right] = m[i]; right++;
}
}
}
qsort(Left,left);
qsort(Right,right);
for(int I=0;I<size;I++){
if (I<left) {
m[I]= Left[I];;
}
else if (I==left) {
m[I]= terek;
}
else {
m[I]= Right[I-(left+1)];
}
}
}
}
template <typename Type>
int FindPozition (Type m[], int size, Type x) {
qsort(m,size);
int left= 0, right= size, mid= (left+right)/2;
while ( (m[mid]!=x) && (right-left>1) ) {
if (m[mid]<x) left= mid;
else right= mid;
mid= (left+right)/2;
}
return mid;
}
Только программа всё равно покажет позицию в уже отсортированном массиве. Но зато теперь мы можем загонять и неотсортированный тоже. Т.е. таким образом мы находим номер элемента, начиная от самого минимального. Минимальный будет нулевым, максимальный- последним (size-1). При этом есть недостаток, что скорость работы замедляется, т.к. если массив уже отсортирован, всё равно придётся это проверить функцией qsort. Можно исправить это новым аргументом:
template <typename Type>
int FindPozition (Type m[], int size, Type x, string s="sorted") {
if(s=="not_sorted") qsort(m,size);
int left= 0, right= size, mid= (left+right)/2;
while ( (m[mid]!=x) && (right-left>1) ) {
if (m[mid]<x) left= mid;
else right= mid;
mid= (left+right)/2;
}
return mid;
}
При этом мы при вызове функции можем не писать строковый аргумент, если по умолчанию у нас массив отсортирован. Чтобы не использовать строки (string), можно использовать кодовый символ (тип char) или вообще булевое значение. Ниже пример с булевым аргументом:
template <typename Type>
int FindPozition (Type m[], int size, Type x, bool sorted= true) {
if(!sorted) qsort(m,size);
int left= 0, right= size, mid= (left+right)/2;
while ( (m[mid]!=x) && (right-left>1) ) {
if (m[mid]<x) left= mid;
else right= mid;
mid= (left+right)/2;
}
return mid;
}
Также я добавил шаблон типа данных в функцию поиска.
Теперь сделаем чтобы если элемент не найден, то выводилось бы -1, как и в линейном поиске. Тогда придётся добавить проверку сначала на наличие элемента в массиве вообще.
Заметим, что если мы просим найти элемента, которого в массиве нет, нам всегда выводится последняя позиция, т.е. size-1. Либо первая, т.е. 0. Используем это в условии и получим новую функцию с добавленным условием:
template <typename Type>
int FindPozition (Type m[], int size, Type x, bool sorted= true) {
if(!sorted) qsort(m,size);
int left= 0, right= size, mid= (left+right)/2;
while ( (m[mid]!=x) && (right-left>1) ) {
if (m[mid]<x) left= mid;
else right= mid;
mid= (left+right)/2;
}
if ( ((mid==size-1)&&(m[size-1]!=x)) || ((mid==0)&&(m[0]!=x)) ) return -1;
else return mid;
}
Алгоритм Хаффмана
Здесь мы рассмотрим самый попсовый принцип архивации и разархивации информации, он реализуется благодаря алгоритму Хаффмана.
Например, у нас есть какой-то текст, и мы хотим чтобы он занимал меньше памяти. Как известно, один символ у нас кодируется определённым числом бит. Например, если это кодировка ASCII, то на 1 символ нужен 8 бит. Но каждый символ используется в тексте с разной частотой. Что если мы самые частые символы будем копировать маленьким количеством бит, а самые редкие- большим. За счёт этого принципа на исходный текст нужно в сумме меньшее количество битов. Этот алгоритм называет алгоритмом Хаффмана.
Рассмотрим упрощённый случай. У нас есть текст, где всего 5 символов: а, б, в, г, д. И с помощью них мы шифруем какое-то послание. Сначала нужно сосчитать, сколько раз используется каждый символ в тексте: пусть (а) используется 12 раз, (б) 7 раз, (в) 5 раз, (г) 2 раза, (д) 6 раз.
Запишем это друг под дружкой:
а б в г д
12 7 5 2 6
Теперь составим двоичное дерево. Цифры частот будут листами дерева. Мы берём 2 самых маленьких упоминания: в нашем случае это в(5) и г(2). И от них ведём 2 линии и составляем новый узел с суммарной частотой: 5+2=7. Одной чёрточке присваиваем 0, другой 1. И продолжаем таким образом. Ниже картинка на примере 3 символов:
Кодирование начинается с корня. Например, для символа б- мы идём от 24 к 12 по дорожке 1, а потом от 12 к 7 по дорожке 0. Итоговый код 10.
Теперь посмотрим как закодировать например текст ААБВБ: ставим поочерёдно: 0, потом 0, потом 10, потом 11, потом опять 10, т.е. 00101110
Вот мы и поняли принцип алгоритма Хаффмана. Самое сложное- это его запрограммировать. Это, конечно, лютый пиздец и тут нам нужны более продвинутые и углублённые программистские знания, поэтому я приведу код, который я нашёл и который работает, хоть и не без косяков:
#include <iostream>
#include <vector>
#include <map>
#include <list>
#include <fstream>
using namespace std;
class Node {
public:
int a; char c;
Node *left, *right;
Node() {left=right=NULL;}
Node(Node *L, Node *R) { left = L; right = R; a = L->a + R->a; }
};
struct MyCompare {
bool operator()(const Node* l, const Node* r) const { return l->a < r->a; }
};
vector<bool> code;
map<char,vector<bool> > table;
void BuildTable(Node *root) {
if (root->left!=NULL) { code.push_back(0); BuildTable(root->left);}
if (root->right!=NULL) { code.push_back(1); BuildTable(root->right);}
if (root->left==NULL && root->right==NULL) table[root->c]=code;
code.pop_back();
}
int main (int argc, char *argv[]) {
////// считаем частоты символов
ifstream f("1.txt", ios::out | ios::binary);
map<char,int> m;
while (!f.eof()) { char c = f.get(); m[c]++;}
////// записываем начальные узлы в список list
list<Node*> t;
for( map<char,int>::iterator itr=m.begin(); itr!=m.end(); ++itr) {
Node *p = new Node;
p->c = itr->first;
p->a = itr->second;
t.push_back(p); }
////// создаем дерево
while (t.size()!=1) {
t.sort(MyCompare());
Node *SonL = t.front();
t.pop_front();
Node *SonR = t.front();
t.pop_front();
Node *parent = new Node(SonL,SonR);
t.push_back(parent);
}
Node *root = t.front(); //root - указатель на вершину дерева
////// создаем пары 'символ-код':
BuildTable(root);
////// Выводим коды в файл output.txt
f.clear(); f.seekg(0); // перемещаем указатель снова в начало файла
ofstream g("output.txt", ios::out | ios::binary);
int count=0; char buf=0;
while (!f.eof()) {
char c = f.get();
vector<bool> x = table[c];
for(int n=0; n<x.size(); n++) {
buf = buf | x[n]<<(7-count);
count++;
if (count==8) { count=0; g<<buf; buf=0; }
}
}
f.close(); g.close();
///// считывание из файла output.txt и преобразование обратно
ifstream F("output.txt", ios::in | ios::binary);
setlocale(LC_ALL,"Russian"); // чтоб русские символы отображались в командной строке
Node *p = root;
count=0; char byte;
byte = F.get();
while(!F.eof()) {
bool b = byte & (1 << (7-count) ) ;
if (b) p=p->right; else p=p->left;
if (p->left==NULL && p->right==NULL) {cout<<p->c; p=root;}
count++;
if (count==8) {count=0; byte = F.get();}
}
F.close();
cout << endl; system("pause");
return 0;
}
Дата добавления: 2018-09-22; просмотров: 347; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!