Files
hello-algo/ru/chapters/chapter_03.md
2026-03-25 16:54:42 +08:00

95 KiB
Raw Blame History

Структуры данных

{width="3.630197944006999in" height="4.697916666666667in"}

классификация структур данных

К распространенным структурам данных относятся массивы, списки, стеки, очереди, хеш-таблицы, деревья, кучи, графы. Их можно классифицировать по двум измерениям: логической структуре и физической структуре.

  1. Логическая структура: линейная и нелинейная

Логическая структура раскрывает логические отношения между элемента- ми данных. В массивах и списках данные расположены в определенном порядке, что отражает линейные отношения между данными. В деревьях данные располо- жены сверху вниз по уровням, что демонстрирует отношения «предок» и «пото- мок». Графы состоят из узлов и ребер, отражая сложные сетевые отношения.

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

  • Линейные структуры данных: массивы, списки, стеки, очереди, хеш- таблицы, в которых элементы связаны последовательно один к одному.

  • Нелинейные структуры данных: деревья, кучи, графы, хеш-таблицы.

Нелинейные структуры данных можно дополнительно разделить на древо- видные и сетевые.

  • Древовидные структуры: деревья, кучи, хеш-таблицы, в которых эле- менты связаны один ко многим.

  • Сетевые структуры: графы, в которых элементы связаны многие ко многим.

Рис. 3.1. Линейные и нелинейные структуры данных

Физическая структура:

непрерывная и разреженная

Во время выполнения программы обрабатываемые данные в основном хранятся в памяти. На рис. 3.2 изображен модуль оперативной памяти ком- пьютера, где каждый черный чип содержит определенный участок памяти. Память можно представить как огромную таблицу Excel, где каждая ячейка может хранить данные определенного размера.

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

Доступное простран- ство памяти

Используемое пространство памяти

Обращение к адресу памяти 0x0012, получение данных

Обращение к адресу памяти 0x0024, получение данных

Рис. 3.2. Модуль памяти, пространство памяти, адреса памяти

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

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

Непрерывное хранение

Память для хранения массива

является непрерывной

Распределенное хранение

Память для хранения связного списка

является распределенной

Доступное пространство памяти Память для хранения массива

Хранение значения узла Хранение

указателя узла

Память для хранения

узлов связного списка

Рис. 3.3. Хранение в непрерывном и в разреженном пространстве

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

  • На основе массивов можно реализовать: стек, очередь, хеш-таблицу, дерево, кучу, граф, матрицу, тензор (массив размерностью ≥ 3) и др.

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

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

Основные типы данных

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

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

  1. целочисленные типы: byte, short, int, long;

  2. типы с плавающей запятой: float, double. Используются для представле- ния дробных чисел;

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

  4. логический тип: bool. Используется для представления концепций «да» и «нет».

Основные типы данных хранятся в компьютере в двоичной форме. Один двоичный разряд равен 1 биту. В большинстве современных операцион- ных систем 1 байт состоит из 8 бит.

Диапазон значений основных типов данных зависит от занимаемого ими объема памяти. Рассмотрим это на примере языка Java.

  • Целочисленный тип byte занимает 1 байт = 8 бит, может представлять 28 чисел.

  • Целочисленный тип int занимает 4 байта = 32 бита, может представлять 232 чисел.

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

Таблица 3.1. Объем памяти и диапазон значений основных типов данных

Целые byte 1 байт 27 (128) 27 1 (127) 0

+---------------+----------+-----------------------------------------------------------------+ | | > short | > 2 байта 215 215 1 0 | +===============+==========+=============+===================+=================+=============+ | | > int | > 4 байта 231 231 1 0 | +---------------+----------+-----------------------------------------------------------------+ | | > long | > 8 байт 263 263 1 0 | +---------------+----------+-------------+-------------------+-----------------+-------------+ | > С плавающей | > float | > 4 байта | > 1.175 × 10--38 | > 3.403 × 1038 | > 0.0f | +---------------+----------+-------------+-------------------+-----------------+-------------+ | | > double | > 8 байт | > 2.225 × 10--308 | > 1.798 × 10308 | > 0.0 | +---------------+----------+-------------+-------------------+-----------------+-------------+ | > Символьный | > char | > 2 байта | > 0 | > 216 1 | > 0 | +---------------+----------+-------------+-------------------+-----------------+-------------+ | > Логический | > bool | > 1 байт | > false | > true | > false | +---------------+----------+-------------+-------------------+-----------------+-------------+

  1. Основные типы данных ❖ 75

Обратите внимание, что табл. 3.1 относится к основным типам данных в языке Java. В каждом языке программирования свои определения типов данных, объем памяти, диапазон значений и значения по умолчанию могут различаться.

  • В Python целочисленный тип int может иметь произвольный размер, ограниченный только доступной памятью. Тип с плавающей запятой float является 64-битным двойной точности. Отсутствует тип char, от- дельный символ фактически является строкой str длиной 1.

  • В C и C++ размер основных типов данных не определен четко и зависит от реализации и платформы. Таблица 3.1 следует модели данных LP64, используемой в Unix-системах на 64-битных операционных системах, включая Linux и macOS.

  • Размер символа char в C и C++ составляет 1 байт. В большинстве языков программирования он зависит от конкретного метода кодирования сим- волов, подробнее см. в разделе «Кодирование символов».

  • Хотя для представления логического значения требуется всего 1 бит (0 или 1), в памяти оно обычно занимает 1 байт. Это связано с тем, что современные процессоры компьютеров обычно используют 1 байт как минимальную адресуемую единицу памяти.

Какова же связь между основными типами данных и структурами дан- ных? Известно, что структура данных -- это способ организации и хранения данных в компьютере. В этом предложении подлежащее -- «структура», а не

«данные».

Если необходимо представить ряд чисел, естественно использовать массив. Это связано с тем, что линейная структура массива может представлять со- седние и последовательные отношения чисел, но что именно хранится (це- лые числа int, дробные числа float или символы char), не имеет отношения к структуре данных.

