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

Лабораторная работа N 1

Тема: Хранение графов в памяти ЭВМ.

Цель работы: 1. Освоение и изучение способов задания графов: матрица


инцидентности, матрица смежности, список смежности.

2. Разработка процедур преобразования видов хранения


графов с выдачей результатов на дисплей.

Способы задания графов


Существует три основных способа дискретного задания графа:

1). Матрица инцидентности;

2). Матрица смежности;

3). Список смежности ( инцидентности );

Рассмотрим подробнее каждый из этих способов задания графа:


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

1, если вершина vi инцидентна ребру еj,


bij  
0, если вершина vi не инцидентна ребру еj.
Матрицей инцидентности для ориентированного графа с n вершинами и m
ребрами называется матрица B  bij , i  1,2,..., n, j  1,2,..., m , строки которой
соответствуют ребрам, а столбцы - вершинам.
Элементы

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



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

000

1
e1
v1
v2
e4
e3 e2
v4
v3

Рис.1

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


имеет следующий вид (рис. 5):

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

Рис. 2
e1 v2
v1

е4 е3 е2

v4
е5
v3

Рис 3.

Для ориентированного графа на рис. 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 ячейки с
ненулевыми значениями ( очевидно, так как одно ребро (дуга) может быть

2
инцидентно не более чем двум вершинам). В результате мы имеем довольно
неэкономное использование оперативной памяти ЭВМ.
Типичный пример задания матрицы инцидентности на языке СИ - при
помощи двумерного массива m на n:
Int Matr_Ints [LinksCount][TopsCount];
Матрица смежности графа
Матрицей смежности неориентированного графа с 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 0
v4 1 1 0 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
При более подробном рассмотрении можно заметить, что в случае графа без
петель матрица смежности имеет ряд особенностей:
во-первых, главная диагональ матрицы всегда заполнена нулями, так как
вершина не может быть смежна сама себе;
во-вторых, если наш граф неориентированный - то часть матрицы под
главной диагональю и над ней абсолютно идентичны.
Петля в матрице смежности может быть представлена соответствующим
единичным диагональным элементом.
На CИ матрица смежности, как и матрица инцидентности чаще всего
задается при помощи двумерного массива:

Int Matr_Sm [TopsCount][TopsCount] ;

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


существенные недостатки, поэтому матрица инцидентности и матрица

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

Список смежности

Список смежности представляет собой список из n строк, ( n - количество


вершин), где в i - ой строке записываются номера вершин, смежных с вершиной
под номером i. Как мы видим, этот список тем больше, чем больше связей
между вершинами графа.
Список инцидентности задается аналогично списку смежности, только с
одной лишь разницей, что в i - ой строке записываются номера ребер ( или
дуг ), инцидентных данной ( i - ой ) вершине.
Задание графов такими способами позволяет более экономно расходовать
память, однако они несколько сложнее при реализации и обработке. Из-за того,
что строки в списках переменной длины, появляется необходимость в
использовании динамических переменных и указателей. Рассмотрим наиболее
тривиальную реализацию списка смежности:
Пусть задан граф на n вершинах и требуется создать некоторую структуру
переменных в памяти ЭВМ, отображающую список смежности, составленный
для данного графа. Для начала выясним, что будет представлять собой данная
структура. Так как строка списка содержит номера вершин, то естественно
предположить, что мы должны иметь некоторую цепочку динамических
переменных целочисленного типа, связанных между собой. Такая связь
обеспечивается использованием указателей, обьединенных вместе с
целочисленной переменной в запись (на СИ struct) для того, чтобы обеспечить
хранение входных указателей в такие цепочки, используется одномерный
массив указателей, имеющий длину, равную количеству вершин графа. Признаком
конца цепочки используется указатель на ноль - NULL в языке СИ. Например, для
ориентированного графа на рис. 3 список смежности имеет следующий вид :
1 - 2,3,4,0
2 - 3,0
3-0
4 - 3,0

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

2 3 4 0
1 * * * *
3 0
2 * *
0
3 *
4 * 3 0
*

массив указателей динамические переменные


на входы в цепочки. состоящие из указателя и

4
целочисленной переменной.
Рис. 7

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

-- из матрицы смежности в список смежности и обратно;

-- из матрицы инцидентности в список смежности и обратно;


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

-- хранение введенного графа в памяти ЭВМ в виде списка смежности;

-- вывод информации о графе в любом из трех видов ( также по требованию


пользователя ) на дисплей;

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

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

2. Опишите каждый из способов задания, его недостатки и преимущества.

3. Какие типы переменных используются для реализации каждого из


способов представления графа ?

4. Какой способ представления графов самый экономичный и почему ?


Расскажите, как реализуется этот способ хранения на языке Pascal и в как при
этом граф хранится в памяти ЭВМ.

Пример выполнения работы


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

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


typedef struct _node
{
int value;
struct _node *next;
}
node;

5
//----------------------------------------------------------------------
-----
//процедуры варианта выбора
void MIin(); //ввод матрицы инцидентности
void MSin(); //ввод матрицы смежности
void SSin(); //ввод списка смежности

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

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

