Вы находитесь на странице: 1из 310

МИНИСТЕРСТВО ОБРАЗОВАНИЯ

ИНСТИТУТ НЕПРЕРЫВНОГО ФОРМИРОВАНИЯ


КАФЕДРА ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ

Структуры данных
(на базе C++)
МЕТОДИЧЕСКИЕ МАТЕРИАЛЫ

Выпуск 4

Разработаны и адаптированы

Сергей Перетятку, доц., докт.


Александр Перетятку, маг. информ.

Кишинёв - 2017
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Recomandat de către Co misia Ştiinţifico-Metodică a Unstitutului de Formare


Continuă, Process+verbal nr.1 din 14 iunie 201 4

Sergiu Pereteatcu, conferenţiar unuversitar, doctor


Alexandru Pereteatcu, magistru în informatică

Redactor coordonator
Ion Spinei - conferenţiar unuversitar, doc

Tehnoredactare computerizată
Ion Stîngu

Descrierea CIP a Camerei Naţionale a Cărţii


Pereteatcu, Sergiu
Structuri de date (în baza C++): Suport de curs / Sergiu Pereteatcu, Alexandru Pereteatcu;
Inst. de Formare Continuă, Catedra Tehnologii Informaţionale. – Ch.: Inst. de Formare Continuă,
2015. – 214 p.
Bibliogr.: p. 214 (15 tit.).
ISBN 978-9975-4404-3-1.
004.6:519.1/6(075.8)
P52

2
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ОГЛАВЛЕНИЕ

1. ВВЕДЕНИЕ В СТРУКТУРЫ ДАННЫХ ................................................................................ 5


1.1. Общие понятия и определения ....................................................................................... 5
1.2. Базовые классы ................................................................................................................. 6
2. ТАБЛИЦЫ ................................................................................................................................ 11
2.1. Понятие таблицы............................................................................................................ 11
2.2. Неупорядоченные таблицы ........................................................................................... 14
2.3. Неупорядоченные древовидные таблицы.................................................................... 20
2.4. Упорядоченные таблицы ............................................................................................... 28
2.5. Таблицы с прямым доступом ........................................................................................ 36
2.6. Таблицы с распределением (случайное перемешивание) .......................................... 37
Общие понятия ......................................................................................................................... 37
Таблицы с открытым перемешиванием ................................................................................. 37
Таблицы с распределением с внешним связыванием (с внешними цепочками
переполнения) .......................................................................................................................... 44
Таблицы с распределением с внутренним связыванием ...................................................... 57
Функции распределения .......................................................................................................... 66
3. СОРТИРОВКА ........................................................................................................................ 68
3.1. Общие понятия ............................................................................................................... 68
Случай внутренней сортировки .............................................................................................. 68
О сложности алгоритмов сортировки .................................................................................... 69
3.2. Классы для исследования алгоритмов сортировки .................................................... 71
3.3. Сортировка обменами.................................................................................................... 74
3.4. Быстрая сортировка ....................................................................................................... 76
Практические советы по выбору главного элемента............................................................ 78
Алгоритм деления .................................................................................................................... 78
Оценка сложности алгоритма быстрой сортировки ............................................................. 85
Улучшение быстрой сортировки ............................................................................................ 89
3.5. Сортировка вставками ................................................................................................... 94
Простая вставка ........................................................................................................................ 94
Метод Шелла ............................................................................................................................ 95
3.6. Сортировка выборами ................................................................................................. 101
Простой выбор ....................................................................................................................... 101
3.7. Древовидная сортировка (пирамидальная, heapsort) ................................................ 101
3.8. Практическое сравнение различных алгоритмов сортировки ................................. 108
4. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ..................................................................... 109
4.1. Понятие динамической структуры данных ............................................................... 109
4.2. Двоичные деревья ........................................................................................................ 110
Сбалансированные двоичные деревья (уравновешенные) ................................................ 127
4.3. Списки ........................................................................................................................... 128
4.4. Стеки ............................................................................................................................. 143
Обшие понятия ....................................................................................................................... 143
Реализация стеков при помощи буфера фиксированного размера ................................... 143
Реализация стеков при помощи списков ............................................................................. 148
Использование стеков для обхода двоичных деревьев ...................................................... 153
Реализация быстрой сортировки с использованием стека ................................................. 166
4.5. Очереди ......................................................................................................................... 179
Общие понятия ....................................................................................................................... 179
Реализация очередей при помощи циклического буфера .................................................. 180

3
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Реализация очередей при помощи списка ........................................................................... 185


5. МАТРИЦЫ ............................................................................................................................. 190
5.1. Понятие матрицы ......................................................................................................... 190
5.2. Внутренняя структура памяти – вектор ..................................................................... 191
5.3. Представление матриц в оперативной памяти .......................................................... 191
5.4. Представление матриц “по строкам” ......................................................................... 193
5.5. Представление матриц “по столбцам” ....................................................................... 203
5.6. Определяющий вектор ................................................................................................ 210
5.7. Вектор Айлифа ............................................................................................................. 216
6. ЗАДАНИЯ............................................................................................................................... 225
– I – Объявление базовых классов ................................................................................. 225
– II – Работа с таблицами ............................................................................................... 226
– III – Сортировки ........................................................................................................... 227
– IV – Динамические структуры данных ...................................................................... 228
– V – Матрицы ................................................................................................................. 230
БИБЛИОГРАФИЯ....................................................................................................................... 232
ПРИЛОЖЕНИЕ 1. БАЗОВЫЕ КЛАССЫ НА C# .................................................................. 233
ПРИЛОЖЕНИЕ 2. ТАБЛИЦЫ НА C# .................................................................................... 238
ПРИЛОЖЕНИЕ 3. СОРТИРОВКА НА C# ............................................................................. 253
ПРИЛОЖЕНИЕ 4. ДВОИЧНЫЕ ДЕРЕВЬЯ НА C# ............................................................. 260
ПРИЛОЖЕНИЕ 5. СПИСКИ НА C# ....................................................................................... 266
ПРИЛОЖЕНИЕ 6. СТЕКИ НА C# ........................................................................................... 274
ПРИЛОЖЕНИЕ 6. ОЧЕРЕДИ НА C# ..................................................................................... 295
ПРИЛОЖЕНИЕ 8. МАТРИЦЫ НА C# .................................................................................... 300

4
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Программа = Алгоритм + Структуры данных


Niklaus Wirth, 1976

1. ВВЕДЕНИЕ В СТРУКТУРЫ ДАННЫХ

1.1. Общие понятия и определения

Определение:

Под абстрактной структурой данных (АСД), или просто структурой


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

Слово „абстрактной” в определении структуры данных, подчеркивает, структура данных


исследуется безотносительно способа ее представления в памяти компьютера.
Структура данных ничего не говорит об отдельных частях данных, входящих в ее состав
(точнее об их содержимом). Содержимое может формироваться в процессе работы.
Структура может потребовать от ее составных частей, что бы они соответствовали самой
структуре. Однако, любая информация, содержащаяся в элементах данных, никак не должна
зависеть от самой структуры.
Использованный термин элемент означает часть абстрактной структуры данных. Элемент
может быть значением простого типа, или может быть другой структурой даны, или ссылкой
на другие структуры данных. Тем самым можно создавать иерархические структуры данных,
а так же последовательности структур данных.
Будем различать статические и динамические структуры данных.
Определение:

Под статической структурой данных будем понимать такую СД, у которой


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

Пример: Матрицы фиксированных размеров, таблицы с фиксированным количеством


табличных записей, вектора с постоянным количеством элементов.
Определение:

Под динамической структурой данных будем понимать такую СД, у


которой в процессе работы с ней изменяется как информация,
содержащаяся в элементах данных, так и число элементов
(количественный состав)

Пример: Связные списки различных типов, стеки, очереди, двоичные деревья.

5
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

1.2. Базовые классы


Для демонстрации методов организации различных структур, данных будем использовать
шаблонные класс. Каждый такой класс будет параметризован классом-параметром,
представляющим один элемент соответствующей структуры данных. В качестве аргумента
при объявлении объекта шаблонного класса будем использовать конкретные классы,
наследующие от абстрактного класса elem.
Абстрактный класс elem не может быть инстанциирован. Он только объявляет те чистые
виртуальные функции, которые обязательно должны быть переопределены в любом
конкретном классе, который в дальнейшем будет использоваться для представления
элементов при создании конкретных структур данных. Абстрактный класс elem содержит
также перегрузки операций сравнения, основанные на чистой виртуальной функции cmp().
Объявление абстрактного класса elem выглядит следующим образом:
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
//
// A b s t r a c t c l a s s "e l e m"
//
class elem
{
public:
virtual int fscanf_el(FILE *f)=0;

virtual void show(const char* opening=NULL,


const char* ending=NULL)=0;

virtual int free() = 0;

virtual int cmp(elem& e2) = 0;

int operator > (elem& e2)


{
return cmp(e2)>0;
}

int operator >= (elem& e2)


{
return cmp(e2)>=0;
}

6
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int operator < (elem& e2)


{
return cmp(e2)<0;
}

int operator <= (elem& e2)


{
return cmp(e2)<=0;
}

int operator == (elem& e2)


{
return cmp(e2)==0;
}

int operator != (elem& e2)


{
return cmp(e2)!=0;
}

protected:
void error(char* message)
{
fprintf(stderr, ”%s”, message);
printf("Press any key to fin...\n");
getch();
exit(1);
}
};

На базе абстрактного класса elem построим конкретный класс usual_elem, который в


дальнейшем будем использовать для демонстрации организации структур данных различных
типов. Объекты класса usual_elem (обычный элемент, для текущего использования) будут
хранить обычную информацию об одном человеке (будь то сотрудник, или студент). Такая
информация будет содержать фамилию (поле name), год рождения (поле year) и зарплату
(поле salary). Объявление класса usual_elem выглядит следующим образом:
//
// C l a s s "u s u a l _ e l e m"
//
class usual_elem : public elem
{
protected:

7
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

char name[16];
int year;
double salary;

public:
usual_elem()
{
name[0]='\0';
year=0;
salary=0.0;
}

usual_elem(char* init_name, int init_year, double init_salary)


{
strcpy(name, init_name);
year=init_year;
salary=init_salary;
}

virtual int fscanf_el(FILE *f)


{
return fscanf(f, "%s %d %lf", name, &year, &salary);
}

virtual void show(const char* opening=NULL,


const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
if(name[0]) // Or better if(free()==0), or if(!free())
printf("%-15s %4d %.2lf", name, year, salary);
printf("%s", ending);
}

virtual int free()


{
return name[0]=='\0';
}

8
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual int cmp(elem& e2)


{
return strcmp(this->name, ((usual_elem&)e2).name);
}
};

Упражнения.
1. Перегрузите операцию вставки в классе usual_elem.
2. Перегрузите операцию извлечения в классе usual_elem.
3. Перегрузите непосредственно (без использования функции сравнения cmp()) операции
сравнения в классе usual_elem.
Создадим текстовый файл с именем, например, ”stud.txt”, из которого в дальнейшем будем
считывать информацию для загрузки различных структур данных. Содержимое файла
”stud.txt” будет выглядеть следующим образом:
Green 1987 350.00
Red 1980 450.00
Blue 1981 500.00
Gray 1968 900.00
Orange 1984 550.00
White 1980 600.00
Cyan 1975 800.00
Yellow 1988 300.00
Magenta 1983 600.00
Black 1981 500.00

Наконец, создадим абстрактный класс ”SD”, который послужит в дальнейшем в качестве


базового класса для объявления конкретных шаблонных классов для представления
различных структур данных.
//
// A b s t r a c t c l a s s "S D"
//
class SD
{
protected:
static FILE* pf;
static long ncomp;

public:

SD()
{
}

9
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

SD(char* file_name)
{
if(!(pf=fopen(file_name, "rt")))
{
char *mes = new char[5+strlen(file_name)+12+1];
error(strcat(strcat(strcpy(mes,"File "),file_name),
" not found!\n"));
}
}

~SD()
{
if(pf)
fclose(pf);
}

virtual void show(const char* opening=NULL,


const char* ending=NULL, int nlinepp=20)=0;

long get_ncomp()
{
return ncomp;
}

void reset_ncomp()
{
ncomp=0;
}

protected:

void error(char* message)


{
fprintf(stderr, ”%s”, message);
printf("Press any key to fin...\n");
getch();
exit(1);
}
};
FILE* SD::pf;
long SD::ncomp;

10
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2. ТАБЛИЦЫ

2.1. Понятие таблицы

Под таблицей будем понимать набор элементов, каждый из которых


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

Пример 1.
Таблица квадратов натуральных чисел
Ключ Данные
(значение (значение
аргумента) функции)
0 0
1 1
2 4
3 9
4 16
5 25
6 36

Ключом в данной таблице является значение аргумента, а значение функции представляет


информацию.
Пример 2.
Таблица перевода из системы счисления 10 в системы 2, 8 и 16
Ключ Данные
(значение
Значение Значение Значение
числа в
числа в числа в числа в
системе 10)
системе 2 системе 8 системе 16
0 0000 00 0
1 0001 01 1
2 0010 02 2
3 0011 03 3
4 0100 04 4
5 0101 05 5
6 0110 06 6

11
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

7 0111 07 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 1111 17 F

Ключом в данной таблице является значение числа в системе счисления с основанием 10, а
данные несут информацию о значении этого числа в системах счисления с основаниями 2, 8
и 16.
Пример 3:
Таблица символических имен
Ключ Данные
Symbol name Type Value
!!DATE Text ”09/15/15”
??FILENAME Text ”sumvectr”
??TIME Text ”11:28:21”
A10 Near COD:004E
B10 Near COD:00AD
BINSTR Near COD:01A0
C10 Near COD:0105
N Word DATE:0067
NOMBIN Word DATE:02CF
S10 Near COD:0181
Z Byte DATE:0061
ZMAX Byte DATE:005F
ZT Word DATE01F9

Компиляторы и интерпретаторы используют таблицы символических имен. Ключом в таких


таблицах является идентификатор, а данные несут информацию о типе имени, адресе
размещения в памяти и т.д.
В памяти компьютера таблицы обычно представляются в виде вектора табличных записей,
но используются и списки, или комбинация векторов со списками.
Главной проблемой, связанной со списками, является проблема поиска.

Проблема поиска заключается в нахождении по заданному ключу адреса


(позиции) нахождения табличной записи с таким ключом, или

12
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

заключению, что такой табличной записи в таблице нет.

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


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

Под средней длиной поиска будем понимать среднее количество


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

13
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2.2. Неупорядоченные таблицы


В простых неупорядоченных таблицах, записи располагаются одна за другой (по мере
поступления) без пропусков (свободных мест). Такие таблицы легко составляются. Новая
запись просто добавляется в конец таблицы после предыдущей записи.
Для поиска в таких таблицах используется последовательный просмотр, при котором записи
просматриваются по очереди одна за другой, начиная с первой.
Средняя длина поиска при последовательном просмотре простой неупорядоченной таблицы
из n табличных записей вычисляется по формуле:
n
D   ipi (2.1)
i 1

где i – порядковый номер записи, а pi – вероятность того, что искомая табличная запись
имеет порядковый номер i.
Если искомая запись с равной вероятностью может находиться в любой позиции таблицы,
1
тогда pi  , и средняя длина поиска будет равна:
n
n
i 1 n  1 n  n
D1    (2.2)
i 1 n n 2 2

Простые неупорядоченные таблицы не являются экономичными по времени поиска, поэтому


они не используются в качестве постоянных таблиц в трансляторах (компиляторах или
интерпретаторах). Однако добавление новой табличной записи в таких таблицах занимает
минимальное время, поэтому простые неупорядоченные таблицы иногда используются в
трансляторах в качестве временных таблиц, загружаемых на этапе трансляции. Вначале
вектора представления такой таблицы, часто хранится число, характеризующее максимально
допустимое количество табличных записей и номер первой свободной позиции.
Для демонстрации работы с простыми неупорядоченными таблицами, создадим класс table,
как шаблонный, зависящий от параметрического типа, представляющего одну табличную
запись.
//
// C l a s s "t a b l e"
//
template <class el> class table : public SD
{
protected:
int n; // Number of table records
el *t; // Pointer to table records vector

public:
table<el>(int NMAX=200)
{

14
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

t=new el[NMAX];
n=0;
}

table<el>(char* file_name, int NMAX=200) :


SD(file_name)
{
t=new el[NMAX];
n=0;
int repeated;
while(!feof(SD::pf))
if(t[n].fscanf_el(SD::pf)>0)
{
if ( (repeated=search(t[n]))>=0 )
{
char message[60];
char repeated_str[10];
message[0]='\0';
strcat(message,
"Key coincides with the key in the position: ");
strcat(message, itoa(repeated+1, repeated_str, 10));
strcat(message, "!\n");
error(message);
}
n++;
}
fclose(pf) , pf=NULL;
}

virtual void show(const char* opening =NULL,


const char* ending=NULL, int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
printf("%s", opening);
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{

15
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

printf("Press any key to continue...\n");


getch();
clrscr();
printf(”%s”, opening);
}
printf(”%4d. ”, (i+1));
t[i].show();
}
printf(”%s”, ending);
printf("End of Table. Press any key ...\n");
getch();
}

int search(el e)
{
int position = -1;
for(int i=0; (position==-1) && i<n ; i++)
if(SD::ncomp++, e==this->t[i])
position=i;
return position;
}

int get_n()
{
return n;
}
};

В функции main() продемонстрируем использования класса table для конкретного типа


usual_elem.
void main()
{
clrscr();

table<usual_elem> gr("stud.txt");
gr.show("Group:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf(”%s”, surname);

16
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

usual_elem e(surname, 2000, 0.0);


gr.reset_ncomp();
int pos=gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf(”\n”);
}
}

В функции main() создается простая неупорядоченная таблица, загружаемая записями из


файла ”stud.txt". Затем, после отображения таблицы на экране, пользователю
предоставляется возможность испробовать поиск по ключу в диалоговом режиме. Вариант
вывода результатов может выглядеть следующим образом:
Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of Table. Press any key ...
Enter a name to search: White
There are in the position 6. The number of comparisons: 6
Done ? (y/n)
Enter a name to search: Black
There are in the position 10. The number of comparisons: 10
Done ? (y/n)
Enter a name to search: Green
There are in the position 1. The number of comparisons: 1

17
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 10
Done ? (y/n)

В следующем примере функции main() определяется средняя длина поиска для конкретной
простой неупорядоченной таблицы, созданной на основе файла ”stud.txt”.
void main()
{
clrscr();
table<usual_elem> gr("Stud.txt");
gr.show("Group:\n","");

usual_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
gr.reset_ncomp();
if(gr.search(sample)>=0)
NCOMP+=gr.get_ncomp();
}
fclose(pf);
printf("N=%d, NCOMP=%d, ALS=%.2lf\n", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());
getch();
}

На сей раз вывод будет выглядеть следующим образом:


Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of Table. Press any key ...
N=10, NCOMP=55, ALS=5.50

18
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Как видно из примера, практическая средняя длина поиска 5.5 совпадает с теоретической
(n+1)/2=11/2=5.5.
Упражнения.
1. Перегрузите операцию вставки в классе table.
2. Перегрузите операцию извлечения в классе table.
3. Модифицируйте конструктор с параметрами класса table, так чтобы он определял
количество табличных записей в зависимости от размера файла с исходными данными для
табличных записей.

19
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2.3. Неупорядоченные древовидные таблицы


Иногда в качестве временных таблиц используются неупорядоченные древовидные таблицы,
организованные в виде двоичного дерева. К каждой табличной записи в таких таблицах
добавляются по два указателя: один из них указывает на табличную запись с меньшим
значением ключа, а второй указывает на табличную запись с большим значением ключа.
Нова запись добавляется в конец таблицы, как и в случае простых неупорядоченных таблиц.
После добавления в таблицу очередной записи, значение ее ключа сравнивается со
значением ключа первой записи таблицы. По результатам сравнения, с помощью указателей,
определяется местоположение следующей записи, с ключом которой необходимо сравнить
ключ новой табличной записи. Этот процесс продолжается до тех пор, пока не будет найдена
табличная запись с пустым значением указателя в нужном направлении поиска. В этот
указатель записывается адрес (позиция) новой табличной записи.
В качестве примера, на рис. 2.1 показана неупорядочена древовидная таблица созданная на
основе файла Stud.txt.
Для демонстрации работы с неупорядоченными древовидными таблицами, прежде всего,
создадим на базе класса usual_elem класс tree_like, в который добавим два поля для
использования их в качестве указателей позиций.
//
// c l a s s "t r e e l i k e" e l e m e n t s
//
class tree_like: public usual_elem
{
protected:
int less;
int greater;

public:
tree_like()
{
less=greater=-1;
}
tree_like(char* init_name, int init_year, double init_salary):
usual_elem(init_name, init_year, init_salary)
{
less=greater=-1;
}

int get_less()
{
return less;
}

20
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

00 Green 1987, 350


2 1

01 Red 1980, 450


4 5

02 Blue 1981, 500


9 3

03 Gray 1968, 900


6 -1

04 Orange 1984, 550


8 -1

05 White 1980, 600


-1 7

06 Cyan 1975, 800


-1 -1

07 Yellow 1988, 300


-1 -1

08 Magenta 1983, 600


-1 -1

09 Black 1981, 500


-1 -1

Рис. 2.1 Неупорядоченная древовидная таблица

int set_less(int new_less)


{
return less=new_less;
}

int get_greater()

21
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
return greater;
}

int set_greater(int new_greater)


{
return greater=new_greater;
}

virtual void tree_show(const char* opening =NULL,


const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf(”%s”, opening);
usual_elem::show("", "");
printf(" [%4d, %4d]", less, greater);
printf(”%s”, ending);
}

virtual int fscanf_el(FILE *pf)


{
less=greater=-1;
return usual_elem::fscanf_el(pf);
}
};

Класс tree_table объявляем как шаблонный класс, наследующий от известного уже


шаблонного класса table.
//
// C l a s s "t r e e _ t a b l e"
//
template <class el> class tree_table: public table<el>
{
public:
tree_table<el>(int NMAX=200): table<el>(NMAX)
{
}

tree_table<el>(char* file_name, int NMAX=200):


table<el>(file_name, NMAX)

22
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
for(int i=1; i<n; i++)
{
int forward=1;
int j=0;
while(forward)
if(t[i]<t[j])
if(t[j].get_less()==-1)
t[j].set_less(i), forward=0;
else
j=t[j].get_less();
else
if(t[i]>t[j])
if(t[j].get_greater()==-1)
t[j].set_greater(i), forward=0;
else
j=t[j].get_greater();
}
}

virtual void tree_show(const char* opening =NULL,


const char* ending=NULL, int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
printf("%s", opening);
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp ==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1));
t[i].tree_show();
}
printf("%s", ending);

23
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

printf("End of Table. Press any key ...\n");


getch();
}

int search(el e)
{
int position = -1;
int forward=1;
int i=0;
int cmp_result;

while(forward)
if(SD::ncomp++, (cmp_result=e.cmp(t[i]))==0)
position=i, forward=0;
else
{
if(cmp_result<0)
i=t[i].get_less();
else
i=t[i].get_greater();
if(i==-1)
forward=0;
}
return position;
}
};

Поиск по заданному ключу в древовидной таблице осуществляется аналогично поиску места,


в которое записывается адрес хранения.
Средняя длина поиска в неупорядоченной древовидной таблице, зависит от порядка
поступления табличных записей при создании таблицы. В самом худшем случае, когда
табличные записи поступали упорядоченными по возрастанию (или по убыванию) ключей,
соответствующее двоичное дерево будет однонаправленным, а средняя длина поиска
останется равной n/2, как и в случае простой неупорядоченной таблицы. В самом лучшем
случае, когда порядок поступления табличных записей будет таким, что двоичное дерево,
получаемое в результате, окажется симметричным, средняя длина поиска сокращается до
D2=[log2n+2].
В функции main() демонстрируется создание и использование неупорядоченной
древовидной таблицы.
void main()
{

24
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

clrscr();

tree_table<tree_like> tree_gr("stud.txt");
tree_gr.show("Group:\n", "");
tree_gr.tree_show("Group:\n", "");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
tree_like e(surname, 2000, 0.0);
tree_gr.reset_ncomp();
int pos=tree_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", tree_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", tree_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Вариант отображения результатов может выглядеть так:

Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00

25
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

10. Black 1981 500.00


End of Table. Press any key ...

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


стирается и появляется следующая информация:

Group:
1. Green 1987 350.00 [ 2, 1]
2. Red 1980 450.00 [ 4, 5]
3. Blue 1981 500.00 [ 9, 3]
4. Gray 1968 900.00 [ 6, -1]
5. Orange 1984 550.00 [ 8, -1]
6. White 1980 600.00 [ -1, 7]
7. Cyan 1975 800.00 [ -1, -1]
8. Yellow 1988 300.00 [ -1, -1]
9. Magenta 1983 600.00 [ -1, -1]
10. Black 1981 500.00 [ -1, -1]
End of Table. Press any key ...
Enter a name to search: White
There are in the position 6. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Green
There are in the position 1. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 10. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 3
Done ? (y/n)

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


функцию main():
void main()
{
clrscr();

tree_table<tree_like> gr("stud.txt");
gr.tree_show("Group:\n","");

tree_like sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");

26
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
gr.reset_ncomp();
if(gr.search(sample)>=0)
NCOMP+=gr.get_ncomp();
}
fclose(pf);
printf("N=%d, NCOMP=%d, ALS=%.2lf", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());
printf(", MAX=%.2lf, MIN=%.2lf\n", (gr.get_n()+1)/2.0,
log((double)gr.get_n())/log(2.0)+2.0);
getch();
}

Вывод будет выглядеть следующим образом:


Group:
1. Green 1987 350.00 [ 2, 1]
2. Red 1980 450.00 [ 4, 5]
3. Blue 1981 500.00 [ 9, 3]
4. Gray 1968 900.00 [ 6, -1]
5. Orange 1984 550.00 [ 8, -1]
6. White 1980 600.00 [ -1, 7]
7. Cyan 1975 800.00 [ -1, -1]
8. Yellow 1988 300.00 [ -1, -1]
9. Magenta 1983 600.00 [ -1, -1]
10. Black 1981 500.00 [ -1, -1]
End of Table. Press any key ...
N=10, NCOMP=29, ALS=2.90, MAX=5.50, MIN=5.32

Анализ результатов остаётся в качестве упражнения.


К недостаткам неупорядоченных древовидных таблиц можно отнести: дополнительные
затраты памяти (для хранения указателей) и несколько более сложные алгоритмы
формирования таблицы и поиска записей в сравнении с соответствующими алгоритмами для
простых неупорядоченных таблиц.
Упражнения.
1. Перегрузите операцию вставки для класса tree_table.
2. Перегрузите операцию извлечения для класса tree_table.

27
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2.4. Упорядоченные таблицы


Таблицы могут быть упорядочены по возрастанию цифрового кода ключа, или по убыванию
частоты обращения к табличным записям. В первом случае для поиска записей обычно
используется двоичный поиск, а во втором – последовательный просмотр.
Двоичный поиск состоит в разделении таблицы на две максимально равные части, и в
определении в какой из этих двух частей может находиться запись с указанным ключом.
Часть, которая может содержать ключ, подвергается последующему делению и т.д. Т.к.
таблица упорядочена по возрастанию ключа, для определения, в какой из двух частей может
содержаться запись с заданным ключом, последний сравнивается с ключом записи,
находящейся в точке деления.
//
// C l a s s "s o r t e d t a b l e"
//
template <class el> class sorted_table: public table<el>
{
public:

sorted_table<el>(int NMAX=200): table<el>(NMAX)


{
}

sorted_table<el>(char* file_name, int NMAX=200): table<el>(NMAX)


{
el tmp;
SD::pf=fopen(file_name, "rt");
n=0;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i;
for(i=n-1; i>=0 && (tmp<t[i]); i--)
t[i+1]=t[i];
if(n>0 && i>=0 && tmp==t[i])
{
char message[60];
char repeated_str[10];
message[0]='\0';
strcat(message,
"Key coincides with the key in the position: ");
strcat(message, itoa(i+1, repeated_str, 10));
strcat(message, "!\n");

28
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

error(message);
}
t[i+1]=tmp, n++;
}
fclose(SD::pf) , SD::pf=NULL;
}

// Binary search
int search(el e)
{
int a=0, b=n-1;
int result;

while(a<b)
{
int i=(a+b)/2;
if(SD::ncomp++, (result=e.cmp(t[i]))>0)
a=i+1;
else
if(result<0)
b=i;
else
a=b=i;
}
return (SD::ncomp++, e==t[a])? a : -1;
}

// Fibonaccian search
int fibsearch(el e)
{
int result;
int j=1;
while(fib(j)<n+1)
j++;

int f1=fib(j-2);
int mid=n-f1+1;
int f2=fib(j-3);
int foward=1;

while((SD::ncomp++, result=e.cmp(t[mid-1])) && foward)


if(mid<=0 || result>1)

29
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if(f1==1)
foward=0;
else
{
mid+=f2;
f1-=f2;
f2-=f1;
}
else
if(f2==0)
foward=0;
else
{
mid-=f2;
int t=f1-f2;
f1=f2;
f2=t;
}

return foward ? mid-1 : -1;


}

protected:
int fib(int n)
{
int res;
if(n==0 || n==1)
res=n;
else
if(n>=2)
res=fib(n-1)+fib(n-2);
return res;
}

};

В функции main() демонстрируем двоичный поиск в таблице, упорядоченной по ключу.


void main()
{
clrscr();

sorted_table<usual_elem> sorted_gr("stud.txt");
sorted_gr.show("Group:\n", "");

30
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
sorted_gr.reset_ncomp();
int pos=sorted_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", sorted_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", sorted_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Один из вариантов вывода может выглядеть так:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
Enter a name to search: White
There are in the position 9. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Green

31
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

There are in the position 5. The number of comparisons: 2


Done ? (y/n)
Enter a name to search: Black
There are in the position 1. The number of comparisons: 5
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 4
Done ? (y/n)*/

Для демонстрации поиска по методу Fibonacci, заменим в предыдущей функции строку


int pos=sorted_gr.search(e);

на
int pos=sorted_gr.fibsearch(e);

Вариант результата:
Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
Enter a name to search: Black
There are in the position 1. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Cyan
There are in the position 3. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Gray
There are in the position 4. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Green
There are in the position 5. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Magenta

32
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

There are in the position 6. The number of comparisons: 1


Done ? (y/n)
Enter a name to search: Orange
There are in the position 7. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Red
There are in the position 8. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: White
There are in the position 9. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Yellow
There are in the position 10. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 5
Done ? (y/n)

Средняя длина двоичного поиска вычисляется по формуле


D2  log 2 n  2 (2.3),

что для достаточно больших n почти совпадает с теоретическим нижним пределом для
методов поиска, основанных только на сравнении ключей. Теоретически нижний предел
равняется log2(n+1). Двоичный поиск намного эффективнее последовательного просмотра.
Так, для n = 1000, D1 = 500, а D2 = 11.
Для определения средней длины двоичного поиска в упорядоченной таблице, созданной на
основе файла "stud.txt", модифицируем функцию main():
void main()
{
clrscr();

sorted_table<usual_elem> gr("stud.txt");
gr.show("Group:\n","");

usual_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
gr.reset_ncomp();

33
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if(gr.search(sample)>=0)
NCOMP+=gr.get_ncomp();
}
fclose(pf);
printf("N=%d, NCOMP=%d, ALS=%.2lf", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());
printf(", ALS_TEOR=%.2lf\n", log((double)gr.get_n())/log(2.0)+2.0);
getch();
}

Результат будет выглядеть так:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
N=10, NCOMP=38, ALS=3.80, ALS_TEOR=5.32

Для нахождения средней длины поиска по методу Fibonacci в упорядоченной таблице,


созданной на основе файла "stud.txt", заменим в предыдущей функции main() строку
if(gr.search(sample)>=0)

на
if(gr.fibsearch(sample)>=0)

Вывод на экран будет выглядеть так:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...

34
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

N=10, NCOMP=29, ALS=2.90, ALS_TEOR=5.32

Анализ результатов предлагается провести в качестве упражнения.


Средняя длина поиска при последовательном просмотре в таблице, упорядоченной по
убыванию частот обращения к записям, существенно зависит от распределения частот
n
обращения и вычисляется по общей формуле D   i  pi . В том случае, когда относительно
i 1

небольшое количество записей ищутся очень часто, средняя длина поиска может оказаться
значительно меньше средней длины при двоичном поиске. Это иногда используют при
создании таблиц в трансляторах, например таблица ключевых слов входного языка.
Упорядочение таблиц требует дополнительных временных затрат. Поэтому упорядоченные
таблицы используются как постоянные таблицы транслятора. Иногда упорядочиваются и
временные таблицы, хотя их упорядочение создает некоторые сложности. Проблема состоит
в том, что временные таблицы создаются в процессе трансляции и в большинстве случаев
тут же используются и для поиска. Будучи частично заполненными, такие таблицы требуют
проверки: не была ли включена очередная табличная запись на каком-то из предыдущих
этапов работы транслятора. Поэтому упорядочение временных таблиц необходимо
выполнять одновременно с их заполнением.
Для сокращения временных затрат процессора на упорядочение временных таблиц, иногда
используют секционирование, при котором таблица разбивается на разделы,
соответствующие различным интервалам значений ключей. Разделы упорядочены, а внутри
разделов записи не упорядочиваются. Для поиска записей используется комбинированный
метод. Например, раздел отыскивается двоичным поиском, а внутри раздела используется
последовательный просмотр.
Упражнения.
1. Перегрузите операцию вставки в классе sorted_table.
2. Перегрузите операцию извлечения в классе sorted_table.
3. Замените в классе sorted_table рекурсивный вариант функции fib() на итеративный.
4. Измените в классе sorted_table код функции поиска по методу Fibonacci fibsearch()
так, чтобы она не использовала функцию fib().

35
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2.5. Таблицы с прямым доступом


Пусть имеется таблица из m табличных записей, все записи имеют различные значения
ключей k0, k1, k2,..., km-1, и таблица отображается на вектор T[0], T[1], …, T[n-1], где m≤n. Если
определена функция f(k), такая, что для любого ключа ki, i=0, ..., m-1, f(ki) имеет
целочисленное значение в пределах от 0 до n-1, причем f(ki)≠f(kj), i≠j, тогда табличная запись
с ключом K отображается однозначно в вектор в элемент T[f(K)].
Функция f(K) называется функцией распределения (расстановки или Hash функцией). Она
обеспечивает расчет для каждой табличной записи соответствующего индекса вектора T.
Доступ к табличной записи с ключом K осуществляется в этом случае, непосредственно
путем вычисления значения f(K). Таблицы, для которых существует и известна (описана)
функция распределения, называются таблицы с прямым доступом. Средняя длина поиска в
таких таблицах минимальна и равна D3=1.
Выбор функции распределения, которая бы обеспечивала взаимно однозначное отображение
ключа табличной записи в позицию ее хранения, в общем случае, является достаточно
сложной задачей. Практически она решается только для постоянных таблиц с заранее
известным набором значений ключей. Например: таблица перекодировки, таблица символов
входного языка транслятора.

36
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2.6. Таблицы с распределением (случайное перемешивание)

Общие понятия

Так как, на практике, взаимно однозначное отображение ключа в позицию хранения


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

Таблицы с открытым перемешиванием

В методе открытого перемешивания, для отображения таблицы в вектор длины n,


используется следующий алгоритм размещения табличной записи с заданным ключом K:
1. Вычисляем i=f(K). Переходим к шагу 2.
2. Если позиция i свободна, тогда записываем в эту позицию табличную запись с ключом K
и завершаем алгоритм. В противном случае переходим к шагу 3.
i  1, if i  1  n
3. Полагаем i=(i+1) mod n и переходим к шагу 2: i  
 0, if i  1  n
При добавлении новых записей, алгоритм остается детерминированным до тех пор, пока
вектор, в который отображается таблица, содержит, по крайней мере, одну свободную
позицию.
Если это условие не выполняется, возможно зацикливание, против которого надо применять
специальные меры. Например, ввести счетчик проверенных позиций, если это число
становится больше n, необходимо останавливать работу алгоритма.
Для поиска табличной записи с ключом K используется следующий алгоритм:
1. Вычисляем i=f(K). Переходим к шагу 2
2. Если позиция i свободна, делаем вывод, что записи с ключом K в таблице нет, и
завершаем алгоритм. Если позиция занята и ключ записи, находящейся в ней, совпадает с
ключом K, тогда объявляем о нахождении записи и завершаем алгоритм. Если ключи не
совпали, переходим к шагу 3.
i  1, if i  1  n
3. Полагаем i=(i+1) mod n и переходим к шагу 2: i  
 0, if i  1  n

37
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

При поиске алгоритм всегда детерминирован, если таблица содержит табличную запись с
ключом K, или в векторе есть, по крайней мере, одна свободна позиция. Если ни одно из этих
условий не выполняется, возможно зацикливание, против которого надо применять
специальные меры. Например, ввести счетчик проверенных позиций, если это число
становится больше n, необходимо останавливать работу алгоритма.
Создадим на базе класса usual_elem класс hashing_elem, который будет дополнен функцией
распределения.
//
// C l a s s "h a s h i n g _ e l e m"
//
class hashing_elem : public usual_elem
{
public:
hashing_elem()
{
}

hashing_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
}

int hf(int n) // hashing function


{
return (name[0]-'A')%n;
}
};

В этом варианте значения функции распределения совпадают с порядковым номером в


алфавите (начиная с нуля) первой буквы фамилии. Порядковый номер модулируется по
длине вектора, в который отображается таблица.
Вот пример более сложной функции hf():
int hf(int n) // hashing function
{
int sum=0;
for(int i=0; i<4 && i<strlen(name); i++)
sum+=*(unsigned char*)(name+i);
return sum%n;
}

