Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2066 / 0 | Длительность: 63:16:00
Лекция 4:

Абстрактные типы данных

Пример использования АТД

В качестве заключительного примера рассмотрим специализированный АТД, который демонстрирует связь между прикладными областями и алгоритмами и структурами данных, которые обсуждаются в настоящей книге. Этот АТД полинома взят из области символьной математики, в которой компьютеры помогают оперировать с абстрактными математическими объектами. Цель заключается в том, чтобы получить возможность писать программы, которые могут оперировать с полиномами и выполнять вычисления наподобие $$\left(1-x+\dfrac{x^{2}}{2}-\dfrac{x^{3}}{6}\right)(1+x+x^{2}+x^{3})=1+\dfrac{x^{2}}{2}+\dfrac{x^{3}}{3}-\dfrac{2x^{4}}{3}+\dfrac{x^{5}}{3}-\dfrac{x^{6}}{6}$$

Кроме того, необходима возможность вычислять полиномы для заданного значения х. Для x = 0,5 обе стороны приведенного выше уравнения имеют значение 1,1328125. Операции умножения, сложения и вычисления полиномов играют наиболее важную роль во множестве математических вычислений. Программа 4.23 - это простой пример, в котором выполняются символьные операции, соответствующие полиномиальным равенствам

  (х + 1)2 = x2 + 2х + 1,
  (х + 1)3 = x3 + 3x2 + 3х + 1,
  (х + 1)4 = x4 + 4x3 + 6x2 + 4х + 1,
  (х + 1)5 = x5 + 5x4 + 10x3 + 10x2 + 5х + 1,
  ... .
        

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

Вначале необходимо определить АТД полинома так, как показано в интерфейсе из программы 4.24. Для такой хорошо понятной математической абстракции, как полином, спецификация настолько ясна, что ее не стоит выражать словесно (как и в случае АТД комплексных чисел, который был рассмотрен в разделе 4.8): необходимо, чтобы экземпляры АТД вели себя в точности так же, как и эта хорошо понятная математическая абстракция.

Программа 4.23. Клиентская программа для АТД полинома (биномиальные коэффициенты)

Эта клиентская программа использует АТД полинома, определенный в интерфейсе программы 4.24, для выполнения алгебраических операций над полиномами с целыми коэффициентами. Она принимает из командной строки целое число N и число с плавающей точкой р, вычисляет (х + 1)N и проверяет результат, вычисляя значение результирующего полинома для х = р.

#include <iostream.h>
#include <stdlib.h>
#include "POLY.cxx"
int main(int argc, char *argv[])
  { int N = atoi(argv[1]); float p = atof(argv[2]);
    cout << "Биномиальные коэффициенты" << endl;
    POLY<int> x(1,1), one(1,0), t = x + one, y = t;
    for (int i = 0; i < N; i++)
      { y = y*t; cout << y << endl; }
    cout << y.eval(p) << endl;
  }
        

Программа 4.24. Интерфейс АТД полинома

Чтобы можно было задавать коэффициенты различных типов, в этом интерфейсе АТД полинома используется шаблон. Здесь перегружены бинарные операции + и *, поэтому клиентские программы могут использовать полиномы в арифметических выражениях. Конструктор, вызванный с аргументами c и N, создает полином, соответствующий выражению cxN.

template <class Number>
class POLY
  {
    private:
      // Программный код, зависящий от реализации
    public:
      POLY<Number>(Number, int);
      float eval(float) const;
      friend POLY operator+(POLY &, POLY &);
      friend POLY operator*(POLY &, POLY &);
  };
        

Для реализации функций, определенных в этом интерфейсе, потребуется выбрать конкретную структуру данных для представления полиномов и затем реализовать соответствующие алгоритмы для работы с этой структурой данных, чтобы все функционировало в соответствии с ожиданиями клиентской программы. Как обычно, выбор структуры данных влияет на эффективность алгоритмов, так что стоит обдумать несколько вариантов. Как и для стеков и очередей, можно воспользоваться представлением на базе связного списка либо представлением на базе массива. Программа 4.25 - это реализация, в которой используется представление на базе массива; реализация на базе связного списка оставлена на самостоятельную проработку (см. упражнение 4.78).

Для сложения (add) двух полиномов складываются их коэффициенты. Если полиномы представлены в виде массивов, то для их сложения, как показано в программе 4.25, достаточно одного цикла по этим массивам. Для умножения (multiply) двух полиномов применяется элементарный алгоритм, основанный на свойстве дистрибутивности. Один полином умножается на каждый член другого, результаты выравниваются так, чтобы степени х соответствовали друг другу, и затем складываются для получения окончательного результата. В следующей таблице кратко показывается этот вычислительный процесс для (1 - х + x2/ 2 - x3/ 6 ) (1 + х + x2 + x3) :


