Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ответы к тои.docx
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
280.28 Кб
Скачать

Сортировка с помощью дерева (Heapsort)

Начнем с простого метода сортировки с помощью дерева, при использовании которого явно строится двоичное дерево сравнения ключей. Построение дерева начинается с листьев, которые содержат все элементы массива. Из каждой соседней пары выбирается наименьший элемент, и эти элементы образуют следующий (ближе к корню уровень дерева). Из каждой соседней пары выбирается наименьший элемент и т.д., пока не будет построен корень, содержащий наименьший элемент массива. Двоичное дерево сравнения для массива, используемого в наших примерах, показано на рисунке 2.1. Итак, мы уже имеем наименьшее значение элементов массива. Для того, чтобы получить следующий по величине элемент, спустимся от корня по пути, ведущему к листу с наименьшим значением. В этой листовой вершине проставляется фиктивный ключ с "бесконечно большим" значением, а во все промежуточные узлы, занимавшиеся наименьшим элементом, заносится наименьшее значение из узлов - непосредственных потомков (рис. 2.2). Процесс продолжается до тех пор, пока все узлы дерева не будут заполнены фиктивными ключами (рисунки 2.3 - 2.8).

  Рис. 2.1.

  Рис. 2.2. Второй шаг

  Рис. 2.3. Третий шаг

  Рис. 2.4. четвертый шаг

  Рис. 2.5. Пятый шаг

  Рис. 2.6. Шестой шаг

  Рис. 2.7. Седьмой шаг

  Рис. 2.8. Восьмой шаг

На каждом из n шагов, требуемых для сортировки массива, нужно log n (двоичный) сравнений. Следовательно, всего потребуется n?log n сравнений, но для представления дерева понадобится 2n - 1 дополнительных единиц памяти.

Имеется более совершенный алгоритм, который принято называть пирамидальной сортировкой (Heapsort). Его идея состоит в том, что вместо полного дерева сравнения исходный массив a[1], a[2], ..., a[n] преобразуется в пирамиду, обладающую тем свойством, что для каждого a[i] выполняются условия a[i] <= a[2i] и a[i] <= a[2i+1]. Затем пирамида используется для сортировки.

Наиболее наглядно метод построения пирамиды выглядит при древовидном представлении массива, показанном на рисунке 2.9. Массив представляется в виде двоичного дерева, корень которого соответствует элементу массива a[1]. На втором ярусе находятся элементы a[2] и a[3]. На третьем - a[4], a[5], a[6], a[7] и т.д. Как видно, для массива с нечетным количеством элементов соответствующее дерево будет сбалансированным, а для массива с четным количеством элементов n элемент a[n] будет единственным (самым левым) листом "почти" сбалансированного дерева.

  Рис. 2.9.

Очевидно, что при построении пирамиды нас будут интересовать элементы a[n/2], a[n/2-1], ..., a[1] для массивов с четным числом элементов и элементы a[(n-1)/2], a[(n-1)/2-1], ..., a[1] для массивов с нечетным числом элементов (поскольку только для таких элементов существенны ограничения пирамиды). Пусть i - наибольший индекс из числа индексов элементов, для которых существенны ограничения пирамиды. Тогда берется элемент a[i] в построенном дереве и для него выполняется процедура просеивания, состоящая в том, что выбирается ветвь дерева, соответствующая min(a[2?i], a[2?i+1]), и значение a[i] меняется местами со значением соответствующего элемента. Если этот элемент не является листом дерева, для него выполняется аналогичная процедура и т.д. Такие действия выполняются последовательно для a[i], a[i-1], ..., a[1]. Легко видеть, что в результате мы получим древовидное представление пирамиды для исходного массива (последовательность шагов для используемого в наших примерах массива показана на рисунках 2.10-2.13).

  Рис. 2.10.

  Рис. 2.11.

  Рис. 2.12.

  Рис. 2.13.

В 1964 г. Флойд предложил метод построения пирамиды без явного построения дерева (хотя метод основан на тех же идеях). Построение пирамиды методом Флойда для нашего стандартного массива показано в таблице 2.7.

Таблица 2.7 Пример построения пирамиды

Начальное состояние массива

8 23 5 |65| 44 33 1 6

