Конфигурирование Flash-сервера

Чтобы при обращении броузера посетителя Web-сервер мог правильно отработать запрос, он (сервер) должен уметь распознавать файлы Flash-фильма. Для этого необходимо добавить в конфигурационный файл сервера MIME-типы Flash-плеера и связать их с соответствующими расширениями файлов:

  • MIME-тип application/x-shockwave-flash — с расширением .swf;
  • MIME-тип application/futuresplash — с расширением .spl.

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

И еще одно достаточно важное дополнение.

При воспроизведении Flash-фильма в Web-броузере вы можете загружать данные в фильм только из файла, который находится на сервере в той же самой подобласти (в том же домене). Это предотвращает загрузку в фильм информации с других общедоступных серверов. Тем самым повышается защищенность посетителей вашего сайта.

Вообще Flash ориентируется на стандартный уровень защиты, обеспечиваемый возможностями броузера, протоколов HTTP и HTTPS. Поэтому при создании Web-страниц на основе Flash вы должны придерживаться тех же правил, что и при создании безопасных Web-сайтов на HTML.

2.1.6. Сжатое и индексное хранение линейных списков

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

Сжатое хранение. Пусть в списке B= несколько элементов имеют одинаковое значение V, а список B’= получается из B заменой каждого элемента Ki на пару Ki’=(i,Ki). Пусть далее B»= – подсписок B’, получающийся вычеркиванием всех пар Ki=(i,V). Сжатым хранением В является метод хранения В», в котором элементы со значением V умалчиваются. Различают последовательное сжатое хранение и связанное сжатое хранение. Например, для списка B=, содержащего несколько узлов со значением Х, последовательное сжатое и связанное сжатое хранения, с умалчиванием элементов со значением Х.

Достоинство сжатого хранения списка при большом числе элементов со значением V заключается в возможности уменьшения объема памяти для его хранения.

Поиск i-го элемента в связанном сжатом хранении осуществляется методом полного просмотра, при последовательном хранении – методом бинарного поиска.

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

Рассмотрим следующую задачу. На входе заданы две последовательности целых чисел M=, N=, причем 92% элементов последовательности М равны нулю. Составить программу для вычисления суммы произведений Mi * Ni, i=1,2,…,10000.

Предположим, что список М хранится последовательно сжато в массиве структур m с объявлением:

     struct
     { int nm;
       float val; }   m[10000];

Для определения конца списка добавим еще один элемент с порядковым номером m[j].nm=10001, который называется стоппером (stopper) и располагается за последним элементом сжатого хранения списка в массиве m.

