Шаг 92.
Четвертый пример использования топологической сортировки
На этом шаге мы рассмотрим последний пример использования топологической сортировки.
Лемма [1, с.127].
В произвольном ориентированном бесконтурном графе вершины можно перенумеровать так, что каждая дуга будет иметь вид
(vi,vj), где i<j.
Для доказательства леммы предложим алгоритм, конструирующий такую нумерацию (в виде программы на языке С++).
Алгоритм основывается на следующем простом факте: в произвольном бесконтурном графе существует вершина, в которую не заходит ни одна дуга.
Чтобы убедиться в этом, выберем произвольную вершину w1 графа, затем
некоторую вершину w2, такую что w1<-w2, и т.д. Через конечное число шагов
мы должны дойти до некоторой вершины wi, в которую не заходит ни одна
дуга, ибо в силу бесконтурности ни одна вершина не может повторяться в последовательности w1,w2,w3,...
#include <iostream.h>
#define TRUE 1
#define FALSE 0
typedef int Boolean;
// Нумерация вершин в бесконтурном графе.
typedef struct Leader *Lref; //Тип: указатель на заголовочный узел.
typedef struct Trailer *Tref; //Тип: указатель на дуговой узел.
//Описание типа заголовочного узла.
struct Leader
{
int Key; //Имя заголовочного узла.
int Number; //Новый номер вершины.
int Count; //Количество предшественников.
int Count1; //Количество последующих вершин.
Tref Pred; //Указатель на список предшественников.
Tref Trail; //Указатель на список последователей.
Lref Next; //Указатель на следующий узел в
//списке заголовочных узлов.
};
//Описание типа дугового узла.
struct Trailer
{
Lref Id;
Tref Next;
};
//Описание типа узла стека.
struct Zveno
{
Lref Element; //Указатель на вершину.
Zveno* Sled;
};
class Spisok {
private:
Lref Head; //Указатель на список заголовочных узлов.
Lref Tail; //Указатель на фиктивный элемент
//в конце списка заголовочных узлов.
Zveno* Stack; //Указатель на рабочий стек.
Lref SearchGraph (int);
void Udalenie (Lref*);
void V_Stack (Lref);
public:
Spisok () {//Инициализация списка заголовочных узлов.
Head = Tail = new (Leader); };
void MakeGraph();
void PrintGraph();
void Renum();
void Vyvod();
};
void Spisok::MakeGraph ()
//Функция возвращает указатель Head на структуру
//Вирта, соответствующую ориентированному графу.
{
int x,y;
Lref p,q; //Рабочие указатели.
Tref t,r; //Рабочие указатели.
Boolean Res; //Флаг наличия дуги.
cout<<"Вводите начальную вершину дуги: ";
cin>>x;
while (x!=0)
{
cout<<"Вводите конечную вершину дуги: "; cin>>y;
//Определим, существует ли в графе дуга (x,y)?
p=SearchGraph (x); q=SearchGraph (y);
r = (*p).Trail; Res = FALSE;
while ((r!=NULL)&&(!Res))
if ((*r).Id==q) Res = TRUE;
else r = (*r).Next;
if (!Res) //Если дуга отсутствует, то поместим её в граф.
{ t = new (Trailer); (*t).Id = q;
(*t).Next = (*p).Trail; (*p).Trail = t; (*q).Count++;
t = new (Trailer); (*t).Id = p;
(*t).Next = (*q).Pred; (*q).Pred = t; (*p).Count++; }
cout<<"Вводите начальную вершину дуги: "; cin>>x;
}
}
Lref Spisok::SearchGraph (int w)
//Функция возвращает указатель на заголовочный узел
//с ключом w. Если заголовочный узел отсутствует, то он
//добавляется в список. Head - указатель на структуру Вирта.
{
Lref h;
h = Head; (*Tail).Key = w;
while ((*h).Key!=w) h = (*h).Next;
if (h==Tail)
//В списке заголовочных узлов нет узла с ключом w.
//Поместим его в конец списка Head.
{ Tail = new (Leader); (*h).Count = (*h).Count1 = 0;
(*h).Trail = (*h).Pred = NULL; (*h).Next = Tail; }
return h;
}
void Spisok::PrintGraph ()
//Вывод структуры Вирта, заданной указателем
//Head и соответствующей ориентированному графу.
{
Lref p; //Рабочий указатель.
Tref q; //Рабочий указатель.
p = Head;
while (p!=Tail)
{
cout<<(*p).Key<<"(("; q = (*p).Trail;
while (q!=NULL)
{ cout<<(*(*q).Id).Key<<" "; q = (*q).Next; }
cout<<")("; q = p->Pred;
while (q!=NULL)
{ cout << (*(*q).Id).Key<<" "; q = q->Next; }
cout << "))";
p = (*p).Next; cout<<" ";
}
}
void Spisok::Vyvod()
{
Lref p = Head;
while ( p!=Tail )
{
cout << p->Key << '-'<< p->Number;
p = p->Next; cout << ' ';
}
}
void Spisok::Renum()
//Перенумерация вершин графа, заданного структурой Вирта
//с указателем Head.
{
int num;
Lref p,u;
Tref t;
Stack = NULL; p = Head;
while ( p!=Tail )
{
if ( p->Count!=0 ) V_Stack(p);
p = p->Next;
}
num = 0;
while ( Stack!=NULL )
{
Udalenie (&u);
num++; u->Number = num;
t = u->Trail;
while ( t!=NULL )
{
t->Id->Count--;
if ( t->Id->Count==0 ) V_Stack (t->Id);
t = t->Next;
}
}
}
void Spisok::Udalenie (Lref *Klad)
// Удаление элемента из стека Stack и сохранение его в
// переменной Klad.
{
Zveno *q;
if ( Stack==NULL )
cout << "Ошибка! Попытка выбоpа из пустого стека!\n";
else
{
(*Klad) = Stack->Element; q = Stack;
Stack = Stack->Sled; delete q;
}
}
void Spisok::V_Stack (Lref Elem)
// Помещение Elem в стек Stack.
{
Zveno *q;
q = new (Zveno);
q->Element = Elem; q->Sled = Stack; Stack = q;
}
void main()
{
Spisok A;
// Построение графа и вывод его структуры смежности.
A.MakeGraph();
A.PrintGraph(); cout << endl;
A.Renum();
// Вывод новых номеров вершин графа.
A.Vyvod();
}
Текст этой программы можно взять
здесь.
Результат работы программы изображен на рисунке 1:

Рис.1. Результат работы приложения
(1) Липский В. Комбинаторика для программистов: Пер. с польск. - М.:Мир, 1988. - 213 с.
На следующем шаге мы дадим понятие о методе PERT.
Предыдущий шаг
Содержание
Следующий шаг