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

Основы объектно-ориентированного программирования задач на графах. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
Вопросы для самоконтроля
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];
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]