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

ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ МОЛДОВЫ

ИСПОЛЬЗОВАНИЕ СТРУКТУР ДАННЫХ В


АЛГОРИТМАХ НА ГРАФАХ И ДЕРЕВЬЯХ

Методические указания и задания для лабораторных работ

Кишинэу
2015

0
ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ МОЛДОВЫ
Факультет информатики, вычислительной техники и
микроэлектроники

Кафедра прикладной информатики

ИСПОЛЬЗОВАНИЕ СТРУКТУР ДАННЫХ В АЛГОРИТМАХ НА


ГРАФАХ И ДЕРЕВЬЯХ

Методические указания и задания для лабораторных работ


Кишинэу
Т.У.М.
2015

1
Данная работа содержит методические указания и задания
к лабораторным работам, которые предназначены для изучения
использования структур данных, таких как двумерная матрица,
одномерный массив указателей, связный список, очередь, стек
и дерево для задания графов и деревьев и для реализации
алгоритмов поиска в глубину и в ширину на графах и деревьях
на языке С/С++. Для каждой лабораторной работы указаны:
тема, цель работы, теоретические сведения, контрольные
вопросы, условие задачи и приведены 15 вариантов заданий и
примеры базовых программ на языке С/С++.
Работа предназначена для студентов всех специальностей
факультета информатики, вычислительной техники и
микроэлектроники дневной и заочной формы обучения,
изучающих дисциплины «Программирование на языке С/С++»,
«Структуры данных и алгоритмы» и «Дискретная математика».

Авторы: Николай Фалько, старший преподаватель, доктор


Михаил Кулев, конференциар, доктор
Галина Марусик, старший преподаватель, доктор

Ответственный редактор – Михаил Кулев, конференциар,


доктор

Рецензент – Лучия Стадлер, конференциар, доктор

 Т.У.М., 2015
2
Лабораторная работа № 1

Тема: Использование структур данных - двумерных матриц


и связных списков - для различных способов задания
графов

Цель работы: 1. Освоение и изучение использования структур


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

Основные понятия теории графов


Графы могут быть заданы в памяти компьютера с помощью
следующих структур:
1) матрицы инцидентности;
2) матрицы смежности;
2) списков смежности.
Возможны и другие способы задания графов (например,
графический способ). Однако приведенные выше способы
являются основными [1-4].

Матрица инцидентности графа

Матрицей инцидентности для неориентированного графа с n


вершинами и m ребрами называется прямоугольная матрица (в
общем случае m<>n) B  bij , i  1,2,..., m, j  1,2,..., n , строки
которой соответствуют ребрам, а столбцы – вершинам [1, 2].
Элементы 1, если вершина v инцидентна ребру е ,
j i
bij  
0, если вершина vj не инцидентна ребру еi.
3
Например, для неориентированного графа на рис.1

e1
v1
v2
e4
e3 e2
v4 e5
v3

Рис.1. Неориентированный граф

матрица инцидентности имеет следующий вид (рис. 2):


v1 v2 v3 v4
е1 1 1 0 0
е2 0 1 1 0
е3 0 1 0 1
е4 1 0 0 1
e5 0 0 1 1

Рис. 2. Матрица инцидентности для неориентированного


графа

Матрицей инцидентности для ориентированного графа с n


вершинами и m ребрами (дугами) называется прямоугольная
матрица B  bij , i  1,2,..., m, j  1,2,..., n , строки которой
соответствуют дугам, а столбцы - вершинам.
Элементы

 1, если дуга еi выходит из вершины vj,



bij   1, если дуга е входит в вершину v ,
i j
 0,
 если вершина vj не инцидентна дуге еi.

4
000
Например, для ориентированного графа на рис. 3

e1 v2
v1

е4 е3 е2

v4
е5
v3
Рис 3. Ориентированный граф

матрица инцидентности имеет следующий вид (рис. 4):

v1 v2 v3 v4
e1 -1 1 0 0
e2 0 -1 1 0
e3 -1 0 1 0
e4 -1 0 0 1
e5 0 0 1 -1

Рис. 4. Матрица инцидентности для ориентированного графа

Как легко можно заметить, этот способ задания графа


довольно неэффективен: каждая строка такой матрицы
содержит только 2 ячейки с ненулевыми значениями, так как
одно ребро (дуга) может быть инцидентно не более чем двум
вершинам. В результате мы имеем довольно неэкономное
использование оперативной памяти ЭВМ.
Типичный пример задания матрицы инцидентности на
языке С/С++ - при помощи целочисленного двумерного
массива m на n:
int Matr_Ints [m][n];

5
Матрица смежности графа [2, 3]

Матрицей смежности неориентированного графа с n


вершинами называется квадратная матрица
A   aij , i, j  1,2,..., n , в которой

1, если существует ребро (еi, ej),


a ij  
0, если вершины vi, vj не связаны ребром (еi, ej).
Например, для неориентированного графа на рис.1 матрица
смежности имеет следующий вид (рис. 5):

v1 v2 v3 v4
v1 0 1 0 1
v2 1 0 1 1
v3 0 1 0 1
v4 1 1 1 0

Рис.5. Матрица смежности для неориентированного графа

Матрицей смежности ориентированного графа с n вершинами


называется матрица A   aij , i, j  1,2,..., n , в которой
1, если существует дуга (еi, ej),
a ij  
0, если вершины v , v не связаны дугой (е , e ).
i j i j

Например, для ориентированного графа на рис. 3 матрица


смежности имеет следующий вид (рис. 6):

v1 v2 v3 v4
v1 0 1 1 1
v2 0 0 1 0
v3 0 0 0 0
v4 0 0 1 0

