Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Основы объектно-ориентированного программирования задач на графах. Учебное пособие
.pdf
Вопросы для самоконтроля
11
Если необходимо выделить группы студентов, в которых все студен-
ты попарно дружат друг с другом, то для этого необходимо определить в
графе группы вершин, в которых каждая вершина связана с каждой другой
вершиной из этой группы. В нашем примере (см. рис.1) первую группу образуют Первый и Второй студенты, во вторую группу войдут Пятый, Второй и Третий студенты, в третьей останется только Четвертый студент. Такая задача в теории графов называется задачей выделения максимальных
полных подграфов (клик графа).
Клика графа – это максимальный полный подграф, который не вхо-
дит ни в один полный подграф более высого порядка.
Рассмотрим другой пример. Объектами являются перекрестки горо-
да. Тогда вершины графа (см. рис. 1) моделируют теперь не студентов,
а перекрестки. Ребро между двумя вершинами существует в том случае,
если перекрестки на плане города являются соседними. Каждому ребру
можно назначить вес, равный длине улицы или переулка между перекрестками. Тогда, пользуясь полученным графом (см. рис. 1), можно определить
кратчайший маршрут от одного перекрестка до другого. В теории графов
эта задача называется задачей определения кратчайшего пути в графе.
Таких примеров можно приводить достаточно много. Смысл один –
задачи разные, объекты разные, а модель одна – граф. Таким образом, к одной и той же задаче на графах можно свести различные задачи с разными
по своей природе объектами. В этом и есть универсальность графов.
Вопросы для самоконтроля
1. Что такое граф?
2. Какие два множества образуют граф?
3. Какие объекты могут моделировать вершины графа?
4. Какие отношения могут задавать ребра графа?
5. Чем отличается граф от мультиграфа?
6. Приведите пример объекта, который можно моделировать
симметрическим графом.
7. Какая связь между понятием «отношение» и понятие ребра графа?
8. Приведите пример моделирования объекта мультиграфом.

1. Универсальность графов
12
9. Есть ли отличие между максимальным полным подграфом и кликой
графа?
10. Может ли клика графа состоять из одной вершины?
11. Каким свойством должны обладать ребра паросочетания графа?
12. Как моделируется в матрице смежности петля у вершины графа?

13
2. ПРЕДСТАВЛЕНИЕ ГРАФОВ
2.1. Матрица смежности
Достаточно взглянуть на рисунок (см. рис. 2), чтобы понять, что матрицу смежности графа удобно представить двумерным массивом с одинаковым числом строк и столбцов. Массив может быть целого, символьного
или логического типа. Выделить статическую память для матрицы смежности графа на пять вершин можно одним из следующих операторов:
int matr[5][5];
char matr[5][5];
bool matr[5][5];
Массивом целых чисел представляют матрицу смежности с весами на
ребрах, а также матрицу смежности мультиграфов.
Мультиграф – это граф, любые пары вершин которого могут быть со-
единены несколькими ребрами.
Символьным массивом задают матрицу смежности, если вес ребра не
превышает 255, или матрицу смежности мультиграфа, любые две вершины
которого могут быть соединены не более 255 ребер.
Логическим массивом задают матрицу смежности графов, не имею-
щих весов ребер.
Проверить, смежны или нет i-я и j-я вершины графа, можно так:
if (matr[i-1][j-1]) cout<<”смежны”;
else cout<<”Не смежны”;
2.2. Матрица инцидентности
Кроме матрицы смежности для табличного задания графа использу-
ется также матрица инцидентности. Для графа (см. рис. 1) матрица инцидентности будет иметь вид (рис. 3).
Матрица инцидентности имеет столько строк, сколько вершин в гра-
фе, а столбцов – сколько ребер. Элемент столбца будет равен 1, если вершина, которой соответствует строка, образует это ребро, и равен 0 для

