Тема: Задача коммивояжера

Люди добрые, поделитесь решением задачи о коммивояжоре на Lisp.
Коммивояжер должен выйти из первого города, посетить по разу в неизвестном порядке города 2,1,3..n и вернуться в первый город. Расстояния между городами известны. В каком порядке следует обходить города, чтобы замкнутый путь коммивояжера был кратчайшим? Выдавать программа должна кротчайший путь коммивояжера и его длину.
Для нахождения длины пути велели задавать матрицу городов в виде списка расстояний между городами. Допустим есть три города А В С , тога надо задать список расстояний от одного города до каждого из остальных
((0 3 9) (3 0 1) (9 1 0)).
  АВС
А 039
В 301
С 910
Надо реализовывать эвристический алгоритм поиска, с эвристикой "идти в ближайший город" (это наверное "жаданый алгоритм"????).  Я буду рада любому решению, главное, чтобы работало.
Единственное, что мне удалось сделать, так это написать алгоритм нахождения гамильтонова цикла (т.е. прохождение через все города только по одному разуи возвращение в исходный город). Методом поиска в глубину с возвратом. Но я не знаю, будет ли это кратчайший путь и как вычислять пройденный путь (надо как-то добавить еще алгоритм?).

(DEFUN HAMILTCYCLE (LAMBDA (GRAPH)
; GRAPH - граф в виде структуры смежности ;
; Результат: гамильтонов цикл в виде списка вершин, ;
; NIL - если гамильтонова цикла не существует ;
(COND ( (NULL GRAPH) NIL )
( T (COND ( (NULL (CDR GRAPH))
(LIST (CAAR GRAPH)))
( T (HC GRAPH (CAAR GRAPH)
(LIST (CAAR GRAPH))
(CDAR GRAPH)
)
)
)
)
)
))
; ---------------------------------------- ;
(DEFUN HC (LAMBDA (GRAPH START VISITED SONS)
; START - первая вершина графа ;
; VISITED - список пройденных вершин ;
; SONS - соседи просматриваемой вершины ;
(COND ( (NULL SONS) NIL )
( T (COND ( (AND (MEMBER START SONS)
(EQ (LENGTH GRAPH)
(LENGTH VISITED))
)
(REVERSE VISITED)
)
( T (COND
( (MEMBER (CAR SONS) VISITED)
(HC GRAPH START VISITED
(CDR SONS)) )
( T (OR (HC GRAPH START
(CONS
(CAR SONS)
VISITED)
(NEIGHBOUR3
(CAR SONS)
GRAPH)
)
(HC
GRAPH START VISITED
(CDR SONS))) )) )
)
)
)
))
; ------------------------------- ;
(DEFUN NEIGHBOUR3 (LAMBDA (X GRAPH)
(COND ( (NULL (ASSOC X GRAPH)) NIL )
( T (CDR (ASSOC X GRAPH)) )
)
))

Пример:

$ (HAMILTCYCLE '((1 . (2 6)) (2 . (1 3 4)) (3 . (2 4))
(4 . (2 3 5)) (5 . (4 6)) (6 . (1 5))))
(1 2 3 4 5 6)

Еще как назло сроки пождимают, дали только 2 недели. С нетерпением буду ждать ответа (решение). Извините, если назойлива, просто очень очень надо.
Помогите!!!!!
Заранее спасибо!

Re: Задача коммивояжера

http://www.erudition.ru/referat/ref/id.45585_1.html

Re: Задача коммивояжера

Конечно спасибо. Но к сожалению в теории я знаю как решить. Как сделать эту задачку на практике, да еще и на  Lisp, вот в чем вопрос. Поэтому очень прошу тех кто разбирается в этом написать решение. Очень очень надо!!!!

Re: Задача коммивояжера

Хех...это можно решить в MAP.
Я решил это на лиспе совместно с мап. (Так нужно было). Хотя MAP нужен был лишь для создания топологии.

Re: Задача коммивояжера

Я рада, что хоть кто-то решил эту задачу на Лисп. Но совершенно не понимаю, что значит "совместно с мап". Очень хотелось бы посмотреть на решение, можно (что от меня требуется)?

