Популярные услуги

Все письменные КМ под ключ за 7 суток! (КМ-1 + КМ-2 + КМ-3 + КМ-4 + КМ-5)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Оба семинара по программированию под ключ! КМ-2. Разработка циклических алгоритмов + КМ-3. Функции и многофайловые программы в Си
Одно любое задание в mYsql
Любая задача на C/C++
Сделаю ваше задание: Лабораторная работа на Pascal / Lazarus
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе

Сортировка списков

2021-03-09СтудИзба

8. Лекция: Сортировка списков

Рассматривается нахождение суммы элементов списка, среднего и минимального значений; алгоритмы сортировки списков: пузырьковый, выбором, вставкой, слиянием, быстрая сортировка.

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

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

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 и поменяв его название), получим предикат, находящий не минимальный, а максимальный элемент списка.

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

Существует множество алгоритмов сортировки. Заметим, что имеется два класса алгоритмов сортировки: сортировка данных, целиком расположенных в основной памяти (внутренняя сортировка), и сортировка файлов, хранящихся во внешней памяти (внешняя сортировка). Мы займемся исключительно методами внутренней сортировки.

Рассмотрим наиболее известные методы внутренней сортировки и выясним, как можно применить их для сортировки списков в Прологе.

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

Пузырьковая сортировка

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

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

Реализуем пузырьковую сортировку посредством двух предикатов. Один из них, назовем его permutation, будет сравнивать два первых элемента списка и в случае, если первый окажется больше второго, менять их местами. Если же первая пара расположена в правильном порядке, этот предикат будет переходить к рассмотрению хвоста.

Основной предикат bubble будет осуществлять пузырьковую сортировку списка, используя вспомогательный предикат permutation.

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). /* если перестановок не было, значит список

                отсортирован */

Но наш пузырьковый метод работает только до тех пор, пока есть хотя бы пара элементов списка, расположенных в неправильном порядке. Как только такие элементы закончились, предикат permutation терпит неудачу, а 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) реализующий приведенное описание. Так как мы не знаем, какой из списков опустеет раньше, нам необходимо "гнать" рекурсию сразу по обоим базовым спискам. У нас будет два факта — основания рекурсии, которые будут утверждать, что если мы сливаем некий список с пустым списком, то в итоге получим, естественно, тот же самый список. Причем этот факт имеет место и в случае, когда первый список пуст, и в случае, когда пуст второй список.

Шаг рекурсии нам дадут два правила: первое будет утверждать, что если голова первого списка меньше головы второго списка, то именно голову первого списка и нужно дописать в качестве головы в результирующий список, после чего перейти к слиянию хвоста первого списка со вторым. Результат этого слияния будет хвостом итогового списка. Второе правило, напротив, будет дописывать голову второго списка в качестве головы результирующего списка, сливать первый список с хвостом второго списка. Итог этого слияния будет хвостом объединенного списка.

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 */

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

Сортировка слияниями

Метод слияний — один из самых "древних" алгоритмов сортировки. Его придумал Джон фон Нейман еще в 1945 году. Идея этого метода заключается в следующем. Разобьем список, который нужно упорядочить, на два подсписка. Упорядочим каждый из них этим же методом, после чего сольем упорядоченные подсписки обратно в один общий список.

Для начала создадим предикат, который будет делить исходный список на два. Он будет состоять из двух фактов и правила. Первый факт будет утверждать, что пустой список можно разбить только на два пустых подсписка. Второй факт будет предлагать разбиение одноэлементного списка на тот же одноэлементный список и пустой список. Правило будет работать в случаях, не охваченных фактами, т.е. когда упорядочиваемый список содержит не менее двух элементов. В этой ситуации мы будем отправлять первый элемент списка в первый подсписок, второй элемент — во второй подсписок, и продолжать распределять элементы хвоста исходного списка.

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, сливаем полученные упорядоченные подсписки в один список, который и будет результатом упорядочивания элементов исходного списка.

Запишем изложенные выше соображения.

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]).

Контрольные вопросы и литература - лекция, которая пользуется популярностью у тех, кто читал эту лекцию.

                     /* список упорядочен, если первые

                        два элемента расположены

                        в правильном порядке и список,

                        образованный вторым элементом

                        и хвостом исходного, упорядочен */

Самостоятельные задания

  1. Модифицируйте предикат, вычисляющий сумму элементов списка так, чтобы он вычислял произведение элементов списка.
  2. Модифицируйте алгоритм сортировки выбором так, чтобы он был основан на выборе максимального элемента списка и приписывании его в конец.
  3. Измените предикаты, реализующие алгоритм сортировки вставкой, чтобы список получился упорядоченным по невозрастанию.
  4. Реализуйте предикат avg без использования предикатов summa и length.
  5. Измените предикаты, реализующие алгоритм пузырьковой сортировки, чтобы в итоге список получился упорядоченным по невозрастанию.
  6. Реализуйте предикат min_list без использования предиката min.
  7. Внесите изменения в предикат choice, реализующий алгоритм сортировки выбором, чтобы он был основан на выборе максимального элемента списка и приписывании его в конец списка.
Свежие статьи
Популярно сейчас
А знаете ли Вы, что из года в год задания практически не меняются? Математика, преподаваемая в учебных заведениях, никак не менялась минимум 30 лет. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Нашёл ошибку?
Или хочешь предложить что-то улучшить на этой странице? Напиши об этом и получи бонус!
Бонус рассчитывается индивидуально в каждом случае и может быть в виде баллов или бесплатной услуги от студизбы.
Предложить исправление
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5119
Авторов
на СтудИзбе
445
Средний доход
с одного платного файла
Обучение Подробнее