Упражнения.
1. Перегрузите операцию вставки в классе hashing_elem.

38
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2. Перегрузите операцию извлечения в классе hashing_elem.

На базе шаблонного класса table объявляем шаблонный класс hashing_table, который


обеспечит нам работу с таблицами с открытым перемешиванием.
//
// C l a s s "h a s h i n g t a b l e"
//
template<class el> class hashing_table: public table<el>
{
protected:
int m;

public:
hashing_table<el>(char* file_name, int NMAX=200): table<el>(NMAX)
{
n=NMAX, m=0;
el tmp;
int repeated;
SD::pf=fopen(file_name, "rt");
m=0;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i=tmp.hf(n);
repeated=-1;
while((repeated==-1) && !t[i].free())
{
if(tmp==t[i])
repeated=i;
else
i=(i+1)%n;
}
if(repeated!=-1)
{
char message[60];
char repeated_str[10];
message[0]='\0';
strcat(message,
"Key coincides with the key in the position: ");
strcat(message, itoa(repeated+1, repeated_str, 10));
strcat(message, "!\n");
error(message);

39
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
t[i]=tmp;
m++;
}
fclose(SD::pf), SD::pf=NULL;
}

int search(el e)
{
int position=-1;
int i=e.hf(n);
while((position==-1) && !t[i].free())
if(SD::ncomp++, e==t[i])
position=i;
else
i=(i+1)%n;
return position;
}

int get_m()
{
return m;
}
};

В функции main()создаем таблицу с открытым перемешиванием и загружаем ее из файла


Stud.txt. Далее выводим таблицу на экран и осуществляем поиск по ключу нескольких
табличных записей.
void main()
{
clrscr();
hashing_table<hashing_elem> hashing_gr("stud.txt", 15);
hashing_gr.show("Group:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
cout << "Enter a name to search: ";
cin >> surname;
hashing_elem e(surname, 2000, 0.0);
hashing_gr.reset_ncomp();
int pos=hashing_gr.search(e);

40
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if(pos<0)
cout << "No table! " << "The number of comparisons: "
<< hashing_gr.get_ncomp() << "\n";
else
cout << "There are in the position " << (pos+1)
<< ". The number of comparisons: "
<< hashing_gr.get_ncomp() << "\n";
cout << "Done ? (y/n) ";
ch = getch();
cout << endl;
}
}

Ниже представлен один из вариантов вывода:


Group:
1.
2. Blue 1981 500.00
3. Red 1980 450.00
4. Cyan 1975 800.00
5. Black 1981 500.00
6.
7. Green 1987 350.00
8. Gray 1968 900.00
9. White 1980 600.00
10. Yellow 1988 300.00
11.
12.
13. Magenta 1983 600.00
14.
15. Orange 1984 550.00
End of Table. Press any key ...
Enter a name to search: White
There are in the position 9. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 5. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0

41
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Done ? (y/n)

В данном примере таблица выглядит следующим образом:

00│
01│ Blue, 1981, 500
02│ Red, 1980, 450
”Green”
03│ Cyan, 1975, 800
”Red”
04│ Black, 1981, 500
”Blue”
05│
”Gray”
06│ Green, 1987, 350
”Orange”
07│ Gray, 1968, 900
”White”
08│ White, 1980, 600
”Cyan”
09│ Yellow, 1988, 300
”Yellow”
10│
”Magenta”
11│
”Black”
12│ Magenta,1983, 600
13│
14│ Orange, 1984, 550

Из приведенной таблицы видно, что позиции 0, 5, 10, 11, 13 остались свободными. По ходу
формирования таблицы, в позициях 1, 2, 6, 7 возникали коллизии. Стоит заметить, что
коллизия, возникшая в позиции 7 при занесении записи с ключом White, является вторичной.
Теоретические исследования и практические эксперименты для поиска в методе
распределения с открытым перемешиванием, показали, что при случайном и равномерном
распределении табличных записей по позициям вектора в интервале [0, n-1] при помощи
функции расстановки, средняя длина поиска не зависит от длины таблицы, а зависит только
m
от коэффициента заполненности   , где m – длина таблицы, а n – длина вектора в
n
который эта таблица отображается.
Это очень важное свойство, особенно для больших таблиц. Детерминированные таблицы,
как упорядоченные, так и неупорядоченные, не обладают таким свойством. В
детерминированных таблицах средняя длина поиска растет с ростом длины таблицы.
2 
Приближенная формула D( )  , для средней длины поиска при открытом
2  2
перемешивании дает достаточное совпадение с экспериментальными данными для σ≤0,85.
Формула получена в предположении случайного и равномерного распределения табличных
записей по позициям вектора отображения.
Следующая таблица демонстрирует зависимость средней длины поиска от коэффициента
заполненности σ.

42
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

σ 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9


D(σ) 1.06 1.13 1.21 1.33 1.50 1.75 2.17 3.00 5.5
Эксперименты показали также, что для σ=1 средняя длина поиска не превосходит 20.
Если распределение табличных записей по позициям вектора отображения не является
равномерным, тогда числа во второй строке рассматриваемой таблицы возрастают в среднем
в 1.5 – 2 раза. Но даже с такой поправкой распределение с открытым перемешиванием имеет
более маленькую среднюю длину поиска, по сравнению с другими методами. Например, для
таблицы из 400 записей, отображаемой в вектор длины 512, средняя длина поиска при
распределении с открытым перемешиванием, принимая во внимание неравномерное
распределение, не превышает 4.5 – 6. В тех же условиях, средняя длина двоичного поиска
рана 10, а средняя длина последовательного просмотра – 200.
Недостатки: при случайном распределении с открытым перемешиванием примерно 20%
памяти, отводимой под таблицу, не используется.
Для определения средней длины поиска в таблице с открытым перемешиванием из нашего
примера, запускаем следующую функцию main():
void main()
{
clrscr();

hashing_table<hashing_elem> hashing_gr("stud.txt", 15);


hashing_gr.show("Group:\n","");

hashing_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
hashing_gr.reset_ncomp();
if(hashing_gr.search(sample)>=0)
NCOMP+=hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", hashing_gr.get_m(),
hashing_gr.get_n(), NCOMP, (double)NCOMP/hashing_gr.get_m());
double sigma = (double)hashing_gr.get_m()/hashing_gr.get_n();
printf(", m/n=%.2lf, D(m/n)=%.2lf\n", sigma, (2.-sigma)/(2.-2.*sigma));
getch();
}

43
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Получаем следующий результат:


Group:
1.
2. Blue 1981 500.00
3. Red 1980 450.00
4. Cyan 1975 800.00
5. Black 1981 500.00
6.
7. Green 1987 350.00
8. Gray 1968 900.00
9. White 1980 600.00
10. Yellow 1988 300.00
11.
12.
13. Magenta 1983 600.00
14.
15. Orange 1984 550.00
End of Table. Press any key ...
m=10, n=15, NCOMP=16, ALS=1.60, m/n=0.67, D(m/n)=2.00

Анализ результата остается в качестве упражнения.


Упражнения.
1. Перегрузите операцию вставки в классе hashing_table.
2. Перегрузите операцию извлечения в классе hashing_table.
3. Разработайте улучшенный вариант функции распределения.

Таблицы с распределением с внешним связыванием (с внешними цепочками


переполнения)

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


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

44
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Primary Secondary
Table Table
00│ │ 00|Gray, 1968,900│
01│Blue, 1981,500│02 01│Cyan, 1975,800│
02│Red, 1980,450│01 02│Black, 1981,500|
”Green”
03│ │ 03│ │
”Red”
04│ │ 04│ │
”Blue”
05│ │ 05│ │
”Gray”
06│Green, 1987,350│00 06│ │
”Orange”
07│White, 1980,600│ 07│ │
”White”
08│ │ 08│ │
”Cyan”
09│Yellow,1988,300│ 09│ │
”Yellow”
10│ │ 10│ │
”Magenta”
11│ │ 11│ │
”Black”
12│Magenta,1983,600│ 12│ │
13│ │ 13│ │
14│Orange,1984,550│ 14│ │

В таблицах со случайным распределением с внешними цепочками переполнения, средняя


длина поиска, при случайном и равномерном распределении табличных записей,
определяется по формуле:

m 1
Dm, n   1  , n – длина вектора отображения, m – длина таблицы.
2n
Для демонстрации работы с таблицами распределения с внешними цепочками, в первую
очередь, объявляем класс hashing_linked_elem как производный от класса usual_elem и
дополненный полем next, для создания связных цепочек.
//
// C l a s s "h a s h i n g _ l i n k e d _ e l e m"
//
class hashing_linked_elem: public usual_elem
{
protected:
int next;
public:
hashing_linked_elem()
{
next = -1;
}

45
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

hashing_linked_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
next = -1;
}

virtual void hashing_linked_show(const char* opening=NULL,


const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
usual_elem::show("", "");
if(!free())
printf(" [%4d]", next);
printf("%s", ending);
}

int hf(int n) // hashing function


{
return (name[0]-'A')%n;
}

int get_next()
{
return next;
}

int set_next(int new_next)


{
return next=new_next;
}
};

Упражнения.
1. Перегрузите операцию вставки в классе hashing_linked_elem.
2. Перегрузите операцию извлечения в классе hashing_linked_elem.

46
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Затем, на базе абстрактного класса SD, объявим конкретный шаблонный класс


extern_hashing_table, который нам даст возможность создавать распределяемые таблицы с
внешними цепочками переполнения.
//
// C l a s s "e x t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class extern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;

public:
extern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )
{
repeated=i;
t[i].show("", " !!!\n");
}

47
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

else
{
if(t[i].get_next()==-1)
{
int j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )
{
char message[60];
char repeated_str[10];
message[0]='\0';

48
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

//strcat(message, "Key coincides with the key in the position: ");


//strcat(message, itoa(repeated+1, repeated_str, 10));
//strcat(message, "!\n");
strcat(message, "Key coincides !!!\n");
error(message);
}
}
}
fclose(SD::pf), SD::pf=NULL;
}

virtual void show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void primary_show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";

49
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i% nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void secondary_show(const char* opening=NULL,


const char* ending=NULL, int nlinepp=20)
{
//clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); v[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();

50
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int search(el e)
{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
if(SD::ncomp++, e==t[i])
position=i;
else
if((i=t[i].get_next())!=-1)
do
{
if(SD::ncomp++, e==v[i])
position=i;
else
i=v[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}

int get_n()
{
return n;
}

int get_m()
{
return m;
}

protected:
int countn()
{
return 200;
}
};

В функции main(), используя файл stud.txt, создадим таблицу распределения с внешними


цепочками переполнения для случая n=15 и организуем поиск табличных записей.
void main()
{

51
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

clrscr();

extern_hashing_table<hashing_linked_elem> ex_hashing_gr("stud.txt", 15);


ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
ex_hashing_gr.reset_ncomp();
int pos=ex_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n"); }
}

Вариант вывода может выглядеть следующим образом:


Primary table:
1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.

52
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 3. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)

Для n=10 вывод будет:


Primary table:
1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.

53
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

7. Green 1987 350.00 [ 0]


8. Red 1980 450.00 [ -1]
9.
10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 4. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)

Для определения средней длины поиска модифицируем функцию main().


void main()
{
clrscr();

extern_hashing_table<hashing_linked_elem> ex_hashing_gr("stud.txt", 15);


ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");

hashing_linked_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");

54
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
ex_hashing_gr.reset_ncomp();
if(ex_hashing_gr.search(sample)>=0)
NCOMP+=ex_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", ex_hashing_gr.get_m(),
ex_hashing_gr.get_n(), NCOMP, (double)NCOMP/ex_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(ex_hashing_gr.get_m()-1.)/(2.*ex_hashing_gr.get_n()));

getch();
}

Для n=15 результат будет следующим:


Primary table:
1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.

55
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

9.
10.
11.
12.
13.
14.
15.
End of Table. Press any key ...
m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30

А для n=10 результат будет:


Primary table:
1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9.
10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45

Упражнения.
1. Перегрузите операцию вставки в классе extern_hashing_table.
2. Перегрузите операцию извлечения в классе extern_hashing_table.

56
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Таблицы с распределением с внутренним связыванием

Загрузка позиций вектора отображения разбивается на два этапа:


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

00│Black, 1981,500│
01│Blue, 1981,500│00
02│Red, 1980,450│03
”Green”
03│Cyan, 1975,800│
”Red”
04│Gray, 1968,900│
”Blue”
05│ │
”Gray”
06│Green, 1987,350│04
”Orange”
07│White, 1980,600│
”White”
08│ │
”Cyan”
09│Yellow,1988,300│
”Yellow”
10│ │
”Magenta”
11│ │
”Black”
12│Magenta,1983,600│
13│ │
14│Orange,1984,550│

Преимущество этого метода, в сравнении с предыдущим, – экономия памяти, а недостаток –


меньшая гибкость и более сложный алгоритм формирования таблицы.
Средняя длина поиска в данном методе определяется по той же формуле, как и в
m 1
предыдущем: D(m, n)  1  . Этот метод может быть использован для постоянных
2n
таблиц, и для временных которые создаются на одном этапе, скажем трансляции, а
используются на последующих.
Для постоянных таблиц, наиболее часто используемые записи могут быть размещены
первыми, тогда доступ к записям из цепочек будет редкими, что уменьшает среднюю длину
поиска. Примечательно, что при использовании внутренних цепочек, все позиции вектора

57
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

отображения могут быть заполнены (т.е. m=n), а средняя длина поиска при случайном и
однородном распределении не превысит 1.5.
Для демонстрации работы с таблицами организованными по методу распределения с
внутренними цепочками переполнения, создадим на базе абстрактного класса SD шаблонный
класс extern_hashing_table, который позволит нам создавать таблицы по методу
распределения с внутренними цепочками переполнения.
//
// C l a s s "i n t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class intern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;

public:
intern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int i, j;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )

58
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
repeated=i;
t[i].show("", " !!!\n");
}
else
{
if(t[i].get_next()==-1)
{
j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )

59
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
char message[60];
char repeated_str[10];
message[0]='\0';
//strcat(message, "Key coincides with the key in the position: ");
//strcat(message, itoa(repeated+1, repeated_str, 10));
//strcat(message, "!\n");
strcat(message, "Key coincides !!!\n");
error(message);
}
}
}

fclose(SD::pf), SD::pf=NULL;

el empty;
int k=0;
for(j=0; j<n; j++)
if(!v[j].free())
{
i=v[j].hf(n);
int j1=j;
do
{
while(!t[k].free())
k++;
t[i].set_next(k);
i=k;
t[i]=v[j1];
t[i].set_next(-1);
int jtmp=j1;
j1=v[j1].get_next();
v[jtmp]=empty;
}
while (j1!=-1);
}

delete v;
}

virtual void show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)

60
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i% nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void primary_show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();

61
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

int search(el e)
{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
do
{
if(SD::ncomp++, e==t[i])
position=i;
else
i=t[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}

int get_n()
{
return n;
}

int get_m()
{
return m;
}

protected:
int countn()
{
return 200;
}
};

В функции main() создадим на базе файла stud.txt таблицу с внутренними цепочками


переполнения для n=15 и продемонстрируем поиск табличных записей.
void main()
{

62
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

clrscr();

intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 15);


in_hashing_gr.primary_show("Primary table:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
in_hashing_gr.reset_ncomp();
int pos=in_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Вариант вывода может выглядеть следующим образом:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.

63
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 5. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)

Для n=10 вывод будет выглядеть так:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 6. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)

64
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Для определения средней длины поиска модифицируем функцию main().


void main()
{
clrscr();

intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 15);


// intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 10);
in_hashing_gr.primary_show("Primary table:\n","");

hashing_linked_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
in_hashing_gr.reset_ncomp();
if(in_hashing_gr.search(sample)>=0)
NCOMP+=in_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", in_hashing_gr.get_m(),
in_hashing_gr.get_n(), NCOMP, (double)NCOMP/in_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(in_hashing_gr.get_m()-1.)/(2.*in_hashing_gr.get_n()));

getch();
}

Для n=15 результат будет следующим:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.

65
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

13. Magenta 1983 600.00 [ -1]


14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30

Для n=10 получим:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45

Таблицы со случайным перемешиванием формируются достаточно просто, не требуют


упорядочения записей и обеспечивают быстрый поиск. Поэтому такие таблицы на практике
используются достаточно часто.
Упражнения.
1. Перегрузите операцию вставки в классе intern_hashing_table.
2. Перегрузите операцию извлечения в классе intern_hashing_table.

Функции распределения

Время вычисления функции расстановки f(k) входит в среднее время поиска, это необходимо
учитывать при выборе алгоритма реализации функции расстановки.
Хорошая функция расстановки должна обеспечивать равномерное распределение табличных
записей по позициям вектора отображения, т.к. неравномерное распределение увеличивает
среднее время поиска. Однако если вычисление значений функции расстановки будет
требовать выполнения слишком большого количества операций, это может свести на нет всю
экономию, полученную за счет равномерности. Т.е. алгоритм вычисления функции
расстановки не должен быть слишком сложным. Рассмотрим несколько способов реализации
функции расстановки:

66
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

1. Один из простых способов основывается на выделении некоторой части из цифрового


кода ключа. Например, пусть максимально допустимый размер таблицы символических
имен не превосходит 256. Тогда функция расстановки в качестве значения может
выдавать последовательность из 8 битов, т.к. 256=28. Можно просто выделить первые 8
битов двоичного кода идентификатора или взять 8 битов из середины кода. Необходимо
лишь обеспечить, по возможности, более равномерное распределение табличных записей
при помощи функции f(k) в интервале [0, 255].
2. Для обеспечения равномерного распределения используется “суммирование” кода
идентификатора: первая половина кода суммируется со второй и из результата
выделяются 8 битов. Можно разделить код ключа на куски по 8 битов, просуммировать
куски и промодулировать результат по модулю 28. Последняя модификация имеет
некоторую теоретическую вероятность: в предположении статистической независимости
суммирования кусков получается распределение близкое к равномерному.
3. Еще один метод вычисления функции расстановки заключается в делении. Для вектора
представления длины n, двоичные коды ключей рассматриваются как целые
положительные числа и делятся на n. Эксперименты показывают, что остатки от деления
почти равномерно распределяется на интервале [0, n-1] и могут быть использованы в
качестве значений функции распределения.
Экспериментальная проверка описанных методов, для таблиц с распределением открытым
перемешиванием показала, что простое выделение кусков из кода идентификатора,
увеличивает среднюю длину поиска в сравнении с теоретической, определяемой по формуле:
2 
D( )  , в 4-5, а то и более раз. Средняя длина поиска при суммировании кусков с
2  2
последующим модулированием по модулю 2k, почти в два раза больше теоретической, а для
деления, средняя длина поиска практически совпадает с теоретической для σ≤8.85.

67
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

3. СОРТИРОВКА

3.1. Общие понятия

Под сортировкой будем понимать операцию упорядочения элементов


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

Традиционно различают внутреннюю и внешнюю сортировки. Внутренняя сортировка


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

Случай внутренней сортировки

Положим, что данные, подвергающиеся сортировке, хранятся в оперативной памяти в


векторе t. Каждый элемент t[i] этого вектора является объектом класса-параметра el, в
котором перегружены операции сравнения. Т.е. вполне допустимы выражения:
t[i]<t[j], t[i]<=t[j], и т.д.
Положим, что функция swap(i,j) меняет местами элементы t[i] и t[j] вектора,
функция cmp(i,j) возвращает целое число равное
-1, если t[i]<t[j];
0, если t[i]==t[j];
1, если t[i]>t[j].

Определение 1. Пара индексов (i,j), таких, что i<j, а t[i]>t[j]


называется инверсией.

Определение 2. Вектор t называется отсортированным в возрастающем


порядке, если он не содержит ни одной инверсии.

68
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Определение 3. Вектор t называется отсортированным в убывающем


порядке, если для любой пары индексов i, j таких, что i<j, следует что
или (i,j) инверсия, или t[i]==t[j].

Определение 4. Алгоритм сортировки называется устойчивым, если он в


сортируемом векторе никогда не меняет относительный порядок любых
двух элементов с одинаковыми ключами. Т.е. для любой пары i, j: i<j;
t[i]=t[j], i—>i', j—>j' => i'<j'.

Стабильность может быть решающим требованием, например, когда по некоторому ключу


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

О сложности алгоритмов сортировки

При характеристике алгоритмов сортировки используется понятие величины (например,


количество сравнений ключей), которая зависит от натурального числа n (например,
количество элементов), относительно другой известной величины, которая также зависит от
этого натурального числа. Говорится, что величина k имеет порядок сравнимый с величиной
m, обозначается k=O(m), читается “k имеет порядок o от m”, если:
k
lim m  const
n 

Из этой формулы следует, что величины n-1, n, 2n, n+3 имеют один и тот же порядок O(n). А
величина n2 имеет более высокий порядок.
Алгоритм сортировки n элементов, основанный на сравнении ключей имеет минимальную
сложность O(n) или выше.
Покажем это посредством математической индукции:
Для k = 2 – одно сравнение. Предположим, что для k = n-1 требуются n-2 сравнения.
Добавим еще один элемент, тогда необходимо выполнит еще, по крайней мере, одно
сравнение, получаем n-2+1 = n-1 сравнений. Таким образом, минимальная сложность имеет
порядок O(n).
Теоретически доказано [см. 1], что средняя сложность любого алгоритма сортировки,
который оперирует сравнением ключей, не может быть меньше, чем O(nlog2n).
У тривиальных алгоритмов сортировки, основанных на сравнении ключей, как
максимальная, так и средняя сложности имеют порядок O(n2).

69
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Средняя сложность любого хорошего алгоритма сортировки, основанного на сравнении


ключей, имеет порядок O(nlog2n), в то время как его максимальная сложность может иметь
порядок O(nlog2n) или O(n2).

70
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

3.2. Классы для исследования алгоритмов сортировки


Будем использовать абстрактный класс elem и производный от него конкретный класс
usual_elem.

Объявляем шаблонный класс vector параметризованный классом el.


//
// C l a s s "v e c t o r"
//
template <class el> class vector: public SD
{
protected:
int n; // Number of elements
el* t; // Pointer to vector of elements

public:
vector<el>(int NMAX=200)
{
n=NMAX;
t=new el[n];
}

vector<el>(char* file_name, int NMAX=200): SD(file_name)


{
t=new el[NMAX];
n=0;
while(!feof(SD::pf))
if(t[n].fscanf_el(SD::pf)>0)
n++;
fclose(SD::pf), SD::pf=NULL;
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
printf("%s", opening);
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)

71
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1));
t[i].show("", "\n");
}
printf("%s", ending);
printf("End of vector. Press any key ...\n");
getch();
}

int search(el e)
{
int position = -1;
for(int i=0; (position==-1) && i<n ; i++)
if(SD::ncomp++, e==this->t[i])
position=i;
return position;
}

int get_n()
{
return n;
}

long get_ncomp()
{
return SD::ncomp;
}

void reset_ncomp()
{
SD::ncomp=0;
}

protected:

void swap(int i, int j)


{

72
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

el tempor=t[i];
t[i]=t[j];
t[j]=tempor;
}
};

Упражнения.
1. Перегрузите операцию вставки в классе vector.
2. Перегрузите операцию извлечения в классе vector.

73
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

3.3. Сортировка обменами


Идея метода сортировки обменами (engl. Exchange sort) состоит в просмотре элементов
вектора, таким образом, чтобы каждый просмотр уменьшал количество инверсий. Просмотр
продолжается до тех пор, пока не останется ни одной инверсии. Проблема состоит в поиске
очередной инверсии (i, j).
Схематически это выглядит так:
while (este_inversie (i, j))
swap(i, j);

Сортировка обменами состоит в последовательных модификациях типа t[i]t[j], до тех


пор, пока элементы вектора не станут упорядоченными.
К этой категории относятся как пузырьковая сортировка (bubblesort) – один из самых
слабых алгоритмов сортировки, так и быстрая сортировка (quicksort) – один из самых
хороших алгоритмов сортировки.
Пузырьковая сортировка
Состоит в сравнении t[i] с t[i+1], если порядок хороший, сравнивается t[i+1] с t[i+2],
если порядок плохой меняются местами t[i] и t[i+1] и затем сравнивается t[i+1] с
t[i+2]. После первого просмотра вектора, на последнюю позицию попадает элемент с
самым большим значением ключа, после второго просмотра следующий по величине
элемент попадает на предпоследнюю позицию и т.д.
Алгоритм имеет сложность O(n2).

void bubble_sort()
{
BOOLEAN inversion;
do
{
inversion = FALSE;
for(int i=0; i<n-1; i++)
if(ncomp++, t[i]>t[i+1])
{
swap(i,i+1);
inversion = TRUE;
}
}
while (inversion);
}

Минимальная сложность имеет порядок O(n). Если вектор изначально отсортирован, то


переменная inversion никогда не получит значение TRUE.

74
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Максимальная сложность имеет порядок O((n-1)2)=O(n2). Если минимальный элемент


изначально расположен в позиции n-1, тогда потребуется n-1 выполнение внешнего цикла,
что бы минимальный элемент получил индекс 0.
Для каждого исполнения тела внешнего цикла требуются n-1 сравнение. Таким образом
всего будет выполнено: (n-1)*(n-1)=(n-1)2 сравнений.
Средняя сложность так же будет O(n2), если минимальный элемент расположен случайным
образом в одной из позиций 0, 1,…, n-1.
Упражнение: Возможны улучшения этого алгоритма, которые, однако, не изменят
существенно его сложность. Улучшите алгоритм за счет сокращения на единицу количества
сравнений после каждого просмотра.
Сортировка по методу пузырька является одним из наихудших алгоритмов сортировки.
Главный недостаток состоит в том, что на каждом шаге очередной элемент непосредственно
сравнивается только со следующим.

75
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

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


Быстрая сортировка (quicksort) была предложена C.A.R. Hoare и использует принципы
“Разделяй и властвуй” и “Равновесия”.
Идея метода состоит в следующем: выбирается один произвольный элемент, получающий
название главного (или pivot), после чего вектор реорганизуется следующим образом:
главный элемент устанавливается на свою окончательную позицию (зависящую от его
величины), слева от главного оказываются размещенными элементы меньшие или равные
главному, а справа от него – элементы большие или равные главному. Процесс продолжается
независимо над подвекотором с элементами слева от главного и над подвектором с
элементами справа от главного. Обработка на каждом шаге заканчивается, когда достигается
подвектор, состоящий из одного элемента.
На базе шаблонного класса vector объявляем производный класс vector_quicksort,
дополненный алгоритмом быстрой сортировки.
//
// C l a s s " v e c t o r q u i c k s o r t"
//
template <class el> class vector_quicksort: public vector<el>
{
public:
vector_quicksort<el>(char* file_name, int NMAX=200):
vector<el>(file_name, NMAX)
{
}

void quicksort(int i=0, int j=-1)


{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
quicksort_intern(i, j);
}

protected:
void quicksort_intern(int i, int j);
int divide(int i, int j);
};

76
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Предположим, что существует функция divide(), которая некоторым образом выбирает


главный элемент, пусть ключ его будет K, и реорганизует вектор таким образом, что главный
элемент получает окончательный индекс imain, все элементы с ключом ≤ K размещаются
слева от главного (получают индексы < imain), а все элементы с ключами ≥ K размещаются

imain-1 imain+1
0 imain n-1

элементы ≤ t[imain] элементы ≥ t[imain]

главный элемент t[imain]

справа от главного (получают индексы >imain). Что касается элементов с ключами равными
ключу главного, то часть из них может быть размещена слева от главного, а часть – справа.
Имеем типично рекурсивный случай:
 параметризация: обрабатывается подвектор t[i]÷t[j]; изначально i=0, j=n-1;
 тривиальный случай: i=j (нечего сортировать);
 переход от сложного случая к более простому, который возможен благодаря функции
divide().

При наличии такой функции divide(), быстрая сортировка запишется в рекурсивной форме
следующим образом:
template <class el>
void vector_quicksort<el>::quicksort_intern(int i, int j)
{
if (j>i)
{
int imain=divide(i,j);
quicksort_intern(i,imain-1);
quicksort_intern(imain+1,j);
}
}

Алгоритм верен для обоих порядков рекурсивных вызовов.


elementul
  
Рассмотрим схему деления со сложностью O(n): principal
4 20 2 10 15 3 12

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

77
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

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


левая и правая позиции встретятся. Их общее значение и будет imain.
Очевидно, что сложность деления не превосходит O(n), или O(j-i) когда деление применяется
к подвектору t[i]÷t[j].

Практические советы по выбору главного элемента

Выбор гланого элемента должен осуществляться так, что бы вероятность случая, когда после
деления левый и правый отрезки существенно различаются по длине, была бы как можно
меньше.
Первая стратегия: при каждом делении главный элемент выбирается случайным образом
среди позиций i, i+1, …, j. Недостаток этого метода – дополнительные расходы времени на
вызов и работу функции-генератора случайных чисел.
Вторая стратегия: в качестве главного элемента выбирается элемент со срединным
значением ключа, среди небольшой выборки из элементов подвектора, подлежащего
делению. Проще всего взять набор из трех элементов с индексами соответственно i, j и
(i+j)/2.

Оба этих метода уменьшают вероятность катастрофического случая O(n2), однако такая
ситуация не исключается. Быстрая сортировка всегда может деградировать. Парадокс
состоит в том, что, являясь одним из лучших алгоритмов внутренней сортировки, быстрая
сортировка не может быть использована при решении задач, в которых верхнее ограничение
сложности сортировки равное knlog2n является непременным.

Алгоритм деления

Существует множество вариантов деления. Все они преследуют, по крайней мере, две цели:
 ускорить внутренние циклы;
 предусмотреть “случайный” характер вектора. Т.е. исключить случайное внесение
порядка среди элементов подвекторов (отрезков), получаемых в результате деления,
заботясь о производительности алгоритма в целом. Необходимо отказываться от соблазна
попутной сортировки в процессе деления.
Р. Седжвик (Sedgewick R. E.) предложил следующий метод деления:
a) устанавливаем главный элемент в позицию i (меняя его местами, в случае
необходимости, с элементом t[i]).
i i+1 j

78
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

b) делим подвектор t[i+1], t[i+2],… t[j], при помощи значения ключа главного
элемента t[i], оставляя t[i] на своем месте. Получается разделение с промежуточной
позицией, например, imain:
imain-1 imain+1
i imain j

elemente ≤ t[i] elemente ≥ t[i]

c) меняем местами элемент t[i] с элементом t[imain] и выдаем значение imain как
результат, возвращаемый функцией divide().

imain-1 imain+1
i imain j

элементы ≤ t[imain] главный элементы ≥ t[imain]


элемент

template <class el> int vector_quicksort<el>::divide(int i, int j)


{
int imain, jmain, imed;
imed =(i+j)/2;
imain = (SD::ncomp++, t[i] < t[imed]) ?
((SD::ncomp++, t[imed] < t[j]) ?
imed
:
(SD::ncomp++, t[i] < t[j]) ? j : i)
:
((SD::ncomp++, t[imed] > t[j]) ?
imed
:
(SD::ncomp++, t[i] > t[j]) ? j : i);

if(imain > i)
swap(i, imain);
imain = i+1, jmain = j;
while(imain < jmain)
{

79
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

while((imain < jmain)&&(SD::ncomp++, t[imain] <= t[i]))


imain++;
while((jmain > imain)&&(SD::ncomp++, t[jmain] >= t[i]))
jmain--;
if(imain < jmain)
swap(imain, jmain);
}
if(SD::ncomp++, t[imain] > t[i])
imain--;
if(imain > i)
swap(i, imain);
return imain;
}

Очевидно, что функция divide() имеет сложность O(n). Внешний цикл


while(imain < jmain)
{
}

проверяет каждый элемент вектора t[0], t[i],… t[n-1] не более двух раз, а остальные
операции выполняются за фиксированное время.
В функции main() создаем вектор и сортируем его методом quicksort:
void main()
{
clrscr();

vector_quicksort<usual_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

Вывод выглядит следующим образом:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00

80
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

6. White 1980 600.00


7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=39, n*log2(n)=33.22, n*n=100

Анализ результата предлагается в качестве упражнения.


Упражнение.
Реорганизуйте конструктор класса sorted_table из 2.4 таким образом, что бы он создавал
вначале простую неупорядоченную таблицу, а затем сортировал бы ее алгоритмом быстрой
сортировки.

Для сортировки по году рождения, создадим на базе класса usual_elem класс year_elem, в
котором переопределим функцию cmp().
//
// C l a s s "y e a r _ e l e m"
//
class year_elem : public usual_elem
{
public:
year_elem()
{
}

year_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
}

81
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual int cmp(elem& e2)


{
int result;
if(this->year < ((year_elem&)e2).year)
result=-1;
else
if(this->year > ((year_elem&)e2).year)
result=1;
else
result=0;
return result;
}
};

В функции main() инстанциируем шаблонный класс vector_quicksort классом-аргументом


year_elem.
void main()
{
clrscr();

vector_quicksort<year_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by year:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

Получим следующий результат:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00

82
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

End of vector. Press any key ...


Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. Red 1980 450.00
4. White 1980 600.00
5. Black 1981 500.00
6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=42, n*log2(n)=33.22, n*n=100

Анализ результата остается в качестве упражнения.


Упражнения.
1. Перегрузите операцию вставки в классе year_elem.
2. Перегрузите операцию извлечения в классе year_elem.

Наконец, объявим на базе класса usual_elem производный класс salary_elem, который


будет сравнивать объекты по полю salary.
//
// C l a s s "s a l a r y _ e l e m"
//
class salary_elem : public usual_elem
{
public:
salary_elem()
{
}

salary_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
}

virtual int cmp(elem& e2)


{
int result;
if(this->salary < ((salary_elem&)e2).salary)

83
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

result=-1;
else
if(this->salary > ((salary_elem&)e2).salary)
result=1;
else
result=0;
return result;
}
};

В функции main() инстанциируем шаблонный класс vector_quicksort классом-аргументом