Программа для нахождения искомой суммы имеет вид:

   # include
   main()
   { int i,j=0;
     float inp,sum=0;
     struct                           /* объявление  массива  */
     { int nm;                        /* структур             */
       float val;  }   m[10000];

     for(i=0;i<10000;i++) /* чтение списка M */ { scanf("%f",&inp);
 if (inp!="0)" { m[j].nm="i;" m[j++].val="inp;" } }
 m[j].nm="10001;" /* stopper */ for(i="0,j=0;" i<10000; i++)
{ scanf("%f",&inp); /* чтение списка N */ if(i="=m[j].nm)"
 /* вычисление суммы */ sum+="m[j++].val*inp;" }
printf( "сумма произведений Mi*Ni равна %f",sum); }

Индексное хранение используется для уменьшения времени поиска нужного элемента в списке и заключается в следующем. Исходный список B = разбивается на несколько подсписков В1,В2, …,Вm таким образом, что каждый элемент списка В попадает только в один из подсписков, и дополнительно используется индексный список с М элементами, указывающими на начало списков В1,В2, …,Вm.

Считается, что список хранится индексно с помощью подсписков B1,B2, …,Bm и индексного спискa X = , где ADGj – адрес начала подсписка Bj, j=1,M.

При индексном хранении элемент К подсписка Bj имеет индекс j. Для получения индексного хранения исходный список В часто преобразуется в список В’ путем включения в каждый узел еще и его порядкового номера в исходном списке В, а в j-ый элемент индексного списка Х, кроме ADGj, может включаться некоторая дополнительная информация о подсписке Bj. Разбиение списка В на подсписки осуществляется так, чтобы все элементы В, обладающие определенным свойством Рj, попадали в один подсписок Bj.

Достоинством индексного хранения является то, что для нахождения элемента К с заданным свойством Pj достаточно просмотреть только элементы подсписка Bj; его начало находится по индексному списку Х, так как для любого К, принадлежащего Bi, при i не равном j свойство Pj не выполняется.

В разбиении В часто используется индексная функция G(K), вычисляющая по элементу К его индекс j, т.е. G(K)=j. Функция G обычно зависит от позиции К, обозначаемой поз.K, в подсписке В или от значения определенной части компоненты К – ее ключа.

Рассмотрим список B= с элементами

  К1=(17,Y),   K2=(23,H),    K3=(60,I),   K4=(90,S),    K5=(66,T),
  K6=(77,T),   K7=(50,U),    K8=(88,W),   K9=(30,S).

Если для разбиения этого списка на подсписки в качестве индексной функции взять Ga(K)=1+(поз.K-1)/3, то список разделится на три подсписка:

           B1a=<(17,Y),(23,H),(60,I)>,
           B2a=<(90,S),(66,T),(77,T)>,
           B3a=<(50,U),(88,W),(30,S)>.

Добавляя всюду еще и начальную позицию элемента в списке, получаем:

           B1a'=<(1,17,Y),(2,23,H),(3,60,I)>,
           B2a'=<(4,90,S),(5,66,T),(6,77,T)>,
           B3a'=<(7,50,U),(8,88,W),(9,30,S)>.

Если в качестве индексной функции выбрать другую функцию Gb(K)=1+(поз.K-1)%3, то получим списки:

           B1b"=<(1,17,Y),(4,90,S),(7,50,U)>,
           B2b"=<(2,23,H),(5,66,T),(8,88,U)>,
           B3b"=<(3,60,I),(6,77,T),(9,30,S)>.

Теперь для нахождения узла K6 достаточно просмотреть только одну из трех последовательностей (списков). При использовании функции Ga(K) это список B2a’, а при функции Gb(K) список B3b».

Для индексной функции Gc(K)=1+K1/100, где K1 – первая компонента элемента К, находим:

         B1=<(17,Y),(23,H),(60,I),(90,S)>,
         B2=<(66,T),(77,T)>,
         B3=<(50,U),(88,W)>,
         B4=<(30,S)>.

Чтобы найти здесь узел К с первым компонентом-ключом К1=77, достаточно просмотреть список B2.

При реализации индексного хранения применяется методика А для хранения индексного списка Х (функция Ga(X) ) и методика C для хранения подсписков B1,B2,…,Bm (функция Gc(Bi)), т.е. используется, так называемое, A-C индексное хранение.

В практике часто используется последовательно-связанное индексное хранение. Так как обычно длина списка индексов известна, то его удобно хранить последовательно, обеспечивая прямой доступ к любому элементу списка индексов. Подсписки B1,B2,…,Bm хранятся связанно, что упрощает вставку и удаление узлов(элементов). В частности, подобный метод хранения используется в ЕС ЭВМ для организации, так называемых, индексно-последовательных наборов данных, в которых доступ к отдельным записям возможен как последовательно, так и при помощи ключа.

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

Выберем в качестве индексной функции G(K)=K%100+1, а в качестве индексного списка Х – массив из 100 элементов. Следующая функция решает поставленную задачу:

  
   #include 

   #include 

   typedef struct nd
           {  float val;
              struct nd *n; }   ND;
   int index (ND *x[100])
   {  ND *p;
      int i,j=0;
      float inp;
      for (i=0; i<100; i++) x[i]="NULL;" scanf("%d",&inp);
while (inp!="0)" { j++; p="malloc(sizeof(ND));"
i="inp%100+1;" p->val=inp;
          p->n=x[i];
          x[i]=p;
          scanf("%d",&inp);
      }
      return j;
   }

Возвращаемым значением функции index будет число обработанных элементов списка.

Для индексного списка также может использоваться индексное хранение. Пусть, например, имеется список B= с элементами

   K1=(338,Z),  K2=(145,A),  K3=(136,H),  K4=(214,I),  K5 =(146,C),
   K6=(334,Y),  K7=(333,P),  K8=(127,G),  K9=(310,O),  K10=(322,X).

Требуется разделить его на семь подсписков, т.е. X= таким образом, чтобы в каждый список B1,B2,…,B7 попадали элементы, совпадающие в первой компоненте первыми двумя цифрами. Список Х, в свою очередь, будем индексировать списком индексов Y=, чтобы в каждый список Y1,Y2,Y3 попадали элементы из X, у которых в первой компоненте совпадают первые цифры. Если списки B1,B2,…,B7 хранить связанно, а списки индексов X,Y индексно, то такой способ хранения списка B называется связанно-связанным связанным индексным хранением.

2.2.4. Слияние списков

Упорядоченные списки А и В длин М и N сливаются в один упорядоченный список С длины М+N, если каждый элемент из А и В входит в С точно один раз. Так, слияние списков А=<6,17,23,39,47> и В=<19,25,38,60> из 5 и 4 элементов дает в качестве результата список С=<6,17,19,23,25,38,39,47,60> из 9 элементов.

Для слияния списков А и В список С сначала полагается пустым, а затем к нему последовательно приписывается первый узел из А или В, оказавшийся меньшим и отсутствующий в С.

Составим функцию для слияния двух упорядоченных, расположенных рядом частей массива s. Параметром этой функции будет исходный массив s с выделенными в нем двумя расположенными рядом упорядоченными подмассивами: первый с индекса low до индекса low+l, второй с индекса low+l+1 до индекса up, где переменные low, l, up указывают месторасположения подмассивов. Функция merge осуществляет слияние этих подмассивов, образуя на их месте упорядоченный массив с индексами от low до up.

   /*   слияние списков */
   double *merge(double *s, int low, int up, int l)
   {
   double *b,*c,v;
     int i,j,k;
     b=calloc(l,sizeof(double));
     c=calloc(up+1-l,sizeof(double));
     for(i=low;is[up-1]) ?
                            (s[low+l-1]+1) : (s[up-1]+1)));
     i=(j=0);
     k=low;
     while(b[i]

2.2.5. Сортировка списков путем слияния

Для получения упорядоченного списка B’ последовательность значений В= разделяют на N списков В1=, B2=,…,Bn=, длина каждого из которых 1. Затем осуществляется функция прохода, при которой М>=2 упорядоченных списков B1,B2,…,Bm заменяется на М/2 (или (М+1)/2) упорядоченных списков, B(2i-1)-oго и B(2i)-ого ( 2i<=M ) и добавлением Вm при нечетном М. Проход повторяется до тех пор пока не получится одна последовательность длины N.

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

Пример:

     9 / 7 / 18 / 3 / 52 / 4 / 6 / 8 / 5 / 13 / 42 / 30 / 35 / 26;
    7,9 / 3,18 / 4 / 52 / 6 / 8 / 54 / 13 / 30 / 42 / 26 / 35;
    3,7,9,18 /  4,6,8,52 /  5,13,30,42 /  26,35;
    3,4,6,7,8,9,18,52 /   5,13,26,30,35,42;
    3,4,5,6,7,8,9,13,18,26,30,35,42,52.

Количество действий, требуемое для сортировки слиянием, равно Q=N*log2(N), так как за один проход выполняется N сравнений, а всего необходимо осуществить log2(N) проходов. Сортировка слиянием является очень эффективной и часто применяется для больших N, даже при использовании внешней памяти.

Функция smerge упорядочивает массив s сортировкой слиянием, используя описанную ранее функцию merge.

    /* сортировка слиянием */
    double *smerge (double *s, int m, int n)
    { int l,low,up;
      double *merge (double *, int, int, int);
      l=1;
      while(l<=(n-m)) { low="m;" up="m-1;" while (l+up < n)
{ up="(low+2*l-1" < n) ? (low+2*l-1) : n ;
 merge (s,low,up,l); low="up-1;" } l*="2;" } return(a); }

Для сортировки массива путем слияния удобно использовать рекурсию. Составим рекурсивную функцию srecmg для сортировки массива либо его части. При каждом вызове сортируемый массив делится на две равных части, каждая из которых сортируется отдельно, а затем происходит их слияние.

/*    рекурсивная   сортировка слиянием  1/2           */      double *srecmg (double *a, int m, int n)      { double * merge (double *, int, int, int);        double * smerge (double *, int, int);         int i;        if (n>m)           { i=(n+m)/2;             srecmg(a,m,i);             srecmg(a,i+1,n);             merge(a,m,n,(n-m)/2+1);           }        return (a);      }

2.3.5. Выбор в линейных списках

Задача выбора. Задан линейный список целых, различных по значению чисел B=, требуется найти элемент, имеющий i-тое наибольшее значение в порядке убывания элементов. При i=1 задача эквивалентна поиску максимального элемента, при i=2 поиску элемента с вторым наибольшим значением.

Поставленная задача может быть получена из задачи поиска j-того минимального значения заменой i=n-j+1 и поиском i-того максимального значения. Особый интерес представляет задача выбора при i=a/n, 0<a<1, в частности, задача выбора медианы при a=1/2.

Все варианты задачи выбора легко решаются, если список B полностью отсортирован, тогда просто нужно выбрать i-тый элемент. Однако в результате полной сортировки списка B получается больше информации, чем требуется для решения поставленной задачи.

Количество действий можно уменьшить применяя сортировку выбором только частично до i-того элемента. Это можно сделать, напри мер при помощи функции findi

       /*  выбор путем частичной сортировки  */
       int findi(int *s, int n, int i)
       {
          int c,j,k;
          for (k=0; k<=i; k++) for (j="k+1;" j<="n;" j++)
 if (s[k] < s[j]) { c="s[k];" s[k]="s[j];" s[j]="c;" }
return s[i]; }

Эта функция ищет элемент с индексом i, частично сортируя массив s, и выполняет при этом (n*i) сравнений. Отсюда следует, что функция findi приемлима для решения задачи при малом значении i, и малоэффективна при нахождении медианы.

Для решения задачи выбора i-того наибольшего значения в списке B модифицируем алгоритм быстрой сортировки. Список B разбиваем элементом K1 на подсписки B’ и B», такие, что если Ki -B’, то Ki>K1, и если Ki – B», то Ki<K1, и список B реорганизуется в список B’,K1,B». Если K1 элемент располагается в списке на j-том месте и j=i, то искомый элемент найден. При j>i наибольшее значение ищется в списке B’; при j<i будем искать (i-j) значение в подсписке B».

Алгоритм выбора на базе быстрой сортировки в общем эффективен, но для улучшения алгоритма необходимо, чтобы разбиение списка на подсписки осуществлялось почти пополам. Следующий алгоритм эффективно решает задачу выбора i-того наибольшего элемента в списке B, деля его на подсписки примерно равной величины.

1. Если N<21, то выбрать i-тый наибольший элемент списка B обычной сортировкой.

2. Если N>21 разделим список на P=N/7 подсписков по 7 элементов в каждом, кроме последнего в котором mod(N,7) элементов.

3. Определим список W из медиан полученных подсписков (четвертых наибольших значений) и найдем в W его медиану M (рекурсивно при помощи данного алгоритма) т.е. (P/2+1)-й наибольший элемент.

4. С помощью элемента M разобьем список B на два подсписка B’ с j элементами большими или равными M, и B» c N-j элементами меньшими M. При j>i повторим процедуру поиска сначала, но только в подсписке B’. При j=i искомый элемент найден, равен M и поиск прекращается. При j < i будем искать (i-j)-тый наибольший элемент в списке B».

     /*   алгоритм выбора делением списка почти пополам   */
     int search (int *b, int n, int i)
     {
      int findi(int  *, int, int);
      int t, m, j, p, s, *w;
      if (n<21) return findi(b, n, i); /* шаг 1 */ p="(int)(n/7);"
 w="calloc(p+1,sizeof(int));" /* шаги 2 и 3 */
 for (t="0;" t < p; t++) w[t]="findi(b+7*t," 7, 3);
if (n%7!="0)" { w[p]="findi(b+7*p,n%7,(n%7)/2);" p++; }
 m="search(w," p, p/2); free (w);
for (j="0," t="0;" t < n; t++) /* шаг 4 */ if (b[t]>=m) j++;
        if (j>i)
        {
          for (p=0, t=0; p < n; t++)
          if (b[t]>=m)
          { b[p]=b[t]; p++; }
          m=search(b, j, i);             /*   поиск в B"    */
        }
        if (j < i)
        {
          for (p=0, t=0; t < n; t++)
            if (b[t] < m)     b[p++]=b[t];
            m=search(b, n-j, i-j);       /*   поиск в B"    */
        }
      return m;
     }

Рекурсивная функция search реализует алгоритм выбора i-того наибольшего значения. Для ее вызова можно использовать следующую программу

    
     #include 

     #include 

     main()
     {
       int search (int *b, int n, int i);
       int *b;
       int l, i, k, t;
       scanf("%d%d",&l,&i);
       printf
       ("\nВыбор %d максимального элемента из %d штук",i,l);
       b=(int *)(calloc(100,sizeof(int)));
       for (k=0; k<100; k++) b[k]="k;" /* заполнение массива */
for (k="1;" k < l/4; k++) { t="b[k];" /* перемешивание */
 b[k]="b[l-k];" /* массива */ b[l-k]="t;" }
k="search(b,l,i);" /* выбор элемента */
printf ("\n выбран элемент равный %d\n\n",k); return 0; }

Используя метод математической индукции, можно доказать, что для функции search требуется выполнить в самом неблагоприятном случае 28*N сравнений.

Действительно, если N<21, то выполнение функции findi потребует сравнений порядка N*(N-1)/2, т.е. меньше чем 28*N. Предположим, что для любого T<N количество сравнений при выполнении функции search не более 28*T и подсчитаем, сколько сравнений потребуется функции search при произвольном значении N. Для поиска медианы в каждом из подсписков функцией findi требуется не более 7*(7-1)/2=21 сравнений, а для формирования массива W в целом не более 21*(N/7)=3*N сравнений. По предположению индукции для поиска медианы в массиве W длины N/7 требуется 28*(N/7)=4*N сравнений. После удаления из B части элементов с помощью медианы в B’ (или в B») останется не более N*5/7 элементов, и для удаления ненужных элементов необходимо количество сравнений порядка N. Для поиска в оставшейся части массива (в B’ или B») по предположению индукции требуется не более 28*(N*5/7)=20*N сравнений. Таким образом, всего потребуется 3*N+4*N+N+20*N=28*N сравнений, т.е. выдвинутое предположение доказано.

2.3.4. Методы вычисления адреса

Методы вычисления адреса. Пусть в каждом из М элементов массива Т содержится элемент списка (например целое положительное число). Если имеется некоторая функция H(V), вычисляющая однозначно по элементу V его адрес – целое положительное число из интервала [0,M-1],то V можно хранить в массиве T с номером H(V) т.е. V=T(H(V)). При таком хранении поиск любого элемента происходит за постоянное время не зависящее от M.

Массив T называется массивом хеширования, а функция H – функцией хеширования.

При конкретном применении хеширования обычно имеется определенная область возможных значений элементов списка V и некоторая информация о них. На основе этого выбирается размер массива хеширования M и строится функция хеширования. Критерием для выбора M и H является возможность их эффективного использования.

Пусть нужно хранить линейный список из элементов K1,K2,..,Kn, таких, что при Ki=Kj, mod(Ki,26)= mod(Kj,26). Для хранения списка выберем массив хеширования T(26) с пространством адресов 0-25 и функцию хеширования H(V)= mod(V,26). Массив T заполняется элементами T(H(Ki))=Ki и T(j)=0 если j (H(K1), H(K2),..,H(Kn)).

Поиск элемента V в массиве T с присваиванием Z его индекса если V содержится в T, или -1, если V не содержится в T, осуществляется следующим образом

        int t[26],v,z,i;
        i=(int)fmod((double)v,26.0);
        if(t[i]==v) z=i;
        else z=-1;

Добавление нового элемента V в список с возвращением в Z индекса элемента, где он будет храниться, реализуется фрагментом

        z=(int)fmod((doule)v,26.0);
        t[z]=v;

а исключение элемента V из списка присваиванием

       
        t[(int)fmod((double)v,26)]=0;

Теперь рассмотрим более сложный случай, когда условие Ki=Kj H(Ki)=H(Kj) не выполняется. Пусть V – множество возможных элементов списка (целые положительные числа), в котором максимальное число элементов равно 6. Возьмем M=8 и в качестве функции хеширования выберем функцию H(V)=Mod(V,8).

Предположим, что B=, причем H(K1)=5, H(K2)=3, H(K3)=6, H(K4)=3, H(K5)=1, т.е. H(K2)=H(K4) хотя K2=K4. Такая ситуация называется коллизией, и в этом случае при заполнении массива хеширования требуется метод для ее разрешения. Обычно выбирается первая свободная ячейка за собственным адресом. Для нашего случая массив T[8] может иметь вид

         T=<0,K5,0,K2,K4,K1,K3,0> .

При наличии коллизий усложняются все алгоритмы работы с массивом хеширования. Рассмотрим работу с массивом T[100], т.е. с пространством адресов от 0 до 99. Пусть количество элементов N не более 99, тогда в T всегда будет хотя бы один свободный элемент равный нулю. Для объявления массива используем оператор

            int static t[100];

Добавление в массив T нового элемента Z с занесением его адреса в I и числа элементов в N выполняется так:

        i=h(z);
        while (t[i]!=0 && t[i]!=z)
        if (i==99) i=0;
        else i++;
        if (t[i]!=z)  t[i]=z,  n++;

Поиск в массиве T элемента Z с присвоением I индекса Z, если Z имеется в T, или I=-1, если такого элемента нет, реализуется следующим образом:

        i=h(z);
        while (t[i]!=0 && t[i]!=z)
        if (i==99) i=0;
        else i++;
        if (t[i]==0) i=-1;

При наличии коллизий исключение элемента из списка путем пометки его как пустого, т.е. t[i]=0, может привести к ошибке. Например, если из списка B исключить элемент K2, то получим массив хеширования в виде T=<0,K5,0,0,K4,K1,K3,0>, в котором невозможно найти элемент K4, поскольку H(K4)=3, а T(3)=0. В таких случаях при исключении элемента из списка можно записывать в массив хеширования некоторое значение непринадлежащее области значений элементов списка и не равное нулю. При работе с таким массивом это значение будет указывать на то, что нужно просматривать со средние ячейки.

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

Базовые средства языка С/С++

Курс дисциплины «Конструирование программ и языки программирования» включает изучение языка программирования С++ и интегрированной среды разработки ( IDE,  Integrated Development Environment) Microsoft Visual C++, которая является одним из компонентов Microsoft Visual Studio. Visual C++  – это больше, чем просто компонент Visual Studio, т.к.  библиотека MFC (Microsoft FoundationClasses), которая стала стандартом для программирования на языке C++ в среде Windows, является библиотекой классов и не связана со средой разработки.

В Visual Studio входит мощная справочная система по среде и синтаксису языков программирования – это MSDN (Microsoft Developers Network) .

В настоящее время основной средой разработки является Visual C++ 6 или более поздняя версия Visual C++ .NET (Visual C++ 7) – сетевая.

Другой средой разработки на базе стандарта С++ является продукт компании Borland.

ЭтоBorland C++ Builder. Имеется студенческая бесплатная версия системы, ее можно скачать с сайта фирмы Borland.

В 1972 году сотрудником фирмы Bell Laboratories Деннисом Ритчи  при разработке ОС UNIX был создан язык программирования Си. Он проектировался как инструмент для системного программирования.

В начале 80-х годов сотрудник научно-исследовательского вычислительного центра AT&T Bell Laboratories в Мюррей Хилл (Нью-Джерси, США) Бьерн Страуструп разработал язык программирования С++  расширающий возможности С.

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

С++ является языком программирования общего назначения. Естественная для него область применения – системное программирование, понимаемое в широком смысле этого слова. На С++ написана ОС Linux. Кроме того, С++ успешно используется во многих областях приложения, далеко выходящих за указанные рамки. Реализации С++ теперь есть на всех машинах, начиная с самых скромных микрокомпьютеров – до самых больших супер-ЭВМ, и практически для всех операционных систем.

Название С++ (си плюс плюс) , было придумано Риком Маскитти летом 1983 г. Это название отражает эволюционный характер изменений языка С. Обозначение ++ относится к операции наращивания С.

Изначально С++ был задуман для того, чтобы автору и его друзьям не надо было программировать на ассемблере, С или других современных языках высокого уровня.

С++ стал языком, на котором можно создавать и использовать библиотеки.

Примерно в 1987 г. стало очевидно, что работа по стандартизации С++ неизбежна.

В декабре 1989 г. в составе организации ANSI/ISO (ANSI – Американский Национальный Институт Стандартов, ISO – Международная Организация Стандартов ) был образован комитет по стандартам языка С++, который разработал в 1990 г. общемировой документ под названием «Стандартный С++».

Язык программирования С++ задумывался как язык, который будет:

- лучше языка С;

- поддерживать абстракцию данных;

-         поддерживать объектно-ориентированное программирование.

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

Язык C++ явился мощным и стремительным рывком в развитии программирования. C++ и по сей день занимает господствующее положение среди языков программирования в мире.

Ключевым понятием С++ является класс. Класс – это определяемый пользователем тип.

В языке C++ полностью поддерживаются принципы объектно-ориентированного программирования, включая три кита, на которых оно состоит: инкапсуляцию, наследование и полиморфизм.

Инкапсуляция

Совмещение структур данных с функциями (методами), предназначенными для манипулирования этими данными. Инкапсуляция достигается путём введения класса нового механизма структурирования и типизации данных.

Наследование

Создание новых, производных классов, которые наследуют данные и функции от одного или нескольких ранее определённых базовых классов. При этом возможно переопределение  или добавление новых данных и методов. В результате создаётся иерархия классов.

Полиморфизм

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

Стандартный С++ включает в себя много дополнительных возможностей, например стандартную библиотеку шаблонов ( STL, Standart Template Library ) – содержащую массивы, вектора, команды сортировки, поиска и др, библиотеку классов (MFC), библиотеку активных шаблонов ( ATL, Active Template Library ) – набор заголовочных файлов, содержащих коллекцию шаблонов, макросов и классов. ATL – это компактная среда, предназначенная для разработки надежных СОМ-серверов. СОМ – Component Object Model ( модель компонентных объектов) – это технология программирования на основе интерфейсов. Она определяет стандартный механизм, с помощью которого одна часть программного обеспечения предоставляет свои сервисы другой.

Интерфейс СОМ – это перечень методов (функций), которые  компонент СОМ предоставляет пользователю.

C# ( произносится Си шарп, Си острый) – это новый язык программирования от компании Microsoft, который появился в середине 2000 года. Основой языка является С++. Он входит в новую версию Visual Studio.NET. C# создавался специально для новой платформы .NET, как компонентно-ориентированный язык.

Одна из причин – создание альтернативы языку Java ( компания Sun).

Модули, написанные на C# будут совместимы с модулями. Написанными на С++ и Visual Basic.

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

Быстрая сортировка состоит в том, что список В= реорганизуется в список B’,,B», где В’ – подсписок В с элементами, не большими К1, а В» – подсписок В с элементами, большими К1. В списке B’,,B» элемент К1 расположен на месте, на котором он должен быть в результирующем отсортированном списке. Далее к спискам B’ и В» снова применяется упорядочивание быстрой сортировкой. Приведем в качестве примера сортировку списка, отделяя упорядоченные элементы косой чертой, а элементы Ki знаками <и>.

Пример:

      9, 7, 18, 3, 52, 4, 6, 8, 5, 13, 42, 30, 35, 26
      7, 3, 4, 6, 8, 5/ <9>/ 18, 52, 13, 42, 30, 35, 26
      3, 4, 6, 5/<7>/ 8/ 9/ 13/ <18>/ 52, 42, 30, 35, 26
      <3>/ 4, 6, 5/ 7/ 8/ 9/ 13/ 18/ 42, 30, 35, 26/ <52>
      3/ <4>/ 6, 5/ 7/ 8/ 9/ 13/ 18/ 30, 35, 26/ <42>/ 52
      3/ 4/ 5/ <6>/ 7/ 8/ 9/ 13/ 18/ 26/ <30>/ 35/ 42/ 52

Время работы по сортировке списка методом быстрой сортировки зависит от упорядоченности списка. Оно будет минимальным, если на каждом шаге разбиения получаются подсписки B’ и В» приблизительно равной длины, и тогда требуется около N*log2(N) шагов. Если список близок к упорядоченному, то требуется около (N*N)/2 шагов.

Рекурсивная функция quick упорядочивает участок массива s быстрой сортировкой.

      /*               быстрая сортировка             */
      double * quick(double *s,int low,int hi)
      { double cnt,aux;
        int i,j;
        if (hi>low)
           { i=low;
             j=hi;
             cnt=s[i];
             while(i < j)
             {  if (s[i+1]<=cnt) { s[i]="s[i+1];" s[i+1]="cnt;"
 i++; } else { if (s[j]<="cnt)" { aux="s[j];" s[j]="s[i+1];"
 s[i+1]="aux;" } j--; } } quick(s,low,i-1); quick(s,i+1,hi); }
return(s); }

Здесь используются два индекса i и j, проходящие части массива навстречу друг другу (см. рис.30). При этом i всегда фиксирует разделяющий элемент cnt=s[low], слева от которого находятся числа, не большие cnt, а справа от i – числа, большие cnt. Возможны три случая: при s[i+1]<=cnt; при s[i+1]>cnt и s[j]<=cnt; при s[i+1]>cnt и s[j]>cnt. По окончании работы i=j, и cnt=s[i] устанавливается на своем месте.

Быстрая сортировка требует дополнительной памяти порядка log2(N) для выполнения рекурсивной функции quick (неявный стек).

Оценка среднего количества действий, необходимых для выполнения быстрой сортировки списка из N различных чисел, получена как оценка отношения числа различных возможных последовательностей из N различных чисел, равного N!, и общего количества действий C(N), необходимых для выполнения быстрой сортировки всех различных последовательностей. Доказано, что C(N)/N! <2*N*ln(N).

Распределяющая сортировка. Предположим, что элементы линейного списка В есть Т-разрядные положительные десятичные числа D(j,n) – j-я справа цифра в десятичном числе n>=0, т.е. D(j,n)=floor(n/m)%10, где m=10^(j-1). Пусть В0,В1,…,В9 – вспомогательные списки (карманы), вначале пустые.

2.2.7. Распределяющая сортировка

Предположим, что элементы линейного списка В есть Т-разрядные положительные десятичные числа D(j,n) – j-я справа цифра в десятичном числе n>=0, т.е. D(j,n)=floor(n/m)%10, где m=10^(j-1). Пусть В0,В1,…,В9 – вспомогательные списки (карманы), вначале пустые.

Для реализации распределяющей сортировки выполняется процедура, состоящая из двух процессов, называемых распределение и сборка для j=1,2,…,T.

PАСПРЕДЕЛЕНИЕ заключается в том, что элемент Кi (i=1,N) из В добавляется как последний в список Bm, где m=D(j,Ki), и таким образом получаем десять списков, в каждом из которых j-тые разряды чисел одинаковы и равны m.

СБОРКА объединяет списки В0,В1,…,В9 в этом же порядке, образуя один список В.

Рассмотрим реализацию распределяющей сортировки при Т=2 для списка: B=<09,07,18,03,52,04,06,08,05,13,42,30,35,26> .

                РАСПРЕДЕЛЕНИЕ-1:
     B0=<30>,  B1=<>,  B2=<52,42>, B3=<03,13>, B4=<04>,
     B5=<05,35>,  B6=<06,26>,B7=<07>, B8=<18,08>, B9=<09>.
                СБОРКА-1:
     B=<30,52,42,03,13,04,05,35,06,26,07,18,08,09>
                РАСПРЕДЕЛЕНИЕ-2:
     B0=<03,04,05,06,07,08,09>, B1=<13,18>, B2=<26>,
     B3=<30,35>, B4=<42>, B5=<52>, B6=<>,B7=<>,B8=<>, B9=<>.
                СБОРКА-2:
     B=<03,04,05,06,07,08,09,13,18,26,30,35,42,52>.

Количество действий, необходимых для сортировки N T-цифровых чисел, определяется как Q(N*T). Недостатком этого метода является необходимость использования дополнительной памяти под карманы.

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

В представленной ниже программе функция pocket реализует распределяющую сортировку связанного линейного списка (указатель q), в котором содержатся Т-разрядные десятичные положительные числа, без использования дополнительной памяти; в функции a[i], b[i] указывают соответственно на первый и на последний элементы кармана Bi.

     /*   вызов  распределяющeй  сортировки  списка    */
     #include

     #include
     typedef struct str
     { long val;
       struct str *n; } SP1;
     main()
     { int i;
       SP1 *q=malloc(sizeof(SP1)),*r;
       SP1 *pocket(SP1 * ,int );
       long a[14]={ 0,7,18,3,52,4,6,8,5,13,42,30,35,26 };
       q->n=NULL;
       q->val=a[0];
       r=q;
       printf(" %d",a[0]);
       for(i=1;i<14;i++) /* формирование списка */
Разновидностью распределяющей сортировки является битовая
сортировка. В ней элементы списка интерпретируются как двоичные
числа, и D(j,n) обозначает j-ю справа двоичную цифру числа n.
При этой сортировке в процессе РАСПРЕДЕЛЕНИЕ требуются только два
вспомогательных кармана В0 и В1; их можно разместить в одном
массиве, двигая списки В0 и В1 навстречу друг другу и отмечая
точку встречи. Для осуществления СБОРКИ нужно за списком В0
написать инвертированный список В1.

Так как выделение j-го бита требует только операций сдвига, то
битовая сортировка хорошо подходит для внешней сортировки на
магнитных лентах и дисках.
 Разновидностью битовой сортировки является
бинарная сортировка. Здесь из всех элементов
списка B= выделяются его минимальный и
максимальный элементы и находится их среднее
арифметическое m=(MIN+MAX)/2.
Список В разбивается на подсписки В1 и В2,
причем в В1 попадают элементы, не большие m,
а в список В2 - элементы, большие m. Потом
для непустых подсписков В1 и В2 сортировка
продолжается рекурсивно.
 { r->n=malloc(sizeof(SP1));
             r->val=a[i];
             (r->n)->n=NULL;
             r=r->n;
             printf(" %d",a[i]);
         }
       r=pocket(q,2);
       printf("\n");          /*  печать результатов   */
       while (r!=NULL)
         {   printf(" %d",r->val);
             r=r->n;
         }
     }
     /*    распределяющая  сортировка  списка          */
     SP1 *pocket(SP1 *q,int t)
     { int i,j,k,m=1;
       SP1 *r, *gg, *a[10], *b[10];
       gg=q;
       for(j=1;j<=t;j++) { for(i="0;i<=9;i++)" a[i]="(b[i]=NULL);"
 while(q!="NULL)" { k="((int)(q-">val/m))%(int)10;
                 r=b[k];
                 if (a[k]==NULL) a[k]=q;
                 else r->n=q;
                 r=b[k]=q;
                 q=q->n;
                 r->n=NULL;
              }
           for(i=0;i<=9;i++) if (a[i]!="NULL)" break;
q="a[i];" r="b[i];" for(k="i+1;k<=9;k++)"
if(a[k]!="NULL)" { r->n=a[k];
                 r=b[k];
              }
           m=m*10;
         }
       return (gg);
     }

2.3.3. М-блочный поиск

Этот способ удобен при индексном хранении списка. Предполагается, что исходный упорядоченный список B длины N разбит на M подсписков B1,B2,…,Bm длины N1,N2,…,Nm, таким образом, что B=B1,B2,..,Bm.

Для нахождения ключа V, нужно сначала определить первый из списков Bi, i=1,M, последний элемент которого больше V, а потом применить последовательный поиск к списку Bi.

Хранение списков Bi может быть связным или последовательным. Если длины всех подсписков приблизительно равны и M= N, то Max М-блочного поиска равен 2 N. При одинаковой частоте использования элементов Avg М-блочного поиска равен N.

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

Если вместо ключа V имеется упорядоченный список ключей, то последовательный или М-блочный поиск может оказаться более удобным, чем бинарный, поскольку не требуется повторной инициализации для каждого нового ключа из списка V.