Re: Задача коммивояжера

Да ничего от Вас не требуется. От меня требуется время чтобы толково тут выложить код. Могу выложить как есть..но там сам черт ногу сломит...постановка задачи менялась ежедневно....код соответственно тоже...
И к тому же в MAP есть дополнительные функции для работы, например, с топологиями...в этом еще разобраться нужно...Вы готовы?
Совместно с мап означает, что я использовал в работе объекты MAP. Хотя можно обойтись и без него, но тогда часть функций нужно написать самому.

Re: Задача коммивояжера

> Стас

> Eli
Скачал ,не помню где,эту програму. Может Вам подойдёт.
Readme
Демо-версия программы GMC (задача коммивояжёра) для числа пунктов
не более 40 определяет минимальный путь обхода и выводит последо-
вательность номеров и координат пунктов, начиная с 1 в командную
строку. Запуск командой KOML40.
Более подробное описание по адресу www.kmtn.ru/~regul.
Набиуллин Р.Р.
regul@kmtn.ru

Re: Задача коммивояжера

Стас, разобраться готова. Но не слишком  заумная надеюсь прога. Я готова ждать только скажи сколько, чтобы я не дергалась и тебя не беспокоили. Можно и с Map, главное, чтобы задача была написана на Lisp и работала.
Игорь, извините, но не поняла, что мне искать по этой ссылке. Там это задача коммивояжера решается на Lisp???

Re: Задача коммивояжера

Игорь рекламирует деревообрабатывающие станки...

> Eli
Я смогу не раньше понедельника(28 ноября) с Вами разобраться. :)

Re: Задача коммивояжера

Спасибо за скорые сроки, буду ждать. Надеюсь я Вас не очень обременяю. Если захотите заплачу ( в пределах разумного), но только после того как поможете мне разобраться в коде, а от нас требуют рассказывать, что делает каждая строчка программы (а справку выдали для чайников, а задачка ен из легких).

Re: Задача коммивояжера

Судя по всему, "жадный" алгоритм - это в каждом очередном городе принимать решение перемещаться в ближайший еще не посещенный город.
Пронумеруем города в произвольном порядке.
Таблица расстояний между городами:

--- d12 d13 d14 d15
d21 --- d23 d24 d25
d31 d32 --- d34 d35
d41 d42 d43 --- d45
d51 d52 d53 d54 ---

где d12 - расстояние от города 1 до города 2, соответственно d12=d21 (или нет?).
Для LISPа естественным было бы представить матрицу расстояний в виде:

((nil d12 d13 d14 d15)
 (d21 nil d23 d24 d25)
 (d31 d32 nil d34 d35)
 (d41 d42 d43 nil d45)
 (d51 d52 d53 d54 nil))

nil обозначает, что расстояние до текущего города не определено.
Теперь предположим, что коммивояжер находится в городе 1. Для движения в следующий город ему следует найти минимум из чисел в "хвосте" (cdr) первой строки матрицы расстояний. Допустим, что таковым оказалось расстояние до города номер 4 - d14:

nil d12 d13{d14}d15
d21 nil d23 d24 d25
d31 d32 nil d34 d35
d41 d42 d43 nil d45
d51 d52 d53 d54 nil

Идея заключается в том, чтобы перенумеровать оставшиеся города (за исключением текущего и уже пройденных) так, чтобы ближайший город оказался по номеру вслед за текущим.
Для матрицы расстояний эта операция представляет собой перестановку строк (при этом "первая" (то есть следующая за номером текущего города) строка становится последней, и так необходимое количество раз):

nil d12 d13{d14}d15
d41 d42 d43 nil d45
d51 d52 d53 d54 nil
d21 nil d23 d24 d25
d31 d32 nil d34 d35

Так как города перенумерованы, следует переставить и столбцы столько же раз и таким же образом, как до того строки:

nil d12 d13 d14 d15
d41 [b]nil{d45}d42 d43[/b]
d51 [b]d54 nil d52 d53[/b]
d21 [b]d24 d25 nil d23[/b]
d31 [b]d34 d35 d32 nil[/b]

