
- •Д.Г. Хохлов
- •Оглавление
- •Предисловие
- •1. Основные понятия
- •2. Проектирование программы
- •2.1. Постановка и анализ задачи
- •2.2. Внешнее проектирование
- •2.2.1. Методика внешнего проектирования
- •2.2.2. Внешнее представление данных
- •2.3. Проектирование структуры программы
- •2.4. Проектирование модуля
- •2.4.1. Этапы проектирования модуля
- •2.4.2. Стиль программирования
- •2.4.3. Пример проектирования модуля
- •3. Отладка программы
- •3.1. Планирование отладки
- •3.2. Проектирование тестов
- •3.3. Отладочные средства
- •5.1.2. Файл проекта
- •6.2. Выполнение и оформление работы
- •6.2.1. Задание
- •6.2.2. Описание применения
- •6.2.3. Описание программы
- •6.2.3. Подготовка к отладке программы
- •6.2.4. Отладка программы
- •6.2.5. Заключение
- •6.2.6. Общие правила оформления
- •Список литературы
- •Приложение. Пример выполнения курсовой работы
- •1. Задание
- •2. Описание применения
- •2.1. Постановка задачи
- •2.2. Обращение к программе
- •2.3. Входные данные
- •2.4. Выходные данные
- •2.5. Сообщения
- •2.5.1. Информационные сообщения
- •2.5.2. Сообщения об ошибках
- •3. Описание программы
- •3.1. Метод решения задачи
- •3.2. Структура программы
- •3.3. Описание модулей
- •3.3.1. Main - главный модуль
- •3.3.2. Pminc - поиск минимального цикла
- •3.3.3. Vvodg - ввод графа
- •3.3.4. Vyvodp - вывод пути
- •4. Подготовка к отладке программы
- •4.1. План отладки
- •4.2. Проектирование тестов
- •4.2.1. Тесты черного ящика
- •4.2.2. Тесты белого ящика
- •4.3. Отладочные средства
- •1. Отладка программы
- •3. Заключение
- •Список литературы
- •Приложение 1. Системные файлы проекта
- •Приложение 2. Текст программы модуля main
- •Приложение 3. Текст программы модуля pminc
- •Приложение 4. Текст программы модуля vvodg
- •Приложение 5. Текст программы модуля vyvmsm
- •Приложение 6. Текст программы модуля vyvodp
- •Приложение 7. Текст программы модуля vyvsoob
- •Приложение 8. Текст отладочной программы драйвера dvvodg
- •Приложение 9. Результаты тестирования программы
- •Приложение 10. Трудоемкость курсовой работы
- •Приложение 11. Дневник выполнения курсовой работы
2.4.3. Пример проектирования модуля
В качестве примера рассмотрим этапы 1 - 6 проектирования модуля поиска цикла (замкнутого пути) минимальной длины в заданном графе [26, 28] (см. рис. 2.2 и табл. 2.1).
1. Внешнее проектирование. Заголовок подпрограммы модуля:
/**************************************************************/
/* Поиск минимального цикла в графе с количеством вершин n, */
/* матрицей смежности g. Значение функции: 0 - цикл найден, */
/* 1 - граф не имеет циклов. Номера вершин найденного цикла */
/* - в векторе cmin, его длина - dcmin (3..n) */
/* Д.Г. Хохлов 13.03.95 */
/**************************************************************/
int pminc (int n, int g[][NMAX], int *dcmin, int cmin[])
Все входные и выходные данные модуля pminc передаются параметрами, однако, параметр g является массивом (c – адрес массива, т. е. скалярный параметр). Поэтому pminc сцеплен с вызывающими модулями по формату (соответствующий g фактический параметр должен иметь такое же строение). Здесь это приемлемо, т. к. для матрицы g требуется указать хотя бы размер строки и поэтому не удается избежать зависимости от формата, как сделано для одномерного массива cmin.
2. Выбор алгоритма и структуры данных. В литературе [15, 23] не удалось найти методы поиска кратчайшего цикла, но описаны три подхода, применимые к данной задаче: поиск с возвращением, поиск в ширину и получение степеней матрицы смежности [26, 28]. Ниже использован первый вариант.
Как часто бывает, рекурсивный алгоритм проще итеративного, но тратит больше времени и памяти за счет рекурсивных вызовов. Итеративный алгоритм предпочтительнее, если он не слишком сложен. Поэтому для поиска минимального цикла используем итеративный алгоритм 2.1 [26].
Алгоритм 2.1. Итеративный поиск с возвращением всех решений задачи
k = 0;
do
if (существует еще не рассмотренный вариант v очередного шага решения)
{ e[k] = v; /* Вперед: в стек */
if (e[0] … e[k] - решение)
Использовать решение e[0] … e[k];
k++;
} else k--; /* Назад: удаление последнего элемента стека */
while (k ≥ 0); /* Стек не пуст */
3. Описание данных (представление стека в виде вектора).
int v[NMAX+1]; /* стек с номерами вершин текущего пути */
int k; /* указатель стека v */
4. Пошаговая детализация алгоритма и данных. Выбранный алгоритм 2.1 конкретизирован в алгоритме 2.2 для задачи поиска кратчайшего цикла как перебор всех возможных путей обходом графа в глубину [28].
Алгоритм 2.2. Поиск с возвращением кратчайшего цикла графа
/* Поиск кратчайшего цикла обходом графа в глубину */
dcmin = n + 1; /* длина минимального цикла (3..n) */
for (v[0]=0; v[0]<n; v[0]++) /* начальная вершина: 0..n-1 */
{ /* Обход в глубину дерева путей, начинающихся с v[0] */
k = 1; v[1] = 0; /* начальный номер преемников v[0] */
do
Найти номер вершины vn очередного преемника v[k-1];
if (есть vn) /* есть путь вперед */
{ v[k] = vn; /* вперед: vn - в стек */
v[k+1] = 0; /* начальный номер преемников v[k] */
if (v[0]...v[k] - цикл)
if (k < dcmin)
{ dcmin = k;
c[0]...c[k] = v[0]...v[k]; /* запомнить цикл */
}
k++;
}
else /* назад */
{ v[k]++; /* следующий преемник v[k-1] */
k--; /* удалить v[k] из стека */
}
while (k > 0); /* стек не пуст */
}
Перебор путей по алгоритму 2.2 можно сократить, как показано в алгоритме 2.3, используя следующие правила – эвристики (в скобках приведена соответствующая формальная запись).
1) Прекратить поиск, обнаружив цикл длиной 3 (dcmin == 3).
2) Отвергать пути длиннее минимального из найденных циклов или равные ему (i >= dcmin). Поэтому, найдя цикл, можно удалить из стека две вершины.
3) Отвергать пути, ведущие в вершину, ранее использованную в качестве начальной (vn < v[0]), т.к. все проходящие через нее циклы уже просмотрены. По этой причине перебор по возрастанию возможных номеров очередной вершины пути начинать не с 0, а с начальной вершины текущего пути (v[к+1] = v[0]).
Алгоритм 2.3. Алгоритм модуля pminc
int v[NMAX+1]; /* стек с номерами вершин текущего пути */
int к; /* указатель стека v */
/* Поиск кратчайшего цикла обходом графа в глубину */
dcmin = n + 1; /* длина минимального цикла (3..n) */
for (v[0]=0; v[0]<n && dcmin>3; v[0]++) /* начальная вершина */
{ /* Обход в глубину дерева путей, начинающихся с v[0] */
k = 1; v[1] = v[0]+1; /* начальный номер преемников v[0] */
do
Найти номер вершины vn очередного преемника v[k-1];
if (есть vn && k<dcmin) /* есть путь вперед */
{ v[k] = vn; /* вперед: vn - в стек */
v[k+1] = v[0]; /* начальный номер преемников v[k]*/
if (v[0]...v[k] - цикл)
{ dcmin = k;
c[0]...c[k] = v[0]...v[k]; /* запомнить цикл */
k = k - 3; /* назад: удалить 2 вершины пути */
v[k+1]++; /* следующий преемник v[k] */
}
k++;
} else /* назад */
{ v[k]++; /* следующий преемник v[k-1] */
k--; /* удалить v[k] из стека */
}
} while (k > 0 && dcmin>3); /* стек не пуст */
}
5. Ручное тестирование (здесь не приведено) позволило обнаружить две ошибки в первоначальной версии алгоритма 2.3.
6. Программирование. Текст программы модуля pminc поиска кратчайшего цикла графа приведен в алгоритме 2.4.
Алгоритм 2.4. Программа модуля pminc
/************************************************************* */
/* Поиск минимального цикла в графе с количеством вершин n, */
/* матрицей смежности g. Значение функции - 0, если цикл */
/* найден и 1, если граф не имеет циклов. Номера вершин */
/* найденного цикла - в векторе c, его длина - dcmin (3..n) */
/************************************************************* */
int pminc (int n, int g[NMAX][NMAX], int *dcmin, int c[])
{
int v[NMAX+1]; /* стек с номерами вершин текущего пути*/
int k; /* указатель стека v */
int j; /* номер строки матрицы смежности */
/* Поиск кратчайшего цикла обходом графа в глубину */
dcmin = n + 1; /* длина минимального цикла (3..n) */
for (v[0]=0; v[0]<n && dcmin>3; v[0]++) /* начальная вершина */
{ /* Обход в глубину дерева путей, начинающихся с v[0] */
k = 1; v[1] = v[0]+1; /*начальный номер преемников v[0]*/
do
{ /* Найти вершину vn - очередного преемника v[k-1] */
j = v[k-1];
for (vn=v[k]; vn<n && (g[j][vn]==0 || k>1 && vn==v[k-2]); vn++) ;
if (vn<n && k<dcmin) /* можно вперед */
{ v[k] = vn; /* вперед: vn - в стек */
v[k+1] = v[0]; /* начальный преемник v[k] */
if (v[0]==v[k] && k>0) /* нашли цикл */
{ dcmin = k;
/* запомнить цикл v[0]...v[k] */
for (j=0; j<=k; j++) c[j] = v[j];
k = k - 3; /* назад: удалить 2 вершины пути */
v[k+1]++; /* следующий преемник v[k] */
}
k++;
}
else /* назад */
{ v[k]++; /* следующий преемник v[k-1] */
k--; /* удалить v[k] из стека */
}
} while (k > 0 && dcmin>3); /* стек не пуст */
}
}