salary_elem.
void main()
{
clrscr();

vector_quicksort<salary_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.quicksort();
gr.show("Group sorted by salary:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

Резудьтат будет следующим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00

84
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4. Blue 1981 500.00


5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=43, n*log2(n)=33.22, n*n=100

Анализ результата остается в качестве упражнения.


Упражнения.
1. Перегрузите операцию вставки в классе salary_elem.
2. Перегрузите операцию извлечения в классе salary_elem.

Оценка сложности алгоритма быстрой сортировки

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


последовательное деление может давать систематически imain=0 (imain=i) или imain=n-1
(imain=j). В этом случае, при делении вектора на два подвектора, один из них будет иметь
постоянно нулевую длину, а другой – n-1 (j-i). Глубина рекурсивных вызовов может
достигнуть значения n – длины исходного вектора. Т.е. необходимо предусмотреть стек
глубины n (пространственная сложность O(n), что недопустимо). В рекурсивной версии
функции quicksort_intern() невозможно улучшить пространственную сложность. Для
итеративной версии с непосредственным использованием стека (см. 4.4) существует простой
способ: всегда начинать с подвектора меньшей длины. Тогда эта длина будет меньше, чем
половина длины подвектора предыдущего уровня. Тогда максимальное число P(n)
интервалов одновременно находящихся в стеке будет удовлетворять следующему
соотношению
n
P(n)  1  P    , или P(n)  1  1  1  ...  1 P(0)
2 log 2 n

Принимая во внимание, что P(0)=0, получаем, что P(n)<log2n+1. Таким образом,


пространственная сложность сокращается до O(log2n), (для n=106, log2106≈20, необходимо
предусмотреть стек глубины 20, что практически весьма приемлемо). Для будущих целей
модифицируем функцию quicksort_intern() так, чтобы она выглядела следующим
образом:
template <class el>
void vector_quicksort<el>::quicksort_intern(int i, int j)
{
if (j>i)

85
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
int imain=divide(i,j);
if(imain-i > j-imain)
{// We start with the left-hand interval
quicksort_intern(i,imain-1);
quicksort_intern(imain+1,j);
}
else
{// We start with the right-hand interval
quicksort_intern(imain+1,j);
quicksort_intern(i,imain-1);
}
}
}

В силу того, что введенная модификация не изменит полученный ранее вывод, не будем его
повторять.
Пространственная сложность в будущем (когда будем непосредственно использовать стек)
будет приведена к величине P(n)  O(log 2 n). Оценим временную сложность T(n). Т.к.
деление имеет сложность O(n), имеем:
T (n)  O(n)  T (imain  0)  T (n  1  imain) . Т.е. все зависит от imain, а именно как вектор
будет поделен функцией divide().
Идеальный случае, который может буть достигнут применением принципа равновесия,
n
состоит в разделении вектора на две приблизительно расные части, так, чтобы imain  .
2
n  n  n 
T (n)  O(n)  2T    O(n)  2 O   2T    
2  2  4 
Тогда
 n  n  n 
O(n)  2 O   2 O   2T      ...  O(n)  O(n)  ...  O(n)  O(n log 2 n),
 2  4  8 

т.к. T(0)=0. Тким образом, T(n)=O(nlog2n), коэффициент перед nlog2n тот же, что и
коэффициент перед n в сложности деления.
Таким образом, метод дает O(nlog2n), что является нижним пределом сложности алгоритмов
сортировки, основанных на сравнении ключей.
Если же, в результате деления, главные элементы систематически будут устанавливаться в
позиции возле первого или возле последнего элемента соответствующих подвекторов (т.е.
постоянно imain=i, или imain=j), тогда каждый раз остается необходимость сортировать
подвектор количество элементов которого на единицу меньше, чем в предыдущем
подвекторе. В результате сложность будет
T (n)  O(n)  O(1)  T (n  1)  O(n)  O(n  1)  ...  O(1)  O(n 2 ) .

86
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

В этом случае, быстрая сортировка имеет сложность теоретически равную сложности самых
плохих алгоритмов сортировки, например, сортировки методом пузырька, а практическая
сложность вероятно будет и больше, в силу временных затрат на реализацию рекурсии,
управляемой стеком. Для быстрой сортировки, теоретически показано, что средняя
сложность, при равной вероятности всех перестановок, равна O(nlog2n) с примерно 2nlog2n
сравнениями ключей. Так же показано, что вероятность самого неблагоприятного случая со
сложностью O(n2) достаточно мала.
Вероятность самого неблагоприятного случая не исключена, когда данные уже упорядочены
или частично упорядочены (возможно и в обратном порядке).
Парадокс быстрой сортировки, в сравнении с сортировкой простыми вставками или, даже,
пузырьковой, заключается в том, что быстрая сортировка теряет свое качество на частично
упорядоченных векторах. Это является неудобным, т.к. необходимость сортировки “почти
упорядоченных” данных достаточно часто возникает на практике.
Для подтверждения изложенного, создадим текстовый файл с именем, например,
”stud20.txt” и со следующим содержанием:
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00
Green 1987 350.00

В функции main() создадим на основе файла ”stud20.txt” вектор, конкретизированный


классом usual_elem, и отсортируем вектор быстрой сортировкой. Получим следующий
результат:
Unsorted group:
1. Green 1987 350.00
2. Green 1987 350.00

87
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

3. Green 1987 350.00


4. Green 1987 350.00
5. Green 1987 350.00
6. Green 1987 350.00
7. Green 1987 350.00
8. Green 1987 350.00
9. Green 1987 350.00
10. Green 1987 350.00
11. Green 1987 350.00
12. Green 1987 350.00
13. Green 1987 350.00
14. Green 1987 350.00
15. Green 1987 350.00
16. Green 1987 350.00
17. Green 1987 350.00
18. Green 1987 350.00
19. Green 1987 350.00
20. Green 1987 350.00
End of vector. Press any key ...
Group sorted by name:
1. Green 1987 350.00
2. Green 1987 350.00
3. Green 1987 350.00
4. Green 1987 350.00
5. Green 1987 350.00
6. Green 1987 350.00
7. Green 1987 350.00
8. Green 1987 350.00
9. Green 1987 350.00
10. Green 1987 350.00
11. Green 1987 350.00
12. Green 1987 350.00
13. Green 1987 350.00
14. Green 1987 350.00
15. Green 1987 350.00
16. Green 1987 350.00
17. Green 1987 350.00
18. Green 1987 350.00
19. Green 1987 350.00
20. Green 1987 350.00
End of vector. Press any key ...
n=20, ncomp=247, n*log2(n)=86.44, n*n=400

88
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Как видно, в данном случае, количество сравнений ближе к n2, чем к nlog2n.

Улучшение быстрой сортировки

Для того чтобы быстрая сортировка стала по-настоящему эффективным алгоритмом,


требуется одно улучшение. Очевидно, что в предыдущих версиях рекурсия и выбор главного
элемента становятся слишком тяжелыми для малых подвекторов. Быстрая сортировка не
может должна применяться к маленьким векторам. Поэтому рекурсию необходимо
останавливать, когда размер подвектора становится меньше некоторого постоянного
значения, называемого порогом. После этого применяется метод, эффективность которого
улучшается на частично упорядоченных данных, например сортировка простыми вставками.
D. Knuth теоретически показал, что оптимальное значение порога равняется 9.
На практике хорошие результаты получаются при значениях порога в интервале от 8 до 20, а
оптимальное значение заключается в пределах от 14 до 16.
//
// C l a s s " v e c t o r o p t i m q u i c k s o r t "
//
template <class el> class vector_optim_quicksort:
public vector_quicksort<el>
{
public:
vector_optim_quicksort<el>(char* file_name , int threshold_init=15,
int NMAX=200):
vector_quicksort<el>(file_name, NMAX)
{
threshold=threshold_init;
if(threshold<1)
threshold=1;
}

void quicksort(int i=0, int j=-1)


{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
quicksort_intern(i, j);
}

89
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

protected:
int threshold;
void quicksort_intern(int i, int j);
void insertsort(int i, int j);
};

template <class el>


void vector_optim_quicksort<el>::quicksort_intern(int i, int j)
{
if(j-i+1>threshold)
{
int imain=divide(i,j);
if(imain-i > j-imain)
{
quicksort_intern(i,imain-1);
quicksort_intern(imain+1,j);
}
else
{
quicksort_intern(imain+1,j);
quicksort_intern(i,imain-1);
}
}
else
insertsort(i, j);
}

template <class el>


void vector_optim_quicksort<el>::insertsort(int i, int j)
{
for(int k = i+1; k<=j; k++)
for(int l=k; (l>i)&&(ncomp++, t[l]<t[l-1]); l--)
swap(l-1, l);
}

Построим экземпляры функции main(), заменив в предыдущих трех примерах название


класса vector_quicksort на vector_optim_quicksort и добавив к параметрам конструктора
значение порога, например, 4. Результаты, анализ которых остается в качестве упражнения,
будут следующими:
Для вектора, конкретизированного классом usual_elem,
Unsorted group:

90
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

1. Green 1987 350.00


2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=32, n*log2(n)=33.22, n*n=100

Для вектора, конкретизированного классом year_elem,


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. White 1980 600.00
4. Red 1980 450.00

91
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5. Black 1981 500.00


6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=37, n*log2(n)=33.22, n*n=100

Для вектора, конкретизированного классом salary_elem,


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=33, n*log2(n)=33.22, n*n=100

В следующей таблице представлены количества сравнений, выполненных при сортировке


объектов vector_optim_quicksort, созданных на основе файла ”stud.txt”, для различных
значений порога и различных полей сортировки.

Câmpul de sortare
Prag
name year salary
0 39 42 43
1 39 42 43

92
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

2 37 37 41
3 35 37 38
4 32 37 33
5 29 34 28
6 29 34 28
7 29 34 28
8 29 34 28
9 29 34 28
10 30 28 25
11 30 28 25
Анализ результатов, представленных в данной таблице, остается в качестве упражнения.
Упражнение.
Переделайте конструктор класса sorted_table из 2.4 так, что бы он сначала создавал
простую неупорядоченную таблицу, а затем сортировал бы ее оптимизированным
алгоритмом быстрой сортировки.

93
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

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

Простая вставка

Главная идея сортировки простыми вставками (engl. Insert sort) проста: выбираем некоторый
элемент, сортируем остальные, “вставляем” выбранный элемент на подходящее место среди
уже отсортированных.
void recursivinsertsort(int i, int j)
{
if(j>i)
{
recursivinsertsort(i, j-1);
for(int l=j; (l>i)&&(ncomp++, t[l]<t[l-1]; l--))
swap(l-1, l);
}
}

В нерекурсивной форме сортировка простыми вставками запишется следующим образом:


void insertsort(int i, int j)
{
int k;
for(k=i+1; k<=j; k++) // Here is sorted out t[i]÷t[k-1]
{
for(int l=k; (l>i)&&(ncomp++,t[l]<t[l-1]; l--))
swap(l-1, l);
// Here is sorted out t[i]÷t[k]
}
}

Очевидно, что этот метод сортировки является стабильным.


Как максимальная сложность, так и средняя сложность операции вставки имеют порядок
O(k-i), но время выполнения улучшается, если сравнение ключей комбинируется с
перемещением элементов, ключ которых больше, чем ключ элемента t[k].
Легко заметить, что в процессе вставки вставляемый элемент участвует во всех
последовательных перестановках (перемещениях). И если обмены существенно более
трудоемки, чем полуобмены, можно немного повысить эффективность этого процесса:
void insertsort(int i, int j)
{
int k;
for(k=i+1; k<=j; k++) // Here is sorted out t[i]÷t[k-1]
{
element tmp=t[k];
int l;

94
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

for(l=k-1; (l>=i)&&(ncomp++, t[l]<tmp); l--)


t[l+1]=t[l];
l++;
if(l<k)
t[l]=tmp;
// Here is sorted out t[i]÷t[k]
}
}

В предположении, что все перестановки имеют равные вероятности, максимальная


сложность и средняя сложность алгоритма сортировки простыми вставками имеют порядок
n(n  1) n(n  1)
O(n2). Точное число выполнений тела цикла равно для средней сложности и
4 2
для максимальной сложности (случай, когда элементы исходного вектора окажутся
упорядоченными в обратном порядке).
Однако отметим одно самое важное свойство сортировки простыми вставками: в отличие от
остальных методов у нее повышается эффективность, если в исходном векторе установлен
частичный порядок. Если исходный вектор полностью упорядочен, тогда сложность имеет
порядок O(n). В общем случае, алгоритм использует любой частичный порядок среди
элементов сортируемого вектора. И если принять во внимание простоту алгоритма,
приходим к выводу, что этот алгоритм является самым подходящим для завершения работы
более претенциозных методов, например, быстрой сортировки. Т.е. для завершения работы
алгоритмов, которые достаточно быстро делят вектор на множество мелких, упорядоченных
между собой и “почти” упорядоченных внутри, частей, но требуют много времени и
“задыхаются” при окончательной сортировке этих мелких частей (маленьких подвекторов).

Метод Шелла

Существенный недостаток сортировки простыми вставками заключается в том, что на


каждом шаге перестановки переставляемый элемент перемещается всего на одну позицию.
Каждая из таких перестановок устраняет в точности одну инверсию. В результате общее
число перестановок совпадает с начальным количеством инверсий, которое по средней
c 2 n(n  1) n(n  1)
вероятности равняется: n   .
2 2*2 4
Дональд Шелл (Donald L. Shell) в 1959 году предложил вместо систематической вставки
элемента с индексом i в подвектор предыдущих элементов (способ который противоречит
принципу равновесия), вставлять этот элемент в подсписок, содержащий элементы с
индексами i-h, i-2h, i-3h, …, где h – некоторая положительная константа (шаг).
Таким образом, формируется вектор, в котором h–серии элементов (элементы, которые
отстоят друг от друга на расстоянии h) сортируются независимо.

95
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

t[0] t[1] t[2]…t[0+h] t[1+h] t[2+h]…t[0+2*h] t[1+2*h] t[2+2*h]…

После того как были отсортированы отдельно непересекающиеся h–серии, процесс


возобновляется с новым значением шага h'<h. Однако свойство, характерное для сортировки
простыми вставками, облегчит задачу в сравнении с начальной ситуацией, в которой
исходный вектор совсем неупорядочен. Предварительная сортировка сериями с расстоянием
h ускорит сортировку сериями с меньшим расстоянием h'. Метод Шелла существенно
использует это магическое свойство, сортируя сначала серии с расстоянием hi, затем hi-1,…,
наконец h1, где hi, hi-1,…,h1 – упорядочены по убыванию и h1=1. Последний прогон должен
завершить окончательную сортировку вектора. Не существует точного ответа на вопрос:
“Каковы оптимальные значения для hi, hi-1,…,h1?”. Оптимальная последовательность зависит
от многих факторов и, не в последнюю очередь от n – размера сортируемого вектора.
Для достаточно больших векторов, эксперименты показали, что может быть рекомендована
последовательность шагов {hi} такая, что hi+1=3hi+1, т.е. последовательность, включающая в
убывающем порядке шаги: …, 364, 121, 40, 13, 4, 1. Процесс начинается с шага hm-2, где m –
наименьшее целое число, такое что hm≥n, другими словами hm-2 является первым элементом
n
последовательности и равняется   .
9
Создадим на базе шаблонного класса vector шаблонный класс vector_Shellsort
дополненный алгоритмом сортировки по методу Шелла:
//
// C l a s s "v e c t o r _ S h e l l s o r t "
//
template <class el> class vector_Shellsort: public vector<el>
{
public:
vector_Shellsort<el>(char* file_name, int NMAX=200):
vector<el>(file_name, NMAX)
{

void Shellsort();
};

template <class el>


void vector_Shellsort<el>::Shellsort()

96
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
int h;
int i, j, k;
el tmp;
// Defining the initial step
h=1;
while(h<n/9)
h=3*h+1;
// Here the step is equal max(1, hm-2)
do
{
// Sort by series by distance h
for(k=0; k<h; k++)
{ // Insertsort the series number k
for(i=h+k; i<n; i+=h)
{ // The inclusion of t[i] in his place among the precedents
tmp=t[i];
j=i-h;
while((j>=0)&&(SD::ncomp++, t[j]>tmp))
{
t[j+h]=t[j];
j-=h;
}
t[j+h]=tmp;
}
}
// Decrease increment
h=h/3;
} while(h>0);
}

Замечания:
1. Вместо последовательного перевычисления величин шагов h, можно вычислить их
предварительно и запомнить в дополнительном векторе:
2. Можно объединить циклы по k и по i, заменив обмены на полуобмены;
3. Сортировка по методу Шелла не является стабильной, и эта проблема легко не решается.

В функции main() инстанциируем класс vector_Shellsort конкретизированный классом


usual_elem, выводим начальный вектор, сортируем вектор по методу Шелла, выводим
отсортированный вектор и информацию об оценке сложности:
void main()
{
clrscr();

97
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

vector_Shellsort<usual_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.Shellsort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

Вывод будет выглядеть следующим образом:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=30, n*log2(n)=33.22, n*n=100

Как видно, количество сравнений совпало с количеством сравнений при сортировке


простыми вставками.
Упражнение.

98
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Реорганизуйте конструктор класса sorted_table из 2.4 таким образом, чтобы он вначале


создавал простую неупорядоченную таблицу, а затем сортировал бы ее методом Шелла.

Заменим в функции main() класс usual_elem на класс year_elem, тогда получим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=28, n*log2(n)=33.22, n*n=100

Анализ результатов остается в качестве упражнения.


Если заменим в функции main() класс year_elem на класс salary_elem, то получим:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00

99
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

10. Black 1981 500.00


End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=25, n*log2(n)=33.22, n*n=100

Анализ результатов остается в качестве упражнения.


Метод Шелла (Shell) существенно превосходит простые вставки для n больших 100.
Количество сравнений в среднем имеет порядок 1.66n1.25 для достаточно больших n.
Анализируя следующую таблицу:
n 1.66n1.25 nlog2n
10 29,5 33.2193

100 525 664


1000 9335 9966
10000 166000 132877
105 2,95*106 1,66*106
106 5,25*107 1,99*107

видим, что метод Шелла выдерживает соревнование с методами O(nlog2n) до n=105.


Сортировка по методу Шелла плохо приспосабливается к системам с виртуальной памятью
(т.е. в которых вектор разбивается на несколько интервалов).

100
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

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

Простой выбор

Идея метода сортировки простым выбором (engl. Selection sort) очень проста: находится
максимальный элемент в сортируемом векторе и меняется местами с последним элементом
вектора; далее ищется максимальный среди элементов без последнего и меняется местами с
предпоследним и т.д. На каждом просмотре рассматриваются только элементы, которые
остались неупорядоченными, максимальный среди которых и будет присоединен к уже
упорядоченным.
В нерекурсивной форме простой выбор состоит из n-1 этапов. На этапе k ищется элемент с
максимальным ключом среди элементов, которые еще до конца не упорядочены, и
устанавливается в позицию n-k.
Пример:
void selectsort()
{
int imax, i, j;
for(i=n-1; i>0; i--)
{
imax=i;
for(j=0; j<i; j++)
if(ncomp++, t[j]>t[imax])
imax=j;
swap(i,imax);
// Here sub vector from t[j] to t[n-1] it is sorted (loop invariance)
}
}

Сложность этого алгоритма имеет порядок O(n2) во всех случаях. Количество выполнений
n(n  1)
внутреннего цикла равно .
2

3.7. Древовидная сортировка (пирамидальная, heapsort)


Древовидная сортировка исходит от простого выбора. Главный недостаток сортировки
простым выбором состоит в том, что сравнения, выполняемые на каждом этапе, дают
намного больше информации, чем та, которая используется на самом деле для установки
текущего элемента на нужное место. Для того чтобы добиться существенного улучшения,
необходимо использовать более развитую структуру данных, которая бы позволила
сохранять информацию, получаемую при последовательных сравнениях. Например, если
t[i]>=t[k], а t[k]>=t[j], тогда t[i]>=t[j].

101
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Древовидная сортировка использует конкретное полное двоичное дерево, из которого легко


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

Максимизация дерева

6 9

9 8 7 8

7 3 5 6 6 3 5 6

6 5 1 6 5 1
4 4

Полным двоичным деревом называется такое двоичное дерево, у которого на каждом уровне
присутствуют все узлы, за исключением, быть может, самых правых узлов последнего
уровня. Полное двоичное дерево может быть представлено в виде вектора.
Узлу с индексом i соответствуют узлы с индексом 2*i+1 (корень его левого поддерева, если
2*i+1<=n-1) и 2*i+2 (корень его правого поддерева, если 2*i+2<=n-1).

Важно понять, что никакое двоичное дерево отдельно не создается, используя формулы
2*i+1 и 2*i+2, работаем с вектором как с двоичным деревом.

Любое двоичное поддерево полного двоичного дерева, соответствующего вектору t[0]÷t[n-


1], может быть определено парой индексов i, j которые удовлетворяют условию
(0<=i)&&(i<=j)&&(j≤n-1). Дочерние элементы элемента с индексом k, где (i<=k)&&(k<=j),
будут иметь индексы 2*i+1 и 2*i+2, если они не превосходят значение j.
Создадим на базе шаблонного класса vector шаблонный класс vector_heapsort,
снабженный алгоритмом древовидной сортировки:
//
// C l a s s "v e c t o r _ h e a p s o r t "
//
template <class el> class vector_heapsort: public vector<el>
{
public:

102
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

vector_heapsort<el>(char* file_name, int NMAX=200):


vector<el>(file_name, NMAX)
{
}

void heapsort();

protected:
void reorganization(int i, int j);
void planting();
void maxtreesort();
};

Реализуем функцию реорганизации для поддеревьев.


Пусть: i, j пара индексов таких, что 0<=i<=j<=n-1 и поддеревья t[2*i+1], t[2*i+2],
являются максимизирующими деревьями, если они существуют, тогда функция
реорганизации может быть реализована следующим образом:
template <class el>
void vector_heapsort<el>::reorganization(int i, int j)
{
int pred, next, left, right; // Indexes for traversal of tree
next=i ;
do
{
pred=next; // temporary
left=2*pred+1;
if(left<=j) // If it belongs to the sub-tree
{
if(ncomp++, t[left]>t[next])
next=left;
right=left+1; // 2*pred+2
if((right<=j)&&(ncomp++, t[right]>t[next]))
next=right;
if(next!=pred)
swap(pred,next); // We change elements with places
// t[pred] and t[next]
}
} while(pred!=next);
}

Как результат, в двоичном дереве, соответствующем вектору t, двоичное поддереву t[i],


t[2*i+1], t[2*i+2],…, t[j] будет максимизирующим деревом.

103
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Реорганизация имеет сложность порядка O(hi,j), где hi,j – глубина поддерева,


соответствующего элементам t[i], t[2*i+1], t[2*i+2],…, t[m*i+1], t[m*i+2]. Поэтому
hi,j=O(log2(j-i+1)). Таким образом, реорганизация имеет сложность O(log2(j-i+1)), конкретнее
она выполняет не более чем 2(log2(j-i+1)) сравнений и log2(j-i+2) обменов.
Алгоритм древовидной сортировки состоит из двух этапов, каждый из которых использует
реорганизацию:
 Посадка (planting) – преобразует исходный вектор в вектор, которому соответствует
максимизирующее дерево;
 Сортировка максимизирующего дерева (maxtreesort) – выполняет сортировку, принимая
во внимание максимизирующее дерево.
template <class el> void vector_heapsort<el>::heapsort()
{
planting();
maxtreesort();
}

При посадке реорганизация применяется к таким индексам i, поддеревья которых, если они
существуют, являются максимизирующими деревьями. Начинаем с элемента t[(n-1)/2],
т.к. у элементов с большим индексом нет поддеревьев.
template <class el> void vector_heapsort<el>::planting()
{
for(int i=(n-1)/2; i>=0; i--)
reorganization(i, n-1);
}

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


обрабатываемых подвекторов, соответствующих корням поддеревьев:
1. Глубина подвектора h=[log2n]+1 – вектор t[0]÷t[n-1];
2. Глубина подвектора h-1 – вектора t[1]÷t[n-1], t[2]÷t[n-1];
3. Глубина подвектора h-2 – вектора t[3]÷t[n-1], t[4]÷t[n-1], t[5]÷t[n-1],
t[6]÷t[n-1];

4. …

Таким образом, сложность посадки может быть записана в виде: O(1*h*2*(h-1)+...+2h-


2
*2)=O(2h-1)=O(n).
После посадки t[0] стал максимальным элементом, его надо поменять местами с элементом
t[n-1], после чего остается отсортировать подвектор t[0]÷t[n-2]. Этому подвектору будет
соответствовать полное двоичное дерево, которое можно подвергнуть реорганизации. Все
зависит от нового элемента t[0] (предыдущего t[n-1]). Поэтому применяем к этому
подвектору реорганизацию, для установки следующего максимального в t[0], которого
меняем местами с t[n-2], и т.д.
template <class el> void vector_heapsort<el>::maxtreesort()

104
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
for (int i=(n-1);i>=1;i--)
{
swap (0, i);
reorganization(0, i-1);
}
}
n 1
Эта процедура имеет сложность T (n)   O(h0,i ) , где h0,i – глубина поддерева t[0]÷t[i], т.е.
i 1

h0,i  log 2 i   1 . Таким образом, получаем что T(n)=O(nlog2n). Окончательно, сложность


алгоритма древовидной сортировки имеет порядок O(n+nlog2n)=O(nlog2n).
Это будет как средняя, так и максимальная сложность, т.к. при ее выводе не делалось
никаких предположений относительно изначального распределения элементов. Древовидная
сортировка является самым надежным алгоритмом сортировки.
void main()
{
clrscr();

vector_heapsort<usual_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.heapsort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

Вывод для класса usual_elem будет следующим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...

105
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Group sorted by name:


1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=41, n*log2(n)=33.22, n*n=100

Упражнение.
Перепишите конструктор класса sorted_table из 2.4 так, что бы он сначала создавал
простую неупорядоченную таблицу, а затем сортировал бы ее древовидной сортировкой.
Заменим в предыдущей функции main() класс usual_elem на класс year_elem, тогда
получим следующий результат:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. Red 1980 450.00
4. White 1980 600.00
5. Black 1981 500.00
6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00

106
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

End of vector. Press any key ...


n=10, ncomp=38, n*log2(n)=33.22, n*n=100

Заменим в предыдущей функции main() класс year_elem на класс salary_elem, тогда


результат будет следующим:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=40, n*log2(n)=33.22, n*n=100

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


быстрая сортировка.
Упражнения.
1. Создайте класс vector_ternarysort с алгоритмом сортировки, использующим полное
троичное максимизирующее дерево (t[3*i+1], t[3*i+2], t[3*i+3]). Продемонстрируйте
использование этого класса. Приведите теоретическую оценку средней и максимальной
сложности разработанного алгоритма сортировки. Подсчитайте количество сравнений
ключей при сортировке конкретного вектора.
2. Создайте класс vector_quaternarysort с алгоритмом сортировки, использующим полное
четверичное максимизирующее дерево (t[4*i+1], t[4*i+2], t[4*i+3] t[4*i+4]).
Продемонстрируйте использование этого класса. Приведите теоретическую оценку средней

107
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

и максимальной сложности разработанного алгоритма сортировки. Подсчитайте количество


сравнений ключей при сортировке конкретного вектора.
3.8. Практическое сравнение различных алгоритмов сортировки
Были исследованы различные методы сортировки и порой трудно отдать предпочтение
какому-либо из них. Выбор зависит от многих факторов. Но каждый из рассмотренных
алгоритмов может быть достаточно легко и быстро запрограммирован. Поэтому
использование Bubblesort по причине “легко написать ”, не может быть никоим образом
оправдано.
По выбору алгоритма сортировки могут быть использованы следующие рекомендации:
- для малых n (≈100), а может и больше, если не пробовать сэкономить несколько
микросекунд, сортировка простыми вставками дает достаточно хороший результат,
особенно, если исходные данные являются частично упорядоченными;
- для n от нескольких сотен до нескольких тысяч, метод Шелла дает превосходный
результат. Однако его не следует использовать в системах с виртуальной памятью,
если вектор размещен на нескольких страницах;
- для n >100 (например) quicksort, скорее всего, является в общем случае наилучшим
алгоритмом; однако он может вырасти до O(n2) с ненулевой вероятностью
(вероятность все-таки очень мала, в случае если хорошо написано деление);
- для n>100 древовидная сортировка (Heapsort) требует почти в два раза больше
времени, чем быстрая сортировка, однако гарантирована сложность порядка O(nlogn).

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


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

N 10 100 1000 10000 25000 50000

Метод
Bublesort 0,16 20 2400
Extractsort 0,12 7,3 680
Insertsort 0,12 6,7 610
Shellsort 0,07 2 37 600 1800 4200
Heapsort 0,2 3,5 50 660 1830 3960
Quicksort 0,07 2 28 365 1000 2140

Замечание: выводы могут быть разными в зависимости от времени сравнения ключей, от


способа перестановки элементов, и других действий:

108
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ

4.1. Понятие динамической структуры данных


Определение:

Под динамической структурой данных (ДСД) будем понимать такую


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

Для динамических структур данных характерно расположение элементов, разбросанное по


оперативной памяти, в зависимости от выделения места во время их добавления.
Типы динамических структур данных различаются между собой организацией связей между
элементами, входящими в состав ДСД, порядком добавления (записи) новых элементов,
способом доступа к элементам, порядком удаления (исключения) существующих, но
ставших ненужными, элементов.
Самыми распространенными динамическими структурами данных являются:
 двоичные деревья;
 списки различных видов (однонаправленные и двунаправленные, циклические);
 стеки;
 очереди;
 деревья, и др.

109
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4.2. Двоичные деревья


Определение двоичных деревьев является рекуррентным и выглядит следующим образом:

Двоичное дерево представляет конечное множество элементов (узлов),


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

Примеры двоичных деревьев:

Пустое двоичное дерево

Двоичное дерево, состоящее только из корня (точнее из корня и двух пустых поддеревьев)

Двоичное дерево, состоящее из корня и левого поддерева (правое поддерево – пусто)

110
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Двоичное дерево, состоящее из корня и правого поддерева (левое поддерево – пусто)


Замечание: Двоичные деревья из предыдущих двух примеров являются различными. У
первого двоичного дерева – правое поддерево пусто, а у второго двоичного дерева – левое
поддерево пусто.

B C

Двоичное дерево, составленное из корня, непустого левого поддерева и непустого правого


поддерева

B C

D E F

Более сложное двоичное дерево

111
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Определение листового узла:

Узел, у которого и левое и правое поддеревья являются пустыми, называется


листом или листовым узлом, или конечным узлом

У двоичного дерева из предыдущего примера листовыми являются узлы D, E, F.


Узлы каждого двоичного дерева, естественным образом и однозначно, распределяются по
уровням следующим образом:

Пустое двоичное дерево не имеет уровней.


У остальных двоичных деревьев уровню 1 принадлежит единственный узел –
корень двоичного дерева.
Уровню 2 принадлежат существующие корни левого и правого поддеревьев
данного двоичного дерева.
И т. д., уровню i+1 принадлежат существующие корни левых и правых
поддеревьев всех узлов уровня i.

Проследим распределение по уровням узлов двоичного дерева. Пусть дано следующее


двоичное дерево:

right

B C
left right
left right
A F
D
root

112
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Тогда это же двоичное дерево, с узлами, распределенными по уровням, будет выглядеть


следующим образом:

level
1 A

2 B C

3 D E F

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

Двоичное дерево из n узлов может иметь минимум log 2 n  1 узлов. Минимальное


количество узлов, кроме прочих, достигается на таких двоичных деревьях из n узлов, у
которых все уровни полностью заполнены, за возможным исключением для последнего
уровня, для заполнения которого просто может не хватить узлов.
Другой пример двоичного дерева с минимально возможным количеством уровней:

level
1 A

2 B C

3 D E F

4 G H

113
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Под обходом двоичного дерева понимают последовательный переход от одного узла к


другому через все узлы в целях обработки или поиска.

В основном используются три способа обхода (engl. order traversal):


 прямой (engl. preorder traversal, rom. preordine, parcurgere în lăţime sau directă) – прямой
порядок / обход в ширину;
 обратный (engl. symmetric order traversal / inorder traversal, rom. inordine, parcurgere
simetrică) – обратный порядок / симметричный обход ;
 концевой (engl. postorder traversal / endorder traversal, rom. postordine, parcurgere în
adîncime) – концевой порядок / обход в глубину.
Название способа обхода происходит от порядка обработки корня и порядка обхода левого и
правого поддеревьев.
1. Прямой обход состоит из следующих шагов:
a) обработка корня;
b) обход левого поддерева в прямом порядке, если оно не пусто;
c) обход правого поддерева в прямом порядке, если оно не пусто.
Может использоваться для поиска нужного узла. Другой пример использования прямого
обхода – обработка выражения представленного в прямой польской записи.

2. Обратный (симметричный) обход состоит из следующих шагов:


a) обход левого поддерева в обратном порядке, если оно не пусто;
b) обработка корня;
c) обход правого поддерева в обратном порядке, если оно не пусто.
Если двоичное дерево построено по принципу неупорядоченной древовидной таблицы, тогда
симметричный обход обеспечивает обработку узлов в порядке возрастания их ключей. Такие
двоичные деревья называются двоичными деревьями поиска.

3. Концевой обход (в глубину) состоит из следующих шагов:


a) обход левого поддерева в концевом порядке, если оно не пусто;
b) обход правого поддерева в концевом порядке, если оно не пусто;
c) обработка корня.

114
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Может быть использован для вычисления выражения представленного в форме двоичного


дерева:

A ×

B C

Определение полного двоичного дерева:

Двоичное дерево называется полным, если на всех уровнях все узлы


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

Пример полного двоичного дерева: отсутствуют два правых узла последнего уровня.

B C

D E

Следующее двоичное дерево не является полным: на последнем уровне отсутствует один


узел, но он не является самым правым.

B C

D E F

115
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Полные двоичные деревья в памяти могут быть представлены в виде векторов.

Для демонстрации работы с двоичными деревьями, в частности с двоичными деревьями


поиска, создадим шаблонный класс bintree, производный от абстрактного класса SD. Для
конкретизации класса bintree может быть использован любой конкретный класс,
являющийся потомком абстрактного класса elem.
//
// C l a s s "b i n t r e e"
//
template <class el> class bintree : public SD
{
protected:
bintree *pleft;
bintree *pright;
el *pdata;

public:
bintree<el>()
{
pdata=NULL;
pleft=pright=NULL;
}

bintree<el>(char* file_name) : SD(file_name)


{
pdata=NULL;
pleft=pright=NULL;

bintree<el> *pcurrent_nod;
el *pnewel;

while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{
pcurrent_nod=this;
while(pcurrent_nod->pdata!=NULL)
{
if(*pnewel < *(pcurrent_nod->pdata))
{
if(pcurrent_nod->pleft==NULL)
pcurrent_nod->pleft=new bintree<el>();
pcurrent_nod=pcurrent_nod->pleft;

116
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
else
{
if(pcurrent_nod->pright==NULL)
pcurrent_nod->pright=new bintree<el>();
pcurrent_nod=pcurrent_nod->pright;
}
}
pcurrent_nod->pdata=pnewel;
}
fclose(pf);
}

virtual void show_preorder(const char* opening,


const char* ending)
{
printf("%s", opening);

if(pdata)
pdata->show("", "\n");

if(pleft)
pleft->show_preorder("", "");

if(pright)
pright->show_preorder("", "");

printf("%s", ending);
}

virtual void show_inorder(const char* opening,


const char* ending)
{
printf("%s", opening);

if(pleft)
pleft->show_inorder("", "");

if(pdata)
pdata->show("", "\n");

if(pright)

117
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pright->show_inorder("", "");

printf("%s", ending);
}

virtual void show_postorder(const char* opening,


const char* ending)
{
printf("%s", opening);

if(pleft)
pleft->show_postorder("", "");

if(pright)
pright->show_postorder("", "");

if(pdata)
pdata->show("", "\n");

printf("%s", ending);
}

virtual void show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
show_preorder(opening, ending);
}

el* search(el &e)


{
bintree<el>* pcurrent_nod = this;
int found=0;
int forward;
int cmp_result;
if(pcurrent_nod->pdata)
forward=1;
else
forward=0;
while(forward)
if(SD::ncomp++, (cmp_result=e.cmp(*(pcurrent_nod->pdata)))==0)
found=1, forward=0;
else

118
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
if(cmp_result<0)
if(pcurrent_nod->pleft)
pcurrent_nod = pcurrent_nod->pleft;
else
forward=0;
else
if(pcurrent_nod->pright)
pcurrent_nod = pcurrent_nod->pright;
else
forward=0;
}
return found ? pcurrent_nod->pdata : NULL;
}
};

В функции main() создаем двоичное дерево поиска, загружая его из файла "Stud.txt" и
выводим узлы поочередно в прямом порядке, в симметричном порядке и концевом порядке:
void main()
{
clrscr();

bintree<usual_elem> btgr("Stud.txt");

btgr.show_preorder("Group in preorder\n","\n");

btgr.show_inorder("Group in inorder\n","\n");

btgr.show_postorder("Group in postorder\n","\n");

getch();
}

Созданное двоичное дерево выглядит так:

Green

Blue Red

Black Gray Orange White

Cyan Magenta Yellow

119
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Оно соответствует неупорядоченной древовидной таблице, созданной в главе посвященной


таблицам. Выводимые результаты выглядят так:
Group in preorder
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00
Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00

Group in inorder
Black 1981 500.00
Blue 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Green 1987 350.00
Magenta 1983 600.00
Orange 1984 550.00
Red 1980 450.00
White 1980 600.00
Yellow 1988 300.00

Group in postorder
Black 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Blue 1981 500.00
Magenta 1983 600.00
Orange 1984 550.00
Yellow 1988 300.00
White 1980 600.00
Red 1980 450.00
Green 1987 350.00

Press any key to exit...

Функция поиска search() класса tree_table возвращает позицию в таблице искомой


записи, в случае когда поиск завершился успешно, и возвращает -1 – в случае когда поиск
оказался неудачным. Т. к. номер позиции не имеет смысл в случае двоичного дерева,

120
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

