9. Сортиране чрез сравнение и трансформация
    
    План:
        Задачи 7 и 8
       Сортиране - общи положения; сортиране чрез сравнение
      Сортиране чрез трансформация
      Бързо сортиране
      Сортиране в STL
      Задачи 9 и 10 
     
    
    Сортиране -
        общи положения; сортиране чрез сравнение [wiki]
    
    ** Класификации на алгоритмите за сортиране [Глава 3, стр.187]
    
    * В зависимост от местонахождението на данните: 
       - вътрешно (директен достъп), например бързо сортиране и 
       - външно (последователен достъп), например сливане.
     * В зависимост от операцията: 
       - чрез сравнение (<,
      > и ==) на двойки елементи и
      
       - чрез трансформация, напр. сортиране чрез броене.
     * Свойство на алгоритъма за сортиране:
       - устойчиви - относителният ред на елементите с равни
      ключове остава непроменен и
       - неустойчиви - разместване на елементи с равни ключове
      (сортиране с 2 ключа)
     * Ефективност на алгоритмите за сортиране - брой на извършени
      сравнения и размени (присвоявания).
      * Използване на допълнителна памет.
    
    ** Дърво на сравненията; сортиране на 3 числа.
    ** Класически универсални "елементарни" методи за сортиране чрез
    сравнение O(n2):
     - пряко вмъкване - намираме елемент, който "не е сортиран" и
    го поставяме на мястото му в сортираната част;
     - пряка селекция (избор) - намираме най-малкия елемент и го
    поставяме на мястото му в окончателно сортираната част;
     - мехурчето - последователно се разглеждат двойки елементи и
    евентуално се разменят.
    ** Бързо сортиране на Хоор - O(n log2n) средно и O(n2) в най-лошия случай.
    
    ** Пирамидално сортиране, сортиране чрез сливане: O(n log2n) 
      ** 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.
    Пример:
          M = { 5, 3, 2, 6 }, [1, 6], 
               1 2 3 4 5 6      1 2 3
          4 5 6
          S =  0,0,0,0,0,0; S = 0,1,1,0,1,1;  M = { 2,
          3, 5, 6 }
    Сложност O(m+n), където n е
    броят на елементите на M, а m = b - a + 1. 
    
    ** Сортиране чрез броене [3.2.2] 
     Дефинираме масив cnt[], като cnt[i]
    съдържа броя на срещанията на числото i.
    
    ** Побитово сортиране [3.2.3] 
    Идеята на побитовото сортиране се основава на двоичното вътрешно
    представяне на числата в компютъра. 
    Нека е зададено множество от цели числа без знак, чиито елементи ще
    сортираме. 
    Разделяме числата в два списъка в зависимост от стойността на
    най-младшия им двоичен бит. 
    Четните числа попадат в първия списък, а нечетните - във втория. 
    Следва добавяне на втория списък към края на първия, при което
    получаваме общ списък. 
    Повтаряме операцията с предпоследния бит, след това с
    предпредпоследния и т.н. 
    Процесът приключва след извършване на операцията с най-старшия бит.
    Сложност O(n).
      
    ** Метод на бройните системи [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);
        } 
    
    Сортиране чрез сливане
      mergesort.cpp
    
    
    Сортиране в STL 
    
    - sort, qsort 
      - контейнери set, multiset