Рис.6. Матрица смежности для ориентированного графа

6
При более подробном рассмотрении можно заметить, что в
случае графа без петель матрица смежности имеет ряд
особенностей:
во-первых, главная диагональ матрицы всегда заполнена
нулями, так как вершина не может быть смежной сама себе;
во-вторых, если наш граф неориентированный - то часть
матрицы под главной диагональю и над ней абсолютно
идентичны.
Петля в матрице смежности может быть представлена
соответствующим единичным диагональным элементом.
На языке C/С++ матрица смежности чаще всего задается
при помощи целочисленного квадратного двумерного массива
n на n:
int Matr_Sm [n][n];

Как вы видите, этот способ задания графов, как и


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

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

7
быть отведена память для n2 элементов. Поэтому лучшим
решением для представления графа является использование
динамических (связанных) структур данных - списков
смежности.
Списки смежности содержат для каждой вершины v,
принадлежащей множеству вершин V, список смежных ей
вершин [1, 3]. Используя терминологию языка С/С++, можно
утверждать, что каждый элемент такого списка является
структурой с адресом R в памяти, содержащей в поле (*R).Key
вершину графа, а в поле (*R).Sled - указатель на следующую
структуру в списке. Ясно, что для последней структуры в
списке (*R).Sled содержит NULL. Обозначим beg[v] -
указатель на начало списка, содержащего вершины, смежные с
вершиной v, где beg является массивом указателей на входы в
цепочки. Признаком конца цепочки является указатель на
ноль - NULL в языке С/С++. Например, для ориентированного
графа на рис. 3 списки смежности имеют следующий вид :

1 - 2,3,4,0
2 - 3,0
3-0
4 - 3,0
Такой список будет представляться в памяти ЭВМ
следующим образом (Рис. 7):

2 3 4 0
Массив 1 * * * *
указателей 3 0 Динамические
на входы в
2 * * переменные,
цепочки 3 * 0 состоящие из
целого числа и
4 * 3 0 указателя
*
Рис. 7. Схематическое представление ориентированного графа
с помощью списков смежности
8
Контрольные вопросы

1. Какие существуют основные способы задания графов?


2. Опишите каждый из способов задания, его
недостатки и преимущества.
3. Какие структуры данных используются для реализации
каждого из способов представления графа?
4. Какой способ представления графов самый
экономичный и почему? Расскажите, как реализуется этот
способ хранения на языке C/С++ и в как при этом граф
хранится в памяти ЭВМ.

Условие задачи для лабораторной работы № 1

Используя процедуры ввода графа в виде матрицы


инцидентности, матрицы смежности и списков смежности с
возможностью корректировки введенных данных и
преобразования различных форм хранения графа составить
программу, выполняющую следующие функции (пример
программы приведен ниже ):
-- ввод графа в любой из трех форм представления (по
требованию пользователя) ;
-- хранение введенного графа в памяти ЭВМ в виде
списков смежности;
-- вывод информации о графе на дисплей в любом из трех
видов (также по требованию пользователя );

Для заданной формы представления графа:


1. Установить форму представления графа.
2. Нарисовать граф.
3. Используя составленную программу вывести информацию
по двум другим формам представления графа.