Шаг 1

8 23 |5| 6 44 33 1 65

Шаг 2

8 |23| 1 6 44 33 5 65

Шаг 3

|8| 6 1 23 44 33 5 65

Шаг 4

1 6 8 23 44 33 5 65

1 6 5 23 44 33 8 65

В таблице 2.8 показано, как производится сортировка с использованием построенной пирамиды. Суть алгоритма заключается в следующем. Пусть i - наибольший индекс массива, для которого существенны условия пирамиды. Тогда начиная с a[1] до a[i] выполняются следующие действия. На каждом шаге выбирается последний элемент пирамиды (в нашем случае первым будет выбран элемент a[8]). Его значение меняется со значением a[1], после чего для a[1] выполняется просеивание. При этом на каждом шаге число элементов в пирамиде уменьшается на 1 (после первого шага в качестве элементов пирамиды рассматриваются a[1], a[2], ..., a[n-1]; после второго - a[1], a[2], ..., a[n-2] и т.д., пока в пирамиде не останется один элемент). Легко видеть (это иллюстрируется в таблице 2.8), что в результате мы получим массив, упорядоченный в порядке убывания. Можно модифицировать метод построения пирамиды и сортировки, чтобы получить упорядочение в порядке возрастания, если изменить условие пирамиды на a[i] >= a[2?i] и a[1] >= a[2?i+1] для всех осмысленных значений индекса i.

Таблица 2.8 Сортировка с помощью пирамиды

Исходная пирамида

1 6 5 23 44 33 8 65

Шаг 1

65 6 5 23 44 33 8 1

5 6 65 23 44 33 8 1

5 6 8 23 44 33 65 1

Шаг 2

65 6 8 23 44 33 5 1

6 65 8 23 44 33 5 1

6 23 8 65 44 33 5 1

Шаг 3

33 23 8 65 44 6 5 1

8 23 33 65 44 6 5 1

Шаг 4

44 23 33 65 8 6 5 1

23 44 33 65 8 6 5 1

Шаг 5

65 44 33 23 8 6 5 1

33 44 65 23 8 6 5 1

Шаг 6

65 44 33 23 8 6 5 1

44 65 33 23 8 6 5 1

Шаг 7

65 44 33 23 8 6 5 1

Процедура сортировки с использованием пирамиды требует выполнения порядка nxlog n шагов (логарифм - двоичный) в худшем случае, что делает ее особо привлекательной для сортировки больших массивов.

  1. Линейные статические и динамические структуры данных.

На предыдущей лекции мы рассмотрели полустатические структуры данных: стеки, очереди и деки. Одним из основных недостатков этих структур данных является то, что стеку или очереди отводится фиксированный объем памяти, даже если структура использует не весь отведенный объем или же совсем его не использует. С другой стороны невозможность расширения однажды отведенного объема памя­ти создает вероятность возникновения переполнения.

На предыдущей лекции мы рассмотрели полустатические структуры данных: стеки, очереди и деки. Одним из основных недостатков этих структур данных является то, что стеку или очереди отводится фиксированный объем памяти, даже если структура использует не весь отведенный объем или же совсем его не использует. С другой стороны невозможность расширения однажды отведенного объема памя­ти создает вероятность возникновения переполнения.

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

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

Динамическая структура характеризуется следующими чертами.

1. Непостоянство размера (числа элементов) структуры в про­цессе ее обработки. Число элементов может изменяться от нуля до некоторого значения, определяемого спецификой задачи или доступ­ным объемом машинной памяти.

2. Необходимость хранения в оперативной памяти.

3. Отсутствие физической смежности элементов структуры в памя­ти. Элементы динамической структуры могут быть разбросаны в памя­ти хаотическим образом. Следствием этого является усложнение про­цедуры доступа к элементам динамической структуры по сравнению со статическими или полустатическими структурами.

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

Раздел оперативной памяти, распределяемый статически, называется статической памятью; динамически распределяемый раздел памяти называется динамической памятью (динамически распределяемой памятью).

Память под динамические структуры выделяется во время выполнения программы. Порядок работы с динамическими структурами данных следующий:

– создать (отвести место в динамической памяти);

– работать при помощи указателя;

