You are on page 1of 25

Міністерство освіти та науки України

Національний технічний університет України


«Київський політехнічний інститут імені Ігоря Сікорського»

Кафедра автоматизації теплоенергетичних процесів

Лабораторна робота № 3
«БІНАРНІ ДЕРЕВА»
Курс: АЛГОРИТМИ ТА СТРУКТУРИ ДАНИХ
Варіант

Виконав:
Студент групи ТА–,ТЕФ
Перевірив:

Київ 2022
Мета роботи: Вивчити принципи побудови абстрактних типів даних типу
бінарне дерево та алгоритми їх обробки мовою процедурного програмування
Сі.
1.Теоретичні відомості

Двійкове дерево пошуку - це двійкове дерево, для якого виконуються


наступні додаткові умови (властивості дерева пошуку):

 Обидва піддереві - ліве і праве, є двійковими деревами пошуку.


 У всіх вузлів лівого піддерева довільного вузла X значення ключів
даних менше, ніж значення ключа даних самого вузла X.
 В той час, як у всіх вузлів правого піддерева того ж вузла X значення
ключів даних не менше, ніж значення ключа даних вузла X.

Очевидно, дані в кожному вузлі повинні володіти ключами, на яких


визначена операція порівняння менше.

Як правило, інформація, що представляє кожен вузол, є записом, а не


єдиним полем даних. Однак, це стосується реалізації, а не природи
двійкового дерева пошуку.

Для цілей реалізації двійкове дерево пошуку можна визначити так:

 Двійкове дерево складається з вузлів (вершин) - записів виду (data, left,


right), де data - деякі дані прив'язані до вузла, left і right - посилання на
вузли, які є дітьми даного вузла - лівий і правий сини відповідно. Для
оптимізації алгоритмів конкретні реалізації припускають також
визначення поля parent в кожному вузлі (крім кореневого) - посилання
на батьківський елемент.
 Дані (data) володіють ключем (key), на якому визначена операція
порівняння "менше". У конкретних реалізаціях це може бути пара (key,
value) - (ключ і значення), або посилання на таку пару, або просте
визначення операції порівняння на необхідній структурі даних або
посилання на неї.

Двійкове дерево пошуку не слід плутати з двійковій купою, побудованої


за іншими правилами.

Основною перевагою двійкового дерева пошуку перед


іншими структурами даних є можлива висока ефективність реалізації
заснованих на ньому алгоритмів пошуку і сортування.

Двійкове дерево пошуку застосовується для побудови більш абстрактних


структур, таких як безлічі, мультимножини, асоціативні масиви.
2.Завдання

1. Для організації двійкового дерева пошуку визначити відповідний


структурний тип, який містить поля, використані в лабораторній роботі № 2.
Для отримання ключового поля звернутися до викладача.
2. Розробити такі функції:
створення дерева;
 вставки вузла в дерево;
 видалення вузла з дерева;
проходження дерева (показувати на екрані послідовність вузлів):
a. в прямому порядку;
b. в зворотному порядку;
c. симетричним обходом.
 пошук вузла в дереві;
 видалення дерева;
 зберігання дерева в двійковому файлі на диску;
 читання дерева з двійкового файлу на диску.
3. Виконати пошук інформації в дереві. Перевірити варіанти успішного і не
успішного пошуку.
4. Зробити висновки по роботі.

3. Схема алгоритму
3.1 Схема алгоритму до функції пошуку

Рис 3.1 Алгоритм до функції ElemSearch


3.2 Схема алгоритму до функції додавання вершини

Рис 3.2 Алгоритм до функції ElemAdd


3.3 Схема алгоритму до функції видалення вершини

Рис 3.3 Алгоритм до функції ElemRemove


4. Текст програми
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <time.h>
#include <locale.h>
#include <string.h>
#include <process.h>
#include <Windows.h>
#define BRANCH 5

typedef struct TreeNode {//Структура вузла дерева


struct TreeNode* left;//Покажчик на ліву вершину
char* key;//поле рядок
struct TreeNode* right;//Покажчик на праву вершину
};

typedef struct DescTree {//Дескриптор дерева


int size;//к-сть вершин
TreeNode* root;//корінь дерева
};

void printfile(FILE* fp, TreeNode* tree);//друк дерева у файл