9
Варианты заданий для лабораторной работы № 1
Вариант 1
0 1 0 0 0 1 0 0 0 1
0 0 0 1 0 0 0 0 1 0
0 0 1 0 1 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
1 1 0 0 0 0 0 0 0 0
0 0 1 0 1 0 0 0 0 0
0 1 0 0 0 0 1 0 0 0
1 0 0 0 0 1 0 1 0 0
0 0 0 0 0 0 1 0 1 0
Вариант 2
0 -1 0 0 0 0 0 1 0 0
0 0 0 2 0 0 0 0 0 0
0 0 0 0 1 0 0 0 -1 0
0 0 1 0 0 -1 0 0 0 0
1 -1 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 -1 0
0 0 0 1 0 0 0 0 0 -1
0 0 -1 0 0 0 0 0 1 0
0 0 0 0 1 0 -1 0 0 0
0 0 0 -1 0 1 0 0 0 0
0 0 0 0 0 -1 1 0 0 0
0 -1 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 -1 0 0
0 -1 0 0 1 0 0 0 0 0
0 0 -1 1 0 0 0 0 0 0
1 0 0 -1 0 0 0 0 0 0
Вариант 3
10
1 – 2,3,4,7,9,0
2 – 5,6,7,0
3 – 9,10,0
4 – 1,2,0
5 – 1,4,5,0
6–0
7 – 10,7,0
8 – 4,0
9 – 10,0
10 – 4,8,0
Вариант 4
0 1 0 0 0 1 0 0 0 1
0 0 0 1 0 0 0 0 1 0
0 0 0 0 0 0 0 1 0 1
0 0 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
1 1 0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 1 0 0 0 0 1 0 0 0
1 0 0 0 1 1 0 1 0 0
0 0 0 0 0 0 1 0 1 0
Вариант 5
0 -1 0 1 0 0 0 0 0 0
0 0 0 1 0 0 -1 0 0 0
0 0 0 0 1 0 0 0 -1 0
0 0 1 0 0 -1 0 0 0 0
1 -1 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 -1 0
0 0 0 1 0 0 0 0 0 -1
0 0 -1 0 0 0 0 0 1 0
0 0 0 0 1 0 -1 0 0 0
0 0 0 0 0 1 -1 0 0 0
0 0 0 0 0 0 2 0 0 0
0 -1 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 -1 0 0
0 -1 0 0 1 0 0 0 0 0
0 0 -1 1 0 0 0 0 0 0
1 0 0 -1 0 0 0 0 0 0
Вариант 6
11
1 – 2,3,4,5,9,0
2 – 5,6,7,9,0
3 – 9,2 ,0
4 – 1,2,3,0
5 – 1,4,5,0
6 – 1,0
7 – 10,0
8 – 4,0
9 – 10,3,0
10 – 4,0
Вариант 7
1 1 1 0 0 1 0 0 0 1
0 0 0 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0
0 0 1 1 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0
0 0 1 0 1 0 0 0 0 0
0 1 0 0 0 0 1 0 0 0
1 0 0 0 1 1 0 1 0 0
0 0 0 0 0 0 0 0 1 0
Вариант 8
0 0 -1 0 0 0 0 1 0 0
0 -1 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 -2 0
0 0 1 0 0 -1 0 0 0 0
1 -1 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 -1 0
0 0 0 1 0 0 0 0 0 -1
0 0 -1 0 0 0 0 0 1 0
0 0 0 0 1 0 -1 0 0 0
0 0 0 -1 0 1 0 0 0 0
0 0 0 0 0 -1 1 0 0 0
0 -1 1 0 0 0 0 0 0 0
-1 0 0 1 0 0 0 0 0 0
0 -1 0 0 1 0 0 0 0 0
0 0 -1 1 0 0 0 0 0 0
1 0 0 -1 0 0 0 0 0 0
Вариант 9
12
1 – 3,4,7,9,0
2 – 1,5,6,7,0
3 – 2,,10,0
4 – 1,2,0
5 – 1,4,5,0
6 – 5,0
7 – 10,7,0
8 – 4,0
9 – 10,0
10 – 4,8,0
Вариант 10
0 0 0 0 0 1 1 0 0 1
0 1 0 1 0 0 0 0 1 0
0 0 0 0 1 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
1 1 0 0 0 0 0 0 0 0
0 0 1 0 1 0 0 0 0 0
0 1 0 0 1 0 1 0 0 0
1 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 1 0
Вариант 11
0 -1 0 0 0 0 0 1 0 0
1 0 0 -1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 -1 0
0 0 1 0 0 -1 0 0 0 0
1 0 0 0 0 -1 0 0 0 0
0 0 0 0 0 1 0 0 -1 0
0 1 0 0 0 0 0 0 0 -1
0 0 -1 0 0 0 0 0 1 0
0 0 0 0 1 0 -1 0 0 0
0 0 0 0 0 2 0 0 0 0
0 0 0 0 0 -1 1 0 0 0
0 -1 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 -1 0 0
0 -1 0 0 1 0 0 0 0 0
0 0 -1 1 0 0 0 0 0 0
1 0 0 -1 0 0 0 0 0 0
Вариант 12
13
1 – 4,7,9,0
2 – 1,4,5,6,7,0
3–0
4 – 1,2,5,6,0
5 – 1,4,5,0
6–0
7 – 10,7,0
8 – 4,0
9 – 10,1,2,0
10 – 4,8,0
Вариант 13
0 1 0 1 0 1 0 0 0 1
0 0 0 1 0 0 0 0 1 0
0 0 1 0 1 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
1 1 0 1 0 0 0 0 0 0
0 0 1 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
1 0 0 0 0 1 0 1 0 0
0 0 0 0 0 0 0 1 1 1
Вариант 14
0 0 0 0 0 0 0 2 0 0
0 0 0 1 0 0 -1 0 0 0
0 0 0 0 1 0 0 0 -1 0
0 0 1 0 0 0 0 -1 0 0
1 -1 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 -1 0
0 0 0 1 0 0 0 0 0 -1
0 0 -1 0 0 0 0 0 0 1
0 0 0 0 1 0 -1 0 0 0
0 1 0 -1 0 0 0 0 0 0
0 0 0 0 0 -1 1 0 0 0
0 -1 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 -1 0 0
0 -1 0 0 1 0 0 0 0 0
0 0 -1 1 0 0 0 0 0 0
1 0 0 -1 0 0 0 0 0 0
Вариант 15
14
1 – 2,3,4,7,0
2 – 5,6,7,0
3 – 9,10,0
4 – 1,2,0
5 – 1,4,5,0
6 – 2,5,0
7 – 10,7,0
8 – 4,0
9 – 10,0
10 – 4,0

Пример программы на языке С для выполнения


лабораторной работы № 1

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <locale.h>

//объявляем и определяем структуру для списка


//смежности
typedef struct _node
{
int value;
struct _node *next;
}
node;
//---------------------------------------------------
//процедуры варианта выбора
void MIin(); //ввод матрицы инцидентности
void MSin(); //ввод матрицы смежности
void SSin(); //ввод списка смежности

//процедуры ввода
void readArray(int *, int, int); //матрицы
node *readList(); //списка смежности

//процедуры вывода
void printArray(int *, int, int); //матрицы
void printList(node *); //списка смежности

15
//процедуры преобразования
//м. инцидентности -> м. смежности
void MI_MS(int *, int *, int, int);
//м. смежности -> список смежности
void MS_SS(int *, int, node **);
//список смежности -> м. инцидентности
void SS_MI(int *, int , node **);

//дополнительные
//подсчёт элементов списка смежности
int countInList(node **, int);
//освобождение памяти для строки из списка
void destroy(node *);