Так как в 1-ый город возвращаться из бывшего 4-го, а ныне 2-го пока нельзя, на следующем этапе действия повторяются для меньшей матрицы (выделена жирным шрифтом) и так далее, пока матрица не кончится.
Функция COMMI_ROUTE принимает матрицу расстояний, например

(setq M5 '((nil 10 8 13 30)
           (10 nil 14 20 12)
           (8 14 nil 5 15)
           (13 20 5 nil 6)
           (30 12 15 6 nil)))

и перечень городов, например

(setq P '(1 2 3 4 5))

либо

(setq P '("1" "2" "3" "4" "5"))

и возвращает маршрут движения (но только без повторения в конце наименования первого города):

(defun COMMI_ROUTE (_dist_matrix _path)
 (if _dist_matrix
  (cons
   (car _path)
   (apply 'COMMI_ROUTE
    (apply
    '(lambda (_count)
      (list
       (COL_SWAP (mapcar 'cdr (ROW_SWAP (cdr _dist_matrix) _count)) _count)
       (ROW_SWAP (cdr _path) _count)))
     (list
      (apply
      '(lambda (_dist_list)
        (-
         (length _dist_list)
         (length (member (apply 'min _dist_list) _dist_list))))
       (list (cdr (car _dist_matrix))))))))))

Функция ROW_SWAP принимает список (например, строк матрицы расстояний) и счетчик и переставляет элементы списка указанным выше способом:

(defun ROW_SWAP (_list _count)
 (if (> _count 0)
  (ROW_SWAP (append (cdr _list) (list (car _list))) (1- _count))
  _list))

Функция COL_SWAP принимает список списков (например, строк матрицы расстояний) и счетчик и переставляет первые элементы каждого списка в конец соответствующего списка указанным выше способом:

(defun COL_SWAP (_list _count)
 (if (> _count 0)
  (COL_SWAP (mapcar 'append (mapcar 'cdr _list) (mapcar 'list (mapcar 'car _list))) (1- _count))
  _list))

Примечание: так как для нахождения минимального расстояния используются все расстояния от текущего города до остальных, данная программа применима для тех случаев, когда из каждого города есть дорога в каждый из остальных - то есть в "хвосте" первой стороки рассматриваемой на каждом этапе матрицы нет элементов nil (что означало бы отсутствие прямого проезда из текущего города в данный), ибо выражение, например:

(apply 'min '(1 2 nil 4 5))

невыполнимо.
Но если создать и применить функцию, способную "сократить" в списке nil-элементы, например:

(apply 'min ([i]f[/i] '(1 2 nil 4 5)))

то программу можно будет применять и для случаев "бездорожья".
К сожалению, рассказать,

что делает каждая строчка программы

проблематично, ибо в отличие от BASICа операторов и "строк" в LISP-программе нет, а есть только вызовы функций, необходимость которых и была вкратце разъяснена выше. Разумеется, ответы на дальнейшие вопросы последуют незамедлительно.

Re: Задача коммивояжера

> VH
Я извиняюсь, если внесу путаницу, тем не менее,
кажется, что истина где-то рядом:

; Список всех возможных комбинаций маршрутов с учетом
; добавления исходных пунктов :
(defun combo-list (lst)
(defun dispers (lst)
(defun relat (lst / ret)
 (repeat (length lst)
 (setq ret (cons lst ret)
       lst (append (cdr lst) (list (car lst)))))
 ret)
(if (car lst)
(apply 'append
   (mapcar
   (function (lambda (x)
     (mapcar
     (function (lambda (y)
       (cons (car x) y))
      (dispers (cdr x))))))
    (relat lst)))
  (list lst)))
(list (mapcar 'append
(mapcar (function (lambda (z)
        (cons (car z)(reverse z))))
    (dispers lst)))))
;;==================================;;
;; кратчайший путь
; num - номер пункта в списке карты
(defun mini-way (num lst)
(mapcar (function (lambda (x)
(mapcar (function (lambda (q p)
            (distance q p)))
    x (cdr x))))
(vl-remove-if-not (function (lambda (z)
    (eq (car (nth (1- num) lst)) (car z))))        
(combo-list lst))))

~'J'~

Re: Задача коммивояжера

Потерял две строчки в последней функции

;; кратчайший путь
; num - номер пункта в списке карты
; num - номер пункта в списке сети
(defun mini-way (num lst)
 (apply 'min
    (apply '+
(mapcar (function (lambda (x)
(mapcar (function (lambda (q p)
            (distance q p)))
    x (cdr x))))
(vl-remove-if (function (lambda (z)
    (eq (car (nth (1- num) lst)) (car z))))        
(combo-list lst))))))

~'J'~

Re: Задача коммивояжера

;; ***    Исправленная версия    ***    ;;
; Список всех возможных комбинаций маршрутов с учетом
; добавления исходных пунктов :
(defun combo-list (lst)
(defun dispers (lst)
(defun relat (lst / ret)
 (repeat (length lst)
 (setq ret (cons lst ret)
       lst (append (cdr lst) (list (car lst)))))
 ret)
(if (car lst)
(apply 'append
   (mapcar
   (function (lambda (x)
     (mapcar
     (function (lambda (y)
       (cons (car x) y)))
      (dispers (cdr x)))))
    (relat lst)))
(list lst)))
(vl-remove-if (function (lambda (x)
                  (not x)))
(mapcar 'append
(mapcar (function (lambda (z)
        (cons (car z)(reverse z))))
    (dispers lst)))))
;; кратчайший путь
; num - номер пункта в списке карты
; num - номер пункта в списке сети
(defun mini-way (num lst)
(apply 'min
(mapcar (function (lambda (i)
(apply '+ i)))        
(mapcar (function (lambda (x)
(mapcar (function (lambda (q p)
            (distance q p)))
    x (cdr x))))
(vl-remove-if (function (lambda (z)
    (eq (car (nth (1- num) lst)) (car z))))        
(combo-list lst))))))
;; А так наверно можно достать список самих пунктов
;; кратчайшего маршрута из указанного пункта
(defun mini-route (lst num)
 (vl-remove-if-not (function (lambda (x)
        (and (equal (nth (1- num) lst) (car x) 1e-08)    
        (equal (apply '+ (mapcar 'distance x (cdr x)))
               (mini-way num lst)
               1e-08))))
   (combo-list lst)))

~'J'~

Re: Задача коммивояжера

Согласно > Eli (2005-11-21 23:49:47)

Надо реализовывать эвристический алгоритм поиска, с эвристикой "идти в ближайший город" (это наверное "жадный алгоритм"????).

Согласно http://www.erudition.ru/referat/ref/id.45585_1.html

Жадный алгоритм, очевидно, бессилен в этой задаче.

(п.1.2.1).
Однако

Я буду рада любому решению, главное, чтобы работало.

Итак, достаточен ли работающий эвристический "жадный" алгоритм? Дождемся ответа от автора задания. По указанному выше адресу есть описания нескольких алгоритмов, над реализацией которых тоже можно поковыряться.
Пока - "блок-схема" реализации жадного алгоритма:

(defun COMMI_ROUTE (_dist_matrix _path)
 (if _dist_matrix ; пока матрица не сократилась до (nil)
  (cons ; присоединить
   (car _path) ; первый элемент из оставшегося списка городов
   (apply 'COMMI_ROUTE ; к результату выполнения той же функции над
    (apply
    '(lambda (_count)
      (list
       (COL_SWAP (mapcar 'cdr (ROW_SWAP (cdr _dist_matrix) _count)) _count) ; сокращенной матрицей с переставленными строками и элементами
       (ROW_SWAP (cdr _path) _count) ; и укороченным списком городов с переставленными названиями
      )
     )
     (list ; а это вычисление
      (apply
      '(lambda (_dist_list)
        (-
         (length _dist_list)
         (length (member (apply 'min _dist_list) _dist_list))))
       (list (cdr (car _dist_matrix)))
      )
     ) ; количества перестановок
    )
   )
  )
 )
)

То есть на каждом уровне рекурсии по мере сокращения матрицы расстояний определяется очередной элемент будущего маршрута. После завершения сокращения матрицы при возврате не предыдущий уровень рекурсии маршрут наращивается с "головы" (car).

Re: Задача коммивояжера

Насчет жадного алгоритма согласна. Решение наверное подойдет, ведь оно ищет кратчайший путь (минимум в каждой строке матрицы).
Еще бы отчитаться (а то вечно заваливают дурацкими вопросами).
Попробую проверить.
Спасибо....

Re: Задача коммивояжера

Извиняюсь конечно, мы с lambda не работали, поэтому вопрос: при проверке работы программы я ввожу   (print (COMMI_ROUTE '((0 3 9 8 7)
(3 0 8 5 4) (9 8 0 3 7) (8 5 3 0 4) (7 4 7 4 0) ) '(1 2 3 4 5)))
и выдается ошибка ***- APPLY: argument
(lambda (_dist_list)
  (-(length _dist_list)
  (length (member (apply 'min _dist_list) _dist_list))))  is not a function.
To get a function in the current environment, write (function ...).
To get a function in the current environment, write (function ...).
To get a function in the global environment, write (coerce '...' function).
Что это такое и как работать с lambda.

Re: Задача коммивояжера

Попробуем модификацию:

(defun COMMI_ROUTE (_dist_matrix _path)
 (if _dist_matrix
  (cons
   (car _path)
   (apply 'COMMI_ROUTE
    (apply
     (function
      (lambda (_count)
       (list
        (COL_SWAP (mapcar 'cdr (ROW_SWAP (cdr _dist_matrix) _count)) _count)
        (ROW_SWAP (cdr _path) _count))))
     (list
      (apply
       (function
        (lambda (_dist_list)
         (-
          (length _dist_list)
          (length (member (apply 'min _dist_list) _dist_list)))))
       (list (cdr (car _dist_matrix))))))))))

Можно поинтересоваться, каково наименование учреждения образования, где а) при изучении LISPа не успевают добраться до одной из его основ(!!!) - ламбда-нотации и при этом б) позволяют себе "заваливать" дурацкими вопросами?
Цитата из Хювёнена-Сеппянена (раздел 2.5, стр.104):

Лямбда-выражение изображает параметризованные вычисления
Определение функций и их вычисление в Лиспе основано на лямбда-исчислении (lambda calculus) Черча, предлагающем для этого точный и простой формализм. Лямбда-выражение, позаимствованное Лиспом из лямбда-исчисления, является важным механизмом в практическом программировании.

Объявляем лозунг момента - "Забодаем препов!" и включаем режим вопросов/ответов.
Может быть, сходить в разведку: показать текст и попросить разъяснения какого-нибудь малозначительного пункта. Шквал вопросов препа(ов) записать(запомнить), от ответа уйти, сославшись на что-нибудь типа недостатка времени, и обнародовать вопросы здесь.

Re: Задача коммивояжера

Кстати, как называется реализация языка в вашем учреждении? А то приглядевшись выяснилось, что в конструкции функции (defun) есть некий "сюрприз". Снова процитируем Хювёнена-Сеппянена (раздел 2.5 стр.109):

DEFUN дает имя описанию функции
...DEFUN вызывается так:
(DEFUN имя лямбда-список тело)
Что можно представить себе как сокращение записи
(DEFUN имя лямбда-выражение)
из которой для удобства исключены внешние скобки лямбда-выражения и сам атом LAMBDA. Например:
(defun list1 (lambda (x y) (cons x (cons y nil))))
=>
(defun list1         (x y) (cons x (cons y nil)))
DEFUN соединяет символ с лямбда-выражением, и символ начинает представлять (именовать) определенные этим лямбда-выражением вычисления...

Оказывается, что в данной реализации никакого исключения и сокращения нету.
Придется модифицировать текст:

(defun COMMI_ROUTE (lambda (_dist_matrix _path)
 (if _dist_matrix
  (cons
   (car _path)
   (apply 'COMMI_ROUTE
    (apply
     (function
      (lambda (_count)
       (list
        (COL_SWAP (mapcar 'cdr (ROW_SWAP (cdr _dist_matrix) _count)) _count)
        (ROW_SWAP (cdr _path) _count))))
     (list
      (apply
       (function
        (lambda (_dist_list)
         (-
          (length _dist_list)
          (length (member (apply 'min _dist_list) _dist_list)))))
       (list (cdr (car _dist_matrix)))))))))))
(defun ROW_SWAP (lambda (_list _count)
 (if (> _count 0)
  (ROW_SWAP (append (cdr _list) (list (car _list))) (1- _count))
  _list)))
(defun COL_SWAP (lambda (_list _count)
 (if (> _count 0)
  (COL_SWAP (mapcar 'append (mapcar 'cdr _list) (mapcar 'list (mapcar 'car _list))) (1- _count))
  _list)))

Re: Задача коммивояжера

Учусь я в СГУ. В справки есть маленький пункт про lambda, но с примерами как-то плоховато. При изучении (сделала 4 лабораторных) ее знание не понадобилось (очень простенькие были задания), но когда выдали эту лабораторку у всех "волосы дыбом встали" (это мне еще вроде повезло, кому-то досталась игра пятнашки, пирамиды).  А знаний-то маловато для решения задачки коммивояжера (был бы это паскаль или питон я бы решила). Поэтому и приходится просить помощи.

Re: Задача коммивояжера

Попробовала запустить программу от
(2005-12-05 00:56:43). Но выдается ошибка APPLY: too few arguments given to MIN.

Re: Задача коммивояжера

1) Саратовский Государственный Университет,
Ставропольский Государственный Университет,
Санкт-Петербургский Государственный Университет,
Смоленский Гуманитарный Университет?
2) Как называется реализация языка?
3) счас наступим себе на горло и перепишем с еще одной именованной функцией, выдающей количество перестановок

Re: Задача коммивояжера

У нас выражение

(apply 'min '())

вычисляется в 0. Ежели у вас это выражение не вычисляется, то попробуем обходной вариант:

(defun COMMI_ROUTE (lambda (_dist_matrix _path)
 (if _dist_matrix
  (cons
   (car _path)
   (apply 'COMMI_ROUTE
    (apply
     (function
      (lambda (_count)
       (list
        (COL_SWAP (mapcar 'cdr (ROW_SWAP (cdr _dist_matrix) _count)) _count)
        (ROW_SWAP (cdr _path) _count))))
     (list
      (SWAP_COUNT (cdr (car _dist_matrix))))))))))
(defun ROW_SWAP (lambda (_list _count)
 (if (> _count 0)
  (ROW_SWAP (append (cdr _list) (list (car _list))) (1- _count))
  _list)))
(defun COL_SWAP (lambda (_list _count)
 (if (> _count 0)
  (COL_SWAP (mapcar 'append (mapcar 'cdr _list) (mapcar 'list (mapcar 'car _list))) (1- _count))
  _list)))
(defun SWAP_COUNT
 (lambda (_dist_list)
  (if _dist_list
   (-
    (length _dist_list)
    (length (member (apply 'min _dist_list) _dist_list)))
   0)))

Функция SWAP_COUNT вычисляет количество перестановок строк и столбцов.

Re: Задача коммивояжера

Ура она работает! Только я постирала из программы все lambda (надеюсь это на ее работе никак не отразится). Работаем мы на Common Lisp (какого года не знаю).
А Вы откуда (где такие умные и добрые люди живут)?

Re: Задача коммивояжера

1) Ну слава Богу!
2) В определениях DEFUN эти lambda действительно излишние (во всяком случае в Common Lisp по Хювёнену-Сеппянену - см.выше)
3) А вот при применении безымянной функции (которая делает список аргументов для рекурсивного применения функции COMMI_ROUTE) без lambda...?
4) Что же означает буква С в аббревиатуре "СГУ" (интерес чисто географический)