Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
shpory_si.docx
Скачиваний:
33
Добавлен:
25.09.2019
Размер:
205.61 Кб
Скачать
  1. Указатели на структуры. Средство typedef.

Формат указателя на структуру:

struct имя_стуктурного_типа *имя_указательной_пременной;

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

(*имя_указательной_пременной).элемент или имя_указательной_пременной-> элемент

Пример:

struct key *pts; // объявляем указатель на структуру key

Обращение к элементe count структуры key может осуществляться следующим образом:

(*pts).count или же pts->count.

Средство typedef

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

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

Примеры:

typedef double (* MATH)( );

/* MATH - новое имя типа, представляющее указатель на

функцию, возвращающую значения типа double */

MATH cos;

/* cos указатель на функцию, возвращающую

значения типа double */

//Можно провести эквивалентное объявление

double (* cos)( );

typedef char FIO[40]

// FIO - массив из сорока символов

FIO person;

//Переменная person - массив из сорока символов

//Это эквивалентно объявлению

char person[40];

При объявлении переменных и типов здесь были использованы имена типов (MATH FIO). Помимо этого, имена типов могут еще использоваться в трех случаях: в списке формальных параметров, в объявлении функций, в операциях приведения типов и в операции sizeof (операция приведения типа). Именами типов для основных типов, типов перечисления, структуры и смеси являются спецификаторы типов для этих типов. Имена типов для типов указателя массива и функции задаются при помощи абстрактных описателей следующим образом:

спецификатор-типа абстрактный-описатель;

Абстрактный-описатель – это описатель без идентификатора, состоящий из одного или более модификаторов указателя, массива или функции. Модификатор указателя (*) всегда задается перед идентификатором в описателе, а модификаторы массива [] и функции () - после него. Таким образом, чтобы правильно интерпретировать абстрактный описатель, нужно начать интерпретацию с подразумеваемого идентификатора. Абстрактные описатели могут быть сложными. Скобки в сложных абстрактных описателе задают порядок интерпретации подобно тому, как это делалось при интерпретации сложных описателей в объявлениях.

  1. Понятия объединения и перечисления. Битовые поля.

Понятия объединения и перечисления

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

Объединенный тип данных декларируется подобно структурному типу:

union ID_объединения {

описание полей

};

Пример описания объединенного типа:

union word {

int nom;

char str[20];

};

Пример объявления объектов объединенного типа:

union word *p_w, mas_w[100];

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

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

Пример использования переменных типа union:

. . .

typedef union q {

int a;

double b;

char s[5];

} W;

void main(void)

{

W s, *p = &s;

s.a = 4;

printf(“\n Integer a = %d, Sizeof(s.a) = %d”, s.a, sizeof(s.a));

p –> b = 1.5;

printf(“\n Double b = %lf, Sizeof(s.b) = %d”, s.b, sizeof(s.b));

strcpy(p–>s, “Minsk”);

printf(“\n String a = %s, Sizeof(s.s) = %d”, s.s, sizeof(s.s));

printf(“\n Sizeof(s) = %d”, sizeof(s));

}

Результат работы программы:

Integer a = 4, Sizeof(s.a) = 2

Double b = 1.500000, Sizeof(s.b) = 4

String a = Minsk, Sizeof(s.s) = 5

Sizeof(s) = 5

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

Определение перечисляемого типа данных имеет вид

enum ID_перечисляемого_типа {

список_значений

};

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

enum marks {

zero, one, two, three, four, five

};

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

enum level {

low=100, medium=500, high=1000, limit

};

Константа limit по умолчанию получит значение, равное 1001.

Примеры объявления переменных перечисляемого типа:

enum marks Est;

enum level state;

Переменная типа marks может принимать только значения из множества {zero, one, two, three, four, five}.

Основные операции с данными перечисляемого типа:

– присваивание переменных и констант одного типа;

– сравнение для выявления равенства либо неравенства.

Практическое назначение перечисления – определение множества различающихся символических констант целого типа.

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

. . .

typedef enum {

mo=1, tu, we, th, fr, sa, su

} days;

void main(void)