int main()
{
setlocale(0,"Rus");
int choice;
printf("Выберите подходящий вариант:\n");
printf("1 - Перевести из матрицы инцдентности\n");
printf("2 - Перевести из матрицы смежности\n");
printf("3 - Перевести из списка смежности\n");
printf("0 - Закрыть программу\n");
printf("Ваш выбор: "); scanf("%d", &choice);
switch(choice)
{
case 1: MIin(); break;
case 2: MSin(); break;
case 3: SSin(); break;
default: printf("\t Программа
закрывается...\n"); break;
}
getch();
return 0;
}
//определение функций...
void MIin()
{
int M, N, i, choice;
int *A, *B;
node **arrayOfList;

16
printf("Укажите размеры массива:\n");
printf("M="); scanf("%d", &M);
printf("N="); scanf("%d", &N);
A=(int *)malloc(M*N*sizeof(int));
readArray(A, M, N);
printf("-----------------------------------n");
printf("Ваша матрица инцидентности:\n");
printArray(A, M, N);
printf("----------------------------------\n");
printf("Выберите в какой форме отобразить:\n");
если вершина vi не инцидентна
printf("1 - Перевести
ребру е .
в матрицу смежности\n");
j
printf("2 - Перевести в список смежности\n");
scanf("%d", &choice);
switch(choice)
{
case 1: printf("Перевод в матрицу смежности\n");
B=(int *)calloc(N*N, sizeof(int));
//выделение памяти для МС
MI_MS(A, B, M, N);
//преобразование
printf("\nПолученная матрица смежности:\n");
printf("----------------------------------\n");
printArray(B, N, N);
//вывод полученной матрицы
printf("----------------------------------\n");
free(A);
free(B);
break;
case 2: B=(int *)calloc(N*N, sizeof(int));
//выделение памяти для промежуточной МС
MI_MS(A, B, M, N);
//преобразование
free(A);
printf("Перевод в список смежности\n");
arrayOfList=(node **)malloc(N*sizeof(node *));
//выделение памяти для СС
MS_SS(B, N, arrayOfList);
printf("\nПолученный список смежности:\n");
printf("----------------------------------\n");
for(i=0; i<N; i++)
{
printf("%d:", i+1);

17
printList(*(arrayOfList+i));
}

printf("----------------------------------\n");
free(B);
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
break;
default:printf("\nНеверный выбор...\n");
free(A);
}
}
void MSin()
{
int M, N, i, choice;
int *A;
node **arrayOfList;

printf("Укажите кол-во вершин:\n");


printf("N="); scanf("%d", &N);
A=(int *)malloc(N*N*sizeof(int));
readArray(A, N, N);
printf("----------------------------------\n");
printf("Матрица смежности:\n");
printArray(A, N, N);
printf("----------------------------------\n");

printf("Выберите в какой форме отобразить:\n");


printf("1 - Перевести в список смежности\n");
printf("2 - Перевести в матрицу инцидентности\n");
scanf("%d", &choice);
switch(choice)
{
case 1:printf("Перевод в список смежности\n");
arrayOfList=(node **)malloc(N*sizeof(node *));
MS_SS(A, N, arrayOfList);
printf("\nПолученный список смежности:\n");
printf("----------------------------------\n");
for(i=0; i<N; i++)
{

18
printf("%d:", i+1);

printList(*(arrayOfList+i));
}
free(A);
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
break;
сase 2:arrayOfList=(node **)malloc(N*sizeof(node *));
MS_SS(A, N, arrayOfList);
M=countInList(arrayOfList, N);
free(A);
printf("Перевод в матрицу инцидентности\n");
A=(int *)calloc(M*N, sizeof(int));
SS_MI(A, N, arrayOfList);
printf("\nПолученная матрица инцидентности:\n");
printf("----------------------------------------\n");
printArray(A, M, N);
printf("----------------------------------------\n");
free(A);
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
break;
default:printf("\nНеверный выбор...\n");
free(A);
}
}
void SSin()
{
int M, N, i, choice;
int *A, *B;
node **arrayOfList;

printf("\n\t+ Кол-во вершин в графе: ");


scanf("%d", &N);
arrayOfList=(node **)malloc(N*sizeof(node *));
// выделение памяти под СС
for(i=0; i<N; i++)
{
printf("Вершина #%d\n", i+1);

19
*(arrayOfList+i)=readList();
}
printf("----------------------------------\n");
printf("Список смежности:\n");
for(i=0; i<N; i++)
{
printf("%d:", i+1);
printList(*(arrayOfList+i));
}
printf("----------------------------------\n");
M=countInList(arrayOfList, N);
printf("Выберите в какой форме отобразить:\n");
printf("1 - Перевод в матрицу инцидентности\n");
printf("2 - Перевод в матрицу смежности\n");
scanf("%d", &choice);
switch(choice)
{
case 1:printf("Перевод в матрицу инцидентности\n");
A=(int *)calloc(M*N, sizeof(int));
SS_MI(A, N, arrayOfList);
printf("\nПолученная матрица инцидентности:");
printf("\n--------------------------------------\n");
printArray(A, M, N);
printf("----------------------------------------\n");
free(A);
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
break;
case 2: A=(int *)calloc(M*N, sizeof(int));
SS_MI(A, N, arrayOfList);
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
B=(int *)calloc(N*N, sizeof(int));
printf("Перевод в матрицу смежности\n");
MI_MS(A, B, M, N);
printf("\nПолученная матрица смежности:");
printf("\n--------------------------------------\n");
printArray(B, N, N);
printf("----------------------------------------\n");
free(A);

20
free(B);
break;
default:printf("\nНеверный выбор\n");
for(i=0; i<N; i++)
destroy(*(arrayOfList+i));
free(arrayOfList);
}
}

void readArray(int *A, int M, int N)


{
int i, j;
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
printf("A[%d][%d]=", i+1, j+1);
scanf("%d",(A+i*N+j));
}
}

node *readList()
{
int i=0, semn;
int sizeOfNode=sizeof(node);
node *A=NULL, *R1, *R2;
printf("%d : ", ++i); scanf("%d",&semn);
if(semn)
{
R1=(node *)malloc(sizeOfNode);
R1->value=semn; R1->next=NULL;
A=R1;
printf("%d : ", ++i); scanf("%d",&semn);
}
while(semn)
{
R2=(node *)malloc(sizeOfNode);
R2->value=semn; R2->next=NULL;
R1->next=R2; R1=R2;
printf("%d : ", ++i); scanf("%d",&semn);
}
printf("\n");
return A;

21
}

void printArray(int *A, int M, int N)


{
int i, j;
for(i=0; i<M; i++)
{
for(j=0; j<N; j++) printf("%3d", *(A+i*N+j));
printf("\n");
}
}
void printList(node *R)
{
int i=0, semn;
int sizeOfNode=sizeof(node);
while(R)
{
printf("%2d", R->value);
R=R->next;
}
printf("%2d\n", 0);
}
void MI_MS(int *A, int *B, int M, int N)
{
int i, j, a, b;
for(i=0; i<M; i++)
{
for(j=0; j<N; j++)
if(*(A+i*N+j)==2) a=b=j;
else
{
if(*(A+i*N+j)==-1) a=j;
if(*(A+i*N+j)==1) b=j;
}
*(B+a*N+b)=1;
}
}

void MS_SS(int *A, int N, node **arrayOfList)


{

22
int i, j, k;
int sizeOfNode=sizeof(node);
node *R1, *R2;

for(i=0; i<N; i++) //найти первую 1 в строке


{
j=0; k=0;
while(!k&&(j<N))
{
if(*(A+i*N+j)) k=j+1;
j++;
}
//k=0 - нет в строке 1 иначе k - номер
//первой 1 в строке (отсчёт с 1)
if(k)
{
R1=(node *)malloc(sizeOfNode);
R1->value=k; R1->next=NULL;
*(arrayOfList+i)=R1;
//продолжаем исследовать строку
while(j<N)
{
if(*(A+i*N+j))
{
R2=(node *)malloc(sizeOfNode);
R2->value=j+1; R2->next=NULL;
R1->next=R2; R1=R2;
}
j++;
}
}
else *(arrayOfList+i)=NULL;
}
}
void SS_MI(int *A, int N, node **arrayOfList)
{
int i, j, k=0;
node *R;
for(i=0; i<N; i++)
{
R=*(arrayOfList+i);
while(R)

23
{
if((R->value)-1 == i) *(A+k*N+i)=2;
else
{
*(A+k*N+i)=-1;
*(A+k*N+(R->value)-1)=1;
}
R=R->next;
k++;
}
}
}

int countInList(node **arrayOfList, int N)


{
int i, k=0;
node *R;
for(i=0; i<N; i++)
{
R=*(arrayOfList+i);
while(R)
{
R=R->next;
k++;
}
}
return k;
}

void destroy(node *R)


{
node *TMP;
while(R)
{
TMP=R;
R=R->next;
free(TMP);
}
}

Лабораторная работа № 2.
24
Тема: Использование структур данных для поиска на
деревьях и графах. Алгоритм поиска в глубину

Цель работы: Изучение структур данных и алгоритмов для


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

Структуры данных:
списки
Каждый тип списков определяет множество конечных
последовательностей элементов, имеющий заданный базисный
тип. Число элементов списка, называемое его длиной, в разных
списках одного типа может быть различным [1]. Список без
элементов, называется пустым списком. Для списка
определены понятия начального, конечного и текущего
элементов, а также следующего и предыдущего элементов по
отношению к текущему. Текущий элемент - это тот
единственный элемент списка, который в данный доступен для
обработки.
очередь
Очередь используется для реализации такой дисциплины
обработки элементов списка, при которой элементы списка
удаляются из него в порядке их включения в список (т.е. по
принципу "Первым пришел - первым ушел") [1].
Набор базисных операций над списками, являющимися
очередями, состоит из четырех операций:
1) Создание пустой очереди;
2) Проверка очереди на пустоту;
3) Выборки первого элемента из очереди с
одновременным его удалением;

