Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

методички.C++ / С Fundamentals

.pdf
Скачиваний:
75
Добавлен:
24.02.2016
Размер:
1.38 Mб
Скачать

left = left->right; /* узел с правым указателем на NULL*/ left->right = right; /*″цепляем″ за него правое поддерево*/

}

}

}

 

 

 

 

 

 

 

Обход дерева

 

 

 

 

 

 

Используя

указатели, которые

 

35

 

содержит каждый узел дерева,

 

 

 

программа может «посетить» любой

16

38

 

узел. Существуют простые, но

 

эффективные

 

алгоритмы,

 

 

 

обеспечивающие

«подходы»

к

 

 

 

каждому узлу, а также определяющие

18

36

40

порядок,

которого

нужно

придерживаться при нанесении узлам

 

 

 

«визитов».

 

 

 

 

 

 

 

Дерево

можно

обходить,

17

37

 

начиная с левого либо с правого

 

 

 

 

 

поддерева. В первом случае вначале

Рис. 5.16. Удаление элемента,

обрабатывается

левое

поддерево,

имеющего два поддерева

 

затем сам

элемент, а затем правое

 

 

 

 

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

Рекурсивные функции LeftOrder() и RightOrder() реализуют алгоритмы обхода дерева слева и справа. «Обработать узел» в данном случае означает вывести на печать значение члена data. Это может быть и любое другое действие.

void LeftOrder(Item *node)

 

 

 

{

 

/* если узел имеет левое поддерево*/

if (node->left)

 

LeftOrder(node->left);

 

/* обрабатываем вначале его */

 

printf("%5d",node->data); /* затем обрабатываем сам узел */

 

if (node->right)

 

/* (печатаем значение data) */

 

 

/* если есть правое поддерево */

 

LeftOrder(node->right); /* обрабатываем правое поддерево */

}

 

 

 

void RightOrder(Item *node)

 

 

{

/* если узел имеет правое поддерево */

if (node->right)

RightOrder(node->right); /* обрабатываем вначале его */

 

printf("%5d", node->data); /* затем обрабатываем сам узел */

 

if(node->left)

 

/* (печатаем значение data) */

 

 

/* если есть левое поддерево */

 

RightOrder(node->left);

/* обрабатываем левое поддерево */

}

 

 

 

 

 

© Кафедра информатики БГУИР. Мелещенко А.А.

171

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

Поиск в дереве

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

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

Функция SeekNode() ищет указанное значение в бинарном дереве. В случае успеха, функция возвращает указатель на узел, который содержит заданное значение. Если значение не найдено, возвращается NULL.

Item* SeekNode(int data)

 

{

 

/*

начинаем искать с корня дерева */

Item* p = root;

 

while (p != NULL) {

/*

будем искать, пока не ″опустимся″ */

 

 

/*

по дереву до нулевого указателя */

if (data < p->data)/*

если значение в узле меньше заданного*/

p = p->left;

/*

двигаемся влево */

else if (data > p->data)/* если значение больше заданного */

p = p->right;

/*

двигаемся вправо */

else

 

/*

если значение в узле равно заданному*/

return p;

 

/*

возвращаем указатель на узел */

}

/* если значение не найдено, возвращаем NULL */

return NULL;

}

 

 

 

Листинг 5.8 объединяет рассмотренные выше операции над деревьями в рабочую программу.

Листинг 5.8. tree.c (работа с деревьями)

1:#include <stdio.h>

2:#include <conio.h>

3:#include <alloc.h>

© Кафедра информатики БГУИР. Мелещенко А.А.

172

5:typedef struct item {

6:int data;

7:struct item *left;

8:struct item *right;

9:} Item;

10:

11:void AddNode(int data, Item **node);

12:void DeleteNode(int data, Item **node);

13:Item* SeekNode(Item *node, int data);

14:void LeftOrder(Item *node);

15:void RightOrder(Item *node);

16:void Display(void);

17:

18:#define FALSE 0

19:#define TRUE 1

20:

21:int main(void)