– удалить (освободить занятое структурой место).

Из вышеперечисленного вытекают следующие достоинства динамических переменных:

– данные, размещаемые в динамических переменных, могут занимать все свободное ОЗУ;

– после удаления отработавших динамических переменных освободившуюся динамическую память можно снова использовать.

Сами динамические величины не требуют описания в программе, поскольку во время компиляции память под них не выделяется. Во время компиляции память выделяется только под статические величины.

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

Следует отчетливо понимать, что работа с динамическими данными замедляет выполнение программы, поскольку доступ к величине происходит в два шага: сначала ищется указатель, затем по нему – величина.

Как это часто бывает, действует «закон сохранения неприятностей»: выигрыш в памяти компенсируется проигрышем во времени.

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

Вследствие этого динамические структуры часто физически представляются в форме связных списков.

Связный список – такая структура, элементами ко­торой служат записи с одним и тем же форматом, связанные друг с другом с помощью указателей, хранящихся в самих элементах.

Таким образом, в связанном списке элементы линейно упорядочены, но порядок определяется не номерами, как в массиве, а указателями, входящими в состав элементов списка.

Связные списки определены как примитивы в некоторых языках программирования (в частности в Лиспе, Delphi), но не в Паскале. Однако Паскаль предоставляет некоторые примитивные базовые операции, позволяющие легко использовать связанные списки.

Простейшими связными списками являются линейные связные списки – односвязные и двусвязные списки.

Односвязные списки

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

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

В односвязном списке каждый элемент или узел списка состоит из двух различных по назначению полей: поля информации (или поля данных) и поля указателя.

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

Поле указателя хранит адрес следующего элемента списка. С по­мощью указателя можно получить доступ к следующему элементу списка, из следующего – к очередному, и т.д., пока не будет достигнут последний элемент списка.

Поле указателя последнего элемента списка должно содержать признак нулевого, или пустого указателя, свидетельствующего о конце списка.

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

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

– список удобно составлять из переменных типа запись, поскольку нужен и доступ к данным элемента, и работа с ним как с единым целым;

– список может иметь произвольный размер;

– доступ к элементу списка уже не прямой, как в массиве, а последовательный (надо поставить указатель в начало списка, и, двигаясь по цепочке, отсчитать нужное количество элементов), что, разумеется, замедляет работу с ним;

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

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

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

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

На предыдущей лекции мы рассмотрели полустатические структуры данных: стеки, очереди и деки. Одним из основных недостатков этих структур данных является то, что стеку или очереди отводится фиксированный объем памяти, даже если структура использует не весь отведенный объем или же совсем его не использует. С другой стороны невозможность расширения однажды отведенного объема памя­ти создает вероятность возникновения переполнения.

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

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

Динамическая структура характеризуется следующими чертами.

1. Непостоянство размера (числа элементов) структуры в про­цессе ее обработки. Число элементов может изменяться от нуля до некоторого значения, определяемого спецификой задачи или доступ­ным объемом машинной памяти.

2. Необходимость хранения в оперативной памяти.

3. Отсутствие физической смежности элементов структуры в памя­ти. Элементы динамической структуры могут быть разбросаны в памя­ти хаотическим образом. Следствием этого является усложнение про­цедуры доступа к элементам динамической структуры по сравнению со статическими или полустатическими структурами.

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

Раздел оперативной памяти, распределяемый статически, называется статической памятью; динамически распределяемый раздел памяти называется динамической памятью (динамически распределяемой памятью).

Память под динамические структуры выделяется во время выполнения программы. Порядок работы с динамическими структурами данных следующий:

– создать (отвести место в динамической памяти);

– работать при помощи указателя;

– удалить (освободить занятое структурой место).

Из вышеперечисленного вытекают следующие достоинства динамических переменных:

– данные, размещаемые в динамических переменных, могут занимать все свободное ОЗУ;

– после удаления отработавших динамических переменных освободившуюся динамическую память можно снова использовать.

Сами динамические величины не требуют описания в программе, поскольку во время компиляции память под них не выделяется. Во время компиляции память выделяется только под статические величины.

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