4) Занесения некоторого значения базисного типа в


качестве нового последнего элемента очереди.
25
стек
Стек используется для реализации такой дисциплины
обработки элементов списка, при которой элементы списка
удаляются из него в порядке, обратном порядку их занесения
в стек (т.е. по принципу "Последним пришел - первым ушел")
[1].
Набор базисных операций над списками, являющимися
стеками, состоит из пяти операций:
1) Создание пустого стека;
2) Проверка стека на пустоту;
3) Выборки последнего элемента из стека без удаления
его из стека или с его удалением;
5) Занесения некоторого значения базисного типа в
качестве нового последнего элемента стека.
деревья
Над некоторым базисным типом определяют множество
структур, каждая из которых состоит из объекта базисного
типа, называемого вершиной или корнем данного дерева, и
некоторого списка элементов из определяемого множества,
называемых поддеревьями данного дерева. Дерево, в котором
список поддеревьев пуст, называется тривиальным, а корень
тривиального дерева - листом дерева. Корень дерева
называется отцом вершин, являющихся корнями
поддеревьев; а эти вершины называются сыновьями корня
дерева, причем корень первого поддерева является старшим
сыном, а корень каждого следующего поддерева в списке
называется братом корня предыдущего поддерева [1,2].
Набор базисных операций для деревьев состоит из
следующих операций: создание тривиального дерева по
элементу базисного типа и выборки или замены корня дерева
или списка его поддеревьев, а также всех операций (для
списка поддеревьев), которые являются базисными для списка.