функция search() класса bintree адрес искомого объекта в случае успешного поиска, и
возвращает NULL – в случае когда поиск оказался неуспешным. Представленная далее
функция main(), демонстрирует поиск в двоичном дереве, созданном на основе файла
"Stud.txt":
void main()
{
clrscr();

bintree<usual_elem> btgr("Stud.txt");
btgr.show("Group:\n", "");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
btgr.reset_ncomp();
usual_elem* p=btgr.search(e);
if(p==NULL)
printf("No bintree! The number of comparisons: %d\n", btgr.get_ncomp());
else
{
printf("There are in the bintree: ");
p->show("","\n");
printf("The number of comparisons: %d\n", btgr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}

printf("Press any key to exit... ");


getch();
}

Вариант вывода может выглядеть так:


Group:
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00

121
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Cyan 1975 800.00


Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00
Enter a name to search: White
There are in the bintree: White 1980 600.00
The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Black
There are in the bintree: Black 1981 500.00
The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Green
There are in the bintree: Green 1987 350.00
The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Purple
No bintree! The number of comparisons: 3
Done ? (y/n)
Press any key to exit...

Средняя длина поиска в данном двоичном дереве, может быть подсчитана следующим
образом:
void main()
{
clrscr();

bintree<usual_elem> btgr("Stud.txt");
btgr.show("Group:\n", "");

usual_elem sample;
long NCOMP=0;
long nelem = 0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
btgr.reset_ncomp();
if(btgr.search(sample))
NCOMP+=btgr.get_ncomp(), nelem++;

122
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
fclose(pf);
printf("n=%d, NCOMP=%d", nelem, NCOMP);
printf(", ALS=%.2f", (double)NCOMP/nelem);
printf(", MAX=%.2f", (nelem+1)/2.0);
printf(", MIN=%.2f\n", log((double)nelem)/log(2.0)+2.0);

printf("Press any key to exit... ");


getch();
}

Результат совпадает с результатом, полученным для соответствующей неупорядоченной


древовидной таблицы:
Group:
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00
Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00
n=10, NCOMP=29, ALS=2.90, MAX=5.50, MIN=5.32
Press any key to exit...

В случае, когда двоичное дерево не является двоичным деревом поиска, поиск может быть
реализован с использованием любого из рассмотренных способов обхода: прямого,
обратного или концевого. Добавим в класс bintree следующую функцию поиска, которая
использует прямой обход:
el* preorder_search(el &e)
{
el* presult=NULL;
if(pdata)
{
if(SD::ncomp++, e.cmp(*pdata)==0)
presult=pdata;
else
{
if(pleft)
presult=pleft->preorder_search(e);
if(!presult && pright)
presult=pright->preorder_search(e);

123
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
}
return presult;
}

Исследуем этот вариант поиска при помощи следующей функции main():


void main()
{
clrscr();

bintree<usual_elem> btgr("Stud.txt");
btgr.show("Group:\n", "");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
btgr.reset_ncomp();
usual_elem* p=btgr.preorder_search(e);
if(p==NULL)
printf("No bintree! The number of comparisons: %d\n", btgr.get_ncomp());
else
{
printf("There are in the bintree: ");
p->show("","\n");
printf("The number of comparisons: %d\n", btgr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}

printf("Press any key to exit... ");


getch();
}

Возможный вариант вывода:


Group:
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00

124
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Gray 1968 900.00


Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00
Enter a name to search: White
There are in the bintree: White 1980 600.00
The number of comparisons: 9
Done ? (y/n)
Enter a name to search: Black
There are in the bintree: Black 1981 500.00
The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Green
There are in the bintree: Green 1987 350.00
The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Purple
No bintree! The number of comparisons: 10
Done ? (y/n)
Press any key to exit...

Для определения средней длины такого поиска, запустим следующую функцию:


void main()
{
clrscr();

bintree<usual_elem> btgr("Stud.txt");
btgr.show("Group:\n", "");

usual_elem sample;
long NCOMP=0;
long nelem = 0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
btgr.reset_ncomp();
if(btgr.preorder_search(sample))
NCOMP+=btgr.get_ncomp(), nelem++;

125
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
fclose(pf);
printf("n=%d, NCOMP=%d", nelem, NCOMP);
printf(", ALS=%.2f", (double)NCOMP/nelem);
printf(", MAX=%.2f", (nelem+1)/2.0);
printf(", MIN=%.2f\n", log((double)nelem)/log(2.0)+2.0);

printf("Press any key to exit... ");


getch();
}

Результат будет выглядеть следующим образом:


Group:
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00
Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00
n=10, NCOMP=55, ALS=5.50, MAX=5.50, MIN=5.32
Press any key to exit...

На сей раз, средняя длина поиска совпала со средней длиной поиска в соответствующей
простой неупорядоченной таблице.
Упражнения.
1. Добавьте в класс bintree функцию поиска inorder_search() которая бы искала нужный
узел, используя обход в обратном порядке. Продемонстрируйте использование этой
функции. Определите среднюю длину поиска для конкретного двоичного дерева.
2. Добавьте в класс bintree функцию поиска postorder_search()которая бы искала
нужный узел, используя обход в концевом порядке. Продемонстрируйте использование этой
функции. Определите среднюю длину поиска для конкретного двоичного дерева.
3. Перегрузите оператор вставки в классе bintree.
4. Перегрузите оператор извлечения в классе bintree.
5. Добавьте в класс bintree функцию которая определяет высоту (количество уровней)
двоичного дерева.
6. Реализуйте работу с двоичными деревьями созданием двух родственных шаблонных
классов: первый bintree_root, который бы представлял собственно двоичное дерево, и

126
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

второй bintree_nod, который бы представлял один узел. Класс bintree_root может быть
объявлен как класс, дружественный классу bintree_nod.

Сбалансированные двоичные деревья (уравновешенные)

AVL-дерево является двоичным деревом поиска сбалансированным по высоте. Т. е. для


каждого узла высоты (количество уровней) его поддеревьев отличаются не боле, чем на 1.
Название AVL-деревья было установлено по первым буквам фамилий математиков
G.M.Adelison-Veliskii и E.M.Landis, которые в 1962 году первыми предложили использовать
сбалансированные двоичные деревья поиска.
К AVL-деревьям, помимо прочих, применяются следующие операции:
 добавление узла;
 удаление узла;
 балансировка после добавления;
 балансировка после удаления.
Двоичное дерево поиска из предыдущего примера оказалось AVL-деревом.

127
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4.3. Списки

Список (engl. list, finite sequence) это ДСД, представляющая набор элементов,
имеющих одну и ту же структуру, состоящую из двух разделов:
 первый раздел содержит информацию относящуюся к соответствующему
элементу. Обычно это указатель (или ссылка) на внешнюю структуру
(она называется атомом и считается неделимой с точки зрения операций
над списком), или указатель на другой список;
 второй раздел обеспечивает связь между элементами списка. Содержимое
этого раздела зависит от типа списка.

В простых односвязных списках (engl. chained list, linked list) второй раздел состоит из
одного указателя на следующий элемент списка. У последнего элемента этот указатель имеет
значение NULL.
В случае односвязного циклического списка (engl. circular list, ring list), указатель из второго
раздела последнего элемента содержит адрес первого элемента списка.
У двусвязных списков второй раздел содержит два указателя, первый из которых указывает
на следующий элемент списка (имеет значение NULL у последнего элемента), а второй
указывает на предыдущий элемент списка (имеет значение NULL у первого элемента).

D1 D2 Dn

… NULL
NULL

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

D1 D2 Dn

128
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Операции над списками:


 создание списка;
 добавление нового элемента (в начало, в конец, в указанную позицию, после указанного
элемента, перед указанным элементом, вместо указанного элемента);
 удаление элемента (с начала списка, с конца, из указанной позиции, указанного);
 поиск в списке;
 сцепление списков.

Для демонстрации работы с простыми однонаправленными списками, создадим шаблонный


класс list. Для конкретизации класса list может быть использован любой конкретный
класс, являющийся потомком абстрактного класса elem.
//
// C l a s s "l i s t"
//
template <class el> class list: public SD
{
protected:
list<el> *pnext;
el *pdata;

public:
list<el>()
{
pdata=NULL;
pnext=NULL;
}

list<el>(char* file_name): SD(file_name)


{
pdata=NULL;
pnext=NULL;

list<el> *pcurrent_nod;
el *pnewel;

pcurrent_nod=this;
while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{
if(pcurrent_nod->pdata!=NULL)

129
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
pcurrent_nod->pnext=new list<el>();
pcurrent_nod=pcurrent_nod->pnext;
}
pcurrent_nod->pdata=pnewel;
}
fclose(SD::pf);
}

virtual void show(const char* opening, const char* ending,


int nlinepp=20)
{
clrscr();
printf(”%s”, opening);

list *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
int i=1;
printf(”%4d. ”, i);
pcurrent_nod->pdata->show("", "\n");
while(pcurrent_nod=pcurrent_nod->pnext)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf(”%s”, opening);
}
i++;
printf(”%4d. ”, i);
pcurrent_nod->pdata->show("", "\n");
}
}
printf(”%s”, ending);
printf("End of List. Press any key ...\n");
getch();
}

int search(el e)
{

130
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int position = -1;

list *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
int i=0;
if(SD::ncomp++, *(pcurrent_nod->pdata)==e)
position=i;
while((position==-1) && (pcurrent_nod=pcurrent_nod->pnext))
{
i++;
if(SD::ncomp++, *(pcurrent_nod->pdata)==e)
position=i;
}
}
return position;
}
};

В функции main() создадим простой однонаправленный список, загрузив его элементами из


файла "Stud.txt", выведем список на экран и продемонстрируем возможность поиска точно
так, как мы проделывали это с таблицами:

void main()
{
clrscr();

list<usual_elem> gr("Stud.txt");
gr.show("Group:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf(”%s”, surname);
usual_elem e(surname, 2000, 0.0);
int pos=gr.search(e);
if(pos<0)
printf("No list!\n");
else

131
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

printf("There are in the position %d\n", (pos+1));


printf("Done ? (y/n) ");
ch = getch();
printf(”\n”);
}
}

Вариант вывода может выглядеть так:

Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of List. Press any key ...
Enter a name to search: Gray
There are in the position 4
Done ? (y/n)
Enter a name to search: Black
There are in the position 10
Done ? (y/n)
Enter a name to search: Green
There are in the position 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 9
Done ? (y/n)
Enter a name to search: Purple
No list!
Done ? (y/n)

Упражнения.
1. Перегрузите оператор вставки в классе list.
2. Перегрузите оператор извлечения в классе list.
3. Создайте класс circular_list и продемонстрируйте работу с однонаправленными
циклическими списками.

132
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4. Преобразуйте класс extern_hashing_table из 2.6 таким образом, что бы табличные


записи, порождающие коллизии, связывались бы между собой в списки типа list.
5. Преобразуйте класс intern_hashing_table из 2.6 таким образом, что бы табличные
записи, порождающие коллизии, сперва связывались бы между собой в списки типа
list, а затем из списков переносились бы в свободные позиции основной таблицы.

Для демонстрации работы с двунаправленными списками, создадим шаблонный класс


bidirectional_list. Для конкретизации класса bidirectional_list может быть
использован любой конкретный класс, производный от абстрактного класса elem.
//
// C l a s s "b i d i r e c t i o n a l l i s t"
//
template <class el> class bidirectional_list: public SD
{
protected:
el *pdata;
bidirectional_list<el> *pnext;
bidirectional_list<el> *ppred;

public:
bidirectional_list<el>()
{
pdata=NULL;
pnext=ppred=NULL;
}

bidirectional_list<el>(char* file_name): SD(file_name)


{
pdata=NULL;
pnext=ppred=NULL;

bidirectional_list<el> *pcurrent_nod;
el *pnewel;

pcurrent_nod=this;
while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{
if(pcurrent_nod->pdata!=NULL)
{
pcurrent_nod->pnext=new bidirectional_list<el>();

133
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pcurrent_nod->pnext->ppred=pcurrent_nod;
pcurrent_nod=pcurrent_nod->pnext;
}
pcurrent_nod->pdata=pnewel;
}
fclose(SD::pf);
}

virtual void show(const char* opening, const char* ending,


int nlinepp=20)
{
clrscr();
printf("%s", opening);

bidirectional_list<el> *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
int i=1;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
while(pcurrent_nod=pcurrent_nod->pnext)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
i++;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
}
}
printf("%s", ending);
printf("End of List. Press any key ...\n");
getch();
}

virtual void invers_show(const char* opening, const char* ending,


int nlinepp=20)
{

134
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

clrscr();
printf("%s", opening);

bidirectional_list<el> *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
while(pcurrent_nod->pnext)
pcurrent_nod=pcurrent_nod->pnext;
int i=1;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
while(pcurrent_nod=pcurrent_nod->ppred)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
i++;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
}
}
printf("%s", ending);
printf("End of List. Press any key ...\n");
getch();
}

int search(el e)
{
int position = -1;

bidirectional_list<el> *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
int i=0;
if(*(pcurrent_nod->pdata)==e)
position=i;
while((position==-1) && (pcurrent_nod=pcurrent_nod->pnext))
{

135
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

i++;
if(*(pcurrent_nod->pdata)==e)
position=i;
}
}
return position;
}
};

В функции main() создадим двунаправленный список, загрузив его из файла "Stud.txt",