void preOrder(TreeNode* node);//прямий обхід
void inOrder(TreeNode* node);//симетричний обхід
void postOrder(TreeNode* node);//зворотній обхід
void clear_Tree(TreeNode* node);//очистити дерево
int height_tree(TreeNode* node);//функція отримання висоти дерева
int GetSize(DescTree* tree);//перевірка на порожнечу
int countStrs(FILE* fd);//к-сть строк у файлі
int scann(int& N);//зчитування числа
void tree_print(TreeNode* tree, int depth, char* path, int direction);//візуалізація дерева
void prepare_print(TreeNode* tree);//вивід дерева
TreeNode* ElemAdd(DescTree* dsTree, TreeNode* subTree, char* tempdata);//додавання ел-тів до дерева
TreeNode* ElemSearch(TreeNode* root, char* tempdata);//пошук вершини
TreeNode* ElemRemove(TreeNode* root, char* tempdata);//видалення вершини
DescTree* TreeReadFromFile(FILE* fp, DescTree* tree);//читання з файлу
DescTree* InitTree(void);//створення дерева

int main()

FILE* fp = NULL; //дескриптор файлу


DescTree* tree = NULL; //вершина
TreeNode* temproot = NULL;//тимчасова вершина
TreeNode* temp = (TreeNode*)malloc(sizeof(TreeNode));//виділення пам'яті під вершину

if (!temp) {
puts("Помилка виділення пам'яті");
return 0;
}

char filename[256];
char tempdata[256];
int howmany;
int menu;
int initm;
int findkey;
int removekey;
int swork;

SetConsoleCP(1251);
SetConsoleOutputCP(1251);