Обход дерева и графа в глубину

26
При обходе дерева в глубину (известном также как
прохождение в прямом порядке) вершины дерева
посещаются в соответствии со следующей рекурсивной
процедурой: сначала посетить корень дерева q; затем если
корень рассматриваемого дерева не является листом, то для
каждого сына p корня q рекурсивно обратиться к процедуре
обхода в глубину для того, чтобы обойти все поддеревья с
корнями p в порядке упорядоченности вершин p как сыновей
корня q. Если использовать стек S для хранения текущего
пути по дереву, т.е. пути, который начинается в корне дерева
и кончается в вершине, посещаемой в данный момент, то
можно построить нерекурсивный алгоритм для обхода дерева
в глубину [3, 4]:
Посетить корень дерева и поместить его в пустой стек S;
WHILE стек S не является пустым DO
BEGIN
пусть p - вершина, находящаяся на верху стека S;
IF сыновья вершины p еще не посещались
THEN посетить старшего сына вершины p и поместить
его в
стек S
ELSE BEGIN
удалить вершину p из стека S
IF p имеет братьев THEN посетить брата вершины

поместить его в стек S
END
END.

Алгоритм обхода дерева в глубину можно


модифицировать таким образом, чтобы его можно было
использовать для систематического обхода всех вершин
произвольного графа [4]. В приведенной ниже формулировке
алгоритма поиска в глубину на графе предполагается, во-
первых, что зафиксирован некоторый линейный порядок на
27
множестве всех вершин графа, и, во-вторых, что множество
вершин, смежных со всякой вершиной графа, также линейно
упорядочено:

WHILE имеется хотя бы одна не посещенная вершина DO


BEGIN
пусть p - первая (т.е. минимальная) из не посещенных
вершин;
посетить вершину p и поместить ее в пустой стек S;
WHILE стек S не пуст DO
BEGIN
пусть p - вершина, находящаяся на верхушке стека S;
IF у вершины p есть не посещенные смежные вершины
THEN BEGIN
пусть q - первая не посещенная вершина из
вершин,
смежных вершине p;
пройти по ребру (p, q), посетить вершину q и
поместить ее в стек S;
END
ELSE удалить вершину p из стека S
END
END.

В результате работы алгоритма, пройденные ребра графа


образуют вместе с посещенными вершинами одно или
несколько деревьев. Если приписать пройденным ребрам
ориентацию в соответствии с тем направлением, в каком
они проходятся при выполнении алгоритма, то мы получим
совокупность корневых деревьев, причем их корнями будут
служить все те вершины, которые в процессе работы
алгоритма помещались в пустой стек.
В том случае, когда мы имеем дело с произвольным
связанным графом, вершины которого не имеют линейной
упорядоченности, порядок прохода вершин не имеет
28
значения. Поэтому предлагается другой алгоритм, не
обеспечивающий строгого порядка прохода вершин и более
широко использующий возможности стека, что приводит к
увеличению быстродействия программ, построенных на его
основе. В частности, этот алгоритм в рекурсивном
исполнении широко используется в программах,
выполняющих глобальный поиск по подкаталогам на дисках
(например, в антивирусах).
Поместить в стек исходную вершину графа и пометить ее;
WHILE стек не пуст DO
BEGIN
извлечь вершину из стека;
IF есть непомеченные вершины и смежные с данной
THEN пометить их и загрузить в стек;
END.

Контрольные вопросы

1. Что представляют собой структуры данных: списки, очереди,


стеки, деревья?
2. По какому принципу организована обработка элементов в
очередях и стеках?
3. Что такое обход дерева и графа в глубину?
4. Какие структуры данных используются в алгоритмах поиска
в глубину?
5. Для чего используется алгоритм поиска в глубину?
6. Напишите программу, которая проводит только один вид
поиска - поиск в глубину, без использования виртуальных
функций, которые применены в приведенном ниже примере
программы для лабораторных работ №2 и №3

29
Условие задачи для лабораторной работы № 2

1. Основываясь на одном из алгоритмов, разработать


процедуры, выполняющие обход графа и дерева в глубину.
2. a) используя разработанные процедуры, создать для
заданного варианта графа программу, выполняющую
следующие функции:
-- ввод графа в виде списка смежности;
-- обход введенного графа в глубину (стартовая вершина
задается преподавателем) ;
-- выдачу номеров пройденных вершин на дисплей;
b) используя разработанные процедуры, создать для
заданного варианта дерева программу, выполняющую
следующие функции:
-- ввод дерева в виде списка смежности;
-- обход введенного дерева в глубину;
-- выдачу номеров пройденных вершин на дисплей.

Варианты заданий для лабораторных работ №2 и №3


приведены ниже (после условия задачи для лабораторной
работы № 3).

30
Лабораторная работа № 3.

Тема: Использование структуры данных “очередь” на


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

Цель работы: Сравнение алгоритмов поиска в глубину и


ширину для ориентированных графов. Использование
структуры очередь и её сравнение со структурой стек при
обходе графа и дерева в ширину и глубину соответственно.

Алгоритм поиска в ширину


Обход графа (дерева) в ширину, как и обход в глубину
должен обеспечивать однократное посещение каждой вершины
графа (дерева), только по другому принципу, чем обход в
глубину. После посещения исходной вершины, из которой
начинается поиск, посещаются все вершины, смежные с
данной, затем все вершины, смежные с этими вершинами и
т.д., пока не будут посещены все вершины графа (дерева).
Алгоритм обхода графа в ширину аналогичен алгоритму
обхода графа в глубину, за исключением того что используется
не стек а очередь [3, 4].
Алгоритм обхода графа в ширину:

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