22:{

23:Item *root = NULL; /* Корень дерева */

24:int done = FALSE;

25:int c, value;

26:

27:while (!done) {

28:Display();

29:scanf("%d", &c);

30:switch (c) {

31:case 1:

32:printf("Enter an integer: ");

33:scanf("%d", &value);

34:AddNode(value, &root);

35:break;

36:case 2:

37:printf("Enter a value: ");/* предложить пользователю*/

38:

scanf("%d", &value);

/* ввести значение */

39:DeleteNode(value, &root); /* удалить узел, содержащий*/

40:

/* указанное значение */

41:break;

42:case 3:

43:printf("Enter a value: ");/* предложить пользователю*/

44:

scanf("%d", &value);

/* ввести значение */

45:if (SeekNode(root, value))/* искать это значение */

46:printf("Node is founded\n");

47:else

48:printf("Node isn′t founded\n");

49:break;

50:case 4:

51:

if (root) {

/* если дерево непустое */

52:printf("Tree (left-right): ");

53:

LeftOrder(root); /* вывести его, обходя слева */

54:puts("");

55:} else

56:puts("Tree is empty");

© Кафедра информатики БГУИР. Мелещенко А.А.

173

57:break;

58:case 5:

59:

if (root) {

/* если дерево непустое */

60:printf("Tree (right-left): ");

61:RightOrder(root); /* вывести его, обходя справа */

62:puts("");

63:} else

64:puts("Tree is empty");

65:break;

66:case 0:

67:done = TRUE;

68:break;

69:}

70:}

71:return 0;

72:}

73:

74:void AddNode(int data, Item **node)

75:{

76:if (*node == NULL) {

77:*node = (Item *)calloc(1, sizeof(Item));

78:(*node)->data = data;

79:(*node)->left = (*node)->right = NULL;

80:} else {

81:if (data < (*node)->data)

82:AddNode(data, &(*node)->left);

83:else if (data > (*node)->data)

84:AddNode(data, &(*node)->right);

85:else

86:puts("There is such element in the tree");

87:}

88:}

89:

90:void DeleteNode(int data, Item **node)

91:{

92:if (*node == NULL) {

93:puts("There is not such element");

94:return;

95:}

96:if (data < (*node)->data)

97:DeleteNode(data, &(*node)->left);

98:else if (data > (*node)->data)

99:DeleteNode(data, &(*node)->right);

100:else {

101:Item *left = (*node)->left, *right = (*node)->right;

102:free(*node);

103:if (left == NULL)

104:*node = right;

105:else if (right == NULL)

106:*node = left;

107:else {

108:*node = left;

© Кафедра информатики БГУИР. Мелещенко А.А.

174

109:while (left->right != NULL)

110:left = left->right;

111:left->right = right;

112:}

113:}

114:}

115:

116:Item* SeekNode(Item *node, int data)

117:{

118:Item* p = node;

119:

120:while (p != NULL) {

121:if (data < p->data)

122:p = p->left;

123:else if (data > p->data)

124:p = p->right;

125:else

126:return p;

127:}

128:return NULL;

129:}

130:

131:void LeftOrder(Item *node)

132:{

133:if (node->left)

134:LeftOrder(node->left);

135:printf("%5d", node->data);

136:if (node->right)

137:LeftOrder(node->right);

138:}

139:

140:void RightOrder(Item *node)

141:{

142:if (node->right)

143:RightOrder(node->right);

144:printf("%5d", node->data);

145:if(node->left)

146:RightOrder(node->left);

147:}

148:

149:void Display(void)

150:{

151:puts("");

152:puts("1 - Add a node");

153:puts("2 - Delete a node");

154:puts("3 - Seek value");

155:puts("4 - Output (left-right)");

156:puts("5 - Output (right-left)");

157:puts("0 - Exit");

158:printf("\nSelect menu item: ");

159:}

© Кафедра информатики БГУИР. Мелещенко А.А.

175

Упражнение. Какой общий недостаток имеют листинги 5.4-5.8? (Подумайте, прежде чем читать дальше). Правильно, при выходе из программы не освобождается память, занятая элементами динамических структур. Обычно это не вызывает проблем, т.к. при завершении программы память освобождается автоматически, но все же лучше освобождать память явным образом. Реализуйте функцию Clear(), которая

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

Резюме

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

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

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

–>.

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

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

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

В языке С вы можете объявлять массивы структур, а также массивы, являющиеся элементами структур.

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

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

© Кафедра информатики БГУИР. Мелещенко А.А.

176

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

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