//процедуры преобразования
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;

printf("Укажите размеры массива:\n");


printf("M="); scanf("%d", &M);
printf("N="); scanf("%d", &N);

6
A=(int *)malloc(M*N*sizeof(int));
readArray(A, M, N);

printf("----------------------------------------------------------------
---\n");
printf("Ваша матрица инцидентности:\n");
printArray(A, M, N);

printf("----------------------------------------------------------------
---\n");
printf("Выберите в какой форме отобразить:\n");
printf("1 - Перевести в матрицу смежности\n");
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);
printList(*(arrayOfList+i));
}

printf("----------------------------------------------------------------
---\n");
free(B);
for(i=0; i<N; i++) destroy(*(arrayOfList+i));

7
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++)
{
printf("%d:", i+1);
printList(*(arrayOfList+i));
}
free(A);
for(i=0; i<N; i++) destroy(*(arrayOfList+i));
free(arrayOfList);
break;
case 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);

8
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);
*(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);

9
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);
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);
если вершина vi не инцидентна ребру еj.
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;
}

10
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)
{
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))

11
{
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)
{
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);
}
}

12
Лабораторная работа N 2.

Тема: Поиск в графе. Алгоритм поиска в глубину.

Цель работы: Изучение алгоритмов поиска в графе, а также различных


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

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

2) Проверка очереди на пустоту;

3) Выборки первого элемента из очереди с одновременным его удалением;

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


последнего элемента очереди.

стек
Стек используется для реализации такой дисциплины обработки элементов
списка, при которой элементы списка удаляются из него в порядке, обратном
порядку их занесения в стек (т.е. по принципу "Последним пришел - первым
ушел").
Набор базисных операций над списками, являющимися стеками, состоит из
пяти операций:

1) Создание пустого стека;

2) Проверка стека на пустоту;

3,4) Выборки последнего элемента из стека без удаления его из стека или с
его удалением;

13
5) Занесения некоторого значения базисного типа в качестве нового
последнего элемента стека.

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

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


При обходе дерева в глубину (известном также как прохождение в прямом
порядке) вершины дерева посещаются в соответствии со следующей
рекурсивной процедурой: сначала посетить корень дерева q; затем если корень
рассматриваемого дерева не является листом, то для каждого сына p корня q
рекурсивно обратиться к процедуре обхода в глубину для того, чтобы обойти все
поддеревья с корнями p в порядке упорядоченности вершин p как сыновей корня
q. Если использовать стек S для хранения текущего пути по дереву, т.е. пути,
который начинается в корне дерева и кончается в вершине, посещаемой в
данный момент, то можно построить нерекурсивный алгоритм для обхода
дерева в глубину:

Посетить корень дерева и поместить его в пустой стек S;


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

Алгоритм обхода в глубину можно модифицировать таким образом, чтобы


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

14
линейный порядок на множестве всех вершин графа, и, во-вторых, что
множество вершин, смежных со всякой вершиной графа, также линейно
упорядочено:

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

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


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

Поместить в стек исходную вершину графа и пометить ее;


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

Задание

1. Основываясь на одном из алгоритмов, разработать процедуру, выполняю-


щую обход графа в глубину.

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

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

1. Что представляют собой типы списков, очередей, стеков, деревьев ?

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


стеках ?

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

4. Какие структуры данных используются в алгоритмах поиска в глубину ?

5. Для чего используется алгоритм поиска в глубину?


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

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


поиску в ширину.
Лабораторная работа N 3.
Тема: Алгоритм поиска в ширину.
Цель работы: Изучение алгоритмов поиска в графе. Разработка программы,
выполняющей поиск в ширину.

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


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

2. Извлечь из начала очереди узел и пометить его как пройденный. В конец

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


не находятся в очереди.

3. Если очередь пуста, то все узлы связного графа были пройдены и поиск
окончен, иначе вернуться к п. 2.

16
Задание

.Используя разработанные ранее процедуры, создать программу, выполняю-


щую следующие функции:

-- ввод графа в виде списка смежности;

-- обход введенного графа в ширину;

-- выдачу номеров пройденных вершин на дисплей;

Пример программы на СИ в которой реализуются и поиск в глубину и поиск в


ширину. Основная идея заключается в том, что описывается структура
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;}
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);

17
# 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;}
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()

18
{
printf("\nOtvet ");
for(int u=0; u<n; u++)
if(color[u]==0)
DFS_adj_IVsit(u);
printf("\n");
}
void FMI()
{
printf("Matritsa Intidentnosti\n ");
for(int i=0;i<n;i++) {
printf("vvedite %d stroku\n", i+1);
for(int j=0;j<m;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++)
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()
{

19
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;
}
printf("0\n");
}
}
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();
}

Контрольные вопросы.
1. Что такое обход графа в ширину ?

2. Чем отличаются алгоритмы обхода графа в ширину и в глубину?

20
3. Какие структуры данных используются в алгоритмах поиска в ширину ?

4. Для каких целей используется алгоритм поиска в глубину ?


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

21