пустую очередь и пометить ее как пройденную
WHILE очередь не пуста DO
BEGIN
Извлечь из начала очереди вершину .
IF есть непомеченные вершины и смежные с данной
THEN пометить их и загрузить в конец очереди;
END.

31
Контрольные вопросы

1. Что такое обход графа в ширину?


2. Чем отличаются алгоритмы обхода графа в ширину и в
глубину?
3. Какие структуры данных используются в алгоритмах
поиска в ширину?
4. Для каких целей используется алгоритм поиска в ширину?
5. Напишите программу, которая проводит только один вид
поиска - поиск в ширину, без использования виртуальных
функций, которые применены в приведенном ниже примере
программы для лабораторных работ №2 и №3

Условие задачи для лабораторной работы № 3

1. Основываясь на одном из алгоритмов, разработать


процедуры, выполняющие обход графа и дерева в ширину.
2. a) используя разработанные процедуры, создать для
заданного варианта графа программу, выполняющую
следующие функции:
-- ввод графа в виде списка смежности;
-- обход введенного графа в ширину (стартовая вершина
задается преподавателем) ;
-- выдачу номеров пройденных вершин на дисплей;
b) используя разработанные процедуры, создать для
заданного варианта дерева программу, выполняющую
следующие функции:
-- ввод дерева в виде списка смежности;
-- обход введенного дерева в ширину;
-- выдачу номеров пройденных вершин на дисплей.

32
Варианты заданий для лабораторных работ № 2 и №3
Варианты графов
Вариант 1
1 2 3 4 5

6 7 8 9 10

Вариант 2
1 2 3 4 5

6 7 8 9 10

Вариант 3
1 2 3 4 5

6 7 8 9 10

Вариант 4
1 2 3 4 5

6 7 8 9 10

Вариант 5
1 2 3 4 5

6 7 8 9 10

33
Вариант 6
1 2 3 4 5

6 7 8 9 10
Вариант 7
1 2 3 4 5

6 7 8 9 10
Вариант 8
1 2 3 4 5

6 7 8 9 10
Вариант 9
1 2 3 4 5

6 7 8 9 10
Вариант 10
1 2 3 4 5

6 7 8 9 10
Вариант 11
1 2 3 4 5

6 7 8 9 10
Вариант 12
1 2 3 4 5

6 7 8 9 10
34
Вариант 13
1 2 3 4 5

6 7 8 9 10

Вариант 14
1 2 3 4 5

6 7 8 9 10
Вариант 15
1 2 3 4 5

6 7 8 9 10

Варианты деревьев
Вариант 1 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

18 19 20 21

35
Вариант 2 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 3 1 18

2 3 4 19

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 4 18 19 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

18 19 20 21

36
Вариант 5 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 6 18 19 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 7 18 1 19

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

37
18 19 20 21

Вариант 8 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 9 18 19 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 10 1 18 19

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

38
18 19 20 21

Вариант 11 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 12 1 18 19

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 13 18 19 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

39
18 19 20 21

Вариант 14 1

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

Вариант 15 1 18 19

2 3 4

5 6 7 8 9

10 11 12 13 14 15 16 17

18 19

40
Пример программы для выполнения лабораторных работ
№2 и №3

Ниже приведена программа на языке С++ в которой


реализуются как поиск в глубину (лабораторная работа № 2),
так и поиск в ширину (лабораторная работа № 3). Основная
идея заключается в том, что описывается структура

struct Data
{
int data[1000];
virtual void Push(int x) {}
virtual int Pop() {}
virtual int isEmpty() {}
};

в которой не уточнены функции . Они будут уточнены позже,


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

struct Stack: Data


{
unsigned char t;
Stack() {t=0;}
virtual void Push(int x){data[t++]=x;}
virtual int Pop() {return data[--t];}
virtual int isEmpty() {return !t;}
};

а для поиска в ширину - очередь

struct Queue: Data


{
unsigned char q1, q2;
Queue() {q1=q2=0;}
41
virtual void Push(int x){data[q2++]=x;}
virtual int Pop() {return data[q1++];}
virtual int isEmpty() {return q1==q2;}
};
Выбор происходит следующим образом
Data* toProceadj;
if (k) toProceadj = new Stack;
else toProceadj = new Query;
Дело в том , что в языке С/С++ возможно выделение памяти
под указатель
Data* toProceadj;

с последующим уточнением того, на что будет указывать этот


указатель

if (k) toProceadj = new Stack;


else toProceadj = new Queue;

Значение переменной k задается в основной программе

printf("vvedite DFS - 1 BFS - 0\n"); scanf("%d",&k);

Листинг программы
# include <iostream.h>
# include <conio.h>
# include <math.h>
# include <stdio.h>
#include<alloc.h>

int MI[100][100], MS[100][100],color[100],n,m,k;

