В этой лекции речь пойдет о списках, элементами которых являются числа. Хотя в большинстве задач, которые будут рассматриваться, неважно, к какому домену относятся элементы списка, для определенности будем считать, что это целые числа.
Таким образом, списки, с которыми мы планируем работать, могут быть представлены в разделе описания доменов примерно следующим образом:
DOMAINS listI = integer*
Для разминки решим несложный пример.
Пример. Создадим предикат, позволяющий вычислить
Решение будет напоминать подсчет количества элементов списка. Отличаться они будут шагом рекурсии. При подсчете количества элементов нам было неважно, чему равен первый элемент списка, мы просто добавляли единицу к уже подсчитанному количеству элементов хвоста. При вычислении суммы нужно будет учесть значение головы списка.
Так как в пустом списке элементов нет, сумма элементов пустого списка равна нулю. Для вычисления суммы элементов непустого списка нужно к сумме элементов хвоста добавить первый элемент списка. Запишем эту идею:
sum([], 0). /* сумма элементов пустого списка равна
нулю */
sum([H|T], S) :–
sum(T, S_T), /* S_T — сумма элементов хвоста */
S = S_T + H. /* S — сумма элементов исходного
списка */
Попробуйте самостоятельно изменить этот предикат так, чтобы он вычислял не
Еще один вариант данного предиката можно написать, используя накопитель. В нем будем хранить уже насчитанную сумму. Начинаем с пустым накопителем. Переходя от вычисления суммы непустого списка к вычислению суммы элементов его хвоста, будем добавлять первый элемент к уже насчитанной сумме. Когда элементы списка будут исчерпаны (список опустеет), передадим "накопленную" сумму в качестве результата в третий аргумент.
Запишем:
sum_list([],S,S). /* список стал пустым, значит
в накопителе — сумма элементов
списка */
sum_list([H|T],N,S) :–
N_T=H+N,
/* N_T — результат добавления к сумме,
находящейся в накопителе, первого
элемента списка */
sum_list(T,N_T,S).
/* вызываем предикат от хвоста T и N_T */
Если нам нужно вызвать предикат от двух аргументов, а не от трех, то можно добавить вспомогательный предикат:
sum2(L,S):–
sum_list(L,0,S).
Последний вариант, в отличие от первого, реализует
Разберем еще один простой пример.
Пример. Напишем предикат, вычисляющий
Конечно, можно, как всегда, опереться на рекурсию, но проще воспользоваться тем, что у нас уже есть предикаты, которые позволяют вычислить количество и
avg(L,A):–
summa(L,S), /* помещаем в переменную S сумму
элементов списка */
length(L,K), /* переменная K равна количеству
элементов списка */
A=S/K. /* вычисляем среднее как отношение суммы
к количеству */
Единственная проблема возникает при попытке найти среднее арифметическое элементов пустого списка. Если мы попытаемся вызвать цель avg([],A), то получим сообщение об ошибке "Division by zero" ("Деление на ноль"). Это произойдет, потому что предикат length([],K ) конкретизирует переменную K нулем, а при попытке достижения третьей подцели A=S/K и произойдет вышеупомянутая ошибка. Можно посчитать это нормальной реакцией предиката. Раз в пустом списке нет элементов, значит, нет и их среднего арифметического. А можно изменить этот предикат так, чтобы он работал и с пустым списком.
Дабы обойти затруднение с пустым списком, добавим в нашу процедуру, в виде факта, информацию о том, что среднее арифметическое элементов пустого списка равно нулю. Полное решение будет выглядеть следующим образом.
avg([],0):–!.
avg(L,A):–
summa(L,S),
length(L,K),
A=S/K.
Описывая этот предикат в разделе описания предикатов PREDICATES, обратите внимание на то, что второй аргумент будет не целого типа, а вещественного (при делении одного целого числа на другое целое число частное может получиться нецелым).
Пример. Создадим предикат, находящий
Как обычно, наше решение будет рекурсивным. Но так как для пустого списка понятие минимального элемента не имеет смысла, базис рекурсии мы запишем не для пустого, а для одноэлементного списка. В одноэлементном списке, естественно, минимальным элементом будет тот самый единственный элемент списка ("при всем богатстве выбора другой альтернативы нет!").
Шаг рекурсии: найдем минимум из первого элемента списка и минимального элемента хвоста — это и будет минимальный элемент всего списка.
Оформим эти рассуждения:
min_list([X],X). /* единственный элемент одноэлементного
списка является минимальным элементом
списка */
min_list([H|T],M):–
min_list(T,M_T), /* M_T — минимальный элемент хвоста */
min(H,M_T,M). /* M — минимум из M_T и первого элемента
исходного списка */
Обратите внимание на то, что в правиле, позволяющем осуществить шаг рекурсии, использован предикат min, подобный предикату max, который был разобран нами в третьей лекции.
Слегка модифицировав предикат min_list (подставив в правило вместо предиката min предикат max и поменяв его название), получим предикат, находящий не минимальный, а максимальный элемент списка.
Перейдем теперь к более интересной задаче, а именно, к сортировке списков. Под сортировкой обычно понимают расстановку элементов в некотором порядке. Для определенности мы будем упорядочивать элементы списков по неубыванию. То есть, если сравнить любые два соседних элемента списка, то следующий должен быть не меньше предыдущего.
Существует множество алгоритмов сортировки. Заметим, что имеется два класса алгоритмов сортировки: сортировка данных, целиком расположенных в основной памяти ( внутренняя сортировка ), и сортировка файлов, хранящихся во внешней памяти ( внешняя сортировка ). Мы займемся исключительно методами внутренней сортировки.
Рассмотрим наиболее известные методы внутренней сортировки и выясним, как можно применить их для сортировки списков в Прологе.
Начнем с наиболее известного "пузырькового" способа сортировки. Его еще называют методом прямого обмена или методом простого обмена.
Аналогия с пузырьком вызвана тем, что при каждом проходе минимальные элементы как бы "всплывают" к началу списка.
Реализуем , будет сравнивать два первых элемента списка и в случае, если первый окажется больше второго, менять их местами. Если же первая пара расположена в правильном порядке, этот предикат будет переходить к рассмотрению хвоста.
Основной предикат bubble будет осуществлять .
permutation([X,Y|T],[Y,X|T]):–
X>Y,!.
/* переставляем первые два
элемента, если первый больше
второго */
permutation([X|T],[X|T1]):–
permutation(T,T1).
/*переходим к перестановкам
в хвосте*/
bubble(L,L1):–
permutation(L,LL), /* вызываем предикат,
осуществляющий перестановку */
!,
bubble(LL,L1). /* пытаемся еще раз отсортировать
полученный список */
bubble(L,L). /* если перестановок не было, значит список
отсортирован */
Но наш пузырьковый метод работает только до тех пор, пока есть хотя бы пара элементов списка, расположенных в неправильном порядке. Как только такие элементы закончились, предикат терпит неудачу, а bubble переходит от правила к факту и возвращает в качестве второго аргумента отсортированный список.
Теперь рассмотрим
Задача предиката insert — вставить значение (голову исходного списка) в уже отсортированный список (хвост исходного списка), так чтобы он остался упорядоченным. Его первым аргументом будет вставляемое значение, вторым — отсортированный список, третьим — список, полученный вставкой первого аргумента в нужное место второго аргумента так, чтобы не нарушить порядок.
Предикат ins_sort, собственно, и будет организовывать сортировку исходного списка методом вставок. В качестве первого аргумента ему дают произвольный список, который нужно отсортировать. Вторым аргументом он возвращает список, состоящий из элементов исходного списка, стоящих в правильном порядке.
ins_sort([ ],[ ]). /* отсортированный пустой список
остается пустым списком */
ins_sort([H|T],L):–
ins_sort(T,T_Sort),
/* T — хвост исходного списка,
T_Sort — отсортированный хвост
исходного списка */
insert(H,T_Sort,L).
/* вставляем H (первый элемент
исходного списка)в T_Sort,
получаем L (список, состоящий
из элементов исходного списка,
стоящих по неубыванию) */
insert(X,[],[X]). /* при вставке любого значения в пустой
список, получаем одноэлементный
список */
insert(X,[H|T],[H|T1]):–
X>H,!, /* если вставляемое значение
больше головы списка, значит
его нужно вставлять в хвост */
insert(X,T,T1).
/* вставляем X в хвост T,
в результате получаем
список T1 */
insert(X,T,[X|T]). /* это предложение (за счет отсечения
в предыдущем правиле) выполняется,
только если вставляемое значение
не больше головы списка T, значит,
добавляем его первым элементом
в список T */
min_list, который мы придумали в начале этой лекции). Удаляем его из списка (с помощью предиката delete_one, рассмотренного в предыдущей лекции). Оставшийся список сортируем. Приписываем минимальный элемент в качестве головы к отсортированному списку. Так как этот элемент был меньше всех элементов исходного списка, он будет меньше всех элементов отсортированного списка. И, следовательно, если его поместить в голову отсортированного списка, то порядок не нарушится
Запишем:
choice([ ],[ ]). /* отсортированный пустой список
остается пустым списком */
choice(L,[X|T]):– /* приписываем X (минимальный элемент
списка L) к отсортированному
списку T */
min_list(L,X), /* X — минимальный элемент
списка L */
delete_one(X,L,L1),
/* L1 — результат удаления
первого вхождения
элемента X из списка L */
choice(L1,T). /* сортируем список L1,
результат обозначаем T */
Автором так называемой
При воплощении в программный код этой идеи нам, как обычно, понадобится пара предикатов.
Вспомогательный предикат partition будет отвечать за разбиение списка на два подсписка. У него будет четыре аргумента. Первые два элемента — входные: первый — исходный список, второй — барьерный элемент. Третий и четвертый элементы — выходные, соответственно, список элементов исходного списка, которые меньше барьерного, и список, состоящий из элементов, которые не меньше барьерного элемента.
Предикат quick_sort будет реализовывать алгоритм partition разделение непустого списка на два подсписка, затем сортировать каждый из этих подсписков рекурсивным вызовом себя самого, после чего, используя предикат conc (созданный нами ранее), конкретизирует второй аргумент списком, получаемым объединением отсортированного первого подсписка и списка, сконструированного из барьерного элемента (головы исходного списка) и отсортированного второго подсписка. Запишем это:
quick_sort([],[]). /* отсортированный пустой список
остается пустым списком */
quick_sort([H|T],O):–
partition(T,H,L,G),
/* делим список T на L (список
элементов меньших барьерного
элемента H) и G (список
элементов не меньших H) */
quick_sort(L,L_s),
/* список L_s — результат
упорядочивания элементов
списка L */
quick_sort(G,G_s),
/* аналогично, список G_s —
результат упорядочивания
элементов списка G */
conc(L_s,[H|G_s],O).
/* соединяем список L_s со
списком, у которого голова H,
а хвост G_s, результат
обозначаем O */
partition([],_,[],[]). /* как бы мы ни делили элементы
пустого списка, ничего кроме
пустых списков не получим */
partition([X|T],Y,[X|T1],Bs):–
X<Y,!,
partition(T,Y,T1,Bs).
/* если элемент X меньше барьерного
элемента Y, то мы добавляем его
в третий аргумент */
partition([X|T],Y,T1,[X|Bs]):–
partition(T,Y,T1,Bs).
/* в противном случае дописываем
его в четвертый аргумент */
Прежде чем перейти к изучению следующего алгоритма сортировки, решим одну вспомогательную задачу.
Пусть у нас есть два упорядоченных списка, и мы хотим объединить их элементы в один список так, чтобы объединенный список также остался отсортированным.
Идея реализации предиката, осуществляющего слияние двух отсортированных списков с сохранением порядка, довольно проста. Будем на каждом шаге сравнивать головы наших упорядоченных списков и ту из них, которая меньше, будем переписывать в результирующий список. И так до тех пор, пока один из списков не закончится. Когда один из списков опустеет, нам останется дописать остатки непустого списка к уже построенному итогу. В результате получим список, состоящий из элементов двух исходных списков, причем элементы его расположены в нужном нам порядке.
Создадим предикат (назовем его ), реализующий приведенное описание. Так как мы не знаем, какой из списков опустеет раньше, нам необходимо "гнать" рекурсию сразу по обоим базовым спискам. У нас будет два факта — основания рекурсии, которые будут утверждать, что если мы сливаем некий список с пустым списком, то в итоге получим, естественно, тот же самый список. Причем этот факт имеет место и в случае, когда первый список пуст, и в случае, когда пуст второй список.
Шаг рекурсии нам дадут два правила: первое будет утверждать, что если голова первого списка меньше головы второго списка, то именно голову первого списка и нужно дописать в качестве головы в результирующий список, после чего перейти к слиянию хвоста первого списка со вторым. Результат этого слияния будет хвостом итогового списка. Второе правило, напротив, будет дописывать голову второго списка в качестве головы результирующего списка, сливать первый список с хвостом второго списка. Итог этого слияния будет хвостом объединенного списка.
fusion(L1,[ ],L1):–!. /* при слиянии списка L1 с пустым
списком получаем список L1 */
fusion([ ],L2,L2):–!. /* при слиянии пустого списка
со списком L2 получаем список
L2 */
fusion([H1|T1],[H2|T2],[H1|T]):–
H1<H2,!,
/* если голова первого списка H1
меньше головы второго списка H2 */
fusion(T1, [H2|T2],T).
/* сливаем хвост первого списка T1
со вторым списком [H2|T2] */
fusion(L1, [H2|T2],[H2|T]):–
fusion(L1,T2,T).
/* сливаем первый список L1
с хвостом второго списка T2 */
Теперь можно перейти к изучению алгоритма
Для начала создадим предикат, который будет делить исходный список на два. Он будет состоять из двух фактов и правила. Первый факт будет утверждать, что пустой список можно разбить только на два пустых подсписка. Второй факт будет предлагать разбиение одноэлементного списка на тот же одноэлементный список и пустой список. Правило будет работать в случаях, не охваченных фактами, т.е. когда упорядочиваемый список содержит не менее двух элементов. В этой ситуации мы будем отправлять первый элемент списка в первый подсписок, второй элемент — во второй подсписок, и продолжать распределять элементы хвоста исходного списка.
splitting([],[],[])./* пустой список можно расщепить
только на пустые подсписки */
splitting([H],[H],[]). /* одноэлементный список разбиваем
на одноэлементный список
и пустой список */
splitting([H1,H2|T],[H1|T1],[H2|T2]):–
splitting(T,T1,T2).
/* элемент H1 отправляем в первый
подсписок, элемент H2 — во второй
подсписок, хвост T разбиваем
на подсписки T1 и T2 */
Теперь можно приступать к записи основного предиката, который, собственно, и будет осуществлять сортировку списка. Он будет состоять из трех предложений. Первое будет декларировать очевидный факт, что при сортировке пустого списка получается пустой список. Второе утверждает, что одноэлементный список также уже является упорядоченным. В третьем правиле будет содержаться суть метода splitting, затем каждый из них сортируется рекурсивным вызовом предиката fusion_sort, и, наконец, используя предикат , сливаем полученные упорядоченные подсписки в один список, который и будет результатом упорядочивания элементов исходного списка.
Запишем изложенные выше соображения.
fusion_sort([],[]):–!./* отсортированный пустой список
остается пустым списком */
fusion_sort([H],[H]):–!. /* одноэлементный список
упорядочен */
fusion_sort(L,L_s):–
splitting(L,L1,L2),
/* расщепляем список L на два
подсписка */
fusion_sort(L1,L1_s),
/* L1_s — результат сортировки
L1 */
fusion_sort(L2,L2_s),
/* L2_s — результат сортировки
L2 */
fusion(L1_s,L2_s,L_s).
/* сливаем L1_s и L2_s
в список L_s */
Фактически этот алгоритм при прямом проходе дробит список на одноэлементные подсписки, после чего на обратном ходе рекурсии сливает их двухэлементные списки. На следующем этапе сливаются двухэлементные списки и т.д. На последнем шаге два подсписка сливаются в итоговый, отсортированный список.
В качестве завершения темы сортировки разработаем предикат, который будет проверять, является ли список упорядоченным. Это совсем не сложно. Для того чтобы список был упорядоченным, он должен быть либо пустым, либо одноэлементным, либо любые два его соседних элемента должны быть расположены в правильном порядке. Запишем эти рассуждения.
sorted([ ]). /* пустой список отсортирован */
sorted([_])./* одноэлементный список упорядочен */
sorted([X,Y|T]):–
X<=Y,
sorted([Y|T]).
/* список упорядочен, если первые
два элемента расположены
в правильном порядке и список,
образованный вторым элементом
и хвостом исходного, упорядочен */
avg без использования предикатов summa и length.min_list без использования предиката min.choice, реализующий алгоритм В этой лекции речь пойдет о списках, элементами которых являются числа. Хотя в большинстве задач, которые будут рассматриваться, неважно, к какому домену относятся элементы списка, для определенности будем считать, что это целые числа.
Таким образом, списки, с которыми мы планируем работать, могут быть представлены в разделе описания доменов примерно следующим образом:
DOMAINS listI = integer*
Для разминки решим несложный пример.
Пример. Создадим предикат, позволяющий вычислить
Решение будет напоминать подсчет количества элементов списка. Отличаться они будут шагом рекурсии. При подсчете количества элементов нам было неважно, чему равен первый элемент списка, мы просто добавляли единицу к уже подсчитанному количеству элементов хвоста. При вычислении суммы нужно будет учесть значение головы списка.
Так как в пустом списке элементов нет, сумма элементов пустого списка равна нулю. Для вычисления суммы элементов непустого списка нужно к сумме элементов хвоста добавить первый элемент списка. Запишем эту идею:
sum([], 0). /* сумма элементов пустого списка равна
нулю */
sum([H|T], S) :–
sum(T, S_T), /* S_T — сумма элементов хвоста */
S = S_T + H. /* S — сумма элементов исходного
списка */
Попробуйте самостоятельно изменить этот предикат так, чтобы он вычислял не
Еще один вариант данного предиката можно написать, используя накопитель. В нем будем хранить уже насчитанную сумму. Начинаем с пустым накопителем. Переходя от вычисления суммы непустого списка к вычислению суммы элементов его хвоста, будем добавлять первый элемент к уже насчитанной сумме. Когда элементы списка будут исчерпаны (список опустеет), передадим "накопленную" сумму в качестве результата в третий аргумент.
Запишем:
sum_list([],S,S). /* список стал пустым, значит
в накопителе — сумма элементов
списка */
sum_list([H|T],N,S) :–
N_T=H+N,
/* N_T — результат добавления к сумме,
находящейся в накопителе, первого
элемента списка */
sum_list(T,N_T,S).
/* вызываем предикат от хвоста T и N_T */
Если нам нужно вызвать предикат от двух аргументов, а не от трех, то можно добавить вспомогательный предикат:
sum2(L,S):–
sum_list(L,0,S).
Последний вариант, в отличие от первого, реализует
Разберем еще один простой пример.
Пример. Напишем предикат, вычисляющий
Конечно, можно, как всегда, опереться на рекурсию, но проще воспользоваться тем, что у нас уже есть предикаты, которые позволяют вычислить количество и
avg(L,A):–
summa(L,S), /* помещаем в переменную S сумму
элементов списка */
length(L,K), /* переменная K равна количеству
элементов списка */
A=S/K. /* вычисляем среднее как отношение суммы
к количеству */
Единственная проблема возникает при попытке найти среднее арифметическое элементов пустого списка. Если мы попытаемся вызвать цель avg([],A), то получим сообщение об ошибке "Division by zero" ("Деление на ноль"). Это произойдет, потому что предикат length([],K ) конкретизирует переменную K нулем, а при попытке достижения третьей подцели A=S/K и произойдет вышеупомянутая ошибка. Можно посчитать это нормальной реакцией предиката. Раз в пустом списке нет элементов, значит, нет и их среднего арифметического. А можно изменить этот предикат так, чтобы он работал и с пустым списком.
Дабы обойти затруднение с пустым списком, добавим в нашу процедуру, в виде факта, информацию о том, что среднее арифметическое элементов пустого списка равно нулю. Полное решение будет выглядеть следующим образом.
avg([],0):–!.
avg(L,A):–
summa(L,S),
length(L,K),
A=S/K.
Описывая этот предикат в разделе описания предикатов PREDICATES, обратите внимание на то, что второй аргумент будет не целого типа, а вещественного (при делении одного целого числа на другое целое число частное может получиться нецелым).
Пример. Создадим предикат, находящий
Как обычно, наше решение будет рекурсивным. Но так как для пустого списка понятие минимального элемента не имеет смысла, базис рекурсии мы запишем не для пустого, а для одноэлементного списка. В одноэлементном списке, естественно, минимальным элементом будет тот самый единственный элемент списка ("при всем богатстве выбора другой альтернативы нет!").
Шаг рекурсии: найдем минимум из первого элемента списка и минимального элемента хвоста — это и будет минимальный элемент всего списка.
Оформим эти рассуждения:
min_list([X],X). /* единственный элемент одноэлементного
списка является минимальным элементом
списка */
min_list([H|T],M):–
min_list(T,M_T), /* M_T — минимальный элемент хвоста */
min(H,M_T,M). /* M — минимум из M_T и первого элемента
исходного списка */
Обратите внимание на то, что в правиле, позволяющем осуществить шаг рекурсии, использован предикат min, подобный предикату max, который был разобран нами в третьей лекции.
Слегка модифицировав предикат min_list (подставив в правило вместо предиката min предикат max и поменяв его название), получим предикат, находящий не минимальный, а максимальный элемент списка.
Перейдем теперь к более интересной задаче, а именно, к сортировке списков. Под сортировкой обычно понимают расстановку элементов в некотором порядке. Для определенности мы будем упорядочивать элементы списков по неубыванию. То есть, если сравнить любые два соседних элемента списка, то следующий должен быть не меньше предыдущего.
Существует множество алгоритмов сортировки. Заметим, что имеется два класса алгоритмов сортировки: сортировка данных, целиком расположенных в основной памяти ( внутренняя сортировка ), и сортировка файлов, хранящихся во внешней памяти ( внешняя сортировка ). Мы займемся исключительно методами внутренней сортировки.
Рассмотрим наиболее известные методы внутренней сортировки и выясним, как можно применить их для сортировки списков в Прологе.
Начнем с наиболее известного "пузырькового" способа сортировки. Его еще называют методом прямого обмена или методом простого обмена.
Аналогия с пузырьком вызвана тем, что при каждом проходе минимальные элементы как бы "всплывают" к началу списка.
Реализуем , будет сравнивать два первых элемента списка и в случае, если первый окажется больше второго, менять их местами. Если же первая пара расположена в правильном порядке, этот предикат будет переходить к рассмотрению хвоста.
Основной предикат bubble будет осуществлять .
permutation([X,Y|T],[Y,X|T]):–
X>Y,!.
/* переставляем первые два
элемента, если первый больше
второго */
permutation([X|T],[X|T1]):–
permutation(T,T1).
/*переходим к перестановкам
в хвосте*/
bubble(L,L1):–
permutation(L,LL), /* вызываем предикат,
осуществляющий перестановку */
!,
bubble(LL,L1). /* пытаемся еще раз отсортировать
полученный список */
bubble(L,L). /* если перестановок не было, значит список
отсортирован */
Но наш пузырьковый метод работает только до тех пор, пока есть хотя бы пара элементов списка, расположенных в неправильном порядке. Как только такие элементы закончились, предикат терпит неудачу, а bubble переходит от правила к факту и возвращает в качестве второго аргумента отсортированный список.
Теперь рассмотрим
Задача предиката insert — вставить значение (голову исходного списка) в уже отсортированный список (хвост исходного списка), так чтобы он остался упорядоченным. Его первым аргументом будет вставляемое значение, вторым — отсортированный список, третьим — список, полученный вставкой первого аргумента в нужное место второго аргумента так, чтобы не нарушить порядок.
Предикат ins_sort, собственно, и будет организовывать сортировку исходного списка методом вставок. В качестве первого аргумента ему дают произвольный список, который нужно отсортировать. Вторым аргументом он возвращает список, состоящий из элементов исходного списка, стоящих в правильном порядке.
ins_sort([ ],[ ]). /* отсортированный пустой список
остается пустым списком */
ins_sort([H|T],L):–
ins_sort(T,T_Sort),
/* T — хвост исходного списка,
T_Sort — отсортированный хвост
исходного списка */
insert(H,T_Sort,L).
/* вставляем H (первый элемент
исходного списка)в T_Sort,
получаем L (список, состоящий
из элементов исходного списка,
стоящих по неубыванию) */
insert(X,[],[X]). /* при вставке любого значения в пустой
список, получаем одноэлементный
список */
insert(X,[H|T],[H|T1]):–
X>H,!, /* если вставляемое значение
больше головы списка, значит
его нужно вставлять в хвост */
insert(X,T,T1).
/* вставляем X в хвост T,
в результате получаем
список T1 */
insert(X,T,[X|T]). /* это предложение (за счет отсечения
в предыдущем правиле) выполняется,
только если вставляемое значение
не больше головы списка T, значит,
добавляем его первым элементом
в список T */
min_list, который мы придумали в начале этой лекции). Удаляем его из списка (с помощью предиката delete_one, рассмотренного в предыдущей лекции). Оставшийся список сортируем. Приписываем минимальный элемент в качестве головы к отсортированному списку. Так как этот элемент был меньше всех элементов исходного списка, он будет меньше всех элементов отсортированного списка. И, следовательно, если его поместить в голову отсортированного списка, то порядок не нарушится
Запишем:
choice([ ],[ ]). /* отсортированный пустой список
остается пустым списком */
choice(L,[X|T]):– /* приписываем X (минимальный элемент
списка L) к отсортированному
списку T */
min_list(L,X), /* X — минимальный элемент
списка L */
delete_one(X,L,L1),
/* L1 — результат удаления
первого вхождения
элемента X из списка L */
choice(L1,T). /* сортируем список L1,
результат обозначаем T */
Автором так называемой
При воплощении в программный код этой идеи нам, как обычно, понадобится пара предикатов.
Вспомогательный предикат partition будет отвечать за разбиение списка на два подсписка. У него будет четыре аргумента. Первые два элемента — входные: первый — исходный список, второй — барьерный элемент. Третий и четвертый элементы — выходные, соответственно, список элементов исходного списка, которые меньше барьерного, и список, состоящий из элементов, которые не меньше барьерного элемента.
Предикат quick_sort будет реализовывать алгоритм partition разделение непустого списка на два подсписка, затем сортировать каждый из этих подсписков рекурсивным вызовом себя самого, после чего, используя предикат conc (созданный нами ранее), конкретизирует второй аргумент списком, получаемым объединением отсортированного первого подсписка и списка, сконструированного из барьерного элемента (головы исходного списка) и отсортированного второго подсписка. Запишем это:
quick_sort([],[]). /* отсортированный пустой список
остается пустым списком */
quick_sort([H|T],O):–
partition(T,H,L,G),
/* делим список T на L (список
элементов меньших барьерного
элемента H) и G (список
элементов не меньших H) */
quick_sort(L,L_s),
/* список L_s — результат
упорядочивания элементов
списка L */
quick_sort(G,G_s),
/* аналогично, список G_s —
результат упорядочивания
элементов списка G */
conc(L_s,[H|G_s],O).
/* соединяем список L_s со
списком, у которого голова H,
а хвост G_s, результат
обозначаем O */
partition([],_,[],[]). /* как бы мы ни делили элементы
пустого списка, ничего кроме
пустых списков не получим */
partition([X|T],Y,[X|T1],Bs):–
X<Y,!,
partition(T,Y,T1,Bs).
/* если элемент X меньше барьерного
элемента Y, то мы добавляем его
в третий аргумент */
partition([X|T],Y,T1,[X|Bs]):–
partition(T,Y,T1,Bs).
/* в противном случае дописываем
его в четвертый аргумент */
Прежде чем перейти к изучению следующего алгоритма сортировки, решим одну вспомогательную задачу.
Пусть у нас есть два упорядоченных списка, и мы хотим объединить их элементы в один список так, чтобы объединенный список также остался отсортированным.
Идея реализации предиката, осуществляющего слияние двух отсортированных списков с сохранением порядка, довольно проста. Будем на каждом шаге сравнивать головы наших упорядоченных списков и ту из них, которая меньше, будем переписывать в результирующий список. И так до тех пор, пока один из списков не закончится. Когда один из списков опустеет, нам останется дописать остатки непустого списка к уже построенному итогу. В результате получим список, состоящий из элементов двух исходных списков, причем элементы его расположены в нужном нам порядке.
Создадим предикат (назовем его ), реализующий приведенное описание. Так как мы не знаем, какой из списков опустеет раньше, нам необходимо "гнать" рекурсию сразу по обоим базовым спискам. У нас будет два факта — основания рекурсии, которые будут утверждать, что если мы сливаем некий список с пустым списком, то в итоге получим, естественно, тот же самый список. Причем этот факт имеет место и в случае, когда первый список пуст, и в случае, когда пуст второй список.
Шаг рекурсии нам дадут два правила: первое будет утверждать, что если голова первого списка меньше головы второго списка, то именно голову первого списка и нужно дописать в качестве головы в результирующий список, после чего перейти к слиянию хвоста первого списка со вторым. Результат этого слияния будет хвостом итогового списка. Второе правило, напротив, будет дописывать голову второго списка в качестве головы результирующего списка, сливать первый список с хвостом второго списка. Итог этого слияния будет хвостом объединенного списка.
fusion(L1,[ ],L1):–!. /* при слиянии списка L1 с пустым
списком получаем список L1 */
fusion([ ],L2,L2):–!. /* при слиянии пустого списка
со списком L2 получаем список
L2 */
fusion([H1|T1],[H2|T2],[H1|T]):–
H1<H2,!,
/* если голова первого списка H1
меньше головы второго списка H2 */
fusion(T1, [H2|T2],T).
/* сливаем хвост первого списка T1
со вторым списком [H2|T2] */
fusion(L1, [H2|T2],[H2|T]):–
fusion(L1,T2,T).
/* сливаем первый список L1
с хвостом второго списка T2 */
Теперь можно перейти к изучению алгоритма
Для начала создадим предикат, который будет делить исходный список на два. Он будет состоять из двух фактов и правила. Первый факт будет утверждать, что пустой список можно разбить только на два пустых подсписка. Второй факт будет предлагать разбиение одноэлементного списка на тот же одноэлементный список и пустой список. Правило будет работать в случаях, не охваченных фактами, т.е. когда упорядочиваемый список содержит не менее двух элементов. В этой ситуации мы будем отправлять первый элемент списка в первый подсписок, второй элемент — во второй подсписок, и продолжать распределять элементы хвоста исходного списка.
splitting([],[],[])./* пустой список можно расщепить
только на пустые подсписки */
splitting([H],[H],[]). /* одноэлементный список разбиваем
на одноэлементный список
и пустой список */
splitting([H1,H2|T],[H1|T1],[H2|T2]):–
splitting(T,T1,T2).
/* элемент H1 отправляем в первый
подсписок, элемент H2 — во второй
подсписок, хвост T разбиваем
на подсписки T1 и T2 */
Теперь можно приступать к записи основного предиката, который, собственно, и будет осуществлять сортировку списка. Он будет состоять из трех предложений. Первое будет декларировать очевидный факт, что при сортировке пустого списка получается пустой список. Второе утверждает, что одноэлементный список также уже является упорядоченным. В третьем правиле будет содержаться суть метода splitting, затем каждый из них сортируется рекурсивным вызовом предиката fusion_sort, и, наконец, используя предикат , сливаем полученные упорядоченные подсписки в один список, который и будет результатом упорядочивания элементов исходного списка.
Запишем изложенные выше соображения.
fusion_sort([],[]):–!./* отсортированный пустой список
остается пустым списком */
fusion_sort([H],[H]):–!. /* одноэлементный список
упорядочен */
fusion_sort(L,L_s):–
splitting(L,L1,L2),
/* расщепляем список L на два
подсписка */
fusion_sort(L1,L1_s),
/* L1_s — результат сортировки
L1 */
fusion_sort(L2,L2_s),
/* L2_s — результат сортировки
L2 */
fusion(L1_s,L2_s,L_s).
/* сливаем L1_s и L2_s
в список L_s */
Фактически этот алгоритм при прямом проходе дробит список на одноэлементные подсписки, после чего на обратном ходе рекурсии сливает их двухэлементные списки. На следующем этапе сливаются двухэлементные списки и т.д. На последнем шаге два подсписка сливаются в итоговый, отсортированный список.
В качестве завершения темы сортировки разработаем предикат, который будет проверять, является ли список упорядоченным. Это совсем не сложно. Для того чтобы список был упорядоченным, он должен быть либо пустым, либо одноэлементным, либо любые два его соседних элемента должны быть расположены в правильном порядке. Запишем эти рассуждения.
sorted([ ]). /* пустой список отсортирован */
sorted([_])./* одноэлементный список упорядочен */
sorted([X,Y|T]):–
X<=Y,
sorted([Y|T]).
/* список упорядочен, если первые
два элемента расположены
в правильном порядке и список,
образованный вторым элементом
и хвостом исходного, упорядочен */
avg без использования предикатов summa и length.min_list без использования предиката min.choice, реализующий алгоритм Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.