\begin{align*}
1-x+\dfrac{x^{2}}{2}&-\dfrac{x^{3}}{6}\\
+x-x^{2}&+\dfrac{x^{3}}{2}-\dfrac{x^{4}}{6}\\
+x^{2}&-x^{3}+\dfrac{x^{4}}{2}-\dfrac{x^{5}}{6}\\
&+x^{3}-x^{4}+\dfrac{x^{5}}{2}-\dfrac{x^{6}}{6}\\
1+\dfrac{x^{2}}{2}&+\dfrac{x^{3}}{3}-\dfrac{2x^{4}}{3}+\dfrac{x^{5}}{3}-\dfrac{x^{5}}{6}
\end{align*}

Очевидно, что время, необходимое для умножения таким способом двух полиномов, пропорционально N2. Отыскать более быстрый алгоритм решения данной задачи весьма непросто. Эта тема рассматривается более подробно в части VIII, где будет показано, что время, необходимое для решения такой задачи с помощью алгоритма "разделяй-и-властвуй", пропорционально N3/2, а время, необходимое для ее решения с помощью быстрого преобразования Фурье, пропорционально N lgN.

В реализации функции evaluate (вычислить) из программы 4.25 используется эффективный классический алгоритм, известный как алгоритм Горнера (Horner). Cамая простая реализация этой функции заключается в непосредственном вычислении выражения с использованием функции, вычисляющей х N. При таком подходе требуется время с квадратичной зависимостью от N. В более сложной реализации значения xi запоминаются в таблице и затем используются при непосредственных вычислениях. При таком подходе требуется дополнительный объем памяти, линейно зависящий от N.

Программа 4.25. Реализация АТД полинома на базе массива

В этой реализации АТД для полиномов данные представлены степенью полинома и указателем на массив коэффициентов. Это не АТД первого класса: авторы клиентских программ должны знать о возможности утечек памяти и о выполнении копирования лишь указателей (см. упр. 4.79).

template <class Number>
class POLY
  {
    private:
      int n; Number *a;
    public:
      POLY<Number>(Number c, int N)
        { a = new Number[N+1]; n = N+1; a[N] = c;
          for (int i = 0; i < N; i++) a[i] = 0;
        }
      float eval(float x) const
        { double t = 0.0;
          for (int i = n-1; i >= 0; i - )
t = t*x + a[i];
          return t;
        }
      friend POLY operator+(POLY &p, POLY &q)
        { POLY t(0, p.n>q.n ? p.n-1 : q.n-1);
          for (int i = 0; i < p.n; i++)
t.a[i] += p.a[i];
          for (int j = 0; j < q.n; j++)
t.a[j] += q.a[j];
          return t;
        }
      friend POLY operator*(POLY &p, POLY &q)
        { POLY t(0, (p.n-1)+(q.n-1));
          for (int i = 0; i < p.n; i++)
for (int j = 0; j < q.n; j++)
  t.a[i+j] += p.a[i]*q.a[j];
          return t;
        }
 };
        

Алгоритм Горнера - это прямой оптимальный линейный алгоритм, основанный на следующем использовании круглых скобок: $$a_{4}x^{4}+a_{3}x^{3}+a_{2}x^{2}+a_{1}x+a_{0}=(((a_{4}x+a_{3})x+a_{2})x+a_{1})x+a_{0}$$.

Алгоритм Горнера часто представляют как ловкий прием, экономящий время, но в действительности это первый выдающийся пример элегантного и эффективного алгоритма, который сокращает время, необходимое для выполнения этой важной вычислительной задачи, с квадратичного до линейного. Разновидностью алгоритма Горнера является преобразование строк с ASCII-символами в целые числа, выполняемое в программе 4.5. Мы еще встретимся с алгоритмом Горнера в "Хеширование" , где на нем основаны важные вычисления, связанные с некоторыми реализациями таблиц символов и поиска строк.

В ходе выполнения перегруженных операций + и * создаются новые полиномы, поэтому данная реализация является источником утечки памяти. Ее нетрудно ликвидировать, добавив в реализацию конструктор копирования, перегруженную операцию присваивания и деструктор. Так следовало бы сделать в случае полиномов очень больших размеров, обработки огромного количества небольших полиномов, а также создания АТД для использования в каком-нибудь приложении (см. упражнение 4.79).

Как обычно, применение в реализации АТД полинома представления на базе массива - это лишь одна из возможностей. Если показатели степени очень большие, а членов в полиномах немного, то более подходящим будет представление на базе связного списка. Например, не стоит применять программу 4.25 для выполнения такого умножения:

(1 + х1000000)(1 + х2000000) = 1+ х1000000 + х2000000 + хЗОООООО

поскольку в ней будет использоваться память под массив с миллионами неиспользуемых коэффициентов. Более подробно вариант со связным списком рассматривается в упражнении 4.78.