2. Представление графов
14
вершин, не образующих это ребро. Таким образом, каждый столбец матрицы инцидентности может содержать только две единицы.
Рис. 3. Матрица инцидентности графа
В нашем примере граф (см. рис. 1) содержит четыре ребра. Ребро
e1 соединяет первую и вторую вершины, ребро e2 – вторую и третью вершины, ребро e3 – вторую и пятую вершины, e4 – третью и пятую вершины.
Матрица инцидентности имеет разное количество строк и столбцов, поэтому для ее представления в памяти следует выбирать двумерный массив
общего вида. Память для матрицы инцидентности графа, имеющего пять
вершин и четыре ребра, можно выделить следующим оператором:
bool matr[5][4];
Проверить, смежны или нет i-я и j-я вершины графа, заданного
матрицей инцидентности, сложнее, чем для графа, заданного матрицей
смежности. Для этого необходимо просмотреть все ребра графа, и найти то
ребро, столбец которого в матрице инцидентности содержит 1 в строках
i-1 и j-1:
int key=0;
for(int k=0; k<m; k++)
if (matr[i-1][k]==1 && matr[j-1][k]==1)
{ cout<<”смежны”; key=1; break;}
if(key) cout<<”Не смежны”;
где m – количество ребер графа.
0 0 0
1 1 0
1 0 1 0 0 0 1 1 0
0
0 0 1
1
1 2 3
4
5
e1 e2 e3 e4

2.3. Списочная форма
15
2.3. Списочная форма
Кроме табличного и графического способов задания графов, используется также и списочный способ задания графов. Этот способ заключается
в перечислении номеров вершин, смежных каждой из вершин графа.
Граф (см. рис. 1) в списочной форме показан на рис. 4.
Рис. 4. Списочная форма задания графа
Для представления в памяти списочной формы можно использовать
два одномерных массива. Первый массив является основным. В нем перечисляются списки номеров вершин, смежных вершинам графа. Второй
массив является вспомогательным массивом разделителей. Каждый i-й
элемент этого массива равен порядковому номеру элемента основного массива, с которого начинается список вершин, смежных с i-й вершиной. На
рис. 5 схематично показаны массивы для представления графа (см. рис. 1) в
списочной форме.
Рис. 5. Массивы для представления графа в списочной форме
Выделить память для массивов (см. рис. 5) можно следующим оператором:
int raz[6], list[25];
2 1 3 5 2 5 2
3
0 1 2 3 4 5 6 7 8
0 1 4 6 6
8
0 1 2 3 4
5
1:{2}; 2:{1,3,5};
3:{2,5};
4:{};
5:{2,3}.

2. Представление графов
16
Массив разделителей должен содержать один добавочный элемент,
указывающий на фиктивный элемент в основном массиве. Этот элемент
необходим для того, чтобы обеспечить единый алгоритм для перечисления
вершин, смежных любой из вершин графа.
Номера вершин графа, смежных с i-й вершиной графа, хранятся в
элементах основного массива с номерами от raz[i-1] до raz[i]. Следующие операторы выводят на экран номера вершин, смежных i-й вершине
графа:
for(int k = raz[i-1]; k<raz[i]; k++)
cout << list[k];
Проверить, смежны или нет i-я и j-я вершины графа, можно так:
int key = 0;
for (int k = raz[i-1]; k < raz[i]; k++)
if (list[k] == j)
{cout << ”смежны”; key = 0; break;}
if (key) cout << ”Не смежны”;
Вопросы для самоконтроля
1. Какие табличными способами можно задать граф?
2. Какими массивами можно моделировать матрицу смежности и матрицу
инцидентности?
3. Какова структура данных для списочной формы задания графа?
4. Составьте цикл определения локальной степени заданной вершины
графа при задании графа матрицей смежности, матрицей инцидентности и в списочной форме.
5. Составьте цикл определения количества ребер графа при задании графа
матрицей смежности, матрицей инцидентности и в списочной форме?

17
3. РАЗРАБОТКА КЛАССА ДЛЯ РЕШЕНИЯ ЗАДАЧ НА ГРАФАХ
3.1. Описание класса
Рассмотрим разработку простого класса для решения задач на гра-
фах, заданных матрицей смежности. Для простоты будем рассматривать
симметрические графы – неориентированные графы без петель и кратных
ребер. Разработку программ можно проводить в любых системах программирования, используя консольные приложения.
Матрицу смежности можно представить двумерным массивом. По-
этому разработка класса для работы с графами сводится, по существу, к
разработке класса для работы с двумерным массивом.
Создайте консольное приложение. Назовем класс Graph. В редакторе кода вставьте заголовок класса и пустое пока тело класса в шаблон консольного приложения. Операторы, которые будут добавляться в текст программы, и ключевые слова будем выделять жирным шрифтом:
//----------------------------------------------------
#include <vcl.h>
#pragma hdrstop
//----------------------------------------------------
class Graph
{
private:
public:
};
//----------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
return 0;
}
//----------------------------------------------------
Обычно в личной части класа private располагаются данные, а в
общей части public – функции класса, для работы с данными класса.