struct Data
{ int data[1000];
virtual void Push(int x) {}
virtual int Pop() {}
virtual int isEmpty() {}
};
struct Queue: Data
{ unsigned char q1, q2;
Queue() {q1=q2=0;}
42
virtual void Push(int x){data[q2++]=x;}
virtual int Pop() {return data[q1++];}
virtual int isEmpty() {return q1==q2;}
};
struct Stack: Data
{ unsigned char t;
Stack() {t=0;}
virtual void Push(int x){data[t++]=x;}
virtual int Pop() {return data[--t];}
virtual int isEmpty() {return !t;}
};
struct node{ int v;
node *next;
node(int x, node * t){v=x;next=t; }
};
typedef node *lnk;
lnk adj[100];
void DFS_adj_IVsit(int u){
Data* toProceadj;
if (k) toProceadj = new Stack;
else toProceadj = new Queue;
toProceadj->Push(u);
color[u]=2;
do
{
u = toProceadj->Pop();
printf("%d ",u+1);
lnk tmp = adj[u];
while(tmp){
int v =tmp->v;
if(color[v]==0){
color[v]=2;
toProceadj->Push(v);
}
tmp=tmp->next;
}
} while (!toProceadj->isEmpty());
delete toProceadj;
}
void DFS_adj_Iterative()
{ printf("\nOtvet ");
for(int u=0; u<n; u++)

43
if(color[u]==0)
DFS_adj_IVsit(u);
printf("\n");
}
void FMI()
{
printf("Matritsa Intidentnosti\n ");
for(int i=0;i<m;i++) {
printf("vvedite %d stroku\n", i+1);
for(int j=0;j<n;j++)
scanf("%d",&MI[i][j]);
}
}
void FMS()
{
printf("Matrita Smejnosti\n ");
for(int i=0;i<n;i++){
printf("vvedite %d stroku\n",i+1);
for(int j=0;j<n;j++)
scanf("%d",&MS[i][j]);
}
}
void FSS()
{
int tmp;
printf("Spisok Smejnosti\n ");
for(int i=0;i<n;i++){
printf("vvedit vershinu sviazanui s
%d\n",i+1);
printf( "%d: ",i+1);
scanf("%d",&tmp);
while(tmp){
adj[i]=new node(tmp-
1,adj[i]);
scanf("%d",&tmp);
}
}
}

void FMSvSS()
{
for(int i=0;i<n;i++)

44
for(int j=0;j<n;j++)
if(MS[i][j])
adj[i]=new node(j,adj[i]);
}
void FSSvMI()
{ int m=0;
for(int i=0;i<n;i++){
lnk tmp=adj[i];
while(tmp){ MI[m][i]=-1;
MI[m++][tmp->v]=1;
tmp=tmp->next;
}
}
}
void FMIvMS()
{ int i,j,k,l;
for (i=0;i<m;i++){
for (j=0;j<n;j++){
if(MI[i][j]==-1) k=j;
if(MI[i][j]==1) l=j;
}
MS[k][l]=1;
}
}
void printm()
{ printf("Matritsa Intidentnosti\n");
for(int i=0;i<m;i++){
for(int j=0;j<n;j++)
printf("%2d ",MI[i][j]);
printf("\n");
}
printf("Matritsa Smejnosti\n");
for(i=0;i<n;i++){ for(int j=0;j<n;j++)
printf("%2d ",MS[i][j]);
printf("\n");
}
printf("\n\tspisok smeznosti\n");
for(i=1;i<=n;i++){
printf("%d : ",i);
lnk tmp=adj[i-1];
while(tmp){printf("%d ",tmp->v+1);
tmp=tmp->next;

45
}
printf("0\n");
}
}
int main()
{
clrscr();
printf("Vvedi kol-vo ver6in i Dug\n");
scanf("%d%d",&n,&m);
printf("Sposob vvoda\n1.Matritsa
Intidentnosti\n2.Matritsa Smejnosti\n3.Spisok
Smejnosti\n");
scanf("%d",&k);
switch(k){
case 1: FMI() ; FMIvMS(); FMSvSS();
break;
case 2: FMS(); FMSvSS(); FSSvMI();
break;
case 3: FSS(); FSSvMI(); FMIvMS();
break;
}
printm();
printf("vvedite DFS - 1 BFS - 0\n");
scanf("%d",&k);
DFS_adj_Iterative();
return 0;
}

46
ЛИТЕРАТУРА

1. А.В.Ахо, Д.Э.Хопкрофт, Д.Д.Ульман. Структуры данных и


алгоритмы. М., Вильямс, 2000. - 382c.
2. Ф.А.Новиков. Дискретная математика для программистов.
Учебник. С.Петербург, Питер, 2001.- 301c.
3. Кормен Т.,Лейзерсон Ч.,Ривест Р. Алгоритмы, построение и
анализ. – М., МЦНМО, 2001. – 960 с.
4. Окулов С. Программирование в алгоритмах. – М., БИНОМ,
2002. – 341 с.

47
Содержание
стр.
Лабораторная работа 1
Использование структур данных - двумерных
матриц и связных списков - для различных
способов задания графов

Лабораторная работа 2
Использование структур данных для поиска на
деревьях и графах. Алгоритм поиска в глубину

Лабораторная работа 3
Использование структуры данных “очередь” на
примере поиска в ширину для ориентированного
графа

48
ИСПОЛЬЗОВАНИЕ СТРУКТУР ДАННЫХ В
АЛГОРИТМАХ НА ГРАФАХ И ДЕРЕВЬЯХ

Методические указания и задания для лабораторных работ

Авторы: Николай Фалько, старший преподаватель, доктор


Михаил Кулев, конференциар, доктор
Галина Марусик, старший преподаватель, доктор

Redactor:

________________________________________________________________

Bun de tipar 00.00.00 Formatul hârtiei 60x84 1/16


Hârtie ofset. Tipar RISO Tirajul 100 ex.
Coli de tipar 3,0 Comanda nr. 00
________________________________________________________________

U.T.M., 2015, Chişinău, bd. Ştefan cel Mare, 168.


Secţia Redactare şi Editare a U.T.M.
49
2068, Chişinău, str. Studenţilor, 9/9

50