Шаг 91.
Третий пример использования топологической сортировки

    На этом шаге мы рассмотрим еще пример использования топологической сортировки.

    В монографии [1,с.335,упр.23] Д.Кнут пишет: "Когда алгоритм топологической сортировки заканчивает работу из-за того, что во входной информации обнаружен цикл, то невелика будет польза, если он остановится и сообщит: "Встретился цикл". Полезно в этом случае напечатать один из циклов, показывая ту часть информации, в которой имеется ошибка."

    Дополним алгоритм топологической сортировки так, чтобы в нем предусматривался вывод какого-нибудь найденного контура на печать. Воспользуемся алгоритмом, приведенном в [1, с.647].

    Для того, чтобы читатель смог разобраться в реализации алгоритма на языке С++, мы (редчайший случай!) используем в программе метки и оператор goto.

#include <iostream.h>
typedef struct Leader  *Lref; //Тип: указатель на заголовочный узел.
typedef struct Trailer *Tref; //Тип: указатель на дуговой узел.
#define FALSE 0
#define TRUE 1
//Описание типа заголовочного узла.
struct Leader 
{
  int Key;    //Информационное поле.
  int Count;  //Количество предшественников.
  Tref Trail; //Указатель на список последователей.
  Lref Next;  
};

//Описание типа дугового узла.
struct Trailer 
{
  Lref Id; 
  Tref Next; 
};

class Spisok {
  private:
    Lref Head; //Указатель на список заголовочных узлов.
    Lref Tail; //Указатель на фиктивный элемент 
               //в конце списка заголовочных узлов.
    int z;     //Количество узлов, не имеющих предшественников.
  public:
    Spisok () {//Инициализация списка заголовочных узлов.
               Head = Tail = new (Leader); z = 0;};
    Lref L (int); 
    void Poisk();
    int  Vyvod();
    void Neupor();
};

int VspomSet[256]; // Множество, содержащее вершины графа,
                   // имеющие нулевое количество предшественников.
                   // По умолчанию элементы инициалируются 0.

Lref Spisok::L (int w) 
//Функция возвращает указатель на ведущего с ключом w.
{
  Lref h = Head;

  Tail->Key = w;
  while (h->Key!=w) h = h->Next;
  if (h==Tail)
  // В списке нет элемента с ключом w.
  {
    Tail = new (Leader); z++;
    h->Count = 0; h->Trail = NULL; h->Next = Tail;
  }
  return h;
}

void Spisok::Poisk()
{
  Lref p,q; // Рабочие указатели.
  
  p = Head; Head = NULL;
  while (p!=Tail)
  {
     q = p; p = p->Next;
     if (q->Count==0)
       { q->Next = Head; Head = q; }
   }
}

int Spisok::Vyvod()
{
  Lref p,q; // Рабочие указатели.
  Tref t;
  
  cout << "\nРезультат топологической сортировки:\n";
  q = Head;
  while ( q!=NULL) 
  {
     cout << q->Key << " "; VspomSet[q->Key] = 1;
     z--;
     t = q->Trail; q = q->Next;
     while (t!=NULL)
     {
       p = t->Id; p->Count--;
       if  (p->Count==0) // Включение (*p) в список ведущих.
         { p->Next = q; q = p; }
       t = t->Next;
     }
  }
  if (z!=0)
    {
      cout << "\nМножество не является частично упорядоченным!\n";
      return 1;
    }
  else return 0;
}