Следует отчетливо понимать, что работа с динамическими данными замедляет выполнение программы, поскольку доступ к величине происходит в два шага: сначала ищется указатель, затем по нему – величина.

Как это часто бывает, действует «закон сохранения неприятностей»: выигрыш в памяти компенсируется проигрышем во времени.

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

Вследствие этого динамические структуры часто физически представляются в форме связных списков.

Связный список – такая структура, элементами ко­торой служат записи с одним и тем же форматом, связанные друг с другом с помощью указателей, хранящихся в самих элементах.

Таким образом, в связанном списке элементы линейно упорядочены, но порядок определяется не номерами, как в массиве, а указателями, входящими в состав элементов списка.

Связные списки определены как примитивы в некоторых языках программирования (в частности в Лиспе, Delphi), но не в Паскале. Однако Паскаль предоставляет некоторые примитивные базовые операции, позволяющие легко использовать связанные списки.

Простейшими связными списками являются линейные связные списки – односвязные и двусвязные списки.

Односвязные списки

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

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

В односвязном списке каждый элемент или узел списка состоит из двух различных по назначению полей: поля информации (или поля данных) и поля указателя.

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

Поле указателя хранит адрес следующего элемента списка. С по­мощью указателя можно получить доступ к следующему элементу списка, из следующего – к очередному, и т.д., пока не будет достигнут последний элемент списка.

Поле указателя последнего элемента списка должно содержать признак нулевого, или пустого указателя, свидетельствующего о конце списка.

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

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

– список удобно составлять из переменных типа запись, поскольку нужен и доступ к данным элемента, и работа с ним как с единым целым;

– список может иметь произвольный размер;

– доступ к элементу списка уже не прямой, как в массиве, а последовательный (надо поставить указатель в начало списка, и, двигаясь по цепочке, отсчитать нужное количество элементов), что, разумеется, замедляет работу с ним;

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

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

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

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

6. Табличные структуры данных. Принцип организации взаимосвязей таблиц.

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

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

Планета

Расстояние до Солнца, а.е.

Относительная масса

Количество спутников

Меркурий

0,39

0,056

0

Венера

0,67

0,88

0

Земля

1,0

1,0

1

Марс

1,51

0,1

2

Юпитер

5,2

318

16

Если нужно сохранить таблицу в виде длинной символьной строки, используют один символ-разделитель между элементами, принадлежащими одной строке, и другой разделитель для отделения строк, например так:

Меркурий*0,39*0,056*0#Венера*0,67*0,88*0#Земля*1,0*1,0*1#Марс*1,51*0,1*2*...

Для розыска элемента, имеющего адрес ячейки ( m , n ), надо просмотреть набор данных с самого начала и пересчитать внешние разделители. Когда будет отсчитан m — 1 разделитель, надо пересчитывать внутренние разделители. После того как будет найден n — 1 разделитель, начнется нужный элемент. Он закончится, когда будет встречен любой очередной разделитель.

Еще проще можно действовать, если все элементы таблицы имеют равную длину. Такие таблицы называют матрицами . В данном случае разделители не нужны, поскольку все элементы имеют равную длину и количество их известно. Для розыска элемента с адресом ( m , n ) в матрице, имеющей М строк и N столбцов, надо просмотреть ее с самого начала и отсчитать a[N(m — 1) + ( n — 1)] символ, где а — длина одного элемента. Со следующего символа начнется нужный элемент. Его длина тоже равна а, поэтому его конец определить нетрудно.

Таким образом, табличные структуры данных (матрицы) — это упорядоченные структуры, в которых адрес элемента определяется номером строки и номером столбца, на пересечении которых находится ячейка, содержащая искомый элемент.

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

Номер факультета:

3

Номер курса (на факультете):

2

Номер специальности (на курсе):

2

Номер группы в потоке одной специальности:

1

Номер учащегося в группе:

19

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

7. Древовидные структуры данных.

Дерево — одна из наиболее широко распространённых структур данных в информатике, эмулирующая древовидную структуру в виде набора связанных узлов. Является связанным графом, не содержащим циклы. Большинство источников также добавляют условие на то, что рёбра графа не должны быть ориентированными. В дополнение к этим трём ограничениям, в некоторых источниках указываются, что рёбра графа не должны быть взвешенными.