Иными словами, базовые типы данных предоставляют тип содержимо- го данных, тогда как структуры данных определяют способ организации данных. Например, в следующем коде мы используем одну и ту же структу- ру данных (массив) для хранения и представления различных базовых типов данных, включая int, float, char, bool и др.

# Инициализация массива с использованием различных базовых типов данных. numbers: list[int] = [0] * 5

decimals: list[float] = [0.0] * 5

# В Python символы фактически являются строками длиной 1. characters: list[str] = ['0'] * 5

bools: list[bool] = [False] * 5

# Списки в Python могут свободно хранить различные базовые типы данных и ссылки

на объекты.

data = [0, 0.0, 'a', False, ListNode(0)]

кодирование чисел*

Прямой, обратный и дополнительный коды

В таблице из предыдущего раздела можно заметить, что во всех целочислен- ных типах отрицательных чисел на одно больше, чем положительных. Напри- мер, диапазон значений byte составляет [--128, 127]. Этот факт кажется не со- всем интуитивным, и его внутренняя причина связана с концепциями прямо- го, обратного и дополнительного кодов.

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

го, сначала дадим определения всем трем кодам.

  • Прямой код: старший бит двоичного представления числа рассматри- вается как знак, где 0 обозначает положительное число, 1 -- отрицатель- ное, остальные биты представляют значение числа.

  • Обратный код: обратный код положительного числа совпадает с его прямым кодом, обратный код отрицательного числа получается инвер-

сией всех битов прямого кода, кроме знакового.

  • Дополнительный код: дополнительный код положительного числа со- впадает с его прямым кодом, дополнительный код отрицательного числа получается добавлением 1 к его обратному коду.

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

Число

Прямой код

Обратный код

Дополнительный код

Прямой, обратный и дополнительный коды положительных чисел равны

Инвертировать все биты, кроме знакового

Добавить 1

[к]{.smallcaps} обратному коду

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

Рис. 3.4. Преобразования прямого, обратного и дополнительного кодов

Хотя прямой код наиболее понятен, он имеет некоторые ограничения. С од- ной стороны, прямой код отрицательных чисел нельзя напрямую исполь- зовать в вычислениях. Например, при вычислении 1 + (2) в прямом коде результат будет 3, что явно неверно:

1 + (2)

→ 0000 0001 + 1000 0010

= 1000 0011

3.

Чтобы решить эту проблему, в компьютерах был введен обратный код. Если сначала преобразовать прямой код в обратный, затем выполнить вычисление 1 + (2) в обратном коде и, наконец, преобразовать результат обратно в прямой код, то получится правильный результат 1:

1 + (2)

→ 0000 0001 (прямой код) + 1000 0010 (прямой код)

= 0000 0001 (обратный код) + 1111 1101 (обратный код)

= 1111 1110 (обратный код)

= 1000 0001 (прямой код)

1.

С другой стороны, у числа ноль в прямом коде есть два представления:

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

+0 → 0000 0000

0 → 1000 0000.

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

0 → 1000 0000 (прямой код)

= 1111 1111 (обратный код)

= 1 0000 0000 (дополнительный код).

При добавлении 1 к обратному коду отрицательного нуля возникает перенос, но длина типа byte составляет только 8 бит, поэтому единица, перенесенная в 9-й бит, будет отброшена. Это означает, что дополнительный код отрицательно- го нуля равен 0000 0000, что совпадает с дополнительным кодом положи- тельного нуля. Таким образом, в дополнительном коде существует только один ноль, и неоднозначность положительного и отрицательного нуля устраняется.

Остается последний вопрос: как получается дополнительное отрицательное число 128 в диапазоне значений типа byte [128, 127]? Заметим, что все це- лые числа в диапазоне [127, +127] имеют соответствующие прямой, обратный и дополнительный коды, а между прямым и дополнительным кодами можно выполнять взаимные преобразования.

Однако дополнительный код 1000 0000 является исключением, у него нет соответствующего прямого кода. Согласно методу преобразования пря- мой код для этого дополнительного кода будет 0000 0000. Это явное противо- речие, так как этот прямой код представляет число 0, дополнительный код которого должен быть равен самому себе. Компьютеры определяют, что этот особый дополнительный код 1000 0000 представляет значение 128. Фактиче- ски результат вычисления (1) + (127) в дополнительном коде равен 128:

(127) + (1)

→ 1111 1111 (прямой код) + 1000 0001 (прямой код)

= 1000 0000 (обратный код) + 1111 1110 (обратный код)

= 1000 0001 (дополнительный код) + 1111 1111 (дополнительный код)

= 1000 0000 (дополнительный код)

128.

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

Путем сочетания сложения с некоторыми базовыми логическими опера- циями компьютер может выполнять и другие математические операции. Например, вычитание a b можно преобразовать в сложение a + (b). Умноже- ние и деление можно преобразовать в многократное сложение или вычитание.

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

Кодирование чисел с плавающей запятой

Внимательный читатель, возможно, заметил, что типы int и float имеют оди- наковую длину в 4 байта. Но почему тогда диапазон значений float значитель- но больше, чем у int? Этот факт, возможно, противоречит интуиции, так как float представляет дробные числа, и диапазон значений должен уменьшиться.

На самом деле это связано с тем, что числа с плавающей запятой float используют иной способ представления. 32-битное двоичное число запи- сывается следующий образом:

b31b30b29 ... b2b1b0.

Согласно стандарту IEEE 754 32-битное число float состоит из следующих трех частей:

  1. знакового бита S: занимает 1 бит, соответствует b31;

  2. показателя E: занимает 8 бит, соответствует b30b29 ... b23;

  3. мантиссы N: занимает 23 бита, соответствует b22b21 ... b0.

Метод вычисления значения двоичного числа float:

val = (1)***^b^***31 ⌡ 2^(b^30 b29 ... b23 )2 127 ⌡ (1.b b ...b ) .

22 21 0 2

Формула для вычисления в десятичной системе:

val = (1)^S^ ⌡ 2^E127^ ⌡(1 + N).

где диапазоны значений каждой из частей:

)

(1 + N)= (1 + Ι23 b

ξ 2^i^ ∴ 〉 ℘1, 2 2^23^ λ .

Λ

 

  i  1

23i   ϑ

Знаковый бит S

Длина float 4 байта = 32 бита

Для этого примера легко получить:

По стандарту IEEE 754

Рис. 3.5. Пример вычисления значения float по стандарту IEEE 754

Рассмотрим рис. 3.5, где приведены данные со значениями S = 0, E = 124, N = 22 + 23 = 0.375, тогда:

val = (1)^0^ ⌡ 2^124127^ ⌡(1 + 0.375) = 0.171875.

Теперь можно ответить на первоначальный вопрос: представление типа float включает показатель степени, поэтому его диапазон значений зна- чительно больше, чем у int. Согласно вышеуказанным вычислениям макси- мальное положительное число, которое может быть представлено float, равно 2254 127 × (2 223) ≈ 3.4 × 1038, а переключение знакового бита позволяет полу- чить минимальное отрицательное число.

Несмотря на то что числа с плавающей запятой float расширяют диа- пазон значений, их побочным эффектом является потеря точности. Це- лочисленный тип int использует все 32 бита для представления чисел, которые распределены равномерно. А наличие показателя приводит к тому, что чем больше значение числа с плавающей запятой float, тем больше будет разница между двумя соседними числами.

Показатели E = 0 и E = 255 имеют особое значение, используемое для пред- ставления нуля, бесконечности, NaN и т. д, как показано в табл. 3.2.

Таблица 3.2. Значения показателя

+--------------------+----------------------+--------------------------+----------------------------+ | > Показатель E | > Мантисса N = 0 | > Мантисса N ≠ 0 | > Формула вычисления | +====================+======================+==========================+============================+ | > 0 | > ±0 | > Слабо нормальное число | > (1)S × 2126 × (0.N) | +--------------------+----------------------+--------------------------+----------------------------+ | > 1, 2, ..., 254 | > Нормально число | > Нормально число | > (1)S × 2(E127) × (1.N) | +--------------------+----------------------+--------------------------+----------------------------+ | > 255 | > ±∞ | > NaN | | +--------------------+----------------------+--------------------------+----------------------------+

Стоит отметить, что слабо нормальные числа значительно повышают точ- ность чисел с плавающей запятой. Минимальное положительное нормальное число равно 2126, минимальное положительное слабо нормальное число равно 2126 × 223.

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

кодирование символов*

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

Таблица символов ASCII

ASCII является первой таблицей символов, ее полное название -- American Standard Code for Information Interchange (Американский стандартный код для обмена информацией). Для представления символов в ней используются 7-битные двоичные числа (нижние 7 бит одного байта), что позволяет пред- ставить до 128 различных символов. Как показано на рис. 3.6, таблица ASCII включает в себя заглавные и строчные буквы английского алфавита, цифры от 0 до 9, некоторые знаки препинания, а также некоторые управляющие сим- волы (например, символы новой строки и табуляции).

+--------------------------+------------------------+--------------+-------------------------------+ | > Десятичная система | > Двоичная система | > Символ | > Значение | +:========================:+:======================:+:============:+:=============================:+ | > 0 | > 0000 0000 | > NUL | > Пустой символ (Null) | +--------------------------+------------------------+--------------+-------------------------------+ | > 1 | > 0000 0001 | > SOH | > Начало заголовка | +--------------------------+------------------------+--------------+-------------------------------+ | > 2 | > 0000 0010 | > STX | > Начало текста | +--------------------------+------------------------+--------------+-------------------------------+ | > 3 | > 0000 0011 | > ETX | > Конец текста | +--------------------------+------------------------+--------------+-------------------------------+ | > 4 | > 0000 0100 | > EOT | > Конец передачи | +--------------------------+------------------------+--------------+-------------------------------+ | > 5 | > 0000 0101 | > ENQ | > Запрос | +--------------------------+------------------------+--------------+-------------------------------+ | > 6 | > 0000 0110 | > ACK | > Подтверждение | +--------------------------+------------------------+--------------+-------------------------------+ | > 7 | > 0000 0111 | > BEL | > Звонок | +--------------------------+------------------------+--------------+-------------------------------+ | > 8 | > 0000 1000 | > BS | > Backspace | +--------------------------+------------------------+--------------+-------------------------------+ | > 9 | > 0000 1001 | > TAB | > Горизонтальная табуляция | +--------------------------+------------------------+--------------+-------------------------------+ | > 10 | > 0000 1010 | > LF | > Перевод строки | +--------------------------+------------------------+--------------+-------------------------------+ | > 11 | > 0000 1011 | > VT | > Вертикальная табуляция | +--------------------------+------------------------+--------------+-------------------------------+ | > 12 | > 0000 1100 | > FF | > Новая страница | +--------------------------+------------------------+--------------+-------------------------------+ | > 13 | > 0000 1101 | > CR | > Возврат каретки | +--------------------------+------------------------+--------------+-------------------------------+ | > 14 | > 0000 1110 | > SO | > Включить сдвиг | +--------------------------+------------------------+--------------+-------------------------------+ | > 15 | > 0000 1111 | > SI | > Выключить сдвиг | +--------------------------+------------------------+--------------+-------------------------------+ | > 16 | > 0001 0000 | > DLE | > Экранирование канала данных | +--------------------------+------------------------+--------------+-------------------------------+ | > 17 | > 0001 0001 | > DC1 | > Управление устройством 1 | +--------------------------+------------------------+--------------+-------------------------------+ | > 18 | > 0001 0010 | > DC2 | > Управление устройством 2 | +--------------------------+------------------------+--------------+-------------------------------+ | > 19 | > 0001 0011 | > DC3 | > Управление устройством 3 | +--------------------------+------------------------+--------------+-------------------------------+ | > 20 | > 0001 0100 | > DC4 | > Управление устройством 4 | +--------------------------+------------------------+--------------+-------------------------------+ | > 21 | > 0001 0101 | > NAK | > Отрицание | +--------------------------+------------------------+--------------+-------------------------------+ | > 22 | > 0001 0110 | > SYN | > Синхронизация | +--------------------------+------------------------+--------------+-------------------------------+ | > 23 | > 0001 0111 | > ETB | > Конец блока передачи | +--------------------------+------------------------+--------------+-------------------------------+ | > 24 | > 0001 1000 | > CAN | > Отмена | +--------------------------+------------------------+--------------+-------------------------------+ | > 25 | > 0001 1001 | > EM | > Конец носителя | +--------------------------+------------------------+--------------+-------------------------------+ | > 26 | > 0001 1010 | > SUB | > Замена | +--------------------------+------------------------+--------------+-------------------------------+ | > 27 | > 0001 1011 | > ESC | > Экранирование | +--------------------------+------------------------+--------------+-------------------------------+ | > 28 | > 0001 1100 | > FS | > Разделитель файлов | +--------------------------+------------------------+--------------+-------------------------------+ | > 29 | > 0001 1101 | > GS | > Разделитель групп | +--------------------------+------------------------+--------------+-------------------------------+ | > 30 | > 0001 1110 | > RS | > Разделитель записей | +--------------------------+------------------------+--------------+-------------------------------+ | > 31 | > 0001 1111 | > US | > Разделитель элементов | +--------------------------+------------------------+--------------+-------------------------------+ | > 32 | > 0010 0000 | > (пробел) | > Пробел | +--------------------------+------------------------+--------------+-------------------------------+ | > 33 | > 0010 0001 | ! | > Восклицательный знак | +--------------------------+------------------------+--------------+-------------------------------+ | > 34 | > 0010 0010 | " | > Двойная кавычка | +--------------------------+------------------------+--------------+-------------------------------+ | > 35 | > 0010 0011 | # | > Решетка (октоторп) | +--------------------------+------------------------+--------------+-------------------------------+ | > 36 | > 0010 0100 | $ | > Знак доллара | +--------------------------+------------------------+--------------+-------------------------------+

Рис. 3.6. Таблица символов ASCII

+--------------------------+------------------------+--------------+-------------------------+ | > Десятичная система | > Двоичная система | > Символ | > Значение | +:========================:+:======================:+:============:+:=======================:+ | > 37 | > 0010 0101 | % | > Процент | +--------------------------+------------------------+--------------+-------------------------+ | > 38 | > 0010 0110 | & | > Амперсанд | +--------------------------+------------------------+--------------+-------------------------+ | > 39 | > 0010 0111 | ' | > Апостроф | +--------------------------+------------------------+--------------+-------------------------+ | > 40 | > 0010 1000 | ( | > Левая круглая скобка | +--------------------------+------------------------+--------------+-------------------------+ | > 41 | > 0010 1001 | ) | > Правая круглая скобка | +--------------------------+------------------------+--------------+-------------------------+ | > 42 | > 0010 1010 | * | > Звёздочка (астериск) | +--------------------------+------------------------+--------------+-------------------------+ | > 43 | > 0010 1011 | + | > Знак плюс | +--------------------------+------------------------+--------------+-------------------------+ | > 44 | > 0010 1100 | , | > Запятая | +--------------------------+------------------------+--------------+-------------------------+ | > 45 | > 0010 1101 | - | > Дефис/минус | +--------------------------+------------------------+--------------+-------------------------+ | > 46 | > 0010 1110 | . | > Точка | +--------------------------+------------------------+--------------+-------------------------+ | > 47 | > 0010 1111 | / | > Слеш (косая черта) | +--------------------------+------------------------+--------------+-------------------------+ | > 48 | > 0011 0000 | 0 | > Цифра 0 | +--------------------------+------------------------+--------------+-------------------------+ | > 49 | > 0011 0001 | 1 | > Цифра 1 | +--------------------------+------------------------+--------------+-------------------------+ | > 50 | > 0011 0010 | 2 | > Цифра 2 | +--------------------------+------------------------+--------------+-------------------------+ | > 51 | > 0011 0011 | 3 | > Цифра 3 | +--------------------------+------------------------+--------------+-------------------------+ | > 52 | > 0011 0100 | 4 | > Цифра 4 | +--------------------------+------------------------+--------------+-------------------------+ | > 53 | > 0011 0101 | 5 | > Цифра 5 | +--------------------------+------------------------+--------------+-------------------------+ | > 54 | > 0011 0110 | 6 | > Цифра 6 | +--------------------------+------------------------+--------------+-------------------------+ | > 55 | > 0011 0111 | 7 | > Цифра 7 | +--------------------------+------------------------+--------------+-------------------------+ | > 56 | > 0011 1000 | 8 | > Цифра 8 | +--------------------------+------------------------+--------------+-------------------------+ | > 57 | > 0011 1001 | 9 | > Цифра 9 | +--------------------------+------------------------+--------------+-------------------------+ | > 58 | > 0011 1010 | : | > Двоеточие | +--------------------------+------------------------+--------------+-------------------------+ | > 59 | > 0011 1011 | ; | > Точка с запятой | +--------------------------+------------------------+--------------+-------------------------+ | > 60 | > 0011 1100 | < | > Знак «меньше» | +--------------------------+------------------------+--------------+-------------------------+ | > 61 | > 0011 1101 | = | > Знак равенства | +--------------------------+------------------------+--------------+-------------------------+ | > 62 | > 0011 1110 | > | > Знак «больше» | +--------------------------+------------------------+--------------+-------------------------+ | > 63 | > 0011 1111 | ? | > Вопросительный знак | +--------------------------+------------------------+--------------+-------------------------+ | > 64 | > 0100 0000 | @ | > Собака (at sign) | +--------------------------+------------------------+--------------+-------------------------+ | > 65 | > 0100 0001 | A | > Латинская A | +--------------------------+------------------------+--------------+-------------------------+ | > 66 | > 0100 0010 | B | > Латинская B | +--------------------------+------------------------+--------------+-------------------------+ | > 67 | > 0100 0011 | C | > Латинская C | +--------------------------+------------------------+--------------+-------------------------+ | > 68 | > 0100 0100 | D | > Латинская D | +--------------------------+------------------------+--------------+-------------------------+ | > 69 | > 0100 0101 | E | > Латинская E | +--------------------------+------------------------+--------------+-------------------------+ | > 70 | > 0100 0110 | F | > Латинская F | +--------------------------+------------------------+--------------+-------------------------+ | > 71 | > 0100 0111 | G | > Латинская G | +--------------------------+------------------------+--------------+-------------------------+ | > 72 | > 0100 1000 | H | > Латинская H | +--------------------------+------------------------+--------------+-------------------------+ | > 73 | > 0100 1001 | I | > Латинская I | +--------------------------+------------------------+--------------+-------------------------+

Рис. 3.6. Продолжение

+--------------------------+------------------------+--------------+----------------------------+ | > Десятичная система | > Двоичная система | > Символ | > Значение | +:========================:+:======================:+:============:+:==========================:+ | > 74 | > 0100 1010 | J | > Латинская J | +--------------------------+------------------------+--------------+----------------------------+ | > 75 | > 0100 1011 | K | > Латинская K | +--------------------------+------------------------+--------------+----------------------------+ | > 76 | > 0100 1100 | L | > Латинская L | +--------------------------+------------------------+--------------+----------------------------+ | > 77 | > 0100 1101 | M | > Латинская M | +--------------------------+------------------------+--------------+----------------------------+ | > 78 | > 0100 1110 | N | > Латинская N | +--------------------------+------------------------+--------------+----------------------------+ | > 79 | > 0100 1111 | O | > Латинская O | +--------------------------+------------------------+--------------+----------------------------+ | > 80 | > 0101 0000 | P | > Латинская P | +--------------------------+------------------------+--------------+----------------------------+ | > 81 | > 0101 0001 | Q | > Латинская Q | +--------------------------+------------------------+--------------+----------------------------+ | > 82 | > 0101 0010 | R | > Латинская R | +--------------------------+------------------------+--------------+----------------------------+ | > 83 | > 0101 0011 | S | > Латинская S | +--------------------------+------------------------+--------------+----------------------------+ | > 84 | > 0101 0100 | T | > Латинская T | +--------------------------+------------------------+--------------+----------------------------+ | > 85 | > 0101 0101 | U | > Латинская U | +--------------------------+------------------------+--------------+----------------------------+ | > 86 | > 0101 0110 | V | > Латинская V | +--------------------------+------------------------+--------------+----------------------------+ | > 87 | > 0101 0111 | W | > Латинская W | +--------------------------+------------------------+--------------+----------------------------+ | > 88 | > 0101 1000 | X | > Латинская X | +--------------------------+------------------------+--------------+----------------------------+ | > 89 | > 0101 1001 | Y | > Латинская Y | +--------------------------+------------------------+--------------+----------------------------+ | > 90 | > 0101 1010 | Z | > Латинская Z | +--------------------------+------------------------+--------------+----------------------------+ | > 91 | > 0101 1011 | [ | > Левая квадратная скобка | +--------------------------+------------------------+--------------+----------------------------+ | > 92 | > 0101 1100 | \ | > Обратный слеш | +--------------------------+------------------------+--------------+----------------------------+ | > 93 | > 0101 1101 | ] | > Правая квадратная скобка | +--------------------------+------------------------+--------------+----------------------------+ | > 94 | > 0101 1110 | ^ | > Циркумфлекс | +--------------------------+------------------------+--------------+----------------------------+ | > 95 | > 0101 1111 | _ | > Нижнее подчеркивание | +--------------------------+------------------------+--------------+----------------------------+ | > 96 | > 0110 0000 | ` | > Гравис | +--------------------------+------------------------+--------------+----------------------------+ | > 97 | > 0110 0001 | a | > Латинская a | +--------------------------+------------------------+--------------+----------------------------+ | > 98 | > 0110 0010 | b | > Латинская b | +--------------------------+------------------------+--------------+----------------------------+ | > 99 | > 0110 0011 | c | > Латинская c | +--------------------------+------------------------+--------------+----------------------------+ | > 100 | > 0110 0100 | d | > Латинская d | +--------------------------+------------------------+--------------+----------------------------+ | > 101 | > 0110 0101 | e | > Латинская e | +--------------------------+------------------------+--------------+----------------------------+ | > 102 | > 0110 0110 | f | > Латинская f | +--------------------------+------------------------+--------------+----------------------------+ | > 103 | > 0110 0111 | g | > Латинская g | +--------------------------+------------------------+--------------+----------------------------+ | > 104 | > 0110 1000 | h | > Латинская h | +--------------------------+------------------------+--------------+----------------------------+ | > 105 | > 0110 1001 | i | > Латинская i | +--------------------------+------------------------+--------------+----------------------------+ | > 106 | > 0110 1010 | j | > Латинская j | +--------------------------+------------------------+--------------+----------------------------+ | > 107 | > 0110 1011 | k | > Латинская k | +--------------------------+------------------------+--------------+----------------------------+ | > 108 | > 0110 1100 | l | > Латинская l | +--------------------------+------------------------+--------------+----------------------------+ | > 109 | > 0110 1101 | m | > Латинская m | +--------------------------+------------------------+--------------+----------------------------+ | > 110 | > 0110 1110 | n | > Латинская n | +--------------------------+------------------------+--------------+----------------------------+

Рис. 3.6. Продолжение

+--------------------------------------------------+-------------------------------+ | > Десятичная система Двоичная система Символ | > Значение | +==================================================+===============================+ | > 111 0110 1111 o | > Латинская o | +--------------------------------------------------+-------------------------------+ | > 112 0111 0000 p | > Латинская p | +--------------------------------------------------+-------------------------------+ | > 113 0111 0001 q | > Латинская q | +--------------------------------------------------+-------------------------------+ | > 114 0111 0010 r | > Латинская r | +--------------------------------------------------+-------------------------------+ | > 115 0111 0011 s | > Латинская s | +--------------------------------------------------+-------------------------------+ | > 116 0111 0100 t | > Латинская t | +--------------------------------------------------+-------------------------------+ | > 117 0111 0101 u | > Латинская u | +--------------------------------------------------+-------------------------------+ | > 118 0111 0110 v | > Латинская v | +--------------------------------------------------+-------------------------------+ | > 119 0111 0111 w | > Латинская w | +--------------------------------------------------+-------------------------------+ | > 120 0111 1000 x | > Латинская x | +--------------------------------------------------+-------------------------------+ | > 121 0111 1001 y | > Латинская y | +--------------------------------------------------+-------------------------------+ | > 122 0111 1010 z | > Латинская z | +--------------------------------------------------+-------------------------------+ | > 123 0111 1011 { | > Левая фигурная скобка | +--------------------------------------------------+-------------------------------+ | > 124 0111 1100 | | > Вертикальная черта | +--------------------------------------------------+-------------------------------+ | > 125 0111 1101 } | > Правая фигурная скобка | +--------------------------------------------------+-------------------------------+ | > 126 0111 1110 ~ | > Тильда | +--------------------------------------------------+-------------------------------+ | > 127 0111 1111 DEL | > Удаление | +--------------------------------------------------+-------------------------------+ | > Рис. 3.6. Окончание | > только английский язык. | | > | | | > Однако ASCII-код способен представлять | | +--------------------------------------------------+-------------------------------+

С развитием глобализации вычислительной техники появился расширенный набор символов EASCII, способный представлять больше языков. Он расширяет 7-битный ASCII до 8 бит, что позволяет представлять 256 различных символов. В разных частях мира постепенно появились различные EASCII-наборы символов, адаптированные для разных регионов. Первые 128 символов в этих наборах совпадают с ASCII, а оставшиеся 128 символов определяются по-

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

Таблицы символов GBK

Позднее выяснилось, что EASCII все еще не может удовлетворить требова- ния многих языков по количеству символов. Например, в китайском языке около ста тысяч иероглифов, из которых несколько тысяч используются в по- вседневной жизни. В 1980 году Государственное управление по стандартам Ки- тая выпустило таблицу символов GB2312, которая включала 6763 иероглифа, что в основном удовлетворяет потребности в обработке китайских иерогли- фов на компьютере.

Однако GB2312 не может обрабатывать некоторые редкие и традицион- ные иероглифы. Поэтому таблица GBK была расширена и стала включать 21 886 иероглифов. В кодировке GBK символы ASCII представляются одним бай- том, а иероглифы -- двумя байтами.

Таблица символов Unicode

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

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

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

С момента своего выпуска в 1991 году Unicode постоянно расширяется, добавляя поддержку новых языков и символов. По состоянию на сентябрь 2022 года кодировка Unicode включает 149 186 символов, включая символы различных языков, знаки и даже эмодзи. В обширном наборе символов Uni- code часто используемые символы занимают 2 байта, некоторые редкие сим- волы занимают 3 или даже 4 байта.

Unicode -- это универсальный набор символов, который присваивает каж- дому символу номер (называемый кодовой точкой), но не определяет, как эти кодовые точки должны храниться в компьютере. Возникает вопрос: как система интерпретирует символы, когда в тексте одновременно при- сутствуют кодовые точки Unicode разной длины? Например, как система определяет, что код длиной 2 байта -- это один 2-байтовый символ, а не два 1-байтовых?

Одним из простых решений этой проблемы является хранение всех символов в виде кодов одинаковой длины. В примере на рис. 3.7 показа- но, что каждый символ в слове Hello занимает 1 байт, а каждый символ в ки- тайском слове 算 法 (алгоритм) -- 2 байта. Можно закодировать все символы в выражении «Hello 算法» 2 байтами, заполнив старшие биты нулями. Таким образом, система сможет распознавать один символ каждые 2 байта и восста- новить содержание этой фразы.

Однако ASCII-код уже доказал, что для кодирования английского языка до- статочно 1 байта. Если использовать вышеописанный метод, размер англий- ского текста будет вдвое больше, чем при кодировании с помощью ASCII, что крайне неэффективно по памяти. Поэтому необходим более эффективный ме- тод использования кодировки Unicode.

Рис. 3.7. Пример представления символов в Unicode

Кодирование UTF-8

В настоящее время UTF-8 является наиболее широко используемым методом кодирования Unicode в мире. Это кодировка переменной длины, использую- щая от 1 до 4 байт для представления одного символа, в зависимости от его сложности. ASCII-символы требуют всего 1 байт, латинские и греческие бук- вы -- 2 байта, часто используемые китайские иероглифы -- 3 байта, а некоторые редкие символы -- 4 байта.

Правила кодирования UTF-8 несложны и делятся на два случая.

  1. Для символов длиной в 1 байт старший бит устанавливается в 0, осталь- ные 7 бит содержат кодовую точку Unicode. Следует отметить, что ASCII- символы занимают первые 128 кодовых точек в наборе Unicode. Это означает, что кодировка UTF-8 обратно совместима с ASCII. Таким об- разом, можно использовать UTF-8 для обработки старых текстов в коди- ровке ASCII.

  2. Для символов длиной в n байт (для n > 1) в первом байте старшие n бит устанавливаются в 1, а (n + 1)-й бит -- в 0. Начиная со второго байта, в каждом байте старшие 2 бита устанавливаются в 10, остальные биты используются для заполнения кодовой точки символа Unicode.

На рис. 3.8 изображено кодирование UTF-8 для фразы «Hello 算法». Можно заметить, что, поскольку старшие n бит установлены в 1, система может опре- делить длину символа n, считая количество старших единиц в первом байте.

Но почему старшие 2 бита остальных байтов устанавливаются в 10? На самом деле эти 10 служат в качестве контрольного символа. Если система начнет разбор текста с ошибочного байта, 10 в начале байта поможет быстро выявить ошибку. 10 используется как контрольный символ, потому что в соответствии с пра- вилами кодирования UTF-8 не может быть символа, у которого старшие два бита равны 10. Это можно доказать методом от противного: если предполо- жить, что у символа старшие два бита равны 10, значит длина этого символа равна 1, что соответствует ASCII-коду. Однако в ASCII-коде старший бит дол-

жен быть 0, что противоречит предположению.

Рис. 3.8. Пример кодирования с помощью UTF-8

Кроме UTF-8, существуют еще два распространенных способа кодирования.

  1. Кодировка UTF-16: для представления одного символа используется 2 или 4 байта. Все ASCII-символы и часто используемые неанглийские символы представлены 2 байтами. Некоторые другие символы требу- ют 4 байта. Для символов длиной в 2 байта кодировка UTF-16 равна кодовой точке Unicode.

  2. Кодировка UTF-32: каждый символ представляется 4 байтами. Это оз- начает, что UTF-32 занимает больше места, чем UTF-8 и UTF-16, особен- но для текстов с большим количеством ASCII-символов.

С точки зрения занимаемого пространства использование UTF-8 для пред- ставления английских символов очень эффективно, так как требуется всего 1 байт на символ. Использование UTF-16 для кодирования некоторых неан- глийских символов (например, китайских) более эффективно, так как требуется всего 2 байта, тогда как в UTF-8 может потребоваться 3 байта.

С точки зрения совместимости UTF-8 обладает наилучшей универсаль- ностью, и многие инструменты и библиотеки в первую очередь используют UTF-8.

Кодирование символов в языках программирования

Раньше в большинстве языков программирования строки в процессе выпол- нения программы кодировались с использованием UTF-16 или UTF-32, т. е. кодировок фиксированной длины. В таких кодировках строки можно обраба- тывать как массивы, что имеет следующие преимущества.

  • Случайный доступ: в строках в кодировке UTF-16 легко выполнять слу- чайный доступ. UTF-8 является кодировкой переменной длины, поэтому для нахождения i-го символа необходимо пройти от начала строки до i-го символа, что требует времени O(n).
  • Подсчет символов: аналогично случайному доступу вычисление длины строки в кодировке UTF-16 является операцией O(1). Тогда как для вычис- ления длины строки в кодировке UTF-8 необходимо пройти всю строку.

  • Операции со строками: многие операции над строками в кодировке UTF-16 (такие как разбиение, соединение, вставка, удаление и т. д.) вы- полняются легче. На строках в кодировке UTF-8 выполнение этих опера- ций часто требует дополнительных вычислений, чтобы избежать созда- ния недопустимых последовательностей байтов.

На самом деле проектирование схем кодирования символов в языках про- граммирования является очень интересной темой, затрагивающей множе- ство факторов.

  • Тип String в Java использует кодировку UTF-16, в которой каждый символ занимает 2 байта. Это связано с тем, что при разработке языка Java пред- полагалось, что 16 бит достаточно для представления всех возможных символов. Однако суждение оказалось ошибочным. Впоследствии спец- ификация Unicode расширилась за пределы 16 бит, поэтому в Java сим- волы могут быть представлены парой 16-битных значений, называемых суррогатной парой.

  • Строки в JavaScript и TypeScript также используют кодировку UTF-16 по аналогичным причинам. Когда в 1995 году компания Netscape впер- вые представила язык JavaScript, Unicode находился на ранней стадии развития, и 16-битная кодировка была достаточной для представления всех символов Unicode.

  • В C# используется кодировка UTF-16, главным образом потому что платформа .NET была разработана Microsoft, а многие технологии Mi- crosoft, включая операционную систему Windows, широко используют кодировку UTF-16.

Из-за недооценки количества символов в этих языках программирования пришлось использовать суррогатные пары для представления символов Uni- code, превышающих 16 бит. Это было вынужденной мерой. С одной стороны, в строках, содержащих суррогатные пары, один символ может занимать 2 или 4 байта, что лишает кодировку преимущества равной длины. С другой сторо- ны, обработка суррогатных пар требует дополнительного кода, что увеличива- ет сложность программирования и затрудняет отладку.

По указанным причинам в некоторых языках программирования предло- жили различные схемы кодирования.

  • В Python тип str использует кодировку Unicode и гибкое представле- ние строк, где длина символа зависит от наибольшего кодового пункта Unicode в строке. Если строка содержит только символы ASCII, каждый символ занимает 1 байт. Если есть символы за пределами ASCII, но в пределах базовой многоязычной плоскости (BMP), каждый символ за- нимает 2 байта. Если есть символы за пределами BMP, каждый символ занимает 4 байта.

  • В языке Go тип string внутренне использует кодировку UTF-8. Go также содержит тип rune для представления одного кодового пункта Unicode.

    • В языке Rust типы str и String внутренне используют кодировку UTF-8. Rust также предоставляет тип char для представления одного кодового пункта Unicode.

Следует отметить, что мы обсуждаем способ хранения строк в языках про- граммирования, что отличается от хранения строк в файлах или передачи их по сети. При хранении в файлах и сетевой передаче строки обычно кодируют- ся в формате UTF-8 для достижения наилучшей совместимости и эффектив- ности использования пространства.

резюме

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

  • К распространенным логическим структурам относятся линейные, дре- вовидные и сетевые. Обычно структуры данных делятся на линейные (массивы, списки, стеки, очереди) и нелинейные (деревья, графы, кучи). Реализация хеш-таблиц может включать как линейные, так и нелиней- ные структуры данных.

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

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

  • Основные типы данных в компьютере включают целые числа byte, short, int, long, числа с плавающей запятой float, double, символы char и логиче- ские bool. Их диапазон значений зависит от размера занимаемого про- странства и способа представления.

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

  • Целые числа в компьютере хранятся в виде дополнительного кода. В представлении дополнительного кода компьютер может одинаково обрабатывать сложение положительных и отрицательных чисел без не- обходимости в специальной аппаратной схеме для вычитания. Также при таком подходе отсутствует проблема двусмысленности положитель- ного и отрицательного нуля.

  • Кодирование чисел с плавающей запятой состоит из 1 бита для знака, 8 бит для экспоненты и 23 бит для мантиссы. Благодаря наличию экспоненты диапазон значений чисел с плавающей запятой значительно превышает диапазон целых чисел, но это достигается за счет потери точности.

  • ASCII является первой кодировкой символов для английского языка. Она включает 127 символов, длина кода составляет 1 байт. Набор сим- волов GBK часто используется для китайского языка и включает более двадцати тысяч иероглифов. Стандарт Unicode стремится предоставить стандарт полного набора символов, охватывающий символы различных языков мира, а также решая проблему искажения символов из-за несо- ответствия методов кодирования.

  • UTF-8 является самым популярным методом кодирования символов Unicode и обладает высокой универсальностью. Это метод кодирования переменной длины, который позволяет эффективно использовать про- странство хранилища. UTF-16 и UTF-32 -- это методы кодирования фик- сированной длины. При кодировании китайских символов UTF-16 зани- мает меньше места, чем UTF-8. Такие языки программирования, как Java и C#, по умолчанию используют кодировку UTF-16.

Вопросы и ответы

Вопрос. Почему хеш-таблица одновременно содержит линейные и нелиней- ные структуры данных?

Ответ. Хеш-таблица в своей основе представляет собой массив, и для реше- ния проблемы хеш-коллизий может использоваться адрес цепочки (подробнее см. в разделе «Хеш-коллизии»): каждая корзина массива указывает на связный список, который при превышении определенного порога длины может быть преобразован в дерево (обычно красно-черное дерево). С точки зрения хра- нения основа хеш-таблицы -- это массив, в котором каждая корзина может со- держать значение, связный список или дерево. Таким образом, хеш-таблица может одновременно содержать линейные структуры данных (массивы, связ- ные списки) и нелинейные структуры данных (деревья).

Вопрос. Длина типа char составляет 1 байт?

Ответ. Длина типа char определяется методом кодирования, используемым языком программирования. Например, в Java, JavaScript, TypeScript, C# ис- пользуется кодировка UTF-16 (сохраняющая кодовые точки Unicode), поэтому длина типа char составляет 2 байта.

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

Ответ. Стек действительно может выполнять динамические операции с данными, но структура данных остается статической (неизменной длины). Хотя структуры данных на основе массивов могут динамически добавлять или удалять элементы, их емкость фиксирована. Если объем данных превышает за- ранее выделенный размер, необходимо создать новый, более крупный массив и скопировать в него содержимое старого массива.

Вопрос. При создании стека (очереди) не указывается его размер, почему они являются статическими структурами данных?

Ответ. В высокоуровневых языках программирования не требуется вруч- ную задавать начальную емкость стека (очереди), это выполняется автома- тически внутри класса. Например, начальная емкость ArrayList в Java обычно

составляет 10. Кроме того, операции расширения также выполняются автома- тически. Подробнее см. в разделе «Списки».

Вопрос. Метод перевода прямого кода в дополнительный -- сначала инвер- сия, затем прибавление 1, тогда перевод дополнительного кода в прямой дол- жен быть обратной операцией -- сначала вычитание 1, затем инверсия. Но пе- ревод дополнительного кода в прямой также можно выполнить через «сначала инверсия, затем прибавление 1». Почему это работает?

Ответ. Это связано с тем, что преобразование между прямым и дополни- тельным кодами фактически является процессом вычисления дополнения. Сначала дадим определение дополнения: предположим, что a + b = c, тогда a называется дополнением b до c, и наоборот, b называется дополнением a до c. Рассмотрим двоичное число 0010 длиной n = 4 бита. Если воспринимать это число как прямой код (без учета знакового бита), то его дополнительный код

получается путем инвертирования и прибавления 1:

0010 → 1101 → 1110.

Заметим, что сумма прямого и дополнительного кодов равна 0010 + 1110 = 10000, т. е. дополнительный код 1110 является дополнением пря- мого кода 0010 до 10000. Это означает, что вышеупомянутый процесс ин- вертирования и прибавления 1 фактически является вычислением до- полнения до 10000.

Тогда каково же дополнение дополнительного кода 1110 до 10000? Его также можно получить путем инвертирования и прибавления 1:

1110 → 0001 → 0010.

Другими словами, прямой и дополнительный коды являются взаимными до- полнениями до 10000, поэтому преобразование прямого кода в дополнитель- ный и преобразование дополнительного кода в прямой можно осуществить с помощью одной и той же операции (инвертирование и прибавление 1).

Конечно, можно также использовать обратную операцию для получения прямого кода из дополнительного 1110, т. е. вычитание 1 и инвертирование:

1110 → 1101 → 0010.

В итоге обе операции «инвертирование и прибавление 1» и «вычитание 1 и инвертирование» являются вычислением дополнения до 10000, и они экви- валентны.

По сути, операция инвертирования является вычислением дополнения до 1111 (поскольку всегда выполняется равенство прямой код + инверсный код = 1111). А дополнительный код, полученный путем прибавления 1 к инверсному коду, является дополнением до 10000.

Приведенный пример для n = 4 можно распространить на двоичные числа любой длины.

Глава 4