Notice: Undefined index: moes_p in /home/elpanov/elpanov.com/docs/index.php(3) : eval()'d code on line 1
ElpanovEvgeniy | Рекурсии, урок 9

Новая версия сайта, доступна здесь.




Рекурсии, урок 9

На прошлом уроке, мы рассмотрели программу сортировки списка методом выбора или "Selection sort". Очевидно, что его можно улучшить, выбирая не только минимальные значения из списка, но и максимальные. Т.е. мы будем выбирать из списка минимальное и максимальное значения и добавлять их в результирующий список в начало и конец, а оставшийся после удаления первых вхождений этих элементов список будем снова обрабатывать с целью поиска минимального и максимального значений. Этот алгоритм аналогичен предыдущему. Для реализации потребуется три подпрограммы:
1 — поиск самого минимального и максимального значения списка
2 — удаление первого вхождения элемента, заданного аргументом, из списка
3 — запуск в нужной последовательности, первых двух программ и формирование результирующего списка...

 
(defun rec-min-max (lst mi ma f)
  ; Вычисляем минимальное и максимальные
  ; значения списка применяя тестовую функцию
  (cond
    ((not lst) (list mi ma))
    (((eval f) (car lst) mi)
     (rec-min-max (cdr lst) (car lst) ma f)
    )
    (((eval f) ma (car lst))
     (rec-min-max (cdr lst) mi (car lst) f)
    )
    (t (rec-min-max (cdr lst) mi ma f))
  ) ;_  cond
) ;_  defun

(defun rec-remove-singl (i lst)
  ; Удаляем первое вхождение элемента из списка
  (if lst
    (if (equal i (car lst))
      (cdr lst)
      (cons (car lst) (rec-remove-singl i (cdr lst)))
    ) ;_  if
  ) ;_  if
) ;_  defun

(defun rec-sort-min-max (lst f)
  ;(rec-sort-min-max lst f)
  (cond
    ((not lst) nil)
    ((not(cdr lst)) lst)
    (t
     ((lambda (x)
        (cons
          (car x)
          (append
            (rec-sort-min-max
              (rec-remove-singl
                (car x)
                (rec-remove-singl
                  (cadr x)
                  lst
                ) ;_  rec-remove-singl
              ) ;_  rec-remove-singl
              f
            ) ;_  rec-sort-lists
            (cdr x)
          ) ;_  append
        ) ;_  cons
      ) ;_  lambda
       (rec-min-max (cdr lst) (car lst) (car lst) f)
     )
    )
  ) ;_  cond
) ;_  defun

  ; Проверим:
(setq lst '(7 3 4 6 9 6 7 2 5 3 2 3 6 4 6 3 1)
      f   (function <)
) ;_  setq

(rec-sort-min-max lst f)
; Возвращает
; '(1 2 2 3 3 3 3 4 4 5 6 6 6 6 7 7 9)
 

Т.к. работа рекурсии очень похожа на рассмотренную на восьмом уроке, я собираюсь более коротко рассматривать работу функций, делая акцент на изменениях. Рассмотрим работу рекурсии, которая ищет минимальное и максимальные значения.

 
(defun rec-min-max (lst mi ma f)
  ; Вычисляем минимальное и максимальные
  ; значения списка применяя тестовую функцию
  (cond
    ((not lst) (list mi ma))
    (((eval f) (car lst) mi)
     (rec-min-max (cdr lst) (car lst) ma f)
    )
    (((eval f) ma (car lst))
     (rec-min-max (cdr lst) mi (car lst) f)
    )
    (t (rec-min-max (cdr lst) mi ma f))
  ) ;_  cond
) ;_  defun

; Проверим:
(setq lst '(7 3 4 6 9 6 7 2 5 3 2 3 6 4 6 3 1)
      f   (function <)
) ;_  setq

(rec-min-max (cddr lst) (car lst)(car lst) f)
; Возвращает
; '(1 9)
 