В очередях элементы удаляются с начала, а добавляются в конец. Благодаря этому их называют структурами типа «Первый вошел первый вышел».

Каждый элемент двунаправленного списка содержит указатели на предыдущий и следующий элемент в списке.

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

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

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

© Кафедра информатики БГУИР. Мелещенко А.А.

177

6. Файлы и базы данных

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

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

Что такое файл?

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

Признак

конца

0 1 2 3 4 5 6 7 8 9 . . . .

n-1 файла

 

 

 

 

 

 

 

 

 

. . . .

 

EOF

 

Рис. 6.1. Вид файла из n байтов

Последний байт файла имеет значение EOF (End Of File). Используйте эту константу в ваших программах, чтобы определить конец файла. Например,

do {

/* выполнять */

...

/* операторы */

} while (c != EOF)

/* пока не встретится ″конец файла″ */

Язык С имеет богатую библиотеку функций чтения, записи файлов и выполнения других операций (см. раздел «Обзор функций»). Большинство функций для работы с файлами начинается на букву f. Примите к сведению следующие замечания:

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

© Кафедра информатики БГУИР. Мелещенко А.А.

178

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

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

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

Текстовые файлы

Существует два основных способа обработки текстовых файлов: по символам или по строкам. Выбор обычно диктуют потребности вашей программы.

Чтение в посимвольном режиме

Открытие файла в режиме посимвольного чтения дает вам возможность проверить каждый символ файла. Листинг 6.1 демонстрирует этот метод. Откройте новый проект и скомпилируйте вместе модули gets.c из главы 4 и rchar.c. Перед запуском программы создайте текстовый файл в том же каталоге, где находятся модули. После этого запустите программу на выполнение и введите имя текстового файла.

Листинг 6.1. rchar.c (посимвольное чтение текстового файла)

1:#include <stdio.h>

2:#include <stdlib.h>

3:#include <conio.h>

4:#include <string.h>

5:#include <alloc.h>

6:#include "gets.h"

8:#define SIZE 128 /* макс. размер строки для имени файла */

9:void Error(const char *message);

10:

11:int main(void)

12:{

13:FILE *fp;

14:char c, *filename;

16:printf("File name? ");

17:filename = GetStringAt(SIZE);

18:fp = fopen(filename, "r");

19:if (!fp)

20:Error("Opening file");

21:while ((c = fgetc(fp)) != EOF)

22:putchar(c);

23:fclose(fp);

24:free(filename);

© Кафедра информатики БГУИР. Мелещенко А.А.

179

25:return 0;

26:}

27:

28:void Error(const char *message)

29:{

30:printf("\n\nError: %s\n\n", message);

31:exit(1);

32:}

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

Оператор

filename = GetStringAt(SIZE);

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

fp = fopen(filename, "r");

Первым параметром функции является имя файла, который необходимо открыть, второй параметр, “r”, определяет режим доступа к файлу. Этот параметр может быть одной из строк, перечисленных в табл. 6.1.

 

 

Таблица 6.1.

 

 

 

Режимы доступа к файлам для функции fopen()

 

 

 

 

 

 

 

 

Строка

Описание

 

 

 

″r″

Открывает файл только для чтения. Модификации файла не

 

 

 

разрешены

 

 

 

″w″

Создает новый файл только для записи. Перезаписывает любой

 

 

 

существующий файл с тем же именем. Чтение информации из

 

 

 

файла не разрешено

 

 

 

″а″

Открывает файл в режиме только для записи с добавлением новой

 

 

 

информации в конец файла. Если файла не существует, он

 

 

 

создается, и любой существующий файл с тем же именем

 

 

 

перезаписывается. Чтение информации из файла не разрешено

 

 

 

″r+″

Открывает существующий файл для чтения и записи

 

 

 

″w+″

Создает новый файл для чтения и записи. Перезаписывает любой

 

 

 

существующий файл с тем же именем

 

 

 

″а+″

Открывает файл в режиме чтения и записи для добавления новой

 

 

 

информации в конец файла. Если файла не существует, он

 

 

 

создается, и любой существующий файл с тем же именем

 

 

 

перезаписывается

 

 

 

 

 

 

 

 

© Кафедра информатики БГУИР. Мелещенко А.А.

180

 

Соседние файлы в папке методички.C++