do {

printf("Виберіть пункт меню");


printf("0)Створити дерево\n1)Додати елементи до дерева\n2)Вивести дерево\n3)Знайти елемент\n4)Видалення
дерева\n");
printf("5)Видалити вузол\n6)Записати дерево у файл\n7)Зчитати дерево з файлу\n8)Висота дерева\
n9)Перевірка на порожнечу\n10)Завершити роботу\n11)Дерево як дерево\n");
printf("12)cls");
scann(menu);

if (menu > 12 || menu < 0)


{
puts("Невірний пункт меню,повторіть введення");
break;
}

switch (menu)
{
case 0:

tree = InitTree();
if (InitTree == NULL)
{
puts("Something went wrong");
exit(0);
}
puts("Створено!");

break;

case 1: {

if (!tree)
{
puts("Дерева не існує");
break;

puts("Як хочете ввести данні \n1)Вручну\n2)Kлюч та строка з файлу");


scann(swork);
switch (swork) {

case 1:
puts("Скільки елементів хочете додати ?");
scann(howmany);

for (int i = 0; i < howmany; i++) {

puts("Введіть рядок");
int flag = 0;
do {
fgets(tempdata, 1024, stdin);
if (tempdata[0] == '\n' && flag == 1) {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
flag = 1;
} while (tempdata[0] == '\n');

temproot = ElemSearch(tree->root, tempdata);//якщо було знайдено такий рядок


if (temproot != NULL)
{

puts("Такий рядок вже існує його буде перезаписано");

if (tree->root == NULL)
{
tree->root = ElemAdd(tree, tree->root, tempdata);//додавання елементів
}
else
{
ElemAdd(tree, tree->root, tempdata);
}
if (ElemAdd == NULL)
{
puts("Something went wrong");
exit(0);
}

}
puts("Додано!");
break;
case 2:

puts("Введіть назву файлу:");


do {
fgets(filename, 1024, stdin);
if (filename[0] == '\n') {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
} while (filename[0] == '\n');
filename[strlen(filename) - 1] = '\0';
if (filename[strlen(filename) - 4] != '.' && filename[strlen(filename) - 3] != 't' && filename[strlen(filename) - 2]
!= 'x' &&
filename[strlen(filename) - 1] != 't') {
strcat(filename, ".txt");
}
if ((fp = fopen(filename, "r")) == NULL)
{
printf("Виникла помилка при відкритті файлу %s! \n", filename);
return 1;
}

int c_l = countStrs(fp);

puts("Однакові ключі буде перезаписано");


for (int j = 0; j <= c_l; j++)
{

fscanf(fp, "%s", tempdata);


if (tree->root == NULL)
{
tree->root = ElemAdd(tree, tree->root, tempdata);
}
else
{
ElemAdd(tree, tree->root, tempdata);
}
if (ElemAdd == NULL)
{
puts("Something went wrong");
exit(0);
}

}
puts("Додано!");
break;
}

break;
}
case 2:
{
if (!tree)
{
puts("Дерева не існує");
break;

puts("1.Симетричний\n2.Прямий\n3.Зворотній");
scann(initm);
switch (initm) {
case 1:
puts("Симетричний:\n");
inOrder(tree->root);
break;
case 2:
puts("Прямий:\n");
preOrder(tree->root);
break;
case 3:
puts("Зворотній:\n");
postOrder(tree->root);
break;
}
puts("Виведено!");
break;
}
case 3:
{
if (!tree)
{
puts("Дерева не існує");
break;

puts("Введіть рядок");
int flag = 0;
do {
fgets(tempdata, 1024, stdin);
if (tempdata[0] == '\n' && flag == 1) {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
flag = 1;
} while (tempdata[0] == '\n');

temproot = ElemSearch(tree->root, tempdata);


if (temproot == NULL)
{

puts("Такого ключа не було знайдено!");


break;
}
if (ElemSearch == NULL)
{
puts("Something went wrong");
exit(0);
}
printf("%s\n", temproot->key);
puts("Знайдено!");
break;
}
case 4:
{
if (!tree)
{
puts("Дерева не існує");
break;

}
puts("1)Без дескриптора\n2)З дескриптором");
int cle_tree;
scanf("%d", &cle_tree);
if (cle_tree == 1)
{
clear_Tree(tree->root);
}
else if (cle_tree == 2)
{
clear_Tree(tree->root);
free(tree);
tree = NULL;

}
if (clear_Tree == NULL)
{
puts("Something went wrong");
exit(0);
}
puts("Очищено!");
break;
}
case 5:
{
if (!tree)
{
puts("Дерева не існує");
break;

puts("Введіть рядок");
int flag1 = 0;
do {
fgets(tempdata, 1024, stdin);
if (tempdata[0] == '\n' && flag1 == 1) {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
flag1 = 1;
} while (tempdata[0] == '\n');
temproot = ElemSearch(tree->root, tempdata);
if (temproot != NULL)
{
tree->root = ElemRemove(tree->root, tempdata);
tree->size--;
}
else
{

puts("Такого рядка не знайдено");

puts("Done!");
break;
}
case 6:
if (!tree)
{
puts("Дерева не існує");
break;

}
if (tree->root == NULL)
{
printf("Створено лише дескриптор!\n");
break;
}
puts("Введіть назву файлу:");
do {
fgets(filename, 1024, stdin);
if (filename[0] == '\n') {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
} while (filename[0] == '\n');
filename[strlen(filename) - 1] = '\0';
if (filename[strlen(filename) - 4] != '.' && filename[strlen(filename) - 3] !=
'b' && filename[strlen(filename) - 2] != 'i' &&
filename[strlen(filename) - 1] != 'n') {
strcat(filename, ".bin");
}
if ((fp = fopen(filename, "wb+")) == NULL)
{
printf("Виникла помилка при відкритті файлу %s! \n", filename);
return 1;
}
printfile(fp, tree->root);
if (fclose(fp) == EOF)
{
printf("Виникла помилка при закритті файлу %s! \n", filename);
return 1;
}
puts("Записано!");

break;
case 7:
if (!tree)
{
tree = InitTree();
}
clear_Tree(tree->root);
puts("Введіть назву файлу:");
do {
fgets(filename, 1024, stdin);
if (filename[0] == '\n') {
printf("Текст не повинен бути пустим, спробуйте ще раз:");
}
} while (filename[0] == '\n');
filename[strlen(filename) - 1] = '\0';
if (filename[strlen(filename) - 4] != '.' && filename[strlen(filename) - 3] !=
'b' && filename[strlen(filename) - 2] != 'i' && filename[strlen(filename) - 1] != 'n') {
strcat(filename, ".bin");
}
if ((fp = fopen(filename, "rb")) == NULL)
{
printf("Виникла помилка при відкритті файлу %s! \n", filename);
return 1;
}
tree = TreeReadFromFile(fp, tree);
if (fclose(fp) == EOF)
{
printf("Виникла помилка при закритті файлу %s! \n", filename);
return 1;
}
puts("Зчитано!");
break;
case 8:
if (!tree)
{
puts("Дерева не існує");
break;

}
int height;
height = height_tree(tree->root);
printf("Висота дерева : %d\nК-сть елементів: %d\n", height, tree->size);
break;

case 9:

if (!tree) {
puts("Дерева не існує");
break;

if (GetSize(tree) == 0)
{
puts("Дерево порожнє,к-сть вузлів 0");

}
else
{

puts("Дерево не порожнє");
}
break;

case 10:
return 0;
break;

case 11:
if (!tree) {
puts("Дерева не існує");
break;

}
system("cls");
prepare_print(tree->root);
break;
case 12:
system("cls");
break;
}

} while (menu != 13);

_getch();
return 0;

DescTree* InitTree(void) {
DescTree* dsBST;
dsBST = (DescTree*)malloc(sizeof(DescTree));
if (!dsBST) {
return NULL;
}
dsBST->root = NULL;
dsBST->size = 0;
return dsBST;
}

TreeNode* ElemAdd(DescTree* dsTree, TreeNode* subTree, char* tempdata) {

char* data = NULL;

data = (char*)malloc(sizeof(char) * strlen(tempdata));


if (!data)
{
return NULL;
}

strcpy(data, tempdata);

if (!subTree) {
subTree = (TreeNode*)malloc(sizeof(TreeNode));
if (!subTree)
{
return NULL;
}
int tmp;
if (!(subTree->key = (char*)malloc(sizeof(char) * (strlen(data) + 1))))
{
return NULL;
}

subTree->key = data;

subTree->left = subTree->right = NULL;


dsTree->size++;
return (subTree);
}
else if (strcmp(subTree->key,tempdata)==0)
{
subTree->key = data;
return subTree;
}
else if (strcmp(subTree->key, tempdata) == 1)
{
subTree->left = ElemAdd(dsTree, subTree->left,data);//пірнаємо у ліве піддерево

}
else if (strcmp(subTree->key, tempdata) == -1)
{
subTree->right = ElemAdd(dsTree, subTree->right, data);//пірнаємо у праве піддерево

}
return subTree;
}

TreeNode* ElemSearch(TreeNode* root, char* data) {


if (!root)
{
return NULL;
}
if (strcmp(root->key, data) == 0)
{
return (root);
}
else if (strcmp(root->key, data) == 1)
{
return (ElemSearch(root->left, data));////пірнаємо у ліве піддерево
}
else
{
return (ElemSearch(root->right, data));//пірнаємо у праве піддерево
}
}

TreeNode* min_root(TreeNode* root)


{
return root->left ? min_root(root->left) : root;
}

TreeNode* remove_min_root(TreeNode* node)


{
TreeNode* current = node;
while (current->left != NULL)
{
current = current->left;
}
return current;
}

TreeNode* ElemRemove(TreeNode* root, char* data)


{
if (root == NULL)
{
return root;
}
if (strcmp(root->key, data) == 1)
{
root->left = ElemRemove(root->left, data);
}
else if (strcmp(root->key, data) == -1)
{
root->right = ElemRemove(root->right, data);
}
else
{
if (root->left == NULL)
{
TreeNode* temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL)
{
TreeNode* temp = root->left;
free(root);
return temp;
}
TreeNode* temp = remove_min_root(root->right);

root->key = temp->key;

root->right = ElemRemove(root->right, temp->key);

}
return root;
}

void inOrder(TreeNode* tree) {


if (tree) {
inOrder(tree->left);
printf("%s\n ",tree->key);
inOrder(tree->right);
}
return;
}

void preOrder(TreeNode* tree) {


if (tree) {
printf("%s\n ", tree->key);
preOrder(tree->left);
preOrder(tree->right);
}
return;
}

void postOrder(TreeNode* tree) {


if (tree) {
postOrder(tree->left);
postOrder(tree->right);
printf("%s\n ", tree->key);
}
return;
}

void clear_Tree(TreeNode* node)


{
if (node)
{
clear_Tree(node->left);
clear_Tree(node->right);
free(node);
}
}

void printfile(FILE* fp, TreeNode* tree) {


if (tree) {

fprintf(fp, "%s\n",tree->key);
printfile(fp, tree->left);
printfile(fp, tree->right);
}
return;
}

int height_tree(TreeNode* node)


{
if (node == 0)
{
return 0;
}
int left, right;
if (node->left != NULL) {
left = height_tree(node->left);
}
else
{
left = -1;
}
if (node->right != NULL) {
right = height_tree(node->right);
}
else
{
right = -1;
}
int max = left > right ? left : right;
return max + 1;
}

DescTree* TreeReadFromFile(FILE* fp, DescTree* tree)


{
int key;
char tdata[256];

int file_size = countStrs(fp);

rewind(fp);
for (int i = 0; i <= file_size; i++)
{
if (fscanf(fp, "%s\n", tdata) != 1) {
return tree;
}

if (tree->root == NULL)
{
tree->root = ElemAdd(tree, tree->root,tdata);
}
else
{
ElemAdd(tree, tree->root, tdata);
}
}
return tree;
}

int scann(int& N)
{
int isEntered = 0;
do {
do {
isEntered = scanf_s("%d", &N);// перевірка на те що введене значення є числом
while (getchar() != '\n');
while (isEntered < 1) {
printf("Ви ввели некоректне значення,повторіть введення: ");
isEntered = scanf_s("%d", &N);
while (getchar() != '\n');
}
} while (isEntered < 1);
} while (isEntered != 1);
return N;
}

int GetSize(DescTree* tree)


{
return tree->size;
}

int countStrs(FILE* fd)

{
int quant = 0;
while (!feof(fd))
{
fscanf(fd, "%*[^\n]\n");//ігноруємо перехід на іншу строку
quant++;
}
rewind(fd);//перематуємо файл
return quant;
}

void prepare_print(TreeNode* tree)


{

char* path = (char*)calloc(256, sizeof(char));

tree_print(tree, 0, path, 0);


}

void tree_print(TreeNode* tree, int depth, char* path, int direction)


{

if (tree == NULL)
return;

depth++;

tree_print(tree->right, depth, path, 1);


path[depth - 2] = 0;

if (direction)
path[depth - 2] = 1;

if (tree->left)
path[depth - 1] = 1;

printf("\n");

for (int i = 0; i < depth - 1; i++)


{
if (i == depth - 2)
printf("+");
else if (path[i])
printf("|");
else
printf(" ");

for (int j = 1; j < BRANCH; j++)


if (i < depth - 2)
printf(" ");
else
printf("-");
}

printf("%s\n", tree->key);

for (int i = 0; i < depth; i++)


{
if (path[i])
printf("|");
else
printf(" ");

for (int j = 1; j < BRANCH; j++)


printf(" ");
}
tree_print(tree->left, depth, path, 0);
}

5.Тестування програми
Рис 5.1 Робота програми при невірному введенні пункта меню
Рис 5.2 Створення дерева та його вивід
Рис 5.3 Додавання елементів до дерева з файлу та вивід за допомогою
обходу
Рис 5.4 Видалення вершини дерева
Рис 5.5 Пошук вершини ( що є в дереві та відсутня в ньому)

Рис 5.6 Отримання к-сті вершин та висоти дерева


Рис 5.7 Запис дерева у файл

Висновок
На цій лабораторній роботі було вивчено принципи побудови
абстрактних типів даних типу бінарне дерево та алгоритмів їх обробки
мовою процедурного програмування Сі.
В результаті була написана програма результатом роботи якої є
бінарне дерево пошуку, елементом якого є рядок.
При виконанні лабораторної роботи було виявлено проблему виділення
пам'яті під вузол. З цією проблемою стикався у 2 лабораторній роботі.
Проблему було вирішено створивши в функції main тимчасовий масив в
який ми записуємо рядок, потім, в функції додавання рядка створюємо
динамічний масив, що має розмір тимчасового масиву,і потім вже працюємо
з нашим динамічним масивом. Програму було багаторазово протестованого і
я переконався, що проблема більш не виникає.
Дану програму можна використовувати для роботи з бінарним деревом
пошуку. Доповнивши програму можна буде повноцінно використовувати всі
можливості бінарного дерева пошуку, а саме додавши ще одну функцію
можна буде зливати 2 дерева в одне.
Порівнюючи дерева з іншими АТД можна сказати що використання
бінарного дерева пошуку є дуже зручним при роботі з усіма типами данних.
Також робота з ключем дає нам можливість шукати та виводити
значення за ключем, що зменшує час пошуку.
Роблячи висновок дерево пошуку дає змогу дуже зручно зберігати,
шукати та сортувати записувані в нього значення.

You might also like