{

days w_day; // Переменная перечисляемого типа

int t_day, end, start;

// Текущий день недели, начало и конец недели соответственно

puts(“ Введите день недели (от 1 до 7) : ”);

scanf(“%d”, &t_day);

w_day = su;

start = mo;

end = w_day – t_day;

printf(“\n Понедельник – %d день недели, \

сейчас %d – й день и \n\

до конца недели %d дн. ”, start, t_day, end );

}

Результат работы программы:

Введите день недели (от 1 до 7) : 5

Понедельник – 1 день недели, сейчас 5-й день и

до конца недели 2 дн.

  1. Методы сортировки данных. Пузырьковая сортировка. Сортировка посредством выбора. Сортировка вставками. Улучшенный алгоритм сортировки: сортировка Шелла, быстрая сортировка. Сортировка строк и структур.

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

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

Задача сортировки заключается в следующем: задан список целых чисел (простейший случай) В=< K1, K2,..., Kn >. Требуется переставить элементы списка В так, чтобы получить упорядоченный список B’=< K’1, K’2,...,K’n >, в котором для любого 1<=i<=n элемент K’ (i) <= K’(i+1).

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

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

Пример:

B=<20,-5,10,8,7>, исходный список;

B1=<-5,10,8,7,20>, первый просмотр;

B2=<-5,8,7,10,20>, второй просмотр;

B3=<-5,7,8,10,20>, третий просмотр.

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

Нижеприведенная функция bubble сортирует входной массив методом пузырьковой сортировки.

/* сортировка пузырьковым методом */

float * bubble(float * a, int m, int n)

{

char is=1;

int i;

float c;

while(is)

{

is=0;

for (i=m+1; i<=n; i++)

if ( a[i] < a[i-1] )

{

c=a[i];

a[i]=a[i-1];

a[i-1]=c;

is=1;

}

}

return(a);

}

Пузырьковая сортировка выполняется при количестве действий Q=(n-m)*(n-m) и не требует дополнительной памяти.

Сортировка посредством выбора

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

Например:

B=<20,10,8,-5,7>, B’=< >

B=<20,10,8,7>, B’=<-5>

B=<20,10,8>, B’=<-5,7>

B=<20,10>, B’=<-5,7,8>

B=<20>, B’=<-5,7,8,10>

B=< >, B’=<-5,7,8,10,20>

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

/* сортировка методом выбора */

double *select( double *s, int m, int n)

{

int i,j;

int imin,c;

for(i=m;i< n;i++){ //для каждого элемента

//ищем минимальный элемент в оставшейся части массива

imin=s[i];

for (j=i; j< n;j++) if(s[j]< s[imin]) imin=j;

//меняем текущий элемент с минимальным

c=s[i];

s[i]=s[imin];

s[imin]=c;

}

return(s);

}

Здесь, как и в предыдущем примере оба списка В и В’ размещаются в разных частях массива s. При сортировке посредством выбора требуется Q=(n-m)*(n-m) действий и не требуется дополнительной памяти.

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

Упорядоченный массив B’ получается из В следующим образом: сначала он состоит из единственного элемента К1; далее для i=2,...,N выполняется вставка узла Кi в B’ так, что B’ остается упорядоченным списком длины i.

Например, для начального списка B=< 20,-5,10,8,7 > имеем:

B=< 20,-5,10,8,7> B’=< >

B=< -5,10,8,7 > B’=< 20 >

B=< 10,8,7 > B’=< -5,20 >

B=< 8,7 > B’=< -5,10,20 >

B=< 7 > B’=< -5,8,10,20 >

B=< > B’=< -5,7,8,10,20 >

Функция insert реализует сортировку вставкой.

/* сортировка методом вставки */

int *insert(int *s, int m, int n){

int i,j,k;

int aux;

for (i=m+1; i< n; i++){ //для каждого элемента массива

aux=s[i]; //берем текущий элемент

//ищем место вставки и одновременно освобождаем его сдвигая элементы массива назад

for (k=i-1; k>=0 && s[k]>aux; k--) s[k+1]=s[k];

//вставляем элемент на найденное место

s[k+1]=aux;

}

return(a);

}

Здесь оба списка В и В’ размещаются в массиве s, причем список В занимает часть s c индексами от i до n, а B’ - часть s c индексами от m до i-1.

При сортировке вставкой требуется Q=(n-m)*(n-m) сравнений и не требуется дополнительной памяти.