3. Разработка класса для решения задач на графах
18
Какие данные необходимы для описания графа? Граф состоит из вершин.
Поэтому, прежде всего, необходимо объявить количество вершин графа,
которое зададим целой переменной N:
int N;
Затем необходимо объявить массив, соответствующий матрице
смежности. Будем создавать этот массив в динамической памяти в конструкторе класса. Для этого в классе необходимо объявить указатель на
двумерный массив:
bool **Matr;
Добавьте в описание класса следующие операторы:
class Graph
{
private: int N;
bool **Matr;
};
Таким образом, Matr является указателем на двумерный массив.
В общем, данных для описания простого класса достаточно. Если потребуется, то по ходу разработки класса можете вводить другие данные класса.
Обратите внимание, что в шаблоне консольных приложений, который создает Мастер приложений, заголовок главной функции main() содержит аргументы. Пока в них нет необходимости, поэтому аргументы
функций можно удалить.
3.2. Объект класса
Создавая любой класс, думайте о том, что описание класса – это просто шаблон. В объявлении класса память не выделяется ни для данных, ни
для кода функций. Чтобы работать с классом, необходимо создавать объект этого класса. Только при создании объекта класса выделяется память
под данные класса и код встроенных функций. Для создания объекта класса
Graph необходимо создать указатель на объект класса Graph:
Graph * graph;

3.3. Конструктор класса
19
затем создать объект класса Graph в динамической памяти:
graph = new Graph;
и присвоить адрес, выделенной области памяти, созданному указателю.
Добавьте в главную функцию следующий оператор для создания
объекта класса Graph в динамической памяти:
int main()
{
Graph * graph = new Graph;
return 0;
}
3.3. Конструктор класса
Теперь создадим конструктор класса Graph. Конструктор класса –
это такая функция класса, имя которой совпадает с именем класса. Конструктор не может возвращать значение, вызывается стразу же после создания объекта класса и может выполнять любые действия, которые при
этом необходимо выполнить. В нашем случае конструктор должен создать
массив, соответствующий матрице смежности, в динамической памяти.
Добавьте в описание класса прототип конструктора:
class Graph
{
private: int N;
bool **Matr;
public: Graph(int);
};
Поскольку конструктор вызывается самым первым после создания
объекта класса, то значение числа вершин графа еще неизвестно. Поэтому
для того, чтобы иметь возможеность создать массив в динамической памяти, необходимо передать в конструктор значение числа вершин графа или
ввести это значение при выполнении конструктора. В нашем примере передадим значение числа вершин в виде аргумента конструктора и опреде-

3. Разработка класса для решения задач на графах
20
лим сам конструктор вне описания класса как невстроенную функцию
класса:
Graph::Graph(int n1)
{
N=n1;
Matr=new bool *[N];
for(int i=0; i<N; i++)
Matr[i]= new bool [N];
}
В этом случае перед именем конструктора необходимо указать имя класса с
двумя двоеточиями (::) – операцией разрешения области видимости:
Graph::Graph(int n1)
При вызове конструктора значение фактического аргумента будет присвоено переменной n1 – фиктивному аргументу конструктора.
Затем оператором
N=n1;
будет присвоено значение числу вершин графа – данному класса Graph.
После чего оператором
Matr = new bool *[N];
будет выделена динамическая память для одномерного массива указателей
(обратите внимание на звездочку (*) после типа bool), размерность которого равна числу вершин графа. Каждый i-й элемент массива указателей
должен содержать адрес области динамической памяти, выделенной соответствующей i-й строке матрицы смежности. Это выполняется следующими операторами:
for(int i=0; i<N; i++)
Matr[i] = new bool [N];
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