void Spisok::Neupor()
{
  Lref p,q,r; // Рабочие указатели.
  Tref t;
  int Flag;

  cout << "Имеется следующий контур (контур печатается ";
  cout << "в обратном порядке):\n";
  //В дальнейшем работаем только с копией основного списка.
  p = Head;
  while (p!=Tail)
  {
    if ( VspomSet[p->Key] ) p->Trail = NULL;
    p = p->Next;
  }
  // ------- 
  p = Head;
  while ( p!=Tail ) 
  { p->Count = 0; p = p->Next; }
  // --------------------------------------- 
  p = Head;
  while ( p!=Tail )
  {
    r = p; t = p->Trail; p->Trail = NULL;
    while ( t!=NULL )
    {
      if  (t!=NULL && t->Id->Count==0) t->Id->Count = r->Key;
      t = t->Next;
    }
    p = p->Next;
  }
  // ---------------------- 
  p = Head; Flag = FALSE;
  while (p!=Tail && !Flag) 
    if ( p->Count!=0 ) Flag = TRUE;
    else  p = p->Next;
  // --------------------------------------- 
  t = new (Trailer); // Создали вспомогательную запись.
A: 
  p->Trail = t;
  q = Head; Flag = FALSE;
  while  (q!=Tail && !Flag)
    if ( q->Key==p->Count ) Flag = TRUE;
    else  q = q->Next;
  if ( q->Trail==NULL ) { p = q; goto A; }
  // ----------------------------------- 
  z = p->Key; // Сохранили начало контура.
B: 
  cout << p->Key << " ";
  p->Trail = NULL;
  q = Head; Flag = FALSE;
  while  (q!=Tail && !Flag)
   if ( q->Key==p->Count ) Flag = TRUE;
   else  q = q->Next;
  if ( q->Trail!=NULL ) { p = q; goto B; }
  cout << z; // Напечатали начало контура.
}

void main()
{
  Spisok A,A_Copy;
  Lref p,q; // Рабочие указатели.
  Tref t;
  Lref p1,q1; // Рабочие указатели для создания копии списка.
  Tref t1;
  int  x,y; // Рабочие переменные.

  // Фаза ввода.
  cout << "Задайте отношение частичного порядка...\n";
  cout << "Элемент "; 
  cin >> x; 
  cout << " предшествует элементу ";
  while (x!=0)
  {
    cin >> y;
    //Построение основного списка.
    p = A.L(x); q = A.L(y);
    t = new (Trailer); t->Id = q; t->Next = p->Trail;
    p->Trail = t; q->Count += 1;
    //Построение копии основного списка.
    p1 = A_Copy.L(x); q1 = A_Copy.L(y);
    t1 = new (Trailer); t1->Id = q1; t1->Next = p1->Trail;
    p1->Trail = t1; q1->Count += 1;
    cout << "Элемент "; 
    cin >> x;
    cout << " предшествует элементу ";
  }
  // Поиск ведущих с нулевым количеством предшественников.
  A.Poisk();
  // Фаза вывода.
  if (A.Vyvod()) //Если множество неупорядоченное, 
                 //то работаем с копией.
       A_Copy.Neupor();
}
Текст этой программы можно взять здесь.

    Тестовые примеры:


    Замечания.
  1. Если Вы загляните в [1, с.647], то учтите, что необходимо исправить следующие неточности в записи алгоритма на псевдокоде:
    • шаг T9 алгоритма следует читать так: "Для 1<=k<=n установить P <- TOP[k], TOP[k] <- U и выполнить шаг T10.";
    • второе предложение в шаге T10 должно быть таким: "P <- NEXT(P)";
    • шаг T12 алгоритма следует читать так: "Установить TOP[k] <- W, где W - указатель, отличный от Nil, и k <-QLINK[k]. Теперь, если TOP[k] = Nil, повторить этот шаг.";
    • шаг T13 алгоритма следует читать так: "(Мы нашли начало цикла.) Напечатать значение k, установить TOP[k] <-Nil, k <- QLINK[k], и если TOP[k] = W, повторить этот шаг."
  2. Алгоритм обнаружения контура при топологической сортировке изложен также в [2, с.495-496].

   


(1) Кнут Д. Искусство программирования для ЭВМ. Т.1: Основные алгоритмы. - M.: Мир, 1976. - 736 с.
(2) Трамбле Ж., Соренсон П. Введение в структуры данных. - М.: Машиностроение, 1982. - 784 с.

    На следующем шаге мы продолжим рассматривать примеры использования топологической сортировки.




Предыдущий шаг Содержание Следующий шаг