Улучшенный алгоритм сортировки: быстрая сортировка, сортировка Шелла

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

Пример:

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

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

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

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

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

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

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

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

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

/* быстрая сортировка */

double * quick(double *s,int low,int hi)

{

double cnt,aux;

int i,j;

if (hi>low)

{

i=low;

j=hi;

cnt=s[i];

while(i < j)

{

if (s[i+1]<=cnt)

{

s[i]=s[i+1];

s[i+1]=cnt;

i++;

}

else

{

if (s[j]<=cnt)

{

aux=s[j];

s[j]=s[i+1];

s[i+1]=aux;

}

j--;

}

}

quick(s,low,i-1);

quick(s,i+1,hi);

}

return(s);

}

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

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

Существенными в сортировках вставками являются затраты на обмены или сдвиги элементов. Для их уменьшения желательно сначала производить погружение с большим шагом, сразу определяя элемент помест. Этим отличается СОРТИРОВКА ШЕЛЛА: исходный массив разбивается на n частей, в каждую из которых попадают элементы с шагом m, начиная от 0,1,...,m-1 соответственно, то есть

0, m, 2m, 3m,...

1, m+1, 2m+1, 3m+1,...

2, m+2, 2m+2, 3m+2,...

Каждая часть сортируется отдельно с использованием алгоритма вставок. Затем выбирается меньший шаг, и алгоритм повторяется. Шаг может быть выбран равным степеням 2, например 64, 32, 16, 8, 4,2, 1. Последняя сортировка выполняется с шагом 1.

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

#include

#include

#define MAXLINES 5000 // максимальное число строк

char *lineptr[MAXLINES]; // указатели на строки

int readlines(char *lineptr[], int nlines);

void writelines(char *lineptr[], int nlines);

void qsort(char *lineptr[], int left, int right);

/* сортировка строк */

main()

{

int nlines; /* количество прочитанных строк */

if ((nlines = readlines(lineptr, MAXLINES)) >= 0)

{

qsort(lineptr, 0, nlines-1);

writelines(lineptr, nlines);

return 0;

}

else {

printf("ошибка: слишком много строк\n");

return 1;

}

}

  1. Методы поиска: последовательный и двоичный поиск.

Последовательный поиск

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

Очевидно, что Max последовательного поиска равен N. Если частота использования каждого элемента списка одинакова, т.е. P=1/N, то Avg последовательного поиска равно N/2. При различной частоте использования элементов Avg можно улучшить, если поместить часто встречаемые элементы в начало списка.

Пусть во входном потоке задано 100 целых чисел К1,К2,... К100 и ключ V. Составим программу для последовательного хранения элементов Кi и поиска среди них элемента, равного V, причем такого элемента может и не быть в списке. Без использования стоппера программа может быть реализована следующим образом:

/* последовательный поиск без стоппера */

#include

main()

{

int k[100],v,i;

for (i=0;i<100;i++)

scanf("%d",&k[i]);

scanf("%d",&v);

i=0;

while(k[i]!=v && i<100) i++;

if (k[i]==v) printf("%d %d",v,i);

else printf("%d не найден",v);

}

С использованием стоппера программу можно записать в виде:

/* последовательный поиск со стоппером */

#include

main()

{

int k[101],v,i;

for (i=0;i<100;i++)

scanf("%d",&k[i]); //ввод данных

scanf("%d",&v);

k[100]=v; //стоппер

i=0;

while(k[i]!=v) i++;

if (i<100) printf ("%d %d",v,i);

else printf ("%d не найден",v);

}

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

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

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

Пусть, например, во входном потоке задано 101 число, К1,К2,...,К100, V – элементы списка и ключ. Известно, что список упорядочен по возрастанию, и элемент V в списке имеется. Составим программу для ввода данных и осуществления двоичного поиска ключа V в списке К1,К2,...,К100.

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

#include

main()

{

int k[100],v,i,j,m;

for (i=0;i<100;i++)

scanf("%d",&k[i]);

scanf("%d",&v);

i=0; j=100; m=50;

while (k[m]!=v)

{

if (k[m] < v) i+=m;

else j=m-i;

m=(i+j)/2;

}

printf("%d %d",v,m);

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]