Упражнения

  • 4.78. Напишите реализацию для приведенного в тексте АТД полинома (программа 4.24), в которой в качестве базовой структуры данных используются связные списки. Списки не должны содержать узлов, соответствующих членам с нулевыми коэффициентами.
  • 4.79. Устраните утечку памяти в программе 4.25, добавив в нее конструктор копирования, перегруженную операцию присваивания и деструктор.
  • 4.80. Добавьте перегруженные операции += и *= в АТД полинома из программы 4.25.
  • 4.81. Расширьте приведенный в лекции АТД полинома, включив в него операции интегрирования и дифференцирования полиномов.
  • 4.82. Измените полученный АТД полинома из упражнения 4.81 так, чтобы в нем игнорировались все члены со степенями, большими или равными целому числу M, которое передается из клиента во время инициализации.
  • 4.83. Расширьте АТД полинома из упражнения 4.81 так, чтобы он включал деление и суперпозицию полиномов.
  • 4.84. Разработайте АТД, который позволяет клиентским программам выполнять сложение и умножение целых чисел произвольной точности.
  • 4.85. Используя АТД, разработанный в упражнении 4.84, измените программу вычисления постфиксных выражений из раздела 4.3 так, чтобы она могла вычислять постфиксные выражения, содержащие целые числа произвольной точности.
  • 4.86. Напишите клиентскую программу, которая с помощью АТД полинома из упражнения 4.83 вычисляет интегралы, используя разложение функций в ряды Тейлора и оперируя с ними в символьной форме.
  • 4.87. Разработайте АТД, который позволяет клиентским программам выполнять алгебраические операции с векторами чисел с плавающей точкой.
  • 4.88. Разработайте АТД, который позволяет клиентским программам выполнять алгебраические операции с матрицами абстрактных объектов, для которых определены операции сложения, вычитания, умножения и деления.
  • 4.89. Напишите интерфейс для АТД символьных строк, который включает операции создания строк, сравнения двух строк, конкатенации двух строк, копирования одной строки в другую и получения длины строки. Примечание: полученный интерфейс должен быть похож на интерфейс, доступный в стандартной библиотеке C++.
  • 4.90. Напишите реализацию для интерфейса из упражнения 4.89, используя там, где это необходимо, библиотеку обработки строк C++.
  • 4.91. Напишите реализацию для полученного в упражнении 4.89 интерфейса строки, используя представление на базе связного списка. Проанализируйте время выполнения каждой операции для наихудших случаев.
  • 4.92. Напишите интерфейс и реализацию АТД индексного множества, в котором обрабатываются множества целых чисел в диапазоне от 0 до M - 1 (где M - заданная константа) и имеются операции создания множества, объединения двух множеств, пересечения двух множеств, дополнения множества, разности двух множеств и вывода содержимого множества. Для представления каждого множества используйте массив из M - 1 элементов, принимающих значения 0-1.
  • 4.93. Напишите клиентскую программу, которая тестирует АТД, созданный в упражнении 4.92.

Перспективы

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

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

АТД должны воплощать простой (и здравый) принцип: необходимо точно описывать способы обработки данных. Для этого в языке C++ имеется удобный механизм "кли-ент-интерфейс-реализация", который был подробно рассмотрен в настоящей лекции; он позволяет получать на языке C++ код, обладающий рядом нужных свойств. Во многих современных языках имеются специальные средства поддержки, позволяющие разрабатывать программы с подобными свойствами, однако существует независимый от разных языков подход - при отсутствии специальной языковой поддержки устанавливаются определенные правила программирования, обеспечивающие требуемое разделение на клиенты, интерфейсы и реализации.

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

Один АТД можно создавать на базе другого. Абстракции, подобные указателям и структурам, определенным в языке C++, использовались для построения связных списков. Далее абстракции связных списков и массивов, доступных в C++, применялись при построении стеков магазинного типа. А стеки магазинного типа использовались для организации вычислений арифметических выражений. Понятие АТД позволяет создавать большие системы на основе разных уровней абстракции, от машинных инструкций до разнообразных возможностей языка программирования, вплоть до сортировки, поиска и других возможностей высокого уровня, которые обеспечиваются алгоритмами, рассматриваемыми в частях III и IV Некоторые приложения требуют еще более высокого уровня абстракции, о которых речь идет в частях V - VIII. Абстрактные типы данных - лишь один этап в бесконечном процессе создания все более и более мощных абстрактных механизмов, в чем и заключается суть эффективного использования компьютеров для решения современных задач.

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?

Никита Андриянов
Никита Андриянов
Владимир Хаванских
Владимир Хаванских
Россия, Москва, Высшая школа экономики
Вадим Рычков
Вадим Рычков
Россия, Москва, МГТУ Станкин