выведем его один раз в прямом направлении, затем в обратном направлении, затем
продемонстрируем возможность поиска, как поступали с таблицами:
void main()
{
clrscr();

bidirectional_list<usual_elem> gr("Stud.txt");

gr.show("Group:\n","\n");

gr.invers_show("Group in invers order:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
int pos=gr.search(e);
if(pos<0)
printf("No list!\n");
else
printf("There are in the position %d\n", (pos+1));
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Вариант вывода может выглядеть так:


Group:

136
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

1. Green 1987 350.00


2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00

End of List. Press any key ...


Group in invers order:
1. Black 1981 500.00
2. Magenta 1983 600.00
3. Yellow 1988 300.00
4. Cyan 1975 800.00
5. White 1980 600.00
6. Orange 1984 550.00
7. Gray 1968 900.00
8. Blue 1981 500.00
9. Red 1980 450.00
10. Green 1987 350.00
End of List. Press any key ...
Enter a name to search: Green
There are in the position 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 10
Done ? (y/n)
Enter a name to search: White
There are in the position 6
Done ? (y/n)
Enter a name to search: Purple
No list!
Done ? (y/n)

Упражнения.
1. Перегрузите оператор вставки в классе bidirectional_list.
2. Перегрузите оператор извлечения в классе bidirectional_list.
3. Объявите класс bidirectional_list как класс производный от шаблонного класса list.

137
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4. Создайте класс bidirectional_circular_list и продемонстрируйте работу с


двунаправленным циклическим списком.

Далее следует пример реализации простых однонаправленных списков при помощи двух
родственных классов. Основной класс main_list будет представлять список в целом, а
вспомогательный класс node_list будет представлять один элемент списка. Для облегчения
реализации класса main_list, последний будет объявлен как дружественный класс классу
node_list.

Прежде всего анонсируем класс main_list, чтобы на него можно было ссылаться в классе
node_list.
template <class el> class main_list;

Затем объявляем вспомогательный шаблонный класс node_list объекты которого будут


которого будут представлять элементы списка. Класс node_list может быть
конкретизирован любым конкретным классом, производным от абстрактного класса elem.
//
// C l a s s "n o d e _ l i s t"
//
template <class el> class node_list
{
protected:
node_list<el> *pnext;
el *pdata;

public:
node_list<el>()
{
pnext=NULL;
pdata=NULL;
}

virtual void show(const char* opening, const char* ending)


{
if(pdata)
pdata->show(opening, ending);
}

friend class main_list<el>;


};

Наконец создадим главный класс списка main_list, как конкретный шаблонный класс,
производный от абстрактного класса SD. Класс main_list может быть конкретизирован

138
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

любым конкретным классом, потомком абстрактного класса elem, и который будет


передаваться классу node_list как класс аргумент при создании элементов списка.
//
// C l a s s "m a i n _ l i s t"
//
template <class el> class main_list: public SD
{
protected:
node_list<el> *pfirst;

public:
main_list<el>()
{
pfirst=new node_list<el>();
}

main_list<el>(char* file_name): SD(file_name)


{
pfirst=new node_list<el>();

node_list<el> *pcurrent_nod;
el *pnewel;

pcurrent_nod=this->pfirst;
while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{
if(pcurrent_nod->pdata!=NULL)
{
pcurrent_nod->pnext=new node_list<el>();
pcurrent_nod=pcurrent_nod->pnext;
}
pcurrent_nod->pdata=pnewel;
}
fclose(SD::pf);
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
printf("%s", opening);

139
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

node_list<el> *pcurrent_nod=this->pfirst;
if(pcurrent_nod->pdata)
{
int i=1;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
while(pcurrent_nod=pcurrent_nod->pnext)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
i++;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
}
}
printf("%s", ending);
printf("End of List. Press any key ...\n");
getch();
}

int search(el e)
{
int position = -1;

node_list<el> *pcurrent_nod=this->pfirst;
if(pcurrent_nod->pdata)
{
int i=0;
if(SD::ncomp++, *(pcurrent_nod->pdata)==e)
position=i;
while((position==-1) && (pcurrent_nod=pcurrent_nod->pnext))
{
i++;
if(SD::ncomp++, *(pcurrent_nod->pdata)==e)
position=i;
}
}

140
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

return position;
}
};

Для демонстрации работы с простым однонаправленным списком, реализованным при


помощи двух родственных классов, запустим следующую функцию main():
void main()
{
clrscr();

main_list<usual_elem> gr("Stud.txt");
gr.show("Group:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
int pos=gr.search(e);
if(pos<0)
printf("No list!\n");
else
printf("There are in the position %d\n", (pos+1));
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Вариант вывода может выглядеть следующим образом:


Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of List. Press any key ...

141
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Enter a name to search: Gray


There are in the position 4
Done ? (y/n)
Enter a name to search: Black
There are in the position 10
Done ? (y/n)
Enter a name to search: Green
There are in the position 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 9
Done ? (y/n)
Enter a name to search: Purple
No list!
Done ? (y/n)

Упражнения.
1. Перегрузите оператор вставки в классах main_list и node_list.
2. Перегрузите оператор извлечения в классах main_list и node_list.
3. Реализуйте работу с однонаправленными циклическими списками при помощи двух
родственных шаблонных классов: первый main_circular_list, который будет представлять
однонаправленный циклический список в целом, и второй node_circular_list, который
будет представлять один элемент списка. Класс main_circular_list может быть объявлен
как класс дружественный классу node_circular_list.
4. Реализуйте работу с двунаправленными списками при помощи двух родственных
шаблонных классов: первый main_bidirectional_list, который будет представлять
двунаправленный список в целом, и второй nod_bidirectional_list, который будет
представлять один элемент списка. Класс main_bidirectional_list может быть объявлен
как класс дружественный классу nod_bidirectional_list.
5. Реализуйте работу с двунаправленными циклическими списками при помощи двух
родственных шаблонных классов: первый main_bidirectional_circular_list, который
будет представлять двунаправленный циклический список в целом, и второй
nod_bidirectional_circular_list, который будет представлять один элемент списка.
Класс main_bidirectional_circular_list может быть объявлен как класс дружественный
классу nod_bidirectional_circular_list.

142
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4.4. Стеки

Обшие понятия

Стек (engl. stack, LIFO list, pushdown stack, pushdown list) это динамическая
структура данных, состоящая из конечного и упорядоченного набора
элементов одной и той же структуры, со свойством, что как добавление
новых элементов и доступ к существующим (преимущественно удаление)
осуществляется с одного и того же конца (называемого вершиной стека) по
принципу “последним вошел – первым вышел” (engl. LIFO – Last In, First
Out).

К стекам применяются следующие операции:


 вставка нового элемента в вершину стека (push), предыдущий вершинный становится
следующим элементом;
 извлечение элемента из вершины стека (pop), предыдущий следующий становится
вершинным элементом;
 проверка стека на пустоту (isempty), если стек пуст нельзя применять операцию pop;
 проверка если стек полностью заполнен (isfull), нельзя применять операцию push.
Имеются два способа реализации стеков:
 посредством буфера фиксированной длины;
 посредством простого однонаправленного списка, со свойством, что как добавление так и
удаление элементов осуществляются в начале списка.

Реализация стеков при помощи буфера фиксированного размера

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

size n top 0

0 1 2 3 4 n-2 n-1
buffer …

Пустой стек размера n

143
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Поле size содержит размер стека. Оно инициализируется при создании стека и больше не
меняет своего значения.
Вектор buffer представляет пространство для записи элементов в стек. Память под него
выделяется во время создания стека.
Поле top указывает следующую позицию за вершиной стека (первая позиция доступная для
записи в стек). Оно инициализируется при создании стека и в дальнейшем будет обновляться
при каждой записи в стек (push) и при каждом извлечении из стека (pop).
Например, после трех последовательных записей (push(A), push(B), push(C)) стек будет
выглядеть следующим образом:

size n top 3

0 1 2 3 4 n-2 n-1
buffer A B C …

Стек с тремя элементами

Если далее последует операция извлечения (pop), ее результатом будет C, а стек будет
выглядеть следующим образом:

size n top 2

0 1 2 3 4 n-2 n-1
buffer A B …

Стек после одного извлечения

Для демонстрации работы со стеками в форме буфера фиксированного размера, создадим


шаблонный класс stackFB, который может быть конкретизирован любым конкретным
классом, производным от абстрактного класса elem:
//
// C l a s s "s t a c k f i x e d b u f f e r"
//
template <class el> class stackFB: public SD
{
protected:
el *buffer;
int size, top;

144
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public:
stackFB<el>(int init_size=100)
{
size=init_size;
buffer = new el[size];
top=0;
}

stackFB<el>(char* file_name, int init_size=100): SD(file_name)


{
size=init_size;
buffer = new el[size];
top=0;

el newel;

while(!feof(SD::pf))
if(newel.fscanf_el(SD::pf)>0)
push(newel);
fclose(SD::pf);
}

~stackFB<el>()
{
delete buffer;
}

int isempty(){return top==0;}

int isfull(){return top==size;}

void push(el newel)


{
if(!isfull())
buffer[top++]=newel;
else
error("Stack is full!\n");
}

el pop()
{

145
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if(top)
return buffer[--top];
else
error("Stack is empty!\n");
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
printf(”%s”, opening);

if(!isempty())
{
int i=1;
printf(”%4d. ”, i);
int current=top-1;
buffer[current].show("", "\n");
while(--current>=0)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf(”%s”, opening);
}
i++;
printf(”%4d. ”, i);
buffer[current].show("", "\n");
}
}

printf(”%s”, ending);
printf(”End of Stack. Press any key ...\n");
getch();
}
};

В функции main() создадим стек в виде буфера фиксированного размера в 100 элементов
типа usual_elem, загрузив его из файла "Stud.txt". Затем выведем содержимое стека, после
чего опустошим по шагам, последовательными извлечениями. Далее запишем в стек два
элемента и убедимся, что они будут извлечены в обратном порядке:
void main()

146
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
clrscr();

stackFB<usual_elem> gr("Stud.txt", 100);


gr.show("Group in stack:\n","");

printf("\nExtracting by one:\n");
char ch;
while(!gr.isempty())
{
printf("Press any key...\n");
ch = getch();
usual_elem extracted_el;
extracted_el = gr.pop();
extracted_el.show("","\n");
}
printf("Stack is empty! Press any key...\n");
ch = getch();

gr.push(usual_elem("Purple", 1985, 400));


gr.push(usual_elem("Violet", 1984, 500));

printf(”\n”);
gr.pop().show("","\n");
gr.pop().show("","\n");
printf("Press any key...\n");

ch = getch();
printf(”\n”);
}

Результат будет выглядеть следующим образом:


Group in stack:
1. Black 1981 500.00
2. Magenta 1983 600.00
3. Yellow 1988 300.00
4. Cyan 1975 800.00
5. White 1980 600.00
6. Orange 1984 550.00
7. Gray 1968 900.00
8. Blue 1981 500.00
9. Red 1980 450.00
10. Green 1987 350.00

147
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

End of Stack. Press any key ...

Extracting by one:
Press any key...
Black 1981 500.00
Press any key...
Magenta 1983 600.00
Press any key...
Yellow 1988 300.00
Press any key...
Cyan 1975 800.00
Press any key...
White 1980 600.00
Press any key...
Orange 1984 550.00
Press any key...
Gray 1968 900.00
Press any key...
Blue 1981 500.00
Press any key...
Red 1980 450.00
Press any key...
Green 1987 350.00
Stack is empty! Press any key...

Violet 1984 500.00


Purple 1985 400.00
Press any key...

Упражнения.
1. Перегрузите оператор вставки в классе stackFB.
2. Перегрузите оператор извлечения в классе stackFB.

Реализация стеков при помощи списков

Операции записи и извлечения выполняются в начале списка, который используется для


представления стека в оперативной памяти.
Пустой стек в форме списка выглядит следующим образом:

148
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pdata NULL
pnext NULL

Пустой стек в форме простого однонаправленного списка

После трех последовательных записей (push(A), push(B), push(C)) стек будет выглядеть
следующим образом:

C B A

pdata
pnext NULL

Стек с тремя элементами

Если далее последует операция извлечения (pop), ее результатом будет C, а стек будет
выглядеть так:

B A

pdata
pnext NULL

Стек после извлечения одного элемента

Для демонстрации работы со стеком в форме списка, с добавлением и извлечением в начале


списка, создадим шаблонный класс stackL, который может быть конкретизирован любым
конкретным классом производным от абстрактного класса elem:
//
// C l a s s "s t a c k l i s t"
//
template <class el> class stackL: public SD
{
protected:
el *pdata;
stackL<el> *pnext;

149
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public:
stackL<el>()
{
pdata=NULL;
pnext=NULL;
}

stackL<el>(char* file_name): SD(file_name)


{
pdata=NULL;
pnext=NULL;

el *pnewel;

while(!feof(SD::pf))
if((pnewel=new el())->fscanf_el(SD::pf)>0)
push(*pnewel);
fclose(SD::pf);
}

~stackL<el>()
{

int isempty(){return pdata==NULL;}

int isfull(){return 0;}

void push(el &newel)


{
if(!isfull())
{
if(this->pdata)
{
stackL<el> *ptemp=new stackL<el>;
ptemp->pdata=this->pdata;
ptemp->pnext=this->pnext;
this->pnext=ptemp;
}
this->pdata=&newel;

150
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
else
error("Stack is full!");
}

el &pop()
{
if(!isempty())
{
el *pret=this->pdata;

if(this->pnext)
{
stackL<el> *ptemp=this->pnext;
this->pnext=ptemp->pnext;
this->pdata=ptemp->pdata;
delete ptemp;
}
else
this->pdata=NULL;

return *pret;
}
else
error("Stack is empty!");
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
printf("%s", opening);

if(!isempty())
{
int i=1;
printf("%4d. ", i);
stackL<el> *pcurrent=this;
pcurrent->pdata->show("", "\n");
while((pcurrent=pcurrent->pnext)!=NULL)
{
if(i>0 && i%nlinepp==0)
{

151
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

printf("Press any key to continue...\n");


getch();
clrscr();
printf("%s", opening);
}
i++;
printf("%4d. ", i);
pcurrent->pdata->show("", "\n");
}
}

printf("%s", ending);
printf("End of Stack. Press any key ...\n");
getch();
}
};

В предыдущей версии функции main(), вместо стека в форме буфера создадим стек в форме
списка:
stackL<usual_elem> gr("Stud.txt");

Результат будет точно таким же, как и в предыдущей версии.


Упражнения.
1. Перегрузите оператор вставки в классе stackL.
2. Перегрузите оператор извлечения в классе stackL.
3. Реализуйте стек в виде списка представленного двумя дружественными шаблонными
классами: main_stackL и node_stackL.

152
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Использование стеков для обхода двоичных деревьев

Далее продемонстрируем использование стеков для обхода двоичных деревьев. В разделе


посвященном двоичным деревьям все виды обходов (прямой, симметричный, концевой)
были реализованы при помощи рекурсивных функций. Теперь воспользуемся стеками. С
этой целью опишем заново шаблонные классы stackFB и stackL упростив их до такой
степени, чтобы они могли быть конкретизированы любыми неабстрактными классами, а
вовсе необязательно только производными от класса elem. Для этого удалим из предыдущих
версий классов stackFB и stackL конструктор загружающий стек из указанного файла, а так
же заменим на пустышку функцию, выводящую содержимое стека. Упрощенные версии
классов stackFB и stackL будут выглядеть так:
//
// C l a s s "s t a c k f i x e d b u f f e r"
//
template <class el> class stackFB: public SD
{
protected:
el *buffer;
int size, top;

public:
stackFB<el>(int init_size=100)
{
size=init_size;
buffer = new el[size];
top=0;
}

~stackFB<el>()
{
delete buffer;
}

int isempty(){return top==0;}

int isfull(){return top==size;}

void push(el newel)


{
if(!isfull())
buffer[top++]=newel;
else

153
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

error("Stack is full!\n");
}

el pop()
{
if(top)
return buffer[--top];
else
error("Stack is empty!\n");
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{

};

//
// C l a s s "s t a c k l i s t"
//
template <class el> class stackL: public SD
{
protected:
el *pdata;
stackL<el> *pnext;

public:
stackL<el>()
{
pdata=NULL;
pnext=NULL;
}

~stackL<el>()
{

int isempty(){return pdata==NULL;}

int isfull(){return 0;}

154
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

void push(el &newel)


{
if(!isfull())
{
if(this->pdata)
{
stackL<el> *ptemp=new stackL<el>;
ptemp->pdata=this->pdata;
ptemp->pnext=this->pnext;
this->pnext=ptemp;
}
this->pdata=&newel;
}
else
error("Stack is full!");
}

el &pop()
{
if(!isempty())
{
el *pret=this->pdata;

if(this->pnext)
{
stackL<el> *ptemp=this->pnext;
this->pnext=ptemp->pnext;
this->pdata=ptemp->pdata;
delete ptemp;
}
else
this->pdata=NULL;

return *pret;
}
else
error("Stack is empty!");
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{

155
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

};

Создадим шаблонный класс bintree_stack который может быть конкретизирован любым


неабстрактным классом – потомком абстрактного класса elem. Класс bintree_stack будет
аналогичным уже исследованному классу bintree. В отличии от последнего, в классе
bintree_stack обходы будут реализованы с использованием стеков, а не рекурсивными
функциями. при реализации обходов в симметричном и концевом порядков, будем заносить
в стек не только адрес конкретного узла, но и сопутствующую информацию о типе
сохраняемого узла (корень или просто узел, подлежащий обработке). С этой целью создадим
еще один класс. Это будет вспомогательный класс, назовем его stack_elem. Он будет
использован для представления расширенного элемента стека.
//
// C l a s s "b i n t r e e s t a c k"
//
template <class el> class bintree_stack: public SD
{
protected:
bintree_stack *pleft;
bintree_stack *pright;
el *pdata;

public:
bintree_stack<el>()
{
pdata=NULL;
pleft=pright=NULL;
}

bintree_stack<el>(char* file_name): SD(file_name)


{
pdata=NULL;
pleft=pright=NULL;

bintree_stack *pcurrent_nod;
el *pnewel;

while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{

156
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pcurrent_nod=this;
while(pcurrent_nod->pdata!=NULL)
{
if(*pnewel < *(pcurrent_nod->pdata))
{
if(pcurrent_nod->pleft==NULL)
pcurrent_nod->pleft=new bintree_stack();
pcurrent_nod=pcurrent_nod->pleft;
}
else
{
if(pcurrent_nod->pright==NULL)
pcurrent_nod->pright=new bintree_stack();
pcurrent_nod=pcurrent_nod->pright;
}
}
pcurrent_nod->pdata=pnewel;
}
fclose(SD::pf);
}

virtual void show_preorderFB(const char* opening,


const char* ending)
{
stackFB<bintree_stack<el>*> st(100);
st.push(this);

printf(”%s”, opening);

while(!st.isempty())
{
bintree_stack<el>* pcur=st.pop();
if(pcur->pright)
st.push(pcur->pright);
if(pcur->pleft)
st.push(pcur->pleft);
if(pcur->pdata)
pcur->pdata->show("","\n");
}
printf(”%s”, ending);
}

157
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual void show_inorderFB(const char* opening,


const char* ending)
{
stackFB<stack_elem<el> > st(100);
stack_elem<el> stel;
stel.p=this;
stel.t='r';

st.push(stel);

printf(”%s”, opening);

while(!st.isempty())
{
stack_elem<el> current=st.pop();
if(current.t=='n')
(current.p)->pdata->show("","\n");
else
{
if((current.p)->pright)
{
stel.p=(current.p)->pright;
stel.t='r';
st.push(stel);
}
if((current.p)->pdata)
{
stel.p=current.p;
stel.t='n';
st.push(stel);
}
if((current.p)->pleft)
{
stel.p=(current.p)->pleft;
stel.t='r';
st.push(stel);
}
}
}
printf(”%s”, ending);
}

158
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual void show_postorderFB(const char* opening,


const char* ending)
{
stackFB<stack_elem<el> > st(100);
stack_elem<el> stel;
stel.p=this;
stel.t='r';

st.push(stel);

printf(”%s”, opening);

while(!st.isempty())
{
stack_elem<el> current=st.pop();
if(current.t=='n')
(current.p)->pdata->show("","\n");
else
{
if((current.p)->pdata)
{
stel.p=current.p;
stel.t='n';
st.push(stel);
}
if((current.p)->pright)
{
stel.p=(current.p)->pright;
stel.t='r';
st.push(stel);
}
if((current.p)->pleft)
{
stel.p=(current.p)->pleft;
stel.t='r';
st.push(stel);
}
}
}
printf("%s", ending);
}

159
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual void show_preorderL(const char* opening,


const char* ending)
{
stackL<bintree_stack<el>*> st;
st.push(this);

printf(”%s”, opening);

while(!st.isempty())
{
bintree_stack<el>* pcur=st.pop();
if(pcur->pright)
st.push(pcur->pright);
if(pcur->pleft)
st.push(pcur->pleft);
if(pcur->pdata)
pcur->pdata->show("","\n");
}
printf(”%s”, ending);
}

virtual void show_inorderL(const char* opening,


const char* ending)
{
stackL<stack_elem<el> > st;
stack_elem<el>* pstel = new stack_elem<el>;
pstel->p=this;
pstel->t='r';

st.push(*pstel);

printf(”%s”, opening);

while(!st.isempty())
{
stack_elem<el> current=st.pop();
if(current.t=='n')
(current.p)->pdata->show("","\n");
else
{
if((current.p)->pright)
{

160
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pstel = new stack_elem<el>;


pstel->p=(current.p)->pright;
pstel->t='r';
st.push(*pstel);
}
if((current.p)->pdata)
{
pstel = new stack_elem<el>;
pstel->p=current.p;
pstel->t='n';
st.push(*pstel);
}
if((current.p)->pleft)
{
pstel = new stack_elem<el>;
pstel->p=(current.p)->pleft;
pstel->t='r';
st.push(*pstel);
}
}
}
printf(”%s”, ending);
}

virtual void show_postorderL(const char* opening,


const char* ending)
{
stackL<stack_elem<el> > st;
stack_elem<el>* pstel = new stack_elem<el>;
pstel->p=this;
pstel->t='r';

st.push(*pstel);

printf(”%s”, opening);

while(!st.isempty())
{
stack_elem<el> current=st.pop();
if(current.t=='n')
(current.p)->pdata->show("","\n");
else

161
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
if((current.p)->pdata)
{
pstel = new stack_elem<el>;
pstel->p=current.p;
pstel->t='n';
st.push(*pstel);
}
if((current.p)->pright)
{
pstel = new stack_elem<el>;
pstel->p=(current.p)->pright;
pstel->t='r';
st.push(*pstel);
}
if((current.p)->pleft)
{
pstel = new stack_elem<el>;
pstel->p=(current.p)->pleft;
pstel->t='r';
st.push(*pstel);
}
}
}
printf(”%s”, ending);
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
show_preorderFB(opening, ending);
}
};

Далее следует объявление шаблонного класса-структуры stack_elem с двумя полями. Это


указатель на узел двоичного дерева, и поле в один символ с возможными значениями 'r' –
корень и 'n' – узел.
template <class el> struct stack_elem
{
bintree_stack<el> *p;
char t;
};

В функции main() создаем двоичное дерево, загрузив его из файла "Stud.txt", затем
выводим последовательно информацию об его узлах соответственно обходу в прямом,

162
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

симметричном и концевом порядках по два раза. Один раз с использованием стека в форме
буфера фиксированной длины и второй раз с использованием стека в форме простого
однонаправленного списка.
void main()
{
clrscr();

bintree_stack<usual_elem> btgr("Stud.txt");

btgr.show_preorderFB("Group in preorder with buffer\n","\n");

btgr.show_inorderFB("Group in inorder with buffer\n","\n");

btgr.show_postorderFB("Group in postorder with buffer\n","\n");

btgr.show_preorderL("Group in preorder with list\n","\n");

btgr.show_inorderL("Group in inorder with list\n","\n");

btgr.show_postorderL("Group in postorder with list\n","\n");

getch();
}

Результат будет аналогичен уже известному и будет выглядеть следующим образом:


Group in preorder with buffer
Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00
Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00

Group in inorder with buffer


Black 1981 500.00
Blue 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Green 1987 350.00

163
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Magenta 1983 600.00


Orange 1984 550.00
Red 1980 450.00
White 1980 600.00
Yellow 1988 300.00

Group in postorder with buffer


Black 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Blue 1981 500.00
Magenta 1983 600.00
Orange 1984 550.00
Yellow 1988 300.00
White 1980 600.00
Red 1980 450.00
Green 1987 350.00

Group in preorder with list


Green 1987 350.00
Blue 1981 500.00
Black 1981 500.00
Gray 1968 900.00
Cyan 1975 800.00
Red 1980 450.00
Orange 1984 550.00
Magenta 1983 600.00
White 1980 600.00
Yellow 1988 300.00

Group in inorder with list


Black 1981 500.00
Blue 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Green 1987 350.00
Magenta 1983 600.00
Orange 1984 550.00
Red 1980 450.00
White 1980 600.00
Yellow 1988 300.00

164
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Group in postorder with list


Black 1981 500.00
Cyan 1975 800.00
Gray 1968 900.00
Blue 1981 500.00
Magenta 1983 600.00
Orange 1984 550.00
Yellow 1988 300.00
White 1980 600.00
Red 1980 450.00
Green 1987 350.00

Упражнение.
1. Продемонстрируйте использование стеков для обхода двоичных деревьев, реализовав стек
в виде списка с помощью двух родственных шаблонных классов: main_stackL и
node_stackL.

165
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Реализация быстрой сортировки с использованием стека

В 3.4 был исследован алгоритм быстрой сортировки, реализованный при помощи


рекурсивной функции. Рассмотрим реализацию этого же алгоритма с использованием стека.
Прежде всего введем класс interval для представления и сохранения в стеке интервалов
обработки подвекторов сортируемого вектора. Класс interval создается как конкретный
класс, производный от абстрактного класса elem.
//
// C l a s s "i n t e r v a l"
//
class interval: public elem
{
public:

int i;
int j;

interval(int init_i=0, int init_j=0)


{
i=init_i;
j=init_j;
}

virtual int fscanf_el(FILE *f)


{
return fscanf(f, "%d %d", i, j);
}

virtual void show(const char* opening, const char* ending)


{
printf("%s", opening);
printf("[%4d, %4d]", i, j);
printf("%s", ending);
}

virtual int free()


{
return i>j;
}

virtual int cmp(elem& e2)


{

166
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

return (this->i==((interval&)e2).i) && (this->j==((interval&)e2).j);


}
};

Далее создаем шаблонный класс vector_quicksort_stack снабженный алгоритмом быстрой


сортировки. В отличии от класса vector_quicksort из 3.4 в классе vector_quicksort_stack
рекурсивная функция quicksort_intern() будет заменена нерекурсивной, но использующей
стек функцией quicksort_stack().
//
// C l a s s "v e c t o r _ q u i c k s o r t _ s t a c k"
//
template <class el> class vector_quicksort_stack: public vector<el>
{
public:
vector_quicksort_stack<el>(char* file_name, int NMAX=200):
vector<el>(file_name, NMAX)
{
}

void quicksort(int i=0, int j=-1)


{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
quicksort_stack(i, j);
}

protected:
void quicksort_stack(int i, int j);
int divide(int i, int j);
};

template <class el>


void vector_quicksort_stack<el>::quicksort_stack(int i, int j)
{
stackFB<interval> stack_interv((int)(log((double)n)/log(2.0))+1);
stack_interv.push(*(new interval(i, j)));

while(!stack_interv.isempty())
{
interval tinterv=stack_interv.pop();
i=tinterv.i;

167
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

j=tinterv.j;

tinterv.show("From Stack: ","\n");


getch();

if(j>i)
{
int imain=divide(i,j);
stack_interv.push(*(new interval(i, imain-1)));
stack_interv.push(*(new interval(imain+1, j)));
}
else
printf("Not processed!\n"), getch();
}
}

template <class el> int vector_quicksort_stack<el>::divide(int i, int j)


{
int imain, jmain, imed;
imed =(i+j)/2;
imain = (SD::ncomp++, t[i] < t[imed]) ?
((SD::ncomp++, t[imed] < t[j]) ?
imed
:
(SD::ncomp++, t[i] < t[j]) ? j : i)
:
((SD::ncomp++, t[imed] > t[j]) ?
imed
:
(SD::ncomp++, t[i] > t[j]) ? j : i);

if(imain > i)
swap(i, imain);
imain = i+1, jmain = j;
while(imain < jmain)
{
while((imain < jmain)&&(SD::ncomp++, t[imain] <= t[i]))
imain++;
while((jmain > imain)&&(SD::ncomp++, t[jmain] >= t[i]))
jmain--;
if(imain < jmain)
swap(imain, jmain);

168
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
if(ncomp++, t[imain] > t[i])
imain--;
if(imain > i)
swap(i, imain);
return imain;
}

Далее следует ряд функций main() аналогичных соответствующим функциям из 3.4.


Полученные результаты будут идентичны результатам, полученным в 3.4.
Для сортировки по полю name:
void main()
{
clrscr();

vector_quicksort_stack<usual_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();

результат будет следующим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
From Stack: [ 0, 9]
From Stack: [ 5, 9]
From Stack: [ 9, 9]
Not processed!

169
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

From Stack: [ 5, 7]
From Stack: [ 7, 7]
Not processed!
From Stack: [ 5, 5]
Not processed!
From Stack: [ 0, 3]
From Stack: [ 3, 3]
Not processed!
From Stack: [ 0, 1]
From Stack: [ 2, 1]
Not processed!
From Stack: [ 0, 0]
Not processed!
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=39, n*log2(n)=33.22, n*n=100

Для сортировки по полю year:


void main()
{
clrscr();

vector_quicksort_stack<year_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by year:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

170
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

результат будет следующим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
From Stack: [ 0, 9]
From Stack: [ 8, 9]
From Stack: [ 10, 9]
Not processed!
From Stack: [ 8, 8]
Not processed!
From Stack: [ 0, 6]
From Stack: [ 2, 6]
From Stack: [ 6, 6]
Not processed!
From Stack: [ 2, 4]
From Stack: [ 4, 4]
Not processed!
From Stack: [ 2, 2]
Not processed!
From Stack: [ 0, 0]
Not processed!
Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. Red 1980 450.00
4. White 1980 600.00
5. Black 1981 500.00
6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...

171
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

n=10, ncomp=42, n*log2(n)=33.22, n*n=100

Для сортировки по полю salary:


void main()
{
clrscr();

vector_quicksort_stack<salary_elem> gr("Stud.txt");
gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by salary:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

rezultatul va fi:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
From Stack: [ 0, 9]
From Stack: [ 5, 9]
From Stack: [ 7, 9]
From Stack: [ 9, 9]
Not processed!
From Stack: [ 7, 7]
Not processed!
From Stack: [ 5, 5]
Not processed!
From Stack: [ 0, 3]
From Stack: [ 2, 3]
From Stack: [ 4, 3]

172
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Not processed!
From Stack: [ 2, 2]
Not processed!
From Stack: [ 0, 0]
Not processed!
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=43, n*log2(n)=33.22, n*n=100

Упражнение.
1. Создайте шаблонный класс vector_quicksort_stack как производный от шаблонного
класса vector_quicksort из 3.4.

В отличии от класса vector_optim_quicksort из классе


3.4, в
vector_optim_quicksort_stack так же рекурсивная функция quicksort_intern() будет
заменена нерекурсивной, однако использующей стек функцией quicksort_stack().
//
// C l a s s " v e c t o r _ o p t i m _ q u i c k s o r t _ s t a c k"
//
template <class el> class vector_optim_quicksort_stack:
public vector_quicksort_stack<el>
{
public:
vector_optim_quicksort_stack<el>(char* file_name, int threshold_init=15,
int NMAX=200):
vector_quicksort_stack<el>(file_name, NMAX)
{
threshold=threshold_init;
if(threshold<1)
threshold=1;
}

173
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

void quicksort(int i=0, int j=-1)


{
if(j>=n || j==-1)
j=n-1;
if(i<0 || i>j)
i=0;
optim_quicksort_stack(i, j);
}

protected:
int threshold;
void optim_quicksort_stack(int i, int j);
void insertsort(int i, int j);
};

template <class el>


void vector_optim_quicksort_stack<el>::optim_quicksort_stack(int i, int j)
{
stackFB<interval> stack_interv((int)(log((double)n)/log(2.0))+1);

stack_interv.push(*(new interval(i, j)));

while(!stack_interv.isempty())
{
interval tinterv=stack_interv.pop();
i=tinterv.i;
j=tinterv.j;

if(j-i+1>threshold)
{
int imain=divide(i,j);
if(imain-i > j-imain)
{
stack_interv.push(*(new interval(i, imain-1)));
stack_interv.push(*(new interval(imain+1, j)));
}
else
{
stack_interv.push(*(new interval(imain+1, j)));
stack_interv.push(*(new interval(i, imain-1)));
}

174
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
else
insertsort(i, j);
}
}

template <class el>


void vector_optim_quicksort_stack<el>::insertsort(int i, int j)
{
for(int k = i+1; k<=j; k++)
for(int l=k; (l>i)&&(ncomp++, t[l]<t[l-1]); l--)
swap(l-1, l);
}

Далее, так же следует ряд функций main(), аналогичных соответствующим функциям


main() из 3.4. Результаты будут идентичны соответствующим результатам из 3.4.

Для сортировки по полю name:


void main()
{
clrscr();

vector_optim_quicksort_stack<usual_elem> gr("Stud.txt", 4);


gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by name:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

результат будет следующим:


Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00

175
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

9. Magenta 1983 600.00


10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by name:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=32, n*log2(n)=33.22, n*n=100

Для сортировки по полю year:


void main()
{
clrscr();

vector_optim_quicksort_stack<year_elem> gr("Stud.txt", 4);


gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by year:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

результат будет:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00

176
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

9. Magenta 1983 600.00


10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by year:
1. Gray 1968 900.00
2. Cyan 1975 800.00
3. White 1980 600.00
4. Red 1980 450.00
5. Black 1981 500.00
6. Blue 1981 500.00
7. Magenta 1983 600.00
8. Orange 1984 550.00
9. Green 1987 350.00
10. Yellow 1988 300.00
End of vector. Press any key ...
n=10, ncomp=37, n*log2(n)=33.22, n*n=100

Для сортировки по полю salary:

void main()
{
clrscr();

vector_optim_quicksort_stack<salary_elem> gr("Stud.txt", 4);


gr.show("Unsorted group:\n","");
gr.reset_ncomp();
gr.quicksort();
gr.show("Group sorted by salary:\n","");
printf("n=%d, ncomp=%d, n*log2(n)=%.2lf, n*n=%.0lf\n",
gr.get_n(),gr.get_ncomp(),
gr.get_n()*log((double)gr.get_n())/log(2.0),
(double)gr.get_n()*gr.get_n());
getch();
}

результат будет:
Unsorted group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00

177
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

8. Yellow 1988 300.00


9. Magenta 1983 600.00
10. Black 1981 500.00
End of vector. Press any key ...
Group sorted by salary:
1. Yellow 1988 300.00
2. Green 1987 350.00
3. Red 1980 450.00
4. Blue 1981 500.00
5. Black 1981 500.00
6. Orange 1984 550.00
7. White 1980 600.00
8. Magenta 1983 600.00
9. Cyan 1975 800.00
10. Gray 1968 900.00
End of vector. Press any key ...
n=10, ncomp=33, n*log2(n)=33.22, n*n=100

Упражнение.
1. Создайте шаблонный класс vector_optim_quicksort_stack, как производный от
шаблонного класса vector_optim_quicksort из 3.4.

178
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

4.5. Очереди

Общие понятия

Очередь (engl. queue, FIFO list, pushup stack, pushup list) это динамическая
структура данных, состоящая из конечного упорядоченного набора
элементов одного и того же типа, организованная таким образом, что
добавление новых элементов осуществляется только в конец очереди, а
доступ к ее элементам (преимущественно извлечение) осуществляется
только с начала очереди, согласно принципу “первым вошел – первым
вышел ” (engl. FIFO – First In, First Out ).

К очередям применяются следующие операции:


 добавление нового элемента к очереди (put), предыдущий последний становится
предпоследним;
 извлечение очередного элемента из очереди (get), предыдущий следующий элемент
становится первым;
 проверка если очередь пуста (isempty), т. е. нельзя применять операцию get;
 проверка если очередь полностью заполнена (isfull), т. е. нельзя применять операцию
put.

В устройствах очередь может быть реализована в виде безадресной памяти специальной


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

179
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Реализация очередей при помощи циклического буфера

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

size n nextin 0

0 1 2 3 4 n-2 n-1
buffer …

nextout 0 count 0

Пустая очередь размера n


Поле size содержит размер очереди. Оно инициализируется в момент создания очереди и в
дальнейшем не меняется.
Вектор buffer представляет пространство для записи в очередь. Память под него выделяется
во время создания очереди.
Поле nextin указывает на первую позицию доступную для записи. Оно инициализируется
при создании очереди и в дальнейшем изменяет свое значение при каждой записи в очередь
(put).
Поле nextout указывает на первый элемент доступный для извлечения из очереди. Оно
инициализируется при создании очереди и в дальнейшем изменяет свое значение при
каждом извлечении из очереди (get).
Например, после трех последовательных записей (put(A), put(B), put(C)) очередь будет
выглядеть следующим образом:

size n nextin 3

0 1 2 3 4 n-2 n-1
buffer A B C …

nextout 0 count 3

Очередь с тремя элементами

180
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Если далее последует одно извлечение из очереди (get), то результатом будет A, а очередь
будет выглядеть так:

size n nextin 3

0 1 2 3 4 n-2 n-1
buffer B C …

nextout 1 count 2

Очередь после извлечения

Для демонстрации работы с очередью в виде циклического буфера, создадим шаблонный


класс queueCB, который может быть конкретизирован любым конкретным классом,
производным от абстрактного класса elem:
//
// C l a s s "q u e u e c i r c u l a r b u f f e r"
//
template <class el> class queueCB: public SD
{
protected:
el *buffer;
int size, nextin, nextout, count;
public:

queueCB<el>(int init_size=100)
{
size=init_size;
buffer = new el[size];
nextin=nextout=count=0;
}

queueCB<el>(char* file_name, int init_size=100): SD(file_name)


{
size=init_size;
buffer = new el[size];

181
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

nextin=nextout=count=0;
el newel;

while(!feof(SD::pf))
if(newel.fscanf_el(SD::pf)>0)
put(newel);
fclose(SD::pf);
}

~queueCB()
{
delete buffer;
}

int isempty(){return count==0;}

int isfull(){return count==size;}

void put(el newel)


{
if(count++ < size)
{
buffer[nextin]=newel;
nextin=++nextin%size;
}
else
error("Queue is full!");
}

el get()
{
if(count-- > 0)
{
int outpos = nextout;
nextout=++nextout%size;
return buffer[outpos];
}
else
error("Queue is empty!");
}

virtual void show(const char* opening, const char* ending, int nlinepp=20)

182
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
clrscr();
printf("%s”, opening);

if(count>0)
{
int i=1;
printf("%4d. ”, i);
int current=nextout;
buffer[current].show("", "\n");
while((++current%size)!=nextin)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s”, opening);
}
i++;
printf("%4d. ”, i);
buffer[current].show("", "\n");
}
}
printf("%s”, ending);

printf("End of Queue. Press any key ...\n");


getch();
}
};

В функции main() создадим очередь в виде циклического буфера размера в 100 элементов
типа usual_elem, загрузив ее из файла "Stud.txt". Затем выведем содержимое очереди,
после чего очистим ее пошаговым извлечением. Далее запишем в очередь два элемента и
убедимся, что они будут извлекаться в порядке, соответствующем записи:
void main()
{
clrscr();

queueCB<usual_elem> gr("Stud.txt", 100);


gr.show("Group in queue:\n","");

183
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

printf("\nExtracting by one:\n");
while(!gr.isempty())
{
printf(”\nPress any key...\n");
getch();
usual_elem extel;
extel = gr.get();
extel.show("","");
}
printf("\nQueue is empty! Press any key...\n");

gr.put(usual_elem("Purple", 1985, 400));


gr.put(usual_elem("Violet", 1984, 500));

printf(”\n”);
gr.get().show("","\n");
gr.get().show("","\n");
printf(”Press any key...\n");

getch();
}

Вывод будет выглядеть следующим образом:


Group in queue:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of Queue. Press any key ...

Extracting by one:

Press any key...


Green 1987 350.00
Press any key...
Red 1980 450.00

184
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Press any key...


Blue 1981 500.00
Press any key...
Gray 1968 900.00
Press any key...
Orange 1984 550.00
Press any key...
White 1980 600.00
Press any key...
Cyan 1975 800.00
Press any key...
Yellow 1988 300.00
Press any key...
Magenta 1983 600.00
Press any key...
Black 1981 500.00
Queue is empty! Press any key...

Purple 1985 400.00


Violet 1984 500.00
Press any key...

Упражнения.
1. Перегрузите оператор вставки в классе queueCB.
2. Перегрузите оператор извлечения в классе queueCB.

Реализация очередей при помощи списка

Операции добавления выполняются в конце, а извлечения в начале списка, используемого


для представления очереди в оперативной памяти (или наоборот).
Пустая очередь в форме списка выглядит так:

pdata NULL
pnext NULL

Пустая очередь в форме простого однонаправленного списка

После трех последовательных записей (put(A), put(B), put(C)) очередь будет выглядеть
следующим образом:

185
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

A B C

pdata
pnext NULL

Очередь с тремя элементами

Если далее последует одно извлечение (get), то результатом будет A, а очередь будет
выглядеть так:

B C

pdata
pnext NULL

Очередь после одного извлечения

Для демонстрации работы с очередью в форме списка с добавлением в конец и извлечением


с начала, создадим шаблонный класс queueL который может быть конкретизирован любым
конкретным классом, производным от абстрактного класса elem:
//
// C l a s s "q u e u e l i s t"
//
template <class el> class queueL: public SD
{
protected:
el *pdata;
queueL<el> *pnext;

public:

queueL<el>()
{
pdata=NULL;
pnext=NULL;
}

queueL<el>(char* file_name): SD(file_name)

186
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
pdata=NULL;
pnext=NULL;

queueL *pcurrent_nod=this;
el *pnewel;

while(!feof(SD::pf))
if((pnewel = new el())->fscanf_el(SD::pf)>0)
{
if(pcurrent_nod->pdata!=NULL)
{
pcurrent_nod->pnext=new queueL<el>();
pcurrent_nod=pcurrent_nod->pnext;
}
pcurrent_nod->pdata=pnewel;
}
fclose(SD::pf);
}

~queueL()
{

int isempty(){return pdata==NULL;}

int isfull(){return 0;}

void put(el newel)


{
if(!isfull())
{
queueL<el> *pcurrent_nod=this;
el *pnewel=new el();
*pnewel=newel;
if(pcurrent_nod->pdata)
{
while(pcurrent_nod->pnext)
pcurrent_nod=pcurrent_nod->pnext;
pcurrent_nod->pnext=new queueL<el>();
pcurrent_nod=pcurrent_nod->pnext;

187
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
pcurrent_nod->pdata=pnewel;
}
else
error("Queue is full!");
}

el get()
{
if(!isempty())
{
el* pret=pdata;
queueL<el>* pdel=pnext;
if(pdel)
{
pdata=pnext->pdata;
pnext=pnext->pnext;
delete pdel;
}
else
pdata=NULL;
return *pret;
}
else
error("Queue is empty!");
}
virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
printf("%s", opening);

queueL<el> *pcurrent_nod=this;
if(pcurrent_nod->pdata)
{
int i=1;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
while(pcurrent_nod=pcurrent_nod->pnext)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue...\n");

188
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

getch();
clrscr();
printf("%s", opening);
}
i++;
printf("%4d. ", i);
pcurrent_nod->pdata->show("", "\n");
}
}
printf("%s", ending);

printf("End of Queue. Press any key ...\n");


getch();
}
};

В предыдущей версии функции main(), вместо очереди в форме циклического буфера,


создадим очередь в форме списка:
queueL<usual_elem> gr("Stud.txt");

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


Упражнения.
1. Перегрузите оператор вставки в классе queueL.
2. Перегрузите оператор извлечения в классе queueL.
3. Реализуйте работу с очередями в форме списка при помощи двух дружественных классов:
main_queueL, и node_queueL.

189
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5. МАТРИЦЫ

5.1. Понятие матрицы


Определение:

Под матрицей будем понимать структуру данных, состоящую из конечного


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

Пример. Используя нотацию языка Pascal элемент матрицы A может быть обозначен как
A[i1,i2,i3,i4], а в языке C – A[i1][i2][i3][i4].
Количество чисел (индексов) которые входят в упорядоченный набор целых чисел
определяет размерность матрицы. Если этот набор содержит n чисел, тогда матрица
называется n-мерной.
Итак, целые числа i1, i2,…,in, входящие в упорядоченный набор, называются индексами.
Каждый индекс имеет собственную область допустимых значений, определенную тем или
иным образом, и которая может состоять из нескольких непересекающихся между собой
интервалов.
Важным подклассом матриц, которые используются как в компиляторах так и в языках
программирования, является класс прямоугольных матриц. У прямоугольных матриц
область допустимых значений у каждого индекса представляет постоянный и непрерывный
интервал.
Пример из языка Pascal.
Var
A: array[2..4, -3..-1, 0..1] of char;

Была объявлена прямоугольная матрица, размерности 3, с элементами типа char.


Интервалы допустимых значений индексов являются следующими:
[2,4] – с тремя возможными значениями (2, 3, 4) для первого индекса;
[-3,-1] – с тремя возможными значениями (-3, -2, -1) для второго индекса;
[0,1] – с двумя возможными значениями (0, 1) для третьего индекса.
Обращение к конкретному элементу выглядит следующим образом: A[4,-2,1].
Пример из языка C.
unsigned char A[3][3][2];

190
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Была объявлена прямоугольная матрица, размерности 3, с элементами типа unsigned char.


Интервалы допустимых значений индексов являются следующими:
[0,2] – с тремя возможными значениями (0, 1, 2) для первого индекса;
[0,2] – с тремя возможными значениями (0, 1, 2) для второго индекса;
[0,1] – с двумя возможными значениями (0, 1) для третьего индекса.
Обращение к конкретному элементу выглядит следующим образом: A[2][1][1].

5.2. Внутренняя структура памяти – вектор


Определение:

Вектор представляет собой упорядоченный конечный набор элементов


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

Любой вектор определяется базовым адресом (совпадает с адресом первого элемента


вектора), размером одного элемента и длиной вектора (количеством элементов).
Каждый элемент вектора может быть адресован непосредственно указанием базового адреса
и порядкового номера (начиная с нуля) элемента в векторе.
Практически вектор соответствует такой абстрактной структуре данных, как одномерная
матрица. В большинстве случаев одномерные матрицы, в памяти компьютера, отображаются
в вектора.
В языке Pascal вектор, состоящий из 18 элементов типа char, может быть объявлен
следующим образом:
Var
V: array[0..18] of char;

с элементами V[i], i=0,1,2,…,17.


В языке C:
unsigned char V[18];

так же, с элементами V[i], i=0,1,2,…,17.

5.3. Представление матриц в оперативной памяти


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

191
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Одномерная матрица B с интервалом индекса [l, h], l≤h, нормальным способом отображается
в вектор следующим образом:
B[i] → V[i-l], i=l, l+1, …, h,
где V начальный адрес размещения вектора в ОП, т.е. V является базовым адресом, а i-l
является смещением к элементу с индексом i.
Прямоугольные матрицы размерности большей 1, обычно отображаются в вектора одним из
следующих двух способов:
1. Быстрее изменяется последний индекс (так называемый метод “по строкам”, название
происходит от матриц размерности 2);
2. Быстрее изменяется первый индекс (так называемый метод “по столбцам”, название
происходит от матриц размерности 2).

192
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5.4. Представление матриц “по строкам”


Начнем с рассмотрения примера.
Пусть A матрица размерности 3 со следующими интервалами индексов:
[2, 4] для i1;
[-3,-1] для i2;
[0, 1] для i3.
Всего матрица A будет иметь 3×3×2=18 элементов, которые будут размещены “по строкам” в
оперативной памяти, начиная с некоторого адреса V, следующим образом:

V[0] V[1] V[2] V[3] V[4] V[5] V[6] V[7] V[8] V[9] V[10] V[11] V[12] V[13] V[14] V[15] V[16] V[17]

’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ ’K’ ’L’ ’M’ ’N’ ’O’ ’P’ ’Q’ ’R’

2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4
-3 -3 -2 -2 -1 -1 -3 -3 -2 -2 -1 -1 -3 -3 -2 -2 -1 -1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

Видно, что при перемещении от первого элемента, размещенного в памяти, к последнему,


последний индекс изменяется чаще.
Логическая структура матрицы A будет следующая:

i1=2 i1=3 i1=4


i3 0 1 i3 0 1 i3 0 1
i2 i2 i2
-3 ’A’ ’B’ -3 ’G’ ’H’ -3 ’M’ ’N’
-2 ’C’ ’D’ -2 ’I’ ’J’ -2 ’O’ ’P’
-1 ’E’ ’F’ -1 ’K’ ’L’ -1 ’Q’ ’R’

Т. е. матрица A представляет три двумерные матрицы 3×2.


В общем случае, для матрицы размерности n, с элементами, размещаемыми “по строкам”, с
интервалами индексов
[l1, h1] для индекса i1,
[l2, h2] для индекса i2,

[ln, hn] для индекса in,
элемент A[i1, i2,…, in] отображается в элемент

193
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

n
V[  ( ij-lj) Dj] вектора V, где Dj числа, которые вычисляются в обратном порядке, по
j 1

следующей рекуррентной формуле:


Dn=1;
Dj=(hj+1-lj+1+1)×Dj+1, j=n-1, n-2,…,1.
Возвращаясь к нашему примеру, возьмем i1=4, i2=-2, i3=1.
Вспомним, что n=3,
l1=2, h1=4,
l2=-3, h2=-1,
l3=0, h3=1,
тогда
D3=1,
D2=(1-0+1)×1=2×1=2,
D1=(-1-(-3)+1)×2=3×2=6,
и
A[i1, i2,…, in] → V[(4-2)×6+(-2-(-3))×2+(1-0)×1]= V[2×6+1×2+1×1]=V[12+2+1]=V[15]=’P’.
В нижеследующей таблице показано сколько операций типа ’+’ и сколько операций типа ’×’
необходимо выполнить, для доступа к одному элементу матрицы A:
Этап + ×
Вычисление Dj, j=3,2,1 4 2
Вычисление смещения 5 3
Всего 9 5

Упражнение. Рассчитайте сколько операций типа “сложение” и сколько операций типа


“умножение” необходимо выполнить для доступа к одному элементу прямоугольной
матрицы порядка n.
Для демонстрации работы с прямоугольными матрицами, нам понадобится конкретный
класс для представления элементов соответствующих матриц. Создадим на базе
абстрактного класса elem конкретный класс character, один из самых простейших классов
для представления элементов структуры данных.
//
// C l a s s "c h a r a c t e r"
//
class character : public elem
{
protected:

194
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

unsigned char c;

public:

character()
{
c='\0';
}

character(unsigned char init_c)


{
c=init_c;
}

virtual int fscanf_el(FILE *f)


{
return fscanf(f, "%c", &c);
}

virtual void show(const char* opening, const char* ending)


{
printf("%s", opening);
if(c)
printf("%c", c);
printf("%s", ending);
}

virtual int free()


{
return c=='\0';
}

virtual int cmp(elem& e2)


{
return this->c > ((character&)e2).c ?
1 : this->c < ((character&)e2).c ? -1 : 0;
}
};

Упражнения.
1. Перегрузите оператор вставки в классе character.
2. Перегрузите оператор извлечения в классе character.

195
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Далее, для демонстрации использования прямоугольных матриц размерности 3,


размещаемых “по строка”, объявляем конкретный шаблонный класс matrix3l:
//
// C l a s s "m a t r i x 3 l"
//
template <class el> class matrix3l: public SD
{
protected:

el *V; // pointer to matrix reprezentation vector


int l1, h1;
int l2, h2;
int l3, h3;

unsigned long nopadd, nopmul;

public:

matrix3l<el>(int l1, int h1, int l2, int h2, int l3, int h3)
{
this->l1=l1;
this->h1=h1;
this->l2=l2;
this->h2=h2;
this->l3=l3;
this->h3=h3;

int nel=(this->h1-this->l1+1)*(this->h2-this->l2+1)*(this->h3-this->l3+1);
V = new el[nel];

nopadd=0, nopmul=0;
}

matrix3l<el>(char* file_name, int l1, int h1, int l2, int h2,
int l3, int h3):
SD(file_name)
{
this->l1=l1;
this->h1=h1;
this->l2=l2;
this->h2=h2;

196
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

this->l3=l3;
this->h3=h3;

int nel=(this->h1-this->l1+1)*(this->h2-this->l2+1)*(this->h3-this->l3+1);
V = new el[nel];

for(int i=0; i<nel; i++)


V[i].fscanf_el(SD::pf);
fclose(SD::pf);

nopadd=0, nopmul=0;
}

virtual el& elem(int i1, int i2, int i3)


{
int D1, D2, D3;

nopadd+=9, nopmul+=5;

D3=1;
D2=(h3-l3+1)*D3;
D1=(h2-l2+1)*D2;
return V[(i1-l1)*D1+(i2-l2)*D2+(i3-l3)*D3];
}

virtual void show(const char* opening, const char* ending , int nlinepp=20)
{
clrscr();
printf("%s", opening);
int i1, i2, i3;
for(int i1=l1; i1<=h1; i1++)
{
printf("i1=%i\n", i1);
for(i2=l2; i2<=h2; i2++)
{
for(i3=l3; i3<=h3; i3++)
elem(i1, i2, i3).show("", " ");
printf("\n");
}
}
printf("%s", ending);
printf("End of matrix. Press any key ...\n");

197
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

getch();
}

virtual void showvect(const char* opening, const char* ending)


{
clrscr();
int nel=(h1-l1+1)*(h2-l2+1)*(h3-l3+1);
printf("%s", opening);
for(int i=0; i<nel; i++)
{
if(i>0 && i%20==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%i. ", i); V[i].show("", "\n");
}
printf("%s", ending);
printf("End of vector. Press any key ...\n");
getch();
}

int search(el e)
{
int position = -1;
int nel=(h1-l1+1)*(h2-l2+1)*(h3-l3+1);
for(int i=0; (position==-1) && i<nel ; i++)
if(e==this->V[i])
position=i;
return position;
}

int search(el e, int &i1, int &i2, int &i3)


{
int found = 0;
for(int i1t=l1; i1t<=h1 && !found; i1t++)
for(int i2t=l2; i2t<=h2 && !found; i2t++)
for(int i3t=l3; i3t<=h3 && !found; i3t++)
if(e==elem(i1t, i2t, i3t))
found=1, i1=i1t, i2=i2t, i3=i3t;

198
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

return found;
}

void resetnop()
{
nopadd=0, nopmul=0;
}

long int getnopadd()


{
return nopadd;
}

long int getnopmul()


{
return nopmul;
}
};

Шаблонный класс matrix3l может быть конкретизирован любым реальным классом


производным от абстрактного класса elem.
Для загрузки матрицы, создадим текстовый файл с именем "char.txt" следующего
содержания:
ABCDEFGHIJKLMNOPQR

В функции main() создадим прямоугольную матрицу порядка 3, размещаемую “по строкам”,


заполняя значения ее элементов из файла "char.txt". Интервалы для индексов возьмем
такие же как в рассмотренном примере. Далее выведем элементы матрицы в том порядке в
каком они размещены в оперативной памяти. Затем выведем матрицу, демонстрируя ее
логическую структуру, выведем количество операций типа “+” и количество операций типа
“×” выполненных для доступа к элементам матрицы в процессе вывода ее логической
структуры. После чего в цикле продемонстрируем поиск элементов матрицы. В заключение
изменим значения некоторых элементов и выведем измененную матрицу.
void main()
{
clrscr();

matrix3l<character> A("char.txt", 2, 4, -3, -1, 0, 1);

A.showvect("Matrix-vector A:\n","\n");

A.show("Matrix A\n","\n");
printf("\nN op. +/- %i, N op. x/: %i\n", A.getnopadd(), A.getnopmul());

199
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

char ch='n';
char c;
int i1, i2, i3;

while(ch!='y')
{
printf("Enter a char to search: ");
c=getch();
character mychar(c);
int pos=A.search(c);
if(pos<0)
printf("The character %c no matrix!\n", c);
else
{
A.search(c, i1, i2, i3);
printf("The character %c are in the position %i \nwith indices
[%i,%i,%i]\n",
c, pos, i1, i2, i3);
}

printf("Done ? (y/n) ");


ch = getch();
printf("\n");
}

A.elem(2, -3, 0)='a';


A.elem(2, -2, 1)='d';
A.elem(3, -3, 0)='g';
A.elem(3, -2, 1)='j';
A.elem(4, -3, 0)='m';
A.elem(4, -2, 1)='p';

A.show("Matrix A after correction:\n","\n");


}

Один из вариантов вывода может выглядеть так:


Matrix-vector A:
0. A
1. B
2. C
3. D
4. E

200
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5. F
6. G
7. H
8. I
9. J
10. K
11. L
12. M
13. N
14. O
15. P
16. Q
17. R

End of vector. Press any key ...


Matrix A
i1=2
A B
C D
E F
i1=3
G H
I J
K L
i1=4
M N
O P
Q R

End of matrix. Press any key ...

N op. +/- 162, N op. x/: 90


Enter a char to search: The character A are in the position 0
with indices [2,-3,0]
Done ? (y/n)
Enter a char to search: The character R are in the position 17
with indices [4,-1,1]
Done ? (y/n)
Enter a char to search: The character J are in the position 9
with indices [3,-2,1]
Done ? (y/n)
Enter a char to search: The character P are in the position 15

201
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

with indices [4,-2,1]


Done ? (y/n)
Enter a char to search: The character p no matrix!
Done ? (y/n)
Matrix A after correction:
i1=2
a B
C d
E F
i1=3
g H
I j
K L
i1=4
m N
O p
Q R

End of matrix. Press any key ...

Упражнения.
1. Перегрузите с классе matrix3l оператор [].
2. Перегрузите оператор вставки в классе matrix3l.
3. Перегрузите оператор извлечения в классе matrix3l.

202
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5.5. Представление матриц “по столбцам”


Возьмем такой же пример матрицы A размерности 3 с интервалами индексов:
[2, 4] для i1;
[-3,-1] для i2;
[0, 1] для i3.
Матрица A так же 3×3×2=18 элементов, которые для случая “по столбцам” будут размещены
в оперативной памяти, начиная с некоторого адреса V, следующим образом:

V[0] V[1] V[2] V[3] V[4] V[5] V[6] V[7] V[8] V[9] V[10] V[11] V[12] V[13] V[14] V[15] V[16] V[17]

’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ ’K’ ’L’ ’M’ ’N’ ’O’ ’P’ ’Q’ ’R’

2 3 4 2 3 4 2 3 4 2 3 4 2 3 4 2 3 4
-3 -3 -3 -2 -2 -2 -1 -1 -1 -3 -3 -3 -2 -2 -2 -1 -1 -1
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

И логическая структура матрицы A будет следующая:

i3=0 i3=1
i2 -3 -2 -1 i2 -3
-2 -1
i1 i1
2 ’A’ ’D’ ’G’ 2 ’J’ ’M’ ’P’
3 ’B’ ’E’ ’H’ 3 ’K’ ’N’ ’Q’
4 ’C’ ’F’ ’I’ 4 ’L’ ’O’ ’R’

На сей раз матрица A представляет две двумерные матрицы 3×3.


В общем случае, для прямоугольной матрицы размерности n с элементами, размещаемыми
“по столбцам”, с интервалами индексов
[l1, h1] для индекса i1,
[l2, h2] для индекса i2,
...
[ln, hn] для индекса in,
элемент A[i1, i2,…, in] отображается в элемент
n
V[  ( ij-lj) Dj] вектора V, где Dj числа, рассчитываемые в прямом порядке, по следующей
j 1

рекуррентной формуле:

203
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

D1=1;
Dj=(hj-1-lj-1+1)×Dj-1, j=2,3,…,n.
Возвращаясь к нашему примеру, возьмем опять i1=4, i2=-2, i3=1.
Вспомним, что n=3,
l1=2, h1=4,
l2=-3, h2=-1,
l3=0, h3=1,
тогда
D1=1,
D2=(4-2+1)×1=3×1=3,
D3=(-1-(-3)+1)×3=3×3=9,
и
A[i1, i2,…, in] → V[(4-2)×1+(-2-(-3))×3+(1-0)×9]= V[2×1+1×3+1×9]=V[2+3+9]=V[14]=’O’.
Очевидно, что количество операций типа ’+’ и количество операций типа ’×’ выполненных
для доступа к элементу A[i1, i2,…, in] такие же как и в случае размещения “по строкам”.
Для демонстрации использования прямоугольных матриц размерности 3, размещаемых “по
столбцам”, создадим шаблонный класс matrix3c:
//
// C l a s s "m a t r i x 3 c"
//
template <class el> class matrix3c: public SD
{
protected:

el *V; // pointer to matrix reprezentation vector


int l1, h1;
int l2, h2;
int l3, h3;

unsigned long nopadd, nopmul;

public:

matrix3c<el>(int l1, int h1, int l2, int h2, int l3, int h3)
{
this->l1=l1;
this->h1=h1;

204
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

this->l2=l2;
this->h2=h2;
this->l3=l3;
this->h3=h3;

int nel=(this->h1-this->l1+1)*(this->h2-this->l2+1)*(this->h3-this->l3+1);
V = new el[nel];

nopadd=0, nopmul=0;
}

matrix3c<el>(char* file_name, int l1, int h1, int l2, int h2,
int l3, int h3):
SD(file_name)
{
this->l1=l1;
this->h1=h1;
this->l2=l2;
this->h2=h2;
this->l3=l3;
this->h3=h3;

int nel=(this->h1-this->l1+1)*(this->h2-this->l2+1)*(this->h3-this->l3+1);
V = new el[nel];

for(int i=0; i<nel; i++)


V[i].fscanf_el(SD::pf);
fclose(SD::pf);

nopadd=0, nopmul=0;
}

virtual el& elem(int i1, int i2, int i3)


{
int D1, D2, D3;

nopadd+=9, nopmul+=5;

D1=1;
D2=(h1-l1+1)*D1;
D3=(h2-l2+1)*D2;
return V[(i1-l1)*D1+(i2-l2)*D2+(i3-l3)*D3];

205
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

virtual void show(const char* opening, const char* ending, int nlinepp=20)
{
clrscr();
printf("%s", opening);
int i1, i2, i3;
for(int i3=l3; i3<=h3; i3++)
{
printf("i3=%i\n", i3);
for(i1=l1; i1<=h1; i1++)
{
for(i2=l2; i2<=h2; i2++)
elem(i1, i2, i3).show("", " ");
printf("\n");
}
}
printf("%s", ending);
printf("End of matrix. Press any key ...\n");
getch();
}

virtual void showvect(const char* opening, const char* ending)


{
clrscr();
int nel=(h1-l1+1)*(h2-l2+1)*(h3-l3+1);
printf("%s", opening);
for(int i=0; i<nel; i++)
{
if(i>0 && i%20==0)
{
printf("Press any key to continue...\n");
getch();
//clrscr();
printf("%s", opening);
}
printf("%i. ", i); V[i].show("", "\n");
}
printf("%s", ending);
printf("End of vector. Press any key ...\n");
getch();
}

206
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int search(el e)
{
int position = -1;
int nel=(h1-l1+1)*(h2-l2+1)*(h3-l3+1);
for(int i=0; (position==-1) && i<nel ; i++)
if(e==this->V[i])
position=i;
return position;
}

int search(el e, int &i1, int &i2, int &i3)


{
int found = 0;
for(int i1t=l1; i1t<=h1 && !found; i1t++)
for(int i2t=l2; i2t<=h2 && !found; i2t++)
for(int i3t=l3; i3t<=h3 && !found; i3t++)
if(e==elem(i1t, i2t, i3t))
found=1, i1=i1t, i2=i2t, i3=i3t;
return found;
}

void resetnop()
{
nopadd=0, nopmul=0;
}

long int getnopadd()


{
return nopadd;
}

long int getnopmul()


{
return nopmul;
}
};

В функции main() предыдущего примера изменим только объявление матрицы, которое


теперь будет выглядеть следующим образом:
matrix3c<character> A("char.txt", 2, 4, -3, -1, 0, 1);

Теперь вывод будет выглядеть так:


Matrix-vector A:

207
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

0. A
1. B
2. C
3. D
4. E
5. F
6. G
7. H
8. I
9. J
10. K
11. L
12. M
13. N
14. O
15. P
16. Q
17. R

End of vector. Press any key ...


Matrix A
i3=0
A D G
B E H
C F I
i3=1
J M P
K N Q
L O R

End of matrix. Press any key ...

N op. +/- 162, N op. x/: 90


Enter a char to search: The character A are in the position 0
with indices [2,-3,0]
Done ? (y/n)
Enter a char to search: The character R are in the position 17
with indices [4,-1,1]
Done ? (y/n)
Enter a char to search: The character J are in the position 9
with indices [2,-3,1]
Done ? (y/n)

208
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Enter a char to search: The character O are in the position 14


with indices [4,-2,1]
Done ? (y/n)
Enter a char to search: The character o no matrix!
Done ? (y/n)
Matrix A after correction:
i3=0
a D G
g E H
m F I
i3=1
J d P
K j Q
L p R

End of matrix. Press any key ...

Упражнения.
1. Перегрузите в классе matrix3c оператор [].
2. Перегрузите оператор вставки в классе matrix3c.
3. Перегрузите оператор извлечения в классе matrix3c.

209
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5.6. Определяющий вектор


Рассмотрим методы доступа к каждому элементу матрицы в отдельности. Самое простое
решение (которое и было использовано в классах matrix3l и matrix3c), заключается в том,
что в векторе, в который отображается матрица, размещаются только элементы матрицы и,
всяки раз, когда необходим какой-либо элемент матрицы, для доступа к нему вычисляется
величина смещения:
n n n

 (i
j 1
j  l j )  Dj  ij  Dj  l j  Dj
j 1 j 1

Однако, как значения lj, так и числа Dj, зависят только от объявления матрицы и никак не
зависят от индексов элемента, к которому осуществляется обращение. Поэтому, для
n
оптимизации, числа Dj и величина l D
j 1
j j могут храниться в памяти компьютера, что

позволит исключить повторные вычисления. Эти величины могут храниться вместе с


элементами матрицы, но чаще всего они записываются отдельно в специальный вектор,
называемый определяющим. В этом случае, для разных матриц, но с одними и теми же
параметрами, может быть использован один и тот же определяющий вектор.
В определяющий вектор записываются следующие величины:
a) размерность матрицы (n);
b) граничные значения для каждого индекса (lj, hj, j=1,2,...,n);
c) количество элементов в матрице;
d) числа Dj для j=1,2,...,n;
n
e) величина l D
j 1
j j .

Напомним, что числа Dj зависят от размерности матрицы, от граничных значений индексов и


от способа представления матрицы в ОП (“по строкам” или “по столбцам ”).
Пример. Возьмем ту же самую матрицу A размерности n=3; с интервалами индексов i1: [2,4],
i2: [-3,-1], i3: [0,1]; с элементами типа char, размещаемыми “по строкам”. С именем матрицы
A ассоциируются два указателя: один на базовый адрес вектора в который отображаются
элементы матрицы; второй на определяющий вектор, соответствующий матрице A.

210
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Схематически это выглядит следующим образом:


...
... ...
3 n
2 l1
’A’ A2,-3,0 4
A h1
’B’ A2,-3,1 -3 l2
’C’ A2,-2,0 -1 h2
...
... 0 l3
1 h3
’Q’ A4,-1,0
18 Nelem
’R’ A4,-1,1
6 D1
...
2 D2
1 D3
3
6 l D
j1
j j

...

Рассмотрим доступ к элементу A[4,-2,1].


A[4,-2,1] → V[4×6+(-2)×2+1×1 - 6] = V[24-4+1-6] = V[16] = ’P’.
Всего было выполнено 3 операции типа ’×’ и 3 операции типа ’+’. Напомним, что при
прямом методе было выполнено 5 операций типа ’×’ и 9 операций типа ’+’.
Однако метод определяющих векторов для матрицы порядка n=3 требует дополнительно
1+2×3+1+3+1=12 полей памяти для целых чисел плюс один указатель. Если одно целое
число занимает 4 байта, тогда дополнительно использовали 48 байтов плюс sizeof(int *).
Упражнения.
1. Рассчитайте количество полей типа int необходимых для определяющего вектора в
случае матрицы размерности n.
2. Оцените сколько операций типа “сложение” и сколько операций типа “умножение”
необходимо выполнить для доступа к одному элементу прямоугольной матрицы
размерности n при использовании определяющего вектора.
В случае когда имеются несколько матриц одинаковой размерности и с одними и теми же
интервалами возможных значений для индексов, эти матрицы могут использовать один и тот
же определяющий вектор, как это показано на следующем рисунке:

211
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

... ...

n
...
... Elem.A l1
A h1 Vector
definitor
... ... ...
...
... l D j j

...

B ... Elem.B

...

... ...
...
C
... Elem.C
...

...

Замечание. Элементы матриц B, C, D могут быть даже разных типов, т. е. занимать


различное количество байтов.
Для демонстрации использования определяющего вектора при доступе к элементам
прямоугольной матрицы размерности 3, с элементами расположенными “по строкам” в
оперативной памяти, создадим на базе шаблонного класса matrix3l шаблонный класс
matrix3ldv, наделенный определяющим вектором:
//
// C l a s s "m a t r i x 3 l d v"
//
template <class el> class matrix3ldv : public matrix3l<el>
{
protected:

int *d; // poiner to definition vector

public:

matrix3ldv<el>(int l1, int h1, int l2, int h2, int l3, int h3):
matrix3l<el>(l1, h1, l2, h2, l3, h3)
{
d = new int[12];
d[0]=3; // dimension
d[1]=l1;
d[2]=h1;

212
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

d[3]=l2;
d[4]=h2;
d[5]=l3;
d[6]=h3;
d[7]=(h1-l1+1)*(h2-l2+1)*(h3-l3+1); // number of elements
d[10]=1; // D3
d[9]=(h3-l3+1)*d[10]; // D2
d[8]=(h2-l2+1)*d[9]; // D1
d[11]=l1*d[8]+l2*d[9]+l3*d[10]; // Sum(ljxDj)
}

matrix3ldv<el>(char* file_name, int l1, int h1, int l2,


int h2, int l3, int h3):
matrix3l<el>(file_name, l1, h1, l2, h2, l3, h3)
{
d = new int[12];
d[0]=3; // dimension
d[1]=l1;
d[2]=h1;
d[3]=l2;
d[4]=h2;
d[5]=l3;
d[6]=h3;
d[7]=(h1-l1+1)*(h2-l2+1)*(h3-l3+1); // number of elements
d[10]=1; // D3
d[9]=(h3-l3+1)*d[10]; // D2
d[8]=(h2-l2+1)*d[9]; // D1
d[11]=l1*d[8]+l2*d[9]+l3*d[10]; // Sum(ljxDj)
}

virtual el& elem(int i1, int i2, int i3)


{
nopadd+=3, nopmul+=3;
return V[i1*d[8]+i2*d[9]+i3*d[10]-d[11]];
}
};

В функции main() предыдущего примера, изменим только объявление матрицы, которое


будет выглядеть следующим образом:
matrix3ldv<character> A("char.txt", 2, 4, -3, -1, 0, 1);

Запустив программу, получим следующий результат:


Matrix-vector A:
0. A

213
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

1. B
2. C
3. D
4. E
5. F
6. G
7. H
8. I
9. J
10. K
11. L
12. M
13. N
14. O
15. P
16. Q
17. R

End of vector. Press any key ...


Matrix A
i1=2
A B
C D
E F
i1=3
G H
I J
K L
i1=4
M N
O P
Q R

End of matrix. Press any key ...

N op. +/- 54, N op. x/: 54


Enter a char to search: The character A are in the position 0
with indices [2,-3,0]
Done ? (y/n)
Enter a char to search: The character R are in the position 17
with indices [4,-1,1]
Done ? (y/n)

214
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Enter a char to search: The character J are in the position 9


with indices [3,-2,1]
Done ? (y/n)
Enter a char to search: The character P are in the position 15
with indices [4,-2,1]
Done ? (y/n)
Enter a char to search: The character p no matrix!
Done ? (y/n)
Matrix A after correction:
i1=2
a B
C d
E F
i1=3
g H
I j
K L
i1=4
m N
O p
Q R

End of matrix. Press any key ...

Как легко можно заметить, общее количество операций, выполненных при доступе к
элементам матрицы для вывода ее логической структуры, сократилось со 162 до 54 для ’+’ и
с 90 до 54 для ’×’.
Упражнения.
1. Перегрузите в классе matrix3ldv оператор [].
2. Сравните время доступа к элементам матрицы при непосредственном методе и при
использовании определяющего вектора.
3. Создайте на базе шаблонного класса matrix3c, шаблонный класс matrix3cdv,
наделенный определяющим вектором.

215
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

5.7. Вектор Айлифа


Для непосредственного доступа к элементам матрицы или, даже, при помощи
определяющего вектора, необходимо выполнить ряд операций умножения и сложения,
количество которых пропорционально размерности матрицы. Известный специалист в
области информатики John K. Iliffe [16] разработал метод, который исключает умножение и
сводит к минимуму сложения при доступе к элементам матриц любой размерности n, правда
за счет использования дополнительной памяти.
Суть метода состоит в том, что наряду с каждой матрицей строится и сохраняется иерархия
векторов, получившая название векторов Айлиф.
С именем матрицы ассоциируется указатель с адресом первого вектора Айлифа смещенным
в соответствии с нижним значением первого индекса в случае размещения элементов “по
строкам ”, или с нижним значением последнего индекса в случае размещения элементов “по
столбцам”.
Иерархия векторов Айлифа для матрицы A размерности 3 из предыдущих примеров, с
элементами размещаемыми “по строкам”, выглядит так:

0
A 1
2
3
4

-3 -3 -3
-2 -2 -2
-1 -1 -1
0 0 0

’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ ’K’ ’L’ ’M’ ’N’ ’O’ ’P’ ’Q’ ’R’
2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4
-3 -3 -2 -2 -1 -1 -3 -3 -2 -2 -1 -1 -3 -3 -2 -2 -1 -1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

Рассмотрим доступ, например, к элементу A[4,-2,1]. Извлекаем адрес из указателя A и


добавляем к нему смещение 4 в соответствии со значением первого индекса требуемого
элемента. Попадаем на последний элемент первого вектора Айлифа. Извлекаем адрес,
содержащийся в нем, и добавляем к этому адресу смещение -2 в соответствии со значением
второго индекса необходимого элемента. Попадаем на второй элемент третьего вектора
Айлифа второго уровня. Извлекаем содержащийся в нем адрес и добавляем к этому адресу
смещение 1 в соответствии со значением третьего индекса необходимого элемента. В
результате получаем адрес этого элемента. Его значение равно ’P’.
Таким образом, получили адрес элемента A[4,-2,1], при этом не выполнили ни одного
умножения, а только три сложения смещений к адресам. Такие сложения относятся к

216
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

адресной арифметике, т. е. выполняются очень быстро. Однако, дополнительно отвели


память для размещения 12 указателей. Сколько указателей будет содержать иерархия
векторов Айлифа, зависит не только от размерности матрицы, но и от интервалов
допустимых значений индексов, и от способа размещения элементов матрицы (“по строкам”
или “по столбцам”).
Упражнение. Рассчитайте количество указателей которых будет содержать иерархия
векторов Айлифа для матрицы порядка n, с интервалами индексов [l1,h1], [l2,h2], …, [ln,hn], и с
элементами размещенными в памяти “по строкам ”.
Для демонстрации доступа к элементам прямоугольной матрицы порядка 3, с размещением
элементов “по строкам”, используя вектора Айлифа, создадим на базе шаблонного класса
matrix3l шаблонный класс matrix3lIl, дополненный иерархией векторов Айлифа:
//
// C l a s s "m a t r i x 3 l I l"
//
template <class el> class matrix3lIl : public matrix3l<el>
{
protected:

el*** va; // pointer to Iliffes vectors ierarhy

public:

matrix3lIl<el>(int l1, int h1, int l2, int h2, int l3, int h3):
matrix3l<el>(l1, h1, l2, h2, l3, h3)
{
int i1, i2 , d, step;

d=-l3;
step=h3-l3+1;

va = new el**[h1-l1+1] - l1;


for(i1=l1; i1<=h1; i1++)
{
*(va+i1) = new el*[h2-l2+1] - l2;
for(i2=l2; i2<=h2; i2++, d+=step)
*(*(va+i1)+i2)=V+d;
}
}

matrix3lIl<el>(char* file_name, int l1, int h1, int l2, int h2,
int l3, int h3):
matrix3l<el>(file_name, l1, h1, l2, h2, l3, h3)

217
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
int i1, i2 , d, step;

d=-l3;
step=h3-l3+1;

va = new el**[h1-l1+1] - l1;


for(i1=l1; i1<=h1; i1++)
{
*(va+i1) = new el*[h2-l2+1] - l2;
for(i2=l2; i2<=h2; i2++, d+=step)
*(*(va+i1)+i2)=V+d;
}
}

el& elem(int i1, int i2, int i3)


{
nopadd+=3, nopmul+=0;
return *(*(*(va+i1)+i2)+i3);
}
};

В функции main() предыдущего примера изменим только создание матрицы. На сей раз
создадим прямоугольную матрицу размерности 3, снабженную иерархией векторов Айлифа:
matrix3lIl<character> A("char.txt", 2, 4, -3, -1, 0, 1);

Будет получен следующий результат:


Matrix-vector A:
0. A
1. B
2. C
3. D
4. E
5. F
6. G
7. H
8. I
9. J
10. K
11. L
12. M
13. N
14. O
15. P

218
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

16. Q
17. R

End of vector. Press any key ...


Matrix A
i1=2
A B
C D
E F
i1=3
G H
I J
K L
i1=4
M N
O P
Q R

End of matrix. Press any key ...

N op. +/- 54, N op. x/: 0


Enter a char to search: The character A are in the position 0
with indices [2,-3,0]
Done ? (y/n)
Enter a char to search: The character R are in the position 17
with indices [4,-1,1]
Done ? (y/n)
Enter a char to search: The character J are in the position 9
with indices [3,-2,1]
Done ? (y/n)
Enter a char to search: The character P are in the position 15
with indices [4,-2,1]
Done ? (y/n)
Enter a char to search: The character p no matrix!
Done ? (y/n)
Matrix A after correction:
i1=2
a B
C d
E F
i1=3
g H

219
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

I j
K L
i1=4
m N
O p
Q R

End of matrix. Press any key ...

В этих результатах нас интересуют только количество сложений и количество умножений,


выполненных при адресации элементов матрицы в процессе вывода ее логической структуры
(остальные результаты остались прежними). Как видно из выведенного результата,
умножения были исключены, а количество сложений осталось 54, т. е. таким же как и в
случае определяющего вектора. Однако на этот раз были выполнялись адресные сложения,
которые выполняются гораздо быстрее, чем обычные сложения, которые выполнялись при
использовании определяющего вектора.
Упражнения.
1. Перегрузите в классе matrix3lIl оператор [].
2. Сравните время доступа к элементам матрицы при использовании определяющего
вектора с временем доступа к элементам этой же матрицы с использованием векторов
Айлифа.
3. Разработайте класс, который бы позволял использовать одну и ту же иерархию векторов
Айлифа для нескольких матриц, размещаемых “по строкам” и имеющих одну и ту же
структуру.

Иерархия векторов Айлифа для прямоугольной матрицы A порядка 3, с элементами,


размещаемыми “по столбцам”, из предыдущих примеров выглядит следующим образом:

0
A 1

-3 -3
-2 -2
-1 -1
0 0

’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ ’K’ ’L’ ’M’ ’N’ ’O’ ’P’ ’Q’ ’R’
2 3 4 2 3 4 2 3 4 2 3 4 2 3 4 2 3 4
-3 -3 -3 -2 -2 -2 -1 -1 -1 -3 -3 -3 -2 -2 -2 -1 -1 -1
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1

220
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Рассмотрим доступ к элементу A[4,-2,1]. Извлекаем адрес из указателя A и добавляем к этому


адресу смещение 1, в соответствии со значением последнего индекса требуемого элемента.
Попадаем на последний элемент первого вектора Айлифа. Извлекаем содержащийся в нем
адрес и добавляем к последнему смещение -2, в соответствии со значением второго индекса
требуемого элемента. Попадаем на второй элемент третьего вектора Айлифа второго уровня.
Извлекаем содержащийся в нем адрес и добавляем к нему смещение 4, в соответствии со
значением первого индекса требуемого элемента. В результате получаем адрес требуемого
элемента. Значение элемента на сей раз равно ’O’.
И на этот раз для доступа к элементу A[4,-2,1] выполнили только три адресных сложения.
Дополнительно использовалась память для размещения 8 указателей. Т. е. матрицу A, из
рассмотренных примеров с использованием векторов Айлифа, выгоднее использовать
размещая элементы “по столбцам”.
Упражнение. Рассчитайте количество указателей которых будет содержать иерархия
векторов Айлифа для матрицы размерности n, с интервалами индексов [l1,h1], [l2,h2], …,
[ln,hn], с элементами, размещаемыми “по столбцам”.
Для демонстрации доступа к элементам прямоугольных матриц размерности 3, с
размещением элементов “по столбцам”, и с использованием векторов Айлифа, создадим на
базе шаблонного класса matrix3c, шаблонный класс matrix3cIl, дополненный
соответствующей иерархией векторов Айлифа:
//
// C l a s s "m a t r i x 3 c I l"
//
template <class el> class matrix3cIl : public matrix3c<el>
{
protected:

el*** va; // pointer to Iliffes vectors ierarhy

public:

matrix3cIl<el>(int l1, int h1, int l2, int h2, int l3, int h3):
matrix3c<el>(l1, h1, l2, h2, l3, h3)
{
int i3, i2 , d, step;

d=-l1;
step=h1-l1+1;

va = new el**[h3-l3+1] - l3;


for(i3=l3; i3<=h3; i3++)
{

221
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

*(va+i3) = new el*[h2-l2+1] - l2;


for(i2=l2; i2<=h2; i2++, d+=step)
*(*(va+i3)+i2)=V+d;
}
}

matrix3cIl<el>(char* file_name, int l1, int h1, int l2, int h2,
int l3, int h3):
matrix3c<el>(file_name, l1, h1, l2, h2, l3, h3)
{
int i3, i2 , d, step;

d=-l1;
step=h1-l1+1;

va = new el**[h3-l3+1] - l3;


for(i3=l3; i3<=h3; i3++)
{
*(va+i3) = new el*[h2-l2+1] - l2;
for(i2=l2; i2<=h2; i2++, d+=step)
*(*(va+i3)+i2)=V+d;
}
}

el& elem(int i1, int i2, int i3)


{
nopadd+=3, nopmul+=0;
return *(*(*(va+i3)+i2)+i1);
}
};

В функции main()объявим прямоугольную матрицу размерности 3, с элементами,


размещаемыми “по столбцам” в ОП, снабженную набором векторов Айлифа:
matrix3cIl<character> A("char.txt", 2, 4, -3, -1, 0, 1);

При запуске программы, получим следующий результат:


Matrix-vector A:
0. A
1. B
2. C
3. D
4. E
5. F
6. G

222
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

7. H
8. I
9. J
10. K
11. L
12. M
13. N
14. O
15. P
16. Q
17. R

End of vector. Press any key ...


Matrix A
i3=0
A D G
B E H
C F I
i3=1
J M P
K N Q
L O R

End of matrix. Press any key ...

N op. +/- 54, N op. x/: 0


Enter a char to search: The character A are in the position 0
with indices [2,-3,0]
Done ? (y/n)
Enter a char to search: The character R are in the position 17
with indices [4,-1,1]
Done ? (y/n)
Enter a char to search: The character J are in the position 9
with indices [2,-3,1]
Done ? (y/n)
Enter a char to search: The character O are in the position 14
with indices [4,-2,1]
Done ? (y/n)
Enter a char to search: The character o no matrix!
Done ? (y/n)
Matrix A after correction:
i3=0

223
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

a D G
g E H
m F I
i3=1
J d P
K j Q
L p R

End of matrix. Press any key ...

Анализ результатов и выводы такие же как и для случая размещения элементов “по
строкам”.

Упражнения.
1. Перегрузите в классе matrix3cIl оператор [].
2. Разработайте класс, который бы позволял использовать одну и ту же иерархию векторов
Айлифа для нескольких матриц, размещаемых “по столбцам” и имеющих одну и ту же
структуру.

224
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

6. ЗАДАНИЯ

– I – Объявление базовых классов

Выберите по вашему усмотрению предметную область из предложенного списка, или


предложите свою собственную.

1. Personal computer______ 21. Polygon______________ 41. Bicycle______________


2. Processor_____________ 22. Star_________________ 42. Flower_______________
3. Monitor______________ 23. Rhombus_____________ 43. Mushroom____________
4. Printer_______________ 24. City_________________ 44. Subject_______________
5. Winchester____________ 25. Address______________ 45. Flight________________
6. Video card____________ 26. Book________________ 46. Dog_________________
7. Mother board__________ 27. Collaborator__________ 47. Cat__________________
8. Mouse_______________ 28. Student______________ 48. Parrot________________
9. Flush memory_________ 29. Film_________________ 49. Cow_________________
10. Mobile phone__________ 30. Cinematograph________ 50. Bit string_____________
11. Television_____________ 31. Pharmacy____________ 51. Octal________________
12. Fridge________________ 32. Mark________________ 52. Hexadecimal__________
13. Washing machine_______ 33. Coin_________________ 53. Rational number________
14. Triangle______________ 34. Hotel________________ 54. Complex number_______
15. Rectangle_____________ 35. Museum______________ 55. _____________________
16. Ellipse_______________ 36. Shop_________________ 56. _____________________
17. Circle________________ 37. Baggage______________ 57. _____________________
18. Bezier________________ 38. Apartment____________ 58. _____________________
19. Trapeze_______________ 39. Patient_______________ 59. _____________________
20. Parallelogram__________ 40. Car__________________ 60. _____________________

Для выбранной предметной области создайте конкретный класс, производный от


абстрактного класса elem (см. 1.2), который бы содержал не мене 5 полей различных типов
(int, char*, double, и т.д.), не менее трех конструкторов различных типов, переопределение
функции cmp(), переопределение функции ввода, переопределение функции вывода и др.
Создайте текстовый файл содержащий не менее 50 строк. Каждая строка должна содержать
информацию для создания объекта разработанного класса. На базе этого файла в
дальнейшем предстоит создавать различные структуру данных.

225
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

– II – Работа с таблицами
Простые неупорядоченные таблицы
На базе абстрактного класса SD (см. 1.2) создайте конкретный шаблонный класс table для
работы с простыми неупорядоченными таблицами.
Создайте простую неупорядоченную таблицу, загрузив исходные данные из текстового
файла. Продемонстрируйте последовательный поиск табличных записей по ключу.
Определите среднюю длину поиска для данной таблицы и сравните ее с теоретической
оценкой сложности алгоритма последовательного просмотра, выраженной средней длиной
поиска.

Неупорядоченные древовидные таблицы


Создайте на базе класса представления объектов выбранной предметной области,
производный класс, дополненный двумя указателями (индексами, ссылками), один на
табличную запись с меньшим значением ключа, и другой на табличную запись с большим
значением ключа.
На базе класса table создайте производный шаблонный класс tree_table для работы с
неупорядоченными древовидными таблицами. Создайте такую таблицу, загрузив данные для
нее из текстового файла. Продемонстрируйте поиск табличных записей по ключу в
древовидной таблице. Определите среднюю длину поиска для данной таблицы и сравните ее
с теоретическими оценками сложности данного алгоритма (минимальной и максимальной).

Таблицы, упорядоченные по ключу


Создайте на базе класса table шаблонный класс sorted_table, для работы с таблицами,
упорядоченными по возрастанию ключа.
Создайте такую таблицу, загрузив данные для нее из текстового файла. Продемонстрируйте
двоичный поиск записей по ключу. Определите среднюю длину поиска для данной таблицы
и сравните ее с теоретической оценкой сложности алгоритма, выраженной средней длиной
двоичного поиска.

Таблицы со случайным открытым перемешиванием


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

226
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Создайте таблицу распределения с открытым перемешиванием, загрузив табличные записи


из имеющегося текстового файла. Продемонстрируйте поиск в созданной таблице,
табличных записей с использованием функции распределения. Определите среднюю длину
поиска для данной таблицы и сравните ее с теоретической для данной величины σ=m/n.
Поэкспериментируйте с различными вариантами функции распределения и различными
значениями величины σ.

– III – Сортировки
Класс для тестирования алгоритмов сортировки
Создайте на базе абстрактного класса SD конкретный шаблонный класс vector который
будет использован для представления векторов с элементами – объектами выбранной
предметной области. В отличии от таблиц, векторы допускают элементы с равными
ключами.

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

Быстрая сортировка с порогом


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

227
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Древовидная сортировка (Heap sort)


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

– IV – Динамические структуры данных


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

Простые однонаправленные списки


Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
простых однонаправленных списков.
Создайте на основе имеющегося текстового файла конкретный однонаправленный список и
продемонстрируйте поиск в нем по ключу. Определите среднюю длину поиска для этого
списка и сравните с теоретической оценкой алгоритма поиска в однонаправленном списке по
ключу. Продемонстрируйте методы добавления нового элемента в список, удаления
элемента из списка и др.

Однонаправленные циклические списки


Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
однонаправленных циклических списков.
Создайте на основе имеющегося текстового файла конкретный однонаправленный
циклический список и продемонстрируйте поиск в нем по ключу. Определите среднюю
длину поиска для этого списка и сравните с теоретической оценкой алгоритма поиска в

228
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

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


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

Двунаправленные списки
Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
двунаправленных списков.
Создайте на основе имеющегося текстового файла конкретный двунаправленный список и
продемонстрируйте поиск в нем по ключу. Определите среднюю длину поиска для этого
списка и сравните с теоретической оценкой алгоритма поиска в двунаправленном списке по
ключу. Продемонстрируйте методы добавления нового элемента в список, удаления
элемента из списка и др.

Двунаправленные циклические списки


Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
двунаправленных цмклических списков.
Создайте на основе имеющегося текстового файла конкретный двунаправленный
циклический список и продемонстрируйте поиск в нем по ключу. Определите среднюю
длину поиска для этого списка и сравните с теоретической оценкой алгоритма поиска в
двунаправленном циклическом списке по ключу. Продемонстрируйте методы добавления
нового элемента в список, удаления элемента из списка и др.

Стеки в виде буфера фиксированной длины


Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
стеков в виде буфера фиксированной длины.
Создайте на основе имеющегося текстового файла стек в виде буфера фиксированной длины
и продемонстрируйте запись в стек и извлечение из стека. Продемонстрируйте
использование стека для обхода двоичных деревьев.

Стеки в виде списка


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

229
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Очереди в виде циклического буфера


Создайте на базе абстрактного класса SD конкретный шаблонный класс для представления
очередей в виде циклического буфера.
Создайте на основе имеющегося текстового файла очередь в виде циклического буфера и
продемонстрируйте запись в очередь и извлечение из очереди.

Очереди в виде списка


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

– V – Матрицы
Матрицы с прямым доступом
Объявите на базе абстрактного класса SD конкретный шаблонный класс matrix4l для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по строкам.
Покажите на примере использование этого класса, объявив матрицу с типом элементов и с
диапазонами индексов, указанных преподавателем.
Объявите на базе абстрактного класса SD конкретный шаблонный класс matrix3c для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по
столбцам. Покажите на примере использование этого класса, объявив матрицу с типом
элементов и с диапазонами индексов, указанных преподавателем.

Матрицы с определяющими векторами


Объявите на базе шаблонного класса matrix4l шаблонный класс matrix4ldv для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по строкам,
дополненный определяющим вектором. Покажите на примере использование этого класса,
объявив матрицу с типом элементов и с диапазонами индексов, указанных преподавателем.
Объявите на базе шаблонного класса matrix4c, шаблонный класс matrix4cdv для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по
столбцам”, дополненный определяющим вектором. Покажите на примере использование
этого класса, объявив матрицу с типом элементов и с диапазонами индексов, указанных
преподавателем.

230
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Матрицы с векторами Айлифа


Объявите на базе шаблонного класса matrix4l шаблонный класс matrix4lIl для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по строкам,
дополненный векторами Айлифа. Покажите на примере использование этого класса, объявив
матрицу с типом элементов и с диапазонами индексов, указанных преподавателем.
Объявите на базе шаблонного класса matrix4c шаблонный класс matrix4cIl для
представления прямоугольных матриц порядка 4 с элементами, размещаемыми “по
столбцам”, дополненный векторами Айлифа. Покажите на примере использование этого
класса, объявив матрицу с типом элементов и с диапазонами индексов, указанных
преподавателем.

231
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

БИБЛИОГРАФИЯ

1. Donald E. KNUTH. Tratat de programarea calculatoarelor. Vol. 1. Algoritmi fundamentali. Vol.


3. Sortarea şi căutarea. Bucureşti, 1974
2. Julian M. Bucknall. Fundamental Algorithms and Data Strutures in Delphi
3. Schildt H. C++: Manual complet /Trad. din l. engl.– Bucureşti: Teora, 2000.– 644 p.
4. Dorel Lucanu. Bazele proiectării programelor şi algoritmelor. Vol. II. Tehnici de programare.
Vol III. Proiectarea algoritmilor. Editura Universităţii “Al. I. Cuza”, Iaşi, 1996
5. Tudor Sorin. Tehnici de programare. Bucureşti, Editura Teora, 1997
6. Robert E. Sedgewick. Fundamental Algorithms in C/C++. Parts 1-4. Fundamentals. Data
Structures. Sorting. Searching. Part 5. Graph Algorithms
7. Кнут Д. Искусство программирования для ЭВМ/ Пер. с англ. Т. 1. Основные
алгоритмы.— М.: Мир, 1976. — 736 с.
8. Кнут Д. Искусство программирования для ЭВМ/ Пер. с англ. Т. 3. Сортировка и поиск.—
М.: Мир, 1978. — 748 с.
9. Donald E. KNUTH. The art of computer programming. Volume 1. Fundamental Algorithms.
Volume 3. Sorting and Searching.
10. Мейер Б., Бодуэн К. Методы программирования/ Пер. с фр. в 2-х томах.— М.: Мир, 1982
11. Бакнел Джулиан М. Фундаментальные алгоритмы и структуры данных в Delphi: Пер. с
англ. – СПб: ООО «ДиаСофтЮП», 2003.- 560 с.
12. Лэнгсам Й., Огенстайм М., Тененбаум А. Структуры данных для персональных ЭВМ:
Пер. с англ. – М.: Мир, 1989.– 568 с.
13. Седжвик Р. Фундаментальные алгоритмы на С. Части 1-4. Анализ. Структуры данных.
Сортировка. Поиск: Пер. с англ. – СПб: ООО «ДиаСофтЮП», 2003.- 672 с.
14. Седжвик Р. Фундаментальные алгоритмы на С++. Часть 5. Алгоритмы на графах: Пер. с
англ. – СПб: ООО «ДиаСофтЮП», 2002.- 496 с.
15. Седжвик Р. Фундаментальные алгоритмы на JAVA. Части 1-4. Анализ. Структуры
данных. Сортировка. Поиск (3-е издание): Пер. с англ. – СПб: ТИД «ДС», 2003.- 688 с.
16. Iliffe John K., The Use of The Genie System in Numerical Calculations. Annual Review in
Automatic Programming 2: 25, 1961

232
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 1. БАЗОВЫЕ КЛАССЫ НА C#

using System;
using System.IO;

struct MainApp
{

static void Main()


{

StreamReader fstr_in = new StreamReader("Stud.txt");

string s;
char[] separator = new char[] { ' ', '\t' };

while ((s = fstr_in.ReadLine()) != null)


{
Console.WriteLine(s);
string[] se = s.Split(separator,
StringSplitOptions.RemoveEmptyEntries);

foreach (string sbs in se)


Console.WriteLine(sbs);
Console.WriteLine(se[0] + " " + se[1] + " " +se[2]);
}
fstr_in.Close();

Console.Write("Press Enter to exit... ");


Console.Read();
}

using System;
using System.IO;

struct usual_elem
{
public string name;
public int year;
public double salary;

public void Show()


{
Console.WriteLine(name + " " + year + " " + salary);
}
}

struct MainApp
{
static void Main()
{

StreamReader fstr_in = new StreamReader("Stud.txt");

string s;
char[] separator = new char[] { ' ', '\t' };

233
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

usual_elem ue;

while ((s = fstr_in.ReadLine()) != null)


{
Console.WriteLine(s);
string[] se = s.Split(separator,
StringSplitOptions.RemoveEmptyEntries);

ue.name = se[0];
ue.year = int.Parse(se[1]);
ue.salary = double.Parse(se[2]);
ue.Show();
}
fstr_in.Close();

Console.Write("Press Enter to exit... ");


Console.Read();
}

using System;
using System.IO;

//
// a b s t r a c t c l a s s "e l e m"
//
abstract class elem
{
public abstract int fscanf_el(StreamReader f);

public abstract void show(string opening, string ending);

public abstract bool free();

public abstract int cmp(elem e2);

static public bool operator >(elem e1, elem e2)


{
return e1.cmp(e2) > 0;
}

static public bool operator >=(elem e1, elem e2)


{
return e1.cmp(e2) >= 0;
}

static public bool operator <(elem e1, elem e2)


{
return e1.cmp(e2) < 0;
}

static public bool operator <=(elem e1, elem e2)


{
return e1.cmp(e2) <= 0;
}

static public bool operator ==(elem e1, elem e2)


{
return e1.cmp(e2) == 0;

234
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

static public bool operator !=(elem e1, elem e2)


{
return e1.cmp(e2) != 0;
}

protected void error(string message)


{
Console.WriteLine(message);
Console.Write("Press any key to fin... ");
Console.ReadKey();
System.Environment.Exit(0);
}

public override bool Equals(object ob)


{
return false;
}

public override int GetHashCode()


{
return 0;
}
}

//
// c l a s s "u s u a l _ e l e m"
//
class usual_elem : elem
{

protected string name;


protected int year;
protected double salary;

public usual_elem()
{
name = "";
year = 0;
salary = 0.0;
}

public usual_elem(string init_name, int init_year, double init_salary)


{
name = init_name;
year = init_year;
salary = init_salary;
}

public override int fscanf_el(StreamReader f)


{
// char[] separator = new char[] { ' ', '\t', (char)0x0D,
(char)0x0A };
char[] separator = new char[] { ' ', '\t' };
string s = f.ReadLine();
// Console.WriteLine("--> " + s);
bool succes = (s != null) && (s.Length > 0);

if (succes)
{
string[] se = s.Split(separator,
StringSplitOptions.RemoveEmptyEntries);
name = se[0];

235
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

year = int.Parse(se[1]);
salary = double.Parse(se[2]);
}

return succes ? 1 : 0;
}

public override void show(string opening, string ending)


{
Console.Write(opening);
if (!free())
// Console.Write(name + " " + year + " " + salary);
Console.Write("{0,-15:S} {1,4:G} {2,7:F2}", name, year, salary);
Console.Write(ending);
}

public override bool free()


{
return name == "";
}

public override int cmp(elem e2)


{
return name.CompareTo(((usual_elem)e2).name);
}

//public override int cmp(elem e2)


//{
// int result;
// if (this.year < ((usual_elem)e2).year)
// result = -1;
// else
// if (this.year > ((usual_elem)e2).year)
// result = 1;
// else
// result = 0;
// return result;
//}

//public override int cmp(elem e2)


//{
// int result;
// if (this.salary < ((usual_elem)e2).salary)
// result = -1;
// else
// if (this.salary > ((usual_elem)e2).salary)
// result = 1;
// else
// result = 0;
// return result;
//}

public override bool Equals(object ob)


{
return this.name == ((usual_elem)ob).name;
}

public override int GetHashCode()


{
return name[0] - 'A';
}
}

//

236
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

// A b s t r a c t c l a s s "S D"
//
abstract class SD
{
protected static StreamReader pf;
protected static long ncomp;

public SD()
{
}

public SD(string file_name)


{
pf = new StreamReader(file_name);
if (pf == null)
error("File " + file_name + " not found!\n");
}

~SD()
{
if (pf != null)
pf.Close();
}

abstract public void show(string opening, string ending, int nlinepp);

public long Ncomp


{
set
{
ncomp = value;
}
get
{
return ncomp;
}
}

public long get_ncomp()


{
return ncomp;
}

public void reset_ncomp()


{
ncomp = 0;
}

protected void error(string message)


{
Console.WriteLine(message);
Console.Write("Press any key to fin... ");
Console.ReadKey();
System.Environment.Exit(0);
}
}

237
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 2. ТАБЛИЦЫ НА C#

//
// C l a s s "t a b l e"
//
//class table<el> : SD
class table : SD
{
protected int n; // Number of table records
protected usual_elem[] t; // Vector of table records

public table(int NMAX)


{
t = new usual_elem[NMAX];
n = 0;
}

public table(string file_name, int NMAX) :


base(file_name)
{
t = new usual_elem[NMAX];

n = 0;
int repeated;
while (!pf.EndOfStream)
{
t[n] = new usual_elem();
if (t[n].fscanf_el(pf) > 0)
{
// t[n].show("--> ", "\n");
if ((repeated = search(t[n])) >= 0)
{
error("Key coincides with the key in the position: " +
(repeated + 1) + "!\n");
}
n++;
}
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
for (int i = 0; i < n; i++)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
if((object)t[i] != null)
t[i].show("", "\n");

238
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

else
Console.WriteLine();
}
Console.Write(ending);
Console.Write("End of Table. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;
for (int i = 0; (position == -1) && (i < n); i++)
{
ncomp++;
if (e == this.t[i])
position = i;
}
return position;
}

public int get_n()


{
return n;
}

public int N
{
get
{
return n;
}
}
}

//
// c l a s s "t r e e l i k e" e l e m e n t s
//
class tree_like : usual_elem
{
protected int less;
protected int greater;

public tree_like()
{
less = greater = -1;
}

public tree_like(string init_name, int init_year, double init_salary) :


base(init_name, init_year, init_salary)
{
less = greater = -1;
}

public int Less


{
get
{
return less;
}
set
{
less = value;

239
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
}

public int Greater


{
get
{
return greater;
}
set
{
greater = value;
}
}

public virtual void tree_show(string opening, string ending)


{
Console.Write(opening);
base.show("", "");
Console.Write(" [{0,4:D}, {1,4:D}]", less, greater);
Console.Write(ending);
}

public override int fscanf_el(StreamReader f)


{
less = greater = -1;
return base.fscanf_el(f);
}
}

//
// C l a s s "t r e e _ t a b l e"
//
class tree_table : SD
{
protected int n; // Number of table records
protected tree_like[] t; // Vector of table records

public tree_table(int NMAX)


: base()
{
t = new tree_like[NMAX];
n = 0;
}

public tree_table(string file_name, int NMAX) :


base(file_name)
{
t = new tree_like[NMAX];

n = 0;
int repeated;
while (!pf.EndOfStream)
{
t[n] = new tree_like();
if (t[n].fscanf_el(pf) > 0)
{
// t[n].show("--> ", "\n");
if ((repeated = secv_search(t[n])) >= 0)
{
error("Key coincides with the key in the position: " +
(repeated + 1) + "!\n");
}

240
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

n++;
}
}
pf.Close();
pf = null;

for (int i = 1; i < n; i++)


{
bool forward = true;
int j = 0;
while (forward)
if (t[i] < t[j])
if (t[j].Less == -1)
{
t[j].Less = i;
forward = false;
}
else
j = t[j].Less;
else
if (t[i] > t[j])
if (t[j].Greater == -1)
{
t[j].Greater = i;
forward = false;
}
else
j = t[j].Greater;
}
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
for (int i = 0; i < n; i++)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
t[i].show("", "\n");
}
Console.Write(ending);
Console.Write("End of Table. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public virtual void tree_show(string opening, string ending, int nlinepp)


{
Console.Clear();
Console.Write(opening);
for (int i = 0; i < n; i++)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();

241
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
t[i].tree_show("", "\n");
}
Console.Write(ending);
Console.Write("End of Table. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(tree_like e)


{
int position = -1;
bool forward = true;
int i = 0;
int cmp_result;

while (forward)
{
ncomp++;
if ((cmp_result = e.cmp(t[i])) == 0)
{
position = i;
forward = false;
}
else
{
if (cmp_result < 0)
i = t[i].Less;
else
i = t[i].Greater;
if (i == -1)
forward = false;
}
}
return position;
}

public int get_n()


{
return n;
}

public int N
{
get
{
return n;
}
}

protected int secv_search(tree_like e)


{
int position = -1;
for (int i = 0; (position == -1) && (i < n); i++)
{
ncomp++;
if (e == this.t[i])
position = i;
}
return position;
}

242
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

/*
//
// C l a s s "t r e e _ e l e m"
//
class tree_elem
{
protected int less;
protected int greater;

public tree_elem() {
less = greater = -1;
}

public int Less {


get {
return less;
}
set {
less = value;
}
}

public int Greater {


get {
return greater;
}
set {
greater = value;
}
}
}

//
// C l a s s "t r e e _ t a b l e"
//
class tree_table : table
{
protected tree_elem[] p;

public tree_table(int NMAX): base(NMAX) {


p = new tree_elem[NMAX];
for (int i = 0; i < NMAX; i++)
p[i] = new tree_elem();

public tree_table(string file_name, int NMAX): base(file_name, NMAX) {


p = new tree_elem[n];
for (int i = 0; i < n; i++)
p[i] = new tree_elem();

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


bool forward = true;
int j = 0;
while (forward)
if (t[i] < t[j])
if (p[j].Less == -1) {
p[j].Less = i;
forward = false;
}
else

243
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

j = p[j].Less;
else
if (t[i] > t[j])
if (p[j].Greater == -1) {
p[j].Greater = i;
forward = false;
}
else
j = p[j].Greater;
}
}

public virtual void tree_show(string opening, string ending, int nlinepp)


{
Console.Clear();
Console.Write(opening);
for (int i = 0; i < n; i++) {
if ((i > 0) && (i % nlinepp == 0)) {
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
t[i].show("", "");
Console.WriteLine(" [{0,4:D}, {1,4:D}]", p[i].Less, p[i].Greater);
}
Console.Write(ending);

Console.Write("End of Table. Press any key to continue... ");


Console.ReadKey();
Console.WriteLine();
}

// public int search(tree_like e)


public int search(tree_like e) {
int position = -1;
bool forward = true;
int i = 0;
int cmp_result;

while (forward) {
ncomp++;
if ((cmp_result = e.cmp(t[i])) == 0) {
position = i;
forward = false;
}
else {
if (cmp_result < 0)
i = p[i].Less;
else
i = p[i].Greater;
if (i == -1)
forward = false;
}
}
return position;
}
}
*/

//
// C l a s s "s o r t e d t a b l e"

244
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

//
class sorted_table : table
{
public sorted_table(int NMAX)
: base(NMAX)
{
}

public sorted_table(string file_name, int NMAX)


: base(NMAX)
{
int i;
usual_elem tmp;

n = 0;
pf = new StreamReader(file_name);
if (pf == null)
error("File " + file_name + " not found!\n");

while (!pf.EndOfStream)
if ((tmp = new usual_elem()).fscanf_el(pf) > 0)
{
// tmp.show("--> ", "\n");
for (i = n - 1; (i >= 0) && (tmp < t[i]); i--)
t[i + 1] = t[i];
if ((n > 0) && (i >= 0) && (tmp == t[i]))
error("Key coincides with the key in the position: " + (i +
1) + "!\n");
if(n == i + 1)
t[n] = new usual_elem();
t[i + 1] = tmp;
n++;
}
pf.Close();
pf = null;
}

// Binary search
public int search(usual_elem e)
{
int a = 0, b = n - 1;
int result;

while (a < b)
{
int i = (a + b) / 2;
ncomp++;
if ((result = e.cmp(t[i])) > 0)
a = i + 1;
else
if (result < 0)
b = i;
else
a = b = i;
}
ncomp++;
return (e == t[a]) ? a : -1;
}
/*
// Fibonaccian search
public int fibsearch(usual_elem e)
{
int result;
int j = 1;

245
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

while(fib(j) < (n + 1))


j++;

int f1 = fib(j - 2);


int mid = n - f1 + 1;
int f2 = fib(j - 3);
bool forward = true;

ncomp++;
while ( ( (result = e.cmp(t[mid - 1])) != 0 ) && forward
) {
// while ( forward && ( (result = e.cmp(t[mid - 1])) != 0 ) )
{
if ((mid <= 0) || (result > 1))
if (f1 == 1)
forward = false;
else {
mid += f2;
f1 -= f2;
f2 -= f1;
}
else
if (f2 == 0)
forward = false;
else {
mid -= f2;
int tmp = f1 - f2;
f1 = f2;
f2 = tmp;
}

// if(forward)
ncomp++;
}
return forward ? (mid - 1) : -1;
}

protected int fib(int m) {


int res;
if(m == 0 || m == 1)
res = m;
else
// if(m >= 2)
res = fib(m - 1) + fib(m - 2);
return res;
}
*/
}

//
// C l a s s "h a s h i n g _ e l e m"
//
class hashing_elem : usual_elem
{
public hashing_elem()
{
}

public hashing_elem(string init_name, int init_year, double init_salary) :


base(init_name, init_year, init_salary)
{
}

246
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public int hf(int n) // hashing function


{
return (name[0] - 'A') % n;
}
}

//
// C l a s s "h a s h i n g t a b l e"
//
class hashing_table : table
{
protected int m;

public hashing_table(string file_name, int NMAX)


: base(NMAX)
{
n = NMAX;
m = 0;

hashing_elem tmp;
int repeated;

pf = new StreamReader(file_name);
if (pf == null)
error("File " + file_name + " not found!\n");

while (!pf.EndOfStream)
if ((tmp = new hashing_elem()).fscanf_el(pf) > 0)
{
// tmp.show("--> ", "\n");
// Console.ReadKey();
int i = tmp.hf(n);
repeated = -1;
while ((repeated == -1) && ((object)t[i] != null))
{
if (tmp == t[i])
repeated = i;
else
i = (i + 1) % n;
}
if (repeated != -1)
error("Key coincides with the key in the position: " +
(repeated + 1) + "!\n");
t[i] = tmp;
m++;
}
pf.Close();
pf = null;
}

public int search(hashing_elem e)


{
int position = -1;
int i = e.hf(n);
while ((position == -1) && ((object)t[i] != null))
{
ncomp++;
if (e == t[i])
position = i;
else
i = (i + 1) % n;
}
return position;
}

247
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public int M
{
get
{
return m;
}
}
}

/*
// --- Table Searching ---
struct MainApp {
static void Main() {

table gr = new table("Stud.txt", 200);


gr.show("Group:\n", "", 20);

char ch='n';
while(ch!='y') {
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
// e.show("Table record: ", "\n");
gr.reset_ncomp();
int pos=gr.search(e);
if(pos < 0) {
Console.Write("No table! ");
Console.WriteLine("The number of comparisons: {0:D}",
gr.get_ncomp());
}
else {
Console.Write("There are in the position: {0:D}. ", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}",
gr.get_ncomp());
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
// --- Table ALS ---
struct MainApp {
static void Main() {

table gr = new table("Stud.txt", 200);


gr.show("Group:\n", "", 20);

usual_elem sample = new usual_elem();


long NCOMP=0;

StreamReader pf = new StreamReader("Stud.txt");

while (!pf.EndOfStream)
if(sample.fscanf_el(pf) > 0) {
gr.reset_ncomp();
if (gr.search(sample) >= 0)
NCOMP += gr.get_ncomp();

248
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
pf.Close();
Console.WriteLine("N={0:D}, NCOMP={1:D}, ALS={2:F2}", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
// --- tree_table Searching ---
struct MainApp {
static void Main() {

tree_table gr = new tree_table("Stud.txt", 200);


gr.show("Group:\n", "", 20);
gr.tree_show("Group:\n", "", 20);

char ch='n';
while(ch!='y') {
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
tree_like e = new tree_like(surname, 2000, 0.0);
// e.show("Table record: ", "\n");
gr.Ncomp = 0;
int pos=gr.search(e);
if(pos < 0) {
Console.Write("No table! ");
Console.WriteLine("The number of comparisons: {0:D}", gr.Ncomp);
}
else {
Console.Write("There are in the position: {0:D}. ", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}", gr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
// --- tree_table ALS ---
struct MainApp {
static void Main() {

tree_table gr = new tree_table("Stud.txt", 200);


gr.tree_show("Group:\n", "", 20);

tree_like sample = new tree_like();


long NCOMP=0;

StreamReader pf = new StreamReader("Stud.txt");

while (!pf.EndOfStream)
if(sample.fscanf_el(pf) > 0) {
gr.Ncomp = 0;
if (gr.search(sample) >= 0)
NCOMP += gr.Ncomp;
}
pf.Close();

249
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.Write("N={0:D}, NCOMP={1:D}, ALS={2:F2}", gr.N, NCOMP,


(double)NCOMP/gr.N);
Console.WriteLine(", MAX={0:F2}, MIN={1:F2}", (gr.N + 1) / 2.0,
Math.Log((double)gr.N) / Math.Log(2.0) + 2.0);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/

/*
// --- Sorted table Searching ---
struct MainApp {
static void Main() {

sorted_table sorted_gr = new sorted_table("Stud.txt", 200);


sorted_gr.show("Group:\n", "", 20);

char ch='n';
while(ch!='y') {
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
// e.show("Table record: ", "\n");
sorted_gr.Ncomp = 0;
int pos = sorted_gr.search(e);
// int pos = sorted_gr.fibsearch(e);
if(pos < 0) {
Console.Write("No table! ");
Console.WriteLine("The number of comparisons: {0:D}",
sorted_gr.Ncomp);
}
else {
Console.Write("There are in the position: {0:D}. ", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}",
sorted_gr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
// --- Sorted table ALS ---
struct MainApp {
static void Main() {

sorted_table sorted_gr = new sorted_table("Stud.txt", 200);


sorted_gr.show("Group:\n", "", 20);

usual_elem sample = new usual_elem();


long NCOMP=0;

StreamReader pf = new StreamReader("Stud.txt");

while (!pf.EndOfStream)
if(sample.fscanf_el(pf) > 0) {
sorted_gr.Ncomp = 0;
if (sorted_gr.search(sample) >= 0)
// if (sorted_gr.fibsearch(sample) >= 0)

250
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

NCOMP += sorted_gr.Ncomp;
}
pf.Close();
Console.Write("N={0:D}, NCOMP={1:D}, ALS={2:F2}", sorted_gr.N, NCOMP,
(double)NCOMP / sorted_gr.N);
Console.WriteLine(", ALS_TEOR={0:F2}", Math.Log((double)sorted_gr.N) /
Math.Log(2.0) + 2.0);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
// --- Hashing table Searching ---
struct MainApp {
static void Main() {

hashing_table gr = new hashing_table("Stud.txt", 15);


gr.show("Group:\n", "", 20);

char ch='n';
while(ch!='y') {
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
hashing_elem e = new hashing_elem(surname, 2000, 0.0);
// e.show("Table record: ", "\n");
gr.Ncomp = 0;
int pos = gr.search(e);
if(pos < 0) {
Console.Write("No table! ");
Console.WriteLine("The number of comparisons: {0:D}",
gr.get_ncomp());
}
else {
Console.Write("There are in the position: {0:D}. ", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}",
gr.get_ncomp());
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
// --- Hashing table ALS ---
struct MainApp
{
static void Main()
{

hashing_table gr = new hashing_table("Stud.txt", 15);


gr.show("Group:\n", "", 20);

hashing_elem sample = new hashing_elem();


long NCOMP = 0;

StreamReader pf = new StreamReader("Stud.txt");

while (!pf.EndOfStream)
if (sample.fscanf_el(pf) > 0)

251
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
gr.Ncomp = 0;
if (gr.search(sample) >= 0)
NCOMP += gr.Ncomp;
}
pf.Close();

Console.Write("m={0:D}, n={1:D}, NCOMP={2:D}, ALS={3:F2}", gr.M, gr.N,


NCOMP, (double)NCOMP / gr.M);
double sigma = (double)gr.M / gr.N;
Console.WriteLine(", m/n={0:F2}, D(m/n)={1:F2}", sigma, (2.0 - sigma) /
(2.0 - 2.0 * sigma));

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/

252
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 3. СОРТИРОВКА НА C#

//
// C l a s s "v e c t o r"
//
//class vector<el> : SD
class vector : SD
{
protected int n; // Number of elements
protected usual_elem[] t; // Vector of elements

public vector(int NMAX)


{
t = new usual_elem[NMAX];
n = 0;
}

public vector(string file_name, int NMAX) :


base(file_name)
{
t = new usual_elem[NMAX];

n = 0;
while (!pf.EndOfStream)
{
t[n] = new usual_elem();
if (t[n].fscanf_el(pf) > 0)
n++;
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
for (int i = 0; i < n; i++)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
t[i].show("", "\n");
}
Console.Write(ending);
Console.Write("End of Vector. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;

253
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

for (int i = 0; (position == -1) && (i < n); i++)


{
ncomp++;
if (e == this.t[i])
position = i;
}
return position;
}

public int get_n()


{
return n;
}

public int N
{
get
{
return n;
}
}

protected void swap(int i, int j)


{
usual_elem tempor = t[i];
t[i] = t[j];
t[j] = tempor;
}
}

//
// C l a s s " v e c t o r q u i c k s o r t"
//
class vector_quicksort : vector
{
public vector_quicksort(string file_name, int NMAX) :
base(file_name, NMAX)
{
}

public void quicksort()


{
quicksort_intern(0, n - 1);
}

public void quicksort(int i, int j)


{
if (j >= n || j == -1)
j = n - 1;
if (i < 0 || i > j)
i = 0;
quicksort_intern(i, j);
}

//protected void quicksort_intern(int i, int j)


//{
// if (j > i)
// {
// int imain = divide(i, j);
// quicksort_intern(i, imain - 1);
// quicksort_intern(imain + 1, j);
// }
//}

254
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

protected void quicksort_intern(int i, int j)


{
if (j>i)
{
int imain=divide(i,j);
if(imain-i > j-imain)
{// We start with the left-hand interval
quicksort_intern(i, imain - 1);
quicksort_intern(imain + 1,j );
}
else
{// We start with the right-hand interval
quicksort_intern(imain + 1, j);
quicksort_intern(i, imain - 1);
}
}
}

protected int divide(int i, int j)


{
int imain, jmain, imed;

imed = (i + j) / 2;

ncomp++;
if (t[i] < t[imed])
{
ncomp++;
if (t[imed] < t[j])
imain = imed;
else
{
ncomp++;
if (t[i] < t[j])
imain = j;
else
imain = i;
}
}
else
{
ncomp++;
if (t[imed] > t[j])
imain = imed;
else
{
ncomp++;
if (t[i] > t[j])
imain = j;
else
imain = i;
}
}

if (imain > i)
swap(i, imain);
imain = i + 1;
jmain = j;
while (imain < jmain)
{
bool forward = true;
while (forward)
{

255
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if (imain >= jmain)


forward = false;
else
{
ncomp++;
if (t[imain] > t[i])
forward = false;
else
imain++;
}
}

bool backward = true;


while (backward)
{
if (jmain <= imain)
backward = false;
else
{
ncomp++;
if (t[jmain] < t[i])
backward = false;
else
jmain--;
}
}

if (imain < jmain)


swap(imain, jmain);
}

ncomp++;
if (t[imain] > t[i])
imain--;

if (imain > i)
swap(i, imain);

return imain;
}
}

//
// C l a s s " v e c t o r o p t i m q u i c k s o r t "
//
class vector_optim_quicksort : vector_quicksort
{
protected int threshold;

public vector_optim_quicksort(string file_name, int threshold_init, int


NMAX) :
base(file_name, NMAX)
{
threshold = threshold_init;
if (threshold < 1)
threshold = 1;
}

public void quicksort()


{
quicksort_intern(0, n - 1);
}

public void quicksort(int i, int j)

256
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
if (j >= n || j == -1)
j = n - 1;
if (i < 0 || i > j)
i = 0;
quicksort_intern(i, j);
}

protected void quicksort_intern(int i, int j)


{
if(j-i+1>threshold)
{
int imain = divide(i, j);
if (imain - i > j - imain)
{// We start with the left-hand interval
quicksort_intern(imain + 1, j);
quicksort_intern(i, imain - 1);
}
else
{// We start with the right-hand interval
quicksort_intern(i, imain - 1);
quicksort_intern(imain + 1, j);
}
}
else
insertsort(i, j);
}

protected void insertsort(int i, int j)


{
for (int k = i + 1; k <= j; k++)
{
bool forward = true;
int l = k;
while (forward)
if (l <= i)
forward = false;
else
{
ncomp++;
if (t[l] >= t[l - 1])
forward = false;
else
{
swap(l - 1, l);
l--;
}
}

}
}
}

//
// C l a s s "v e c t o r _ h e a p s o r t "
//
class vector_heapsort: vector
{
public vector_heapsort(string file_name, int NMAX):
base(file_name, NMAX)
{
}

public void heapsort()

257
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
planting();
maxtreesort();
}

protected void reorganization(int i, int j)


{
int pred, next, left, right; // Indexes for traversal of tree
next = i;
do
{
pred = next; // temporary
left = 2 * pred + 1;
if(left <=j ) // If it belongs to the sub-tree
{
ncomp++;
if(t[left] > t[next])
next = left;
right = left + 1; // 2 * pred + 2
if(right <= j)
{
ncomp++;
if(t[right] > t[next])
next=right;
}

if(next != pred)
swap(pred, next); // We change elements with places
// t[pred] and t[next]
}
} while(pred!=next);
}

protected void planting()


{
for (int i = (n - 1) / 2; i >= 0; i--)
reorganization(i, n - 1);
}

protected void maxtreesort()


{
for (int i = (n - 1); i >= 1; i--)
{
swap(0, i);
reorganization(0, i - 1);
}
}

/*
// --- Quicksort ---
struct MainApp
{
static void Main()
{

vector_quicksort gr = new vector_quicksort("Stud.txt", 200);


gr.show("Unsorted group:\n", "", 20);
gr.quicksort();

258
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

gr.show("Group sorted by name:\n", "", 20);


//gr.show("Group sorted by year:\n", "", 20);
//gr.show("Group sorted by salary:\n", "", 20);
Console.WriteLine("n={0:D}, ncomp={1:D}, n*log2(n)={2:F2}, n*n={3:F0}",
gr.N, gr.Ncomp,
gr.N * Math.Log((double)gr.N) / Math.Log(2.0),
(double)gr.N * gr.N);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
// --- Optim quicksort ---
struct MainApp
{
static void Main()
{

vector_optim_quicksort gr = new vector_optim_quicksort("Stud.txt", 4,


200);
gr.show("Unsorted group:\n", "", 20);
gr.quicksort();
gr.show("Group sorted by name:\n", "", 20);
//gr.show("Group sorted by year:\n", "", 20);
//gr.show("Group sorted by salary:\n", "", 20);
Console.WriteLine("n={0:D}, ncomp={1:D}, n*log2(n)={2:F2}, n*n={3:F0}",
gr.N, gr.Ncomp,
gr.N * Math.Log((double)gr.N) / Math.Log(2.0),
(double)gr.N * gr.N);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/

/*
// --- Heapsort ---
struct MainApp
{
static void Main()
{

vector_heapsort gr = new vector_heapsort("Stud.txt", 200);


gr.show("Unsorted group:\n", "", 20);
gr.heapsort();
gr.show("Group sorted by name:\n", "", 20);
//gr.show("Group sorted by year:\n", "", 20);
//gr.show("Group sorted by salary:\n", "", 20);
Console.WriteLine("n={0:D}, ncomp={1:D}, n*log2(n)={2:F2}, n*n={3:F0}",
gr.N, gr.Ncomp,
gr.N * Math.Log((double)gr.N) / Math.Log(2.0),
(double)gr.N * gr.N);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/

259
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 4. ДВОИЧНЫЕ ДЕРЕВЬЯ НА C#

//
// C l a s s "b i n t r e e"
//
//class bintree<el> : SD
class bintree : SD
{
protected bintree pleft;
protected bintree pright;
protected usual_elem pdata;

public bintree()
{
pdata = null;
pleft = pright = null;
}

public bintree(string file_name) :


base(file_name)
{
pdata = null;
pleft = pright = null;

bintree pcurrent_nod;
usual_elem pnewel;

while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
//pnewel.show(" --- ","\n");
//Console.ReadKey();

pcurrent_nod = this;
while ((object)pcurrent_nod.pdata != null)
{
if (pnewel < pcurrent_nod.pdata)
{
if ((object)pcurrent_nod.pleft == null)
pcurrent_nod.pleft = new bintree();
pcurrent_nod = pcurrent_nod.pleft;
}
else
{
if ((object)pcurrent_nod.pright == null)
pcurrent_nod.pright = new bintree();
pcurrent_nod = pcurrent_nod.pright;
}
}

pcurrent_nod.pdata = pnewel;
}
}

pf.Close();
pf = null;
}

260
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public void show_preorder(string opening, string ending)


{
Console.Write(opening);

if ((object)pdata != null)
pdata.show("", "\n");

if ((object)pleft != null)
pleft.show_preorder("", "");

if ((object)pright != null)
pright.show_preorder("", "");

Console.Write(ending);
}

public void show_inorder(string opening, string ending)


{
Console.Write(opening);

if ((object)pleft != null)
pleft.show_inorder("", "");

if ((object)pdata != null)
pdata.show("", "\n");

if ((object)pright != null)
pright.show_inorder("", "");

Console.Write(ending);
}

public void show_postorder(string opening, string ending)


{
Console.Write(opening);

if ((object)pleft != null)
pleft.show_postorder("", "");

if ((object)pright != null)
pright.show_postorder("", "");

if ((object)pdata != null)
pdata.show("", "\n");

Console.Write(ending);
}

public override void show(string opening, string ending, int nlinepp)


{
show_preorder(opening, ending);
}

public usual_elem search(usual_elem e)


{
bintree pcurrent_nod = this;
bool found = false;
bool forward;
int cmp_result;

if ((object)pcurrent_nod.pdata != null)
forward = true;
else

261
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

forward = false;
while(forward)
{
ncomp++;
if ((cmp_result = e.cmp(pcurrent_nod.pdata)) == 0)
{
found = true;
forward = false;
}
else
{
if (cmp_result < 0)
if ((object)pcurrent_nod.pleft != null)
pcurrent_nod = pcurrent_nod.pleft;
else
forward = false;
else
if ((object)pcurrent_nod.pright != null)
pcurrent_nod = pcurrent_nod.pright;
else
forward = false;
}
}

return found ? pcurrent_nod.pdata : null;


}
/*
public usual_elem preorder_search(usual_elem e)
{
usual_elem presult = null;
if((object)pdata != null)
{
ncomp++;
if(e.cmp(pdata) == 0)
presult = pdata;
else
{
if ((object)pleft != null)
presult = pleft.preorder_search(e);
if (((object)presult == null) && ((object)pright != null))
presult = pright.preorder_search(e);
}
}
return presult;
}
*/
}

// --- BinTree ---

/*
struct MainApp
{
static void Main()
{

bintree btgr = new bintree("Stud.txt");

btgr.show_preorder("Group in preorder\n","\n");

btgr.show_inorder("Group in inorder\n","\n");

btgr.show_postorder("Group in postorder\n","\n");

262
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
struct MainApp
{
static void Main()
{
bintree btgr = new bintree("Stud.txt");

btgr.show("Group:\n","", 0);

char ch='n';
while(ch!='y')
{
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
btgr.Ncomp = 0;
usual_elem p = btgr.search(e);
if((object)p == null) {
Console.Write("No bintree! ");
Console.WriteLine("The number of comparisons: {0:D}",
btgr.Ncomp);
}
else {
Console.Write("There are in the bintree: ");
p.show("", "\n");
Console.WriteLine("The number of comparisons: {0:D}",
btgr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
struct MainApp
{
static void Main()
{
bintree btgr = new bintree("Stud.txt");
btgr.show("Group:\n","", 0);

usual_elem sample = new usual_elem();


long NCOMP = 0;
long nelem = 0;

StreamReader pf = new StreamReader("Stud.txt");


while (!pf.EndOfStream)
if(sample.fscanf_el(pf) > 0)
{
btgr.Ncomp = 0;
if ((object)(btgr.search(sample)) != null)
{
NCOMP += btgr.Ncomp;
nelem++;
}

263
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
pf.Close();
Console.Write("n={0:D}, NCOMP={1:D}, ALS={2:F2}", nelem, NCOMP,
(double)NCOMP / nelem);
Console.WriteLine(", MAX={0:F2}, MIN={1:F2}", (nelem + 1) / 2.0,
Math.Log((double)nelem) / Math.Log(2.0) + 2.0);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
struct MainApp
{
static void Main()
{
bintree btgr = new bintree("Stud.txt");

btgr.show_preorder("Group in preoder:\n", "");

char ch = 'n';
while (ch != 'y')
{
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
btgr.Ncomp = 0;
usual_elem p = btgr.preorder_search(e);
if ((object)p == null)
{
Console.Write("No bintree! ");
Console.WriteLine("The number of comparisons: {0:D}",
btgr.Ncomp);
}
else
{
Console.Write("There are in the bintree: ");
p.show("", "\n");
Console.WriteLine("The number of comparisons: {0:D}",
btgr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/
/*
struct MainApp
{
static void Main()
{
bintree btgr = new bintree("Stud.txt");
btgr.show_preorder("Group:\n", "");

usual_elem sample = new usual_elem();


long NCOMP = 0;
long nelem = 0;

StreamReader pf = new StreamReader("Stud.txt");


while (!pf.EndOfStream)

264
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if (sample.fscanf_el(pf) > 0)
{
btgr.Ncomp = 0;
if ((object)(btgr.preorder_search(sample)) != null)
{
NCOMP += btgr.Ncomp;
nelem++;
}
}
pf.Close();
Console.Write("n={0:D}, NCOMP={1:D}, ALS={2:F2}", nelem, NCOMP,
(double)NCOMP / nelem);
Console.WriteLine(", MAX={0:F2}, MIN={1:F2}", (nelem + 1) / 2.0,
Math.Log((double)nelem) / Math.Log(2.0) + 2.0);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/

265
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 5. СПИСКИ НА C#

//
// C l a s s "l i s t"
//
class list : SD
{
protected list pnext;
protected usual_elem pdata;

public list()
{
pdata = null;
pnext = null;
}

public list(string file_name) :


base(file_name)
{
pdata = null;
pnext = null;

list pcurrent_nod;
usual_elem pnewel;

pcurrent_nod = this;
while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
//pnewel.show(" --- ","\n");
//Console.ReadKey();
if ((object)pcurrent_nod.pdata != null)
{
pcurrent_nod.pnext = new list();
pcurrent_nod = pcurrent_nod.pnext;
}
pcurrent_nod.pdata = pnewel;
}
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
list pcurrent_nod = this;
if ((object)pcurrent_nod.pdata != null)
{
int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.pnext) != null)
{
if ((i > 0) && (i % nlinepp == 0))

266
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of List. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;
list pcurrent_nod = this;

if ((object)pcurrent_nod.pdata != null)
{
int i = 0;
ncomp++;
if (pcurrent_nod.pdata == e)
position = i;
while (position == -1 && (object)pcurrent_nod.pnext != null )
{
i++;
pcurrent_nod = pcurrent_nod.pnext;
ncomp++;
if (pcurrent_nod.pdata == e)
position = i;
}
}

return position;
}
}

//
// C l a s s "b i d i r e c t i o n a l l i s t"
//
class bidirectional_list : SD
{
protected bidirectional_list pnext;
protected bidirectional_list ppred;
protected usual_elem pdata;

public bidirectional_list()
{
pdata = null;
pnext = ppred = null;
}

public bidirectional_list(string file_name) :


base(file_name)
{
pdata = null;
pnext = ppred = null;

bidirectional_list pcurrent_nod;

267
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

usual_elem pnewel;

pcurrent_nod = this;
while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
//pnewel.show(" --- ","\n");
//Console.ReadKey();
if ((object)pcurrent_nod.pdata != null)
{
pcurrent_nod.pnext = new bidirectional_list();
pcurrent_nod.pnext.ppred = pcurrent_nod;
pcurrent_nod = pcurrent_nod.pnext;
}
pcurrent_nod.pdata = pnewel;
}
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
bidirectional_list pcurrent_nod = this;
if ((object)pcurrent_nod.pdata != null)
{
int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.pnext) != null)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of List. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public void invers_show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
bidirectional_list pcurrent_nod = this;
if ((object)pcurrent_nod.pdata != null)
{
while ((object)pcurrent_nod.pnext != null)
pcurrent_nod = pcurrent_nod.pnext;

268
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.ppred) != null)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of List. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;
bidirectional_list pcurrent_nod = this;

if ((object)pcurrent_nod.pdata != null)
{
int i = 0;
ncomp++;
if (pcurrent_nod.pdata == e)
position = i;
while (position == -1 && (object)pcurrent_nod.pnext != null )
{
i++;
pcurrent_nod = pcurrent_nod.pnext;
ncomp++;
if (pcurrent_nod.pdata == e)
position = i;
}
}

return position;
}
}

//
// C l a s s "n o d e _ l i s t"
//
class node_list
{
protected node_list pnext;
protected usual_elem pdata;

public node_list()
{
pnext = null;
pdata = null;
}

269
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public virtual void show(string opening, string ending)


{
if((object)pdata != null)
pdata.show(opening, ending);
}

public node_list Pnext


{
set
{
pnext = value;
}
get
{
return pnext;
}
}

public usual_elem Pdata


{
set
{
pdata = value;
}
get
{
return pdata;
}
}
}

//
// C l a s s "m a i n _ l i s t"
//
class main_list : SD
{
protected node_list pfirst;

public main_list()
{
pfirst = null;
}

public main_list(string file_name) :


base(file_name)
{
pfirst = new node_list();

node_list pcurrent_nod;
usual_elem pnewel;

pcurrent_nod = pfirst;
while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
//pnewel.show(" --- ","\n");
//Console.ReadKey();
if ((object)pcurrent_nod.Pdata != null)
{
pcurrent_nod.Pnext = new node_list();
pcurrent_nod = pcurrent_nod.Pnext;
}

270
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

pcurrent_nod.Pdata = pnewel;
}
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
node_list pcurrent_nod = this.pfirst;
if ((object)pcurrent_nod.Pdata != null)
{
int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.Pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.Pnext) != null)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.Pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of List. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;
node_list pcurrent_nod = this.pfirst;

if ((object)pcurrent_nod.Pdata != null)
{
int i = 0;
ncomp++;
if (pcurrent_nod.Pdata == e)
position = i;
while (position == -1 && (object)pcurrent_nod.Pnext != null)
{
i++;
pcurrent_nod = pcurrent_nod.Pnext;
ncomp++;
if (pcurrent_nod.Pdata == e)
position = i;
}
}

return position;
}
}

271
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

/*
// List utilisation
struct MainApp
{
static void Main()
{

list gr = new list("Pstud.txt");


// main_list gr = new main_list("Pstud.txt");
gr.show("Group:\n", "\n", 20);

char ch = 'n';
while (ch != 'y')
{
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
gr.Ncomp = 0;
int pos = gr.search(e);
if (pos < 0)
{
Console.Write("No List! ");
Console.WriteLine("The number of comparisons: {0:D}", gr.Ncomp);
}
else
{
Console.Write("There are in the position {0:D}\n", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}\n",
gr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/

/*
// Bidirectional list utilisation
struct MainApp
{
static void Main()
{
bidirectional_list gr = new bidirectional_list("Pstud.txt");

gr.show("Group:\n", "\n", 20);


gr.invers_show("Group in invers order:\n", "", 20);

char ch = 'n';
while (ch != 'y')
{
Console.Write("Enter a name to search: ");
string surname = Console.ReadLine();
usual_elem e = new usual_elem(surname, 2000, 0.0);
gr.Ncomp = 0;
int pos = gr.search(e);
if (pos < 0)
{
Console.Write("No List! ");
Console.WriteLine("The number of comparisons: {0:D}", gr.Ncomp);
}
else

272
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
Console.Write("There are in the position {0:D}\n", pos + 1);
Console.WriteLine("The number of comparisons: {0:D}\n",
gr.Ncomp);
}
Console.Write("Done ? (y/n) ");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}
}
}
*/

273
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 6. СТЕКИ НА C#

//
// C l a s s "s t a c k f i x e d b u f f e r"
//
class stackFB : SD
{
protected usual_elem[] buffer;
protected int size, top;

public stackFB(int init_size)


{
size = init_size;
buffer = new usual_elem[size];
top = 0;
}

public stackFB(string file_name, int init_size) :


base(file_name)
{
size = init_size;
buffer = new usual_elem[size];
top = 0;

usual_elem newel;
while (!pf.EndOfStream)
{
newel = new usual_elem();
if (newel.fscanf_el(pf) > 0)
push(newel);
}

pf.Close();
pf = null;
}

public bool isempty() { return top == 0; }

public bool isfull() { return top == size; }

public void push(usual_elem newel)


{
if (!isfull())
buffer[top++] = newel;
else
error("Stack is full!\n");
}

public usual_elem pop()


{
if (top > 0)
return buffer[--top];
else
{
error("Stack is empty!\n");
return null;
}
}

274
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
if (!isempty())
{
int i = 1;
Console.Write("{0,4:D}. ", i);
int current = top - 1;
buffer[current].show("", "\n");
while (--current >= 0)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
buffer[current].show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of Stack. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}
}

//
// C l a s s "s t a c k l i s t"
//
class stackL : SD
{
protected stackL pnext;
protected usual_elem pdata;

public stackL()
{
pdata = null;
pnext = null;
}

public stackL(string file_name) :


base(file_name)
{
pdata = null;
pnext = null;

stackL pcurrent_nod;
usual_elem pnewel;

pcurrent_nod = this;
while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
push(pnewel);
}
pf.Close();
pf = null;

275
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public bool isempty() { return (object)pdata == null; }

public bool isfull() { return false; }

public void push(usual_elem newel)


{
if(!isfull())
{
if((object)pdata != null)
{
stackL ptemp = new stackL();
ptemp.pdata = this.pdata;
ptemp.pnext = this.pnext;
this.pnext = ptemp;
}
this.pdata = newel;
}
else
error("Stack is full!");
}

public usual_elem pop()


{
if (!isempty())
{
usual_elem pret = this.pdata;

if ((object)this.pnext != null)
{
stackL ptemp = this.pnext;
this.pnext = ptemp.pnext;
this.pdata = ptemp.pdata;
}
else
this.pdata = null;

return pret;
}
else
{
error("Stack is empty!");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
stackL pcurrent_nod = this;
if ((object)pcurrent_nod.pdata != null)
{
int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.pnext) != null)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();

276
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of Stack. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}
}

///*
// Stack utilisation
struct MainApp
{
static void Main()
{

stackFB gr = new stackFB("Stud.txt", 100);


// stackL gr = new stackL("Stud.txt");
gr.show("Group in stack:\n", "", 20);

Console.WriteLine("\nExtracting by one:");
while(!gr.isempty())
{
Console.WriteLine("Press any key...");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
usual_elem extracted_el;
extracted_el = gr.pop();
extracted_el.show("","\n");
}
Console.WriteLine("Stack is empty! Press any key...");
Console.ReadKey();
Console.WriteLine();

gr.push(new usual_elem("Purple", 1985, 400));


gr.push(new usual_elem("Violet", 1984, 500));

Console.WriteLine();
gr.pop().show("","\n");
gr.pop().show("","\n");

Console.WriteLine("Press any key...");


Console.ReadKey();
Console.WriteLine();
}
}
//*/

277
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

//
// C l a s s "s t a c k _ e l e m"
//
class stack_elem
{
protected SD p;
protected char t;

public stack_elem()
{
p = null;
t = '\0';
}

public stack_elem(SD initp, char initt)


{
p = initp;
t = initt;
}

public SD P
{
set
{
p = value;
}
get
{
return p;
}
}

public char T
{
set
{
t = value;
}
get
{
return t;
}
}
}

//
// C l a s s "s t a c k f i x e d b u f f e r"
//
class stackFB : SD
{
protected stack_elem[] buffer;
protected int size, top;

public stackFB(int init_size)


{
size = init_size;
buffer = new stack_elem[size];
top = 0;
}

public bool isempty() { return top == 0; }

278
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public bool isfull() { return top == size; }

public void push(stack_elem newel)


{
if (!isfull())
buffer[top++] = newel;
else
error("Stack is full!\n");
}

public stack_elem pop()


{
if (top > 0)
return buffer[--top];
else
{
error("Stack is empty!\n");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
}
}

//
// C l a s s "s t a c k l i s t"
//
class stackL : SD
{
protected stackL pnext;
protected stack_elem pdata;

public stackL()
{
pdata = null;
pnext = null;
}

public bool isempty() { return (object)pdata == null; }

public bool isfull() { return false; }

public void push(stack_elem newel)


{
if(!isfull())
{
if((object)pdata != null)
{
stackL ptemp = new stackL();
ptemp.pdata = this.pdata;
ptemp.pnext = this.pnext;
this.pnext = ptemp;
}
this.pdata = newel;
}
else
error("Stack is full!");
}

public stack_elem pop()


{
if (!isempty())

279
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
stack_elem pret = this.pdata;

if ((object)this.pnext != null)
{
stackL ptemp = this.pnext;
this.pnext = ptemp.pnext;
this.pdata = ptemp.pdata;
}
else
this.pdata = null;

return pret;
}
else
{
error("Stack is empty!");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
}
}

//
// C l a s s "b i n t r e e s t a c k"
//
class bintree_stack : SD
{
protected bintree_stack pleft;
protected bintree_stack pright;
protected usual_elem pdata;

public bintree_stack()
{
pdata = null;
pleft = pright = null;
}

public bintree_stack(string file_name) :


base(file_name)
{
pdata = null;
pleft = pright = null;

bintree_stack pcurrent_nod;
usual_elem pnewel;

while (!pf.EndOfStream)
{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
//pnewel.show(" --- ","\n");
//Console.ReadKey();

pcurrent_nod = this;
while ((object)pcurrent_nod.pdata != null)
{
if (pnewel < pcurrent_nod.pdata)
{

280
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if ((object)pcurrent_nod.pleft == null)
pcurrent_nod.pleft = new bintree_stack();
pcurrent_nod = pcurrent_nod.pleft;
}
else
{
if ((object)pcurrent_nod.pright == null)
pcurrent_nod.pright = new bintree_stack();
pcurrent_nod = pcurrent_nod.pright;
}
}

pcurrent_nod.pdata = pnewel;
}
}

pf.Close();
pf = null;
}

public void show_preorderFB(string opening, string ending)


{
stackFB st = new stackFB(100);
stack_elem pcur;

st.push(new stack_elem(this, '\0'));


Console.Write(opening);
while (!st.isempty())
{
pcur = st.pop();
if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright, '\0'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, '\0'));
if ((object)(((bintree_stack)pcur.P).pdata) != null)
((bintree_stack)pcur.P).pdata.show("", "\n");
}
Console.Write(ending);
}

public void show_inorderFB(string opening, string ending)


{
stackFB st = new stackFB(100);
stack_elem pcur;

st.push(new stack_elem(this, 'r'));


Console.Write(opening);
while(!st.isempty())
{
pcur = st.pop();
if (pcur.T == 'n')
((bintree_stack)pcur.P).pdata.show("", "\n");
else
{
if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright,
'r'));
if ((object)(((bintree_stack)pcur.P).pdata) != null)
st.push(new stack_elem(pcur.P, 'n'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, 'r'));
}
}
Console.Write(ending);

281
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public void show_postorderFB(string opening, string ending)


{
stackFB st = new stackFB(100);
stack_elem pcur;

st.push(new stack_elem(this, 'r'));


Console.Write(opening);
while (!st.isempty())
{
pcur = st.pop();
if (pcur.T == 'n')
((bintree_stack)pcur.P).pdata.show("", "\n");
else
{
if ((object)(((bintree_stack)pcur.P).pdata) != null)
if ((object)(((bintree_stack)pcur.P).pdata) != null)
st.push(new stack_elem(pcur.P, 'n'));
if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright,
'r'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, 'r'));
}
}
Console.Write(ending);
}

public void show_preorderL(string opening, string ending)


{
stackL st = new stackL();
stack_elem pcur;

st.push(new stack_elem(this, '\0'));


Console.Write(opening);
while (!st.isempty())
{
pcur = st.pop();
if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright, '\0'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, '\0'));
if ((object)(((bintree_stack)pcur.P).pdata) != null)
((bintree_stack)pcur.P).pdata.show("", "\n");
}
Console.Write(ending);
}

public void show_inorderL(string opening, string ending)


{
stackL st = new stackL();
stack_elem pcur;

st.push(new stack_elem(this, 'r'));


Console.Write(opening);
while (!st.isempty())
{
pcur = st.pop();
if (pcur.T == 'n')
((bintree_stack)pcur.P).pdata.show("", "\n");
else
{

282
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright,
'r'));
if ((object)(((bintree_stack)pcur.P).pdata) != null)
st.push(new stack_elem(pcur.P, 'n'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, 'r'));
}
}
Console.Write(ending);
}

public void show_postorderL(string opening, string ending)


{
stackL st = new stackL();
stack_elem pcur;

st.push(new stack_elem(this, 'r'));


Console.Write(opening);
while (!st.isempty())
{
pcur = st.pop();
if (pcur.T == 'n')
((bintree_stack)pcur.P).pdata.show("", "\n");
else
{
if ((object)(((bintree_stack)pcur.P).pdata) != null)
if ((object)(((bintree_stack)pcur.P).pdata) != null)
st.push(new stack_elem(pcur.P, 'n'));
if (((bintree_stack)pcur.P).pright != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pright,
'r'));
if (((bintree_stack)pcur.P).pleft != null)
st.push(new stack_elem(((bintree_stack)pcur.P).pleft, 'r'));
}
}
Console.Write(ending);
}

public override void show(string opening, string ending, int nlinepp)


{
show_preorderFB(opening, ending);
}
}

// --- BinTree_Stack ---

///*
struct MainApp
{
static void Main()
{

bintree_stack btgr = new bintree_stack("Stud.txt");

btgr.show_preorderFB("Group in preorder with buffer\n", "\n");

btgr.show_inorderFB("Group in inorder with buffer\n", "\n");

btgr.show_postorderFB("Group in postorder with buffer\n", "\n");

btgr.show_preorderL("Group in preorder with list\n", "\n");

283
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

btgr.show_inorderL("Group in inorder with list\n", "\n");

btgr.show_postorderL("Group in postorder with list\n", "\n");

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}

284
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

//
// C l a s s "i n t e r v a l"
//
class interval
{
protected int i, j;

public interval()
{
i = j = 0;
}

public interval(int initi, int initj)


{
i = initi;
j = initj;
}

public int I
{
set
{
i = value;
}
get
{
return i;
}
}

public int J
{
set
{
j = value;
}
get
{
return j;
}
}

virtual public void show(string opening, string ending)


{
Console.Write(opening);
Console.Write("[{0,4:D}, {1,4:D}]", i, j);
Console.Write(ending);
}
}

//
// C l a s s "s t a c k f i x e d b u f f e r"
//
class stackFB : SD
{
protected interval[] buffer;
protected int size, top;

public stackFB(int init_size)


{
size = init_size;
buffer = new interval[size];

285
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

top = 0;
}

public bool isempty() { return top == 0; }

public bool isfull() { return top == size; }

public void push(interval newel)


{
if (!isfull())
buffer[top++] = newel;
else
error("Stack is full!\n");
}

public interval pop()


{
if (top > 0)
return buffer[--top];
else
{
error("Stack is empty!\n");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
}
}

/*
//
// C l a s s "s t a c k l i s t"
//
class stackL : SD
{
protected stackL pnext;
protected interval pdata;

public stackL()
{
pdata = null;
pnext = null;
}

public bool isempty() { return (object)pdata == null; }

public bool isfull() { return false; }

public void push(interval newel)


{
if(!isfull())
{
if((object)pdata != null)
{
stackL ptemp = new stackL();
ptemp.pdata = this.pdata;
ptemp.pnext = this.pnext;
this.pnext = ptemp;
}
this.pdata = newel;
}
else

286
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

error("Stack is full!");
}

public interval pop()


{
if (!isempty())
{
interval pret = this.pdata;

if ((object)this.pnext != null)
{
stackL ptemp = this.pnext;
this.pnext = ptemp.pnext;
this.pdata = ptemp.pdata;
}
else
this.pdata = null;

return pret;
}
else
{
error("Stack is empty!");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
}
}
*/

//
// C l a s s "v e c t o r"
//
class vector : SD
{
protected int n; // Number of elements
protected usual_elem[] t; // Vector of elements

public vector(int NMAX)


{
t = new usual_elem[NMAX];
n = 0;
}

public vector(string file_name, int NMAX) :


base(file_name)
{
t = new usual_elem[NMAX];

n = 0;
while (!pf.EndOfStream)
{
t[n] = new usual_elem();
if (t[n].fscanf_el(pf) > 0)
n++;
}
pf.Close();
pf = null;
}

public override void show(string opening, string ending, int nlinepp)

287
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
Console.Clear();

Console.Write(opening);
for (int i = 0; i < n; i++)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0,4:D}. ", (i + 1));
t[i].show("", "\n");
}
Console.Write(ending);
Console.Write("End of Vector. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(usual_elem e)


{
int position = -1;
for (int i = 0; (position == -1) && (i < n); i++)
{
ncomp++;
if (e == this.t[i])
position = i;
}
return position;
}

public int get_n()


{
return n;
}

public int N
{
get
{
return n;
}
}

protected void swap(int i, int j)


{
usual_elem tempor = t[i];
t[i] = t[j];
t[j] = tempor;
}
}

//
// C l a s s " v e c t o r q u i c k s o r t"
//
class vector_quicksort : vector
{
public vector_quicksort(string file_name, int NMAX) :
base(file_name, NMAX)
{

288
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public void quicksort()


{
quicksort_stack(0, n - 1);
}

public void quicksort(int i, int j)


{
if (j >= n || j == -1)
j = n - 1;
if (i < 0 || i > j)
i = 0;
quicksort_stack(i, j);
}

protected void quicksort_stack(int i, int j)


{
stackFB stack_interv = new stackFB(n);
// stackFB stack_interv = new stackFB((int)(Math.Log((double)n) /
Math.Log(2.0)) + 1);
// stackL stack_interv = new stackL();
stack_interv.push(new interval(i, j));

while(!stack_interv.isempty())
{
interval tinterv = stack_interv.pop();
i = tinterv.I;
j = tinterv.J;

tinterv.show("From Stack: ","\n");


Console.ReadKey();

if(j>i)
{
int imain=divide(i,j);
stack_interv.push(new interval(i, imain - 1));
stack_interv.push(new interval(imain + 1, j));
//stack_interv.push(new interval(imain + 1, j));
//stack_interv.push(new interval(i, imain - 1));
}
else
{
Console.WriteLine("Not processed!");
Console.ReadKey();
}
}

/*
protected void quicksort_stack(int i, int j)
{
stackFB stack_interv = new stackFB((int)(Math.Log((double)n) /
Math.Log(2.0)) + 1);
// stackL stack_interv = new stackL();
// stackFB stack_interv = new stackFB(n);
stack_interv.push(new interval(i, j));

while (!stack_interv.isempty())
{
interval tinterv = stack_interv.pop();
i = tinterv.I;
j = tinterv.J;

289
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

tinterv.show("From Stack: ", "\n");


Console.ReadKey();

if (j > i)
{
int imain = divide(i, j);
if (imain - i > j - imain)
{// We'll start with the right-hand interval
stack_interv.push(new interval(i, imain - 1));
stack_interv.push(new interval(imain + 1, j));
}
else
{// We'll start with the left-hand interval
stack_interv.push(new interval(imain + 1, j));
stack_interv.push(new interval(i, imain - 1));
}
}
else
{
Console.WriteLine("Not processed!");
Console.ReadKey();
}
}

}
*/
protected int divide(int i, int j)
{
int imain, jmain, imed;

imed = (i + j) / 2;

ncomp++;
if (t[i] < t[imed])
{
ncomp++;
if (t[imed] < t[j])
imain = imed;
else
{
ncomp++;
if (t[i] < t[j])
imain = j;
else
imain = i;
}
}
else
{
ncomp++;
if (t[imed] > t[j])
imain = imed;
else
{
ncomp++;
if (t[i] > t[j])
imain = j;
else
imain = i;
}
}

if (imain > i)

290
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

swap(i, imain);
imain = i + 1;
jmain = j;
while (imain < jmain)
{
bool forward = true;
while (forward)
{
if (imain >= jmain)
forward = false;
else
{
ncomp++;
if (t[imain] > t[i])
forward = false;
else
imain++;
}
}

bool backward = true;


while (backward)
{
if (jmain <= imain)
backward = false;
else
{
ncomp++;
if (t[jmain] < t[i])
backward = false;
else
jmain--;
}
}

if (imain < jmain)


swap(imain, jmain);
}

ncomp++;
if (t[imain] > t[i])
imain--;

if (imain > i)
swap(i, imain);

return imain;
}
}

//
// C l a s s " v e c t o r o p t i m q u i c k s o r t "
//
class vector_optim_quicksort : vector_quicksort
{
protected int threshold;

public vector_optim_quicksort(string file_name, int threshold_init, int


NMAX) :
base(file_name, NMAX)
{
threshold = threshold_init;
if (threshold < 1)
threshold = 1;

291
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public void optim_quicksort()


{
optim_quicksort_stack(0, n - 1);
}

public void optim_quicksort(int i, int j)


{
if (j >= n || j == -1)
j = n - 1;
if (i < 0 || i > j)
i = 0;
optim_quicksort_stack(i, j);
}

protected void optim_quicksort_stack(int i, int j)


{
stackFB stack_interv = new stackFB((int)(Math.Log((double)n) /
Math.Log(2.0)) + 1);
// stackL stack_interv = new stackL();
// stackFB stack_interv = new stackFB(n);
stack_interv.push(new interval(i, j));

while (!stack_interv.isempty())
{
interval tinterv = stack_interv.pop();
i = tinterv.I;
j = tinterv.J;

tinterv.show("From Stack: ", "\n");


Console.ReadKey();

if (j - i + 1 > threshold)
{
int imain = divide(i, j);
if (imain - i > j - imain)
{// We'll start with the right-hand interval
stack_interv.push(new interval(i, imain - 1));
stack_interv.push(new interval(imain + 1, j));
}
else
{// We'll start with the left-hand interval
stack_interv.push(new interval(imain + 1, j));
stack_interv.push(new interval(i, imain - 1));
}
}
else
{
Console.WriteLine("Processed with insertsort!");
Console.ReadKey();
insertsort(i, j);
}
}
}

protected void insertsort(int i, int j)


{
for (int k = i + 1; k <= j; k++)
{
bool forward = true;
int l = k;
while (forward)
if (l <= i)

292
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

forward = false;
else
{
ncomp++;
if (t[l] >= t[l - 1])
forward = false;
else
{
swap(l - 1, l);
l--;
}
}
}
}

/*
// --- Quicksort + Stack ---
struct MainApp
{
static void Main()
{
vector_quicksort gr = new vector_quicksort("Stud.txt", 200);
//vector_quicksort gr = new vector_quicksort("Stud20.txt", 200);
gr.show("Unsorted group:\n", "", 20);
gr.quicksort();
gr.show("Group sorted by name:\n", "", 20);
//gr.show("Group sorted by year:\n", "", 20);
//gr.show("Group sorted by salary:\n", "", 20);
Console.WriteLine("n={0:D}, ncomp={1:D}, n*log2(n)={2:F2}, n*n={3:F0}",
gr.N, gr.Ncomp,
gr.N * Math.Log((double)gr.N) / Math.Log(2.0),
(double)gr.N * gr.N);

Console.Write("Press any key to exit... ");


Console.ReadKey();
}
}
*/
/*
// --- Optim quicksort + Stack ---
struct MainApp
{
static void Main()
{

vector_optim_quicksort gr = new vector_optim_quicksort("Stud.txt", 4,


200);
gr.show("Unsorted group:\n", "", 20);
gr.optim_quicksort();
gr.show("Group sorted by name:\n", "", 20);
//gr.show("Group sorted by year:\n", "", 20);
//gr.show("Group sorted by salary:\n", "", 20);
Console.WriteLine("n={0:D}, ncomp={1:D}, n*log2(n)={2:F2}, n*n={3:F0}",
gr.N, gr.Ncomp,
gr.N * Math.Log((double)gr.N) / Math.Log(2.0),
(double)gr.N * gr.N);

Console.Write("Press any key to exit... ");


Console.ReadKey();

293
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

}
}
*/

294
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 6. ОЧЕРЕДИ НА C#

//
// C l a s s "q u e u e c i r c u l a r b u f f e r"
//
class queueCB : SD
{
protected usual_elem[] buffer;
protected int size, nextin, nextout, count;

public queueCB(int init_size)


{
size = init_size;
buffer = new usual_elem[size];
nextin = nextout = count = 0;
}

public queueCB(string file_name, int init_size) :


base(file_name)
{
size = init_size;
buffer = new usual_elem[size];
nextin = nextout = count = 0;

usual_elem newel;
while (!pf.EndOfStream)
{
newel = new usual_elem();
if (newel.fscanf_el(pf) > 0)
put(newel);
}

pf.Close();
pf = null;
}

public bool isempty() { return count == 0; }

public bool isfull() { return count == size; }

public void put(usual_elem newel)


{
if (count++ < size)
{
buffer[nextin] = newel;
nextin = ++nextin % size;
}
else
error("Queue is full!\n");
}

public usual_elem get()


{
if (count-- > 0)
{
int outpos = nextout;
nextout = ++nextout % size;
return buffer[outpos];
}

295
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

else
{
error("Queue is empty!\n");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();

Console.Write(opening);
if (!isempty())
{
int i = 1;
Console.Write("{0,4:D}. ", i);
int current = nextout;
buffer[current].show("", "\n");
while ((++current % size) != nextin)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
buffer[current].show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of Queue. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}
}

//
// C l a s s "q u e u e l i s t"
//
class queueL : SD
{
protected queueL pnext;
protected usual_elem pdata;

public queueL()
{
pdata = null;
pnext = null;
}

public queueL(string file_name) :


base(file_name)
{
pdata = null;
pnext = null;

queueL pcurrent_nod;
usual_elem pnewel;

pcurrent_nod = this;
while (!pf.EndOfStream)

296
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

{
pnewel = new usual_elem();
if (pnewel.fscanf_el(pf) > 0)
{
if ((object)pcurrent_nod.pdata != null)
{
pcurrent_nod.pnext = new queueL();
pcurrent_nod = pcurrent_nod.pnext;
}
}
pcurrent_nod.pdata = pnewel;
}
pf.Close();
pf = null;
}

public bool isempty() { return (object)pdata == null; }

public bool isfull() { return false; }

public void put(usual_elem newel)


{
if(!isfull())
{
queueL pcurrent_nod = this;
if((object)pdata != null)
{
while ((object)pcurrent_nod.pnext != null)
pcurrent_nod = pcurrent_nod.pnext;

pcurrent_nod.pnext = new queueL();


pcurrent_nod = pcurrent_nod.pnext;
}
pcurrent_nod.pdata = newel;
}
else
error("Queue is full!");
}

public usual_elem get()


{
if (!isempty())
{
usual_elem pret = this.pdata;
if ((object)this.pnext != null)
{
queueL ptemp = this.pnext;
this.pnext = ptemp.pnext;
this.pdata = ptemp.pdata;
}
else
this.pdata = null;

return pret;
}
else
{
error("Queue is empty!");
return null;
}
}

public override void show(string opening, string ending, int nlinepp)


{

297
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.Clear();

Console.Write(opening);
queueL pcurrent_nod = this;
if ((object)pcurrent_nod.pdata != null)
{
int i = 1;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
while ((object)(pcurrent_nod = pcurrent_nod.pnext) != null)
{
if ((i > 0) && (i % nlinepp == 0))
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
i++;
Console.Write("{0,4:D}. ", i);
pcurrent_nod.pdata.show("", "\n");
}
}
Console.Write(ending);
Console.Write("End of Queue. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}
}

///*
// Stack utilisation
struct MainApp
{
static void Main()
{

queueCB gr = new queueCB("Stud.txt", 100);


// queueL gr = new queueL("Stud.txt");
gr.show("Group in queue:\n", "", 20);

Console.WriteLine("\nExtracting by one:");
while(!gr.isempty())
{
Console.WriteLine("Press any key...");
ConsoleKeyInfo info = Console.ReadKey();
Console.WriteLine();
usual_elem extracted_el;
extracted_el = gr.get();
extracted_el.show("","\n");
}
Console.WriteLine("Queue is empty! Press any key...");
Console.ReadKey();
Console.WriteLine();

gr.put(new usual_elem("Purple", 1985, 400));


gr.put(new usual_elem("Violet", 1984, 500));

Console.WriteLine();
gr.get().show("","\n");
gr.get().show("","\n");

Console.WriteLine("Press any key...");

298
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

Console.ReadKey();
Console.WriteLine();
}
}
//*/

299
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

ПРИЛОЖЕНИЕ 8. МАТРИЦЫ НА C#

//
// C l a s s "m a t r i x 3 l"
//
class matrix3l: SD
{
protected character[] V; // matrix reprezentation vector
protected int l1, h1;
protected int l2, h2;
protected int l3, h3;

protected long nopadd, nopmul;

public matrix3l(int l1, int h1, int l2, int h2, int l3, int h3)
{
this.l1=l1;
this.h1=h1;
this.l2=l2;
this.h2=h2;
this.l3=l3;
this.h3=h3;

int nel=(this.h1 - this.l1 + 1) * (this.h2 - this.l2 + 1) * (this.h3 -


this.l3 + 1);
V = new character[nel];

for (int i = 0; i < nel; i++)


V[i] = new character();

nopadd=0;
nopmul=0;
}

public matrix3l(string file_name, int l1, int h1, int l2, int h2, int l3,
int h3):
base(file_name)
{
this.l1=l1;
this.h1=h1;
this.l2=l2;
this.h2=h2;
this.l3=l3;
this.h3=h3;

int nel=(this.h1 - this.l1 + 1) * (this.h2 - this.l2 + 1) * (this.h3 -


this.l3 + 1);
V = new character[nel];

for (int i = 0; i < nel; i++)


{
V[i] = new character();
V[i].fscanf_el(pf);
}
pf.Close();
pf = null;

nopadd=0;
nopmul=0;

300
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public virtual character elem(int i1, int i2, int i3)


{
int D1, D2, D3;

nopadd+=9;
nopmul+=5;

D3 = 1;
D2 = (h3 - l3 + 1) * D3;
D1 = (h2 - l2 + 1) * D2;
return V[(i1 - l1) * D1 + (i2 - l2) * D2 + (i3 - l3) * D3];
}

public override void show(string opening, string ending , int nlinepp)


{
Console.Clear();
Console.Write(opening);

int i1, i2, i3;


for(i1 = l1; i1 <= h1; i1++)
{
Console.WriteLine("i1={0:D}", i1);
for(i2 = l2; i2 <= h2; i2++)
{
for(i3 = l3; i3 <= h3; i3++)
elem(i1, i2, i3).show("", " ");
Console.WriteLine();
}
}
Console.Write(ending);
Console.Write("End of Matrix. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public virtual void showvect(string opening, string ending)


{
Console.Clear();

int nel = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1);


Console.Write(opening);
for(int i = 0; i < nel; i++)
{
if(i > 0 && i % 20==0)
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0:D}. ", i); V[i].show("", "\n");
}
Console.Write(ending);
Console.Write("End of Vector. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(character e)


{
int position = -1;
int nel = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1);

301
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

for(int i = 0; (position == -1) && i < nel ; i++)


if(e == V[i])
position = i;
return position;
}

public int search(character e, out int i1, out int i2, out int i3)
{
i1 = 0;
i2 = 0;
i3 = 0;
int found = 0;
for(int i1t = l1; i1t <= h1 && (found == 0); i1t++)
for(int i2t = l2; i2t <= h2 && (found == 0); i2t++)
for (int i3t = l3; i3t <= h3 && (found == 0); i3t++)
if(e == elem(i1t, i2t, i3t))
{
found = 1;
i1 = i1t;
i2 = i2t;
i3 = i3t;
}
return found;
}

public void resetnop()


{
nopadd=0;
nopmul=0;
}

public long getnopadd()


{
return nopadd;
}

public long getnopmul()


{
return nopmul;
}

public long Nopadd


{
set
{
nopadd = value;
}
get
{
return nopadd;
}
}

public long Nopmul


{
set
{
nopmul = value;
}
get
{
return nopmul;
}
}

302
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

//
// C l a s s "m a t r i x 3 c"
//
class matrix3c : SD
{
protected character[] V; // matrix reprezentation vector
protected int l1, h1;
protected int l2, h2;
protected int l3, h3;

protected long nopadd, nopmul;

public matrix3c(int l1, int h1, int l2, int h2, int l3, int h3)
{
this.l1 = l1;
this.h1 = h1;
this.l2 = l2;
this.h2 = h2;
this.l3 = l3;
this.h3 = h3;

int nel = (this.h1 - this.l1 + 1) * (this.h2 - this.l2 + 1) * (this.h3 -


this.l3 + 1);
V = new character[nel];

for (int i = 0; i < nel; i++)


V[i] = new character();

nopadd = 0;
nopmul = 0;
}

public matrix3c(string file_name, int l1, int h1, int l2, int h2, int l3,
int h3) :
base(file_name)
{
this.l1 = l1;
this.h1 = h1;
this.l2 = l2;
this.h2 = h2;
this.l3 = l3;
this.h3 = h3;

int nel = (this.h1 - this.l1 + 1) * (this.h2 - this.l2 + 1) * (this.h3 -


this.l3 + 1);
V = new character[nel];

for (int i = 0; i < nel; i++)


{
V[i] = new character();
V[i].fscanf_el(pf);
}
pf.Close();
pf = null;

nopadd = 0;
nopmul = 0;
}

public virtual character elem(int i1, int i2, int i3)


{

303
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int D1, D2, D3;

nopadd += 9;
nopmul += 5;

D1 = 1;
D2 = (h1 - l1 + 1) * D1;
D3 = (h2 - l2 + 1) * D2;
return V[(i1 - l1) * D1 + (i2 - l2) * D2 + (i3 - l3) * D3];
}

public override void show(string opening, string ending, int nlinepp)


{
Console.Clear();
Console.Write(opening);

int i1, i2, i3;


for (i3 = l3; i3 <= h3; i3++)
{
Console.WriteLine("i3={0:D}", i3);
for (i1 = l1; i1 <= h1; i1++)
{
for (i2 = l2; i2 <= h2; i2++)
elem(i1, i2, i3).show("", " ");
Console.WriteLine();
}
}
Console.Write(ending);
Console.Write("End of Matrix. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public virtual void showvect(string opening, string ending)


{
Console.Clear();

int nel = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1);


Console.Write(opening);
for (int i = 0; i < nel; i++)
{
if (i > 0 && i % 20 == 0)
{
Console.Write("Press any key to continue... ");
Console.ReadKey();
Console.Clear();
Console.Write(opening);
}
Console.Write("{0:D}. ", i); V[i].show("", "\n");
}
Console.Write(ending);
Console.Write("End of Vector. Press any key to continue... ");
Console.ReadKey();
Console.WriteLine();
}

public int search(character e)


{
int position = -1;
int nel = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1);
for (int i = 0; (position == -1) && i < nel; i++)
if (e == V[i])
position = i;
return position;

304
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

public int search(character e, out int i1, out int i2, out int i3)
{
i1 = 0;
i2 = 0;
i3 = 0;
int found = 0;
for (int i1t = l1; i1t <= h1 && (found == 0); i1t++)
for (int i2t = l2; i2t <= h2 && (found == 0); i2t++)
for (int i3t = l3; i3t <= h3 && (found == 0); i3t++)
if (e == elem(i1t, i2t, i3t))
{
found = 1;
i1 = i1t;
i2 = i2t;
i3 = i3t;
}
return found;
}

public void resetnop()


{
nopadd = 0;
nopmul = 0;
}

public long getnopadd()


{
return nopadd;
}

public long getnopmul()


{
return nopmul;
}

public long Nopadd


{
set
{
nopadd = value;
}
get
{
return nopadd;
}
}

public long Nopmul


{
set
{
nopmul = value;
}
get
{
return nopmul;
}
}
}

//

305
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

// C l a s s "m a t r i x 3 l d v"
//
class matrix3ldv : matrix3l
{
protected int [] d; // poiner to definition vector

public matrix3ldv(int l1, int h1, int l2, int h2, int l3, int h3):
base(l1, h1, l2, h2, l3, h3)
{
d = new int[12];
d[0] = 3; // dimension
d[1] = l1;
d[2] = h1;
d[3] = l2;
d[4] = h2;
d[5] = l3;
d[6] = h3;
d[7] = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1); // number of
elements
d[10] = 1; // D3
d[9] = (h3 - l3 + 1) * d[10]; // D2
d[8] = (h2 - l2 + 1) * d[9]; // D1
d[11] = l1 * d[8] + l2 * d[9] + l3 * d[10]; // Sum(ljxDj)
}

public matrix3ldv(string file_name, int l1, int h1, int l2,


int h2, int l3, int h3):
base(file_name, l1, h1, l2, h2, l3, h3)
{
d = new int[12];
d[0] =3 ; // dimension
d[1] = l1;
d[2] = h1;
d[3] = l2;
d[4] = h2;
d[5] = l3;
d[6] = h3;
d[7] = (h1 - l1 + 1) * (h2 - l2 + 1) * (h3 - l3 + 1); // number of
elements
d[10] = 1; // D3
d[9] = (h3 - l3 + 1) * d[10]; // D2
d[8] = (h2 - l2 + 1) * d[9]; // D1
d[11] = l1 * d[8] + l2 * d[9] + l3 * d[10]; // Sum(ljxDj)
}

public override character elem(int i1, int i2, int i3)


{
nopadd += 3;
nopmul += 3;
return V[i1 * d[8] + i2 * d[9] + i3 * d[10] - d[11]];
}
}

//
// C l a s s "m a t r i x 3 l I l"
//
class matrix3lIl : matrix3l
{
protected int v0; // Iliffes vector base
protected int[] v1; // Iliffes vectors ierarhy level 1
protected int[] v2; // Iliffes vectors ierarhy level 1

public matrix3lIl(int l1, int h1, int l2, int h2, int l3, int h3) :

306
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

base(l1, h1, l2, h2, l3, h3)


{
int n1 = h1 - l1 + 1;
int n2 = h2 - l2 + 1;
int n3 = h3 - l3 + 1;
int i;

v0 = -l1;
v1 = new int[n1];
v2 = new int[n1 * n2];

v1[0] = -l2;
for(i = 1; i < n1; i++)
v1[i] = v1[i - 1] + n2;

v2[0] = -l3;
for(i = 1; i < n1 * n2; i++)
v2[i] = v2[i - 1] + n3;
}

public matrix3lIl(string file_name, int l1, int h1, int l2, int h2,
int l3, int h3) :
base(file_name, l1, h1, l2, h2, l3, h3)
{
int n1 = h1 - l1 + 1;
int n2 = h2 - l2 + 1;
int n3 = h3 - l3 + 1;
int i;

v0 = -l1;
v1 = new int[n1];
v2 = new int[n1 * n2];

v1[0] = -l2;
for (i = 1; i < n1; i++)
v1[i] = v1[i - 1] + n2;

v2[0] = -l3;
for (i = 1; i < n1 * n2; i++)
v2[i] = v2[i - 1] + n3;
}

public override character elem(int i1, int i2, int i3)


{
nopadd += 3;
nopmul += 0;
return V[v2[v1[v0 + i1] + i2] + i3];
}
}

//
// C l a s s "m a t r i x 3 c I l"
//
class matrix3cIl : matrix3c
{
protected int v0; // Iliffes vector base
protected int[] v1; // Iliffes vectors ierarhy level 1
protected int[] v2; // Iliffes vectors ierarhy level 1

public matrix3cIl(int l1, int h1, int l2, int h2, int l3, int h3) :
base(l1, h1, l2, h2, l3, h3)
{
int n3 = h3 - l3 + 1;

307
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

int n2 = h2 - l2 + 1;
int n1 = h1 - l1 + 1;
int i;

v0 = -l3;
v1 = new int[n3];
v2 = new int[n3 * n2];

v1[0] = -l2;
for (i = 1; i < n3; i++)
v1[i] = v1[i - 1] + n2;

v2[0] = -l1;
for (i = 1; i < n3 * n2; i++)
v2[i] = v2[i - 1] + n1;
}

public matrix3cIl(string file_name, int l1, int h1, int l2, int h2,
int l3, int h3) :
base(file_name, l1, h1, l2, h2, l3, h3)
{
int n3 = h3 - l3 + 1;
int n2 = h2 - l2 + 1;
int n1 = h1 - l1 + 1;
int i;

v0 = -l3;
v1 = new int[n3];
v2 = new int[n3 * n2];

v1[0] = -l2;
for (i = 1; i < n3; i++)
v1[i] = v1[i - 1] + n2;

v2[0] = -l1;
for (i = 1; i < n3 * n2; i++)
v2[i] = v2[i - 1] + n1;
}

public override character elem(int i1, int i2, int i3)


{
nopadd += 3;
nopmul += 0;
return V[v2[v1[v0 + i3] + i2] + i1];
}
}

/*
// --- Pe linii ---
struct MainApp
{
static void Main()
{
// matrix3l A = new matrix3l("char.txt", 2, 4, -3, -1, 0, 1);
// matrix3ldv A = new matrix3ldv("char.txt", 2, 4, -3, -1, 0, 1);
matrix3lIl A = new matrix3lIl("char.txt", 2, 4, -3, -1, 0, 1);

A.showvect("Matrix-vector A:\n", "\n");

A.show("Matrix A\n", "\n", 0);

Console.WriteLine("\nN op. +/- {0:D}, N op. x/: {1:D}\n", A.Nopadd,


A.Nopmul);

308
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

char c;
int i1, i2, i3;
ConsoleKeyInfo info;

char ch='n';
while(ch!='y')
{
Console.Write("Enter a char to search: ");

info = Console.ReadKey();
Console.WriteLine();
c = info.KeyChar;
character mychar = new character(c);
int pos = A.search(mychar);
if (pos < 0)
{
Console.Write("The character ");
mychar.show("'", "'");
Console.WriteLine(" no matrix!");
}
else
{
A.search(mychar, out i1, out i2, out i3);
Console.Write("The character ");
mychar.show("'", "'");
Console.WriteLine(" are in the position {0:D} \nwith indices
[{1:D},{2:D},{3:D}]\n",
pos, i1, i2, i3);
}

Console.Write("Done ? (y/n) ");


info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}

(A.elem(2, -3, 0)).attr('a');


(A.elem(2, -2, 1)).attr('d');
(A.elem(3, -3, 0)).attr('g');
(A.elem(3, -2, 1)).attr('j');
(A.elem(4, -3, 0)).attr('m');
(A.elem(4, -2, 1)).attr('p');

A.show("Matrix A after correction:\n","\n", 0);


}
}
*/
/*
// --- Pe coloane ---
struct MainApp
{
static void Main()
{
// matrix3c A = new matrix3c("char.txt", 2, 4, -3, -1, 0, 1);
matrix3cIl A = new matrix3cIl("char.txt", 2, 4, -3, -1, 0, 1);

A.showvect("Matrix-vector A:\n", "\n");

A.show("Matrix A\n", "\n", 0);

Console.WriteLine("\nN op. +/- {0:D}, N op. x/: {1:D}\n", A.Nopadd,


A.Nopmul);

309
Структуры данных (на базе C++): Метод. матер.
С. Перетятку, А. Перетятку

char c;
int i1, i2, i3;
ConsoleKeyInfo info;

char ch = 'n';
while (ch != 'y')
{
Console.Write("Enter a char to search: ");

info = Console.ReadKey();
Console.WriteLine();
c = info.KeyChar;
character mychar = new character(c);
int pos = A.search(mychar);
if (pos < 0)
{
Console.Write("The character ");
mychar.show("'", "'");
Console.WriteLine(" no matrix!");
}
else
{
A.search(mychar, out i1, out i2, out i3);
Console.Write("The character ");
mychar.show("'", "'");
Console.WriteLine(" are in the position {0:D} \nwith indices
[{1:D},{2:D},{3:D}]\n",
pos, i1, i2, i3);
}

Console.Write("Done ? (y/n) ");


info = Console.ReadKey();
Console.WriteLine();
ch = info.KeyChar;
}

(A.elem(2, -3, 0)).attr('a');


(A.elem(2, -2, 1)).attr('d');
(A.elem(3, -3, 0)).attr('g');
(A.elem(3, -2, 1)).attr('j');
(A.elem(4, -3, 0)).attr('m');
(A.elem(4, -2, 1)).attr('p');

A.show("Matrix A after correction:\n", "\n", 0);


}
}
*/

310