Как видно из кода — программа отличается от rec-min дополнительным аргументом и дополнительной проверкой... Дополнительный аргумент — переменная, в которой будем сохранять максимальное значение, а дополнительная проверка, для его поиска.

 
(defun rec-min-max (lst mi ma f)
  (cond
    ((not lst) ; Если кончился список
     (list mi ma)
  ; Возвращаем список из минимального и максимального значения
    )
    (((eval f) ; Активируем функцию
       (car lst) ; Первый элемент списка
       mi ; Текущее минимальное значение
     ) ; Если Т переходим на следующую строку и меняем минимальное значение
     (rec-min-max ; Самовызов рекурсии
       (cdr lst) ; Укороченный список
       (car lst) ; Новое минимальное значение
       ma ; Старое максимальное значение
       f ; Тестовая функция
     ) ;_  rec-min-max
    )
    (((eval f) ; Активируем функцию
       ma ; Текущее максимальное значение
       (car lst) ; Первый элемент списка
     ) ; Если Т переходим на следующую строку и меняем максимальное значение
     (rec-min-max ; Самовызов рекурсии
       (cdr lst) ; Укороченный список
       mi ; Старое минимальное значение
       (car lst) ; Новое максимальное значение
       f ; Тестовая функция
     ) ;_  rec-min-max
    )
    (t ; Если дошли, всегда правда
     (rec-min-max ; Самовызов рекурсии
       (cdr lst) ; Укороченный список
       mi ; Старое минимальное значение
       ma ; Старое максимальное значение
       f ; Тестовая функция
     ) ;_  rec-min-max
    )
  ) ;_  cond
) ;_  defun

; Проверим:
(setq lst '(10 7 3 4 6 9 6 7 2 5 3 2 3 1 6 4 6 3)
      f   (function <)
) ;_  setq

(rec-min-max (cddr lst) (car lst)(car lst) f)
; Возвращает
; '(1 10)
 

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

 
(not(cdr lst)) ;Если список без первого элемента не пустой.
 

Такой подход быстрее, чем:

 
(> (length lst) 1)
 

Далее все по аналогии с предыдущей функцией rec-sort-min. Два раза вызываем функцию удаления первого вхождения элемента, первый раз для минимального, второй для максимального значения. Потом формируем окончательный список, минимальное значение ставим в начало функцией cons а максимальное append ...

 
(defun rec-sort-min-max (lst f)
  (cond
    ((not lst) ; Если кончился список
     nil
    )
    ((not (cdr lst)) ;Если список без первого элемента не пустой.
     lst ; Список с одним элементом
    )
    (t
     ((lambda (x)
        ;; Пользовательская функция
        ;; С аргументом — список из минимального
        ;; и максимального значения
        (cons ; Формируем начало списка
          (car x) ; Минимальное значение списка
          (append ; Формируем конец списка
            (rec-sort-min-max
              (rec-remove-singl
                (car x)
                (rec-remove-singl
                  (cadr x)
                  lst
                ) ;_  заканчиваем удаление максимального значения
              ) ;_  заканчиваем удаление минимального значения
              f ; Тестовая функция
            ) ;_  заканчиваем рекурсию для дочерней рекурсии с укороченным списком
            (cdr x) ; Максимальное значение списка
          ) ;_  append
        ) ;_  cons
      ) ;_  lambda
       (rec-min-max (cdr lst) (car lst) (car lst) f)
  ; Поиск минимального и максимального значения
     )
    )
  ) ;_  cond
) ;_  defun

  ; Проверим:
(setq lst '(7 3 4 6 9 6 7 2 5 3 2 3 6 4 6 3 1)
      f   (function <)
) ;_  setq

(rec-sort-min-max lst f)
; Возвращает
; '(1 2 2 3 3 3 3 4 4 5 6 6 6 6 7 7 9)
 

Хотелось бы добавить, что этот вариант сортировки быстрее предыдущего не значительно. Его можно еще улучшить, например, изменив функцию удаления, чтоб она брала в качестве аргумента список...

Задание для самостоятельной работы:
Измените программу из урока 9: пусть функция удаления вызывается один раз, со списком удаляемых элементов.