mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-11 15:06:07 +00:00
First version.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
# Основные типы данных
|
||||
|
||||
Когда речь идет о данных в компьютере, мы думаем в первую очередь о тексте, изображениях, видео, аудио, 3D-моделях и других формах представления информации. Хотя способы организации этих данных различны, они все строятся из основных типов данных.
|
||||
|
||||
**Основные типы данных -- это те, которые могут быть непосредственно обработаны процессором и используются в алгоритмах.** Существуют следующие основные типы данных:
|
||||
|
||||
- Целочисленные типы: byte, short, int, long.
|
||||
- Типы с плавающей запятой: float, double. Используются для представления дробных чисел.
|
||||
- Символьный тип: char. Используется для представления букв различных языков, знаков препинания и даже эмодзи.
|
||||
- Логический тип: bool. Используется для представления концепций «да» и «нет».
|
||||
|
||||
**Основные типы данных хранятся в компьютере в двоичной форме.** Один двоичный разряд равен 1 биту. В большинстве современных операционных систем 1 байт состоит из 8 бит.
|
||||
|
||||
Диапазон значений основных типов данных зависит от занимаемого ими объема памяти. Рассмотрим это на примере языка Java.
|
||||
|
||||
- Целочисленный тип byte занимает 1 байт = 8 бит, может представлять 2^8 чисел.
|
||||
- Целочисленный тип int занимает 4 байта = 32 бита, может представлять 2^32 чисел.
|
||||
|
||||
В табл. 3.1 приведены объем памяти, диапазон значений и значения по умолчанию для различных основных типов данных в Java. Эту таблицу не нужно заучивать наизусть, достаточно иметь общее представление и при необходимости обращаться к ней.
|
||||
|
||||
<p align="center"> Таблица 3.1. Объем памяти и диапазон значений основных типов данных </p>
|
||||
|
||||
| Тип | Символ | Объем памяти | Минимальное значение | Максимальное значение | Значение по умолчанию |
|
||||
| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- |
|
||||
| Целые | `byte` | 1 байт | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ |
|
||||
| | `short` | 2 байта | $-2^{15}$ | $2^{15} - 1$ | $0$ |
|
||||
| | `int` | 4 байта | $-2^{31}$ | $2^{31} - 1$ | $0$ |
|
||||
| | `long` | 8 байт | $-2^{63}$ | $2^{63} - 1$ | $0$ |
|
||||
| С плавающей запятой | `float` | 4 байта | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ |
|
||||
| | `double` | 8 байт | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ |
|
||||
| Символьный | `char` | 2 байта | $0$ | $2^{16} - 1$ | $0$ |
|
||||
| Логический | `bool` | 1 байт | $\text{false}$ | $\text{true}$ | $\text{false}$ |
|
||||
|
||||
Обратите внимание, что табл. 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` и др.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
# Инициализация массива с использованием различных базовых типов данных
|
||||
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)]
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
int numbers[5];
|
||||
float decimals[5];
|
||||
char characters[5];
|
||||
bool bools[5];
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
int[] numbers = new int[5];
|
||||
float[] decimals = new float[5];
|
||||
char[] characters = new char[5];
|
||||
boolean[] bools = new boolean[5];
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
int[] numbers = new int[5];
|
||||
float[] decimals = new float[5];
|
||||
char[] characters = new char[5];
|
||||
bool[] bools = new bool[5];
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
var numbers = [5]int{}
|
||||
var decimals = [5]float64{}
|
||||
var characters = [5]byte{}
|
||||
var bools = [5]bool{}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
let numbers = Array(repeating: 0, count: 5)
|
||||
let decimals = Array(repeating: 0.0, count: 5)
|
||||
let characters: [Character] = Array(repeating: "a", count: 5)
|
||||
let bools = Array(repeating: false, count: 5)
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title=""
|
||||
// JavaScript массивы могут свободно хранить различные базовые типы данных и объекты
|
||||
const array = [0, 0.0, 'a', false];
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
const numbers: number[] = [];
|
||||
const characters: string[] = [];
|
||||
const bools: boolean[] = [];
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
List<int> numbers = List.filled(5, 0);
|
||||
List<double> decimals = List.filled(5, 0.0);
|
||||
List<String> characters = List.filled(5, 'a');
|
||||
List<bool> bools = List.filled(5, false);
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
let numbers: Vec<i32> = vec![0; 5];
|
||||
let decimals: Vec<f32> = vec![0.0; 5];
|
||||
let characters: Vec<char> = vec!['0'; 5];
|
||||
let bools: Vec<bool> = vec![false; 5];
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
int numbers[10];
|
||||
float decimals[10];
|
||||
char characters[10];
|
||||
bool bools[10];
|
||||
```
|
||||
|
||||
=== "Kotlin"
|
||||
|
||||
```kotlin title=""
|
||||
// Инициализация массива с использованием различных базовых типов данных
|
||||
val numbers = IntArray(5)
|
||||
val decinals = FloatArray(5)
|
||||
val characters = CharArray(5)
|
||||
val bools = BooleanArray(5)
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title=""
|
||||
# Списки в Ruby могут свободно хранить различные базовые типы данных и ссылки на объекты
|
||||
data = [0, 0.0, 'a', false, ListNode(0)]
|
||||
```
|
||||
|
||||
??? pythontutor "Визуализация выполнения"
|
||||
|
||||
https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20%D1%83%D0%B7%D0%BB%D0%B0%20%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D0%B3%D0%BE%20%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B0%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%D0%97%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%83%D0%B7%D0%BB%D0%B0%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B0%20%D0%BD%D0%B0%20%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B8%D0%B9%20%D1%83%D0%B7%D0%B5%D0%BB%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0%20%D1%81%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC%20%D1%80%D0%B0%D0%B7%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%D1%85%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D1%85%20%D1%82%D0%B8%D0%BF%D0%BE%D0%B2%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20%D0%92%20Python%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B%20%D1%84%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%20%D1%8F%D0%B2%D0%BB%D1%8F%D1%8E%D1%82%D1%81%D1%8F%20%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0%D0%BC%D0%B8%20%D0%B4%D0%BB%D0%B8%D0%BD%D0%BE%D0%B9%201%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20%D0%A1%D0%BF%D0%B8%D1%81%D0%BA%D0%B8%20%D0%B2%20Python%20%D0%BC%D0%BE%D0%B3%D1%83%D1%82%20%D1%81%D0%B2%D0%BE%D0%B1%D0%BE%D0%B4%D0%BD%D0%BE%20%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C%20%D1%80%D0%B0%D0%B7%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%D0%B5%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B5%20%D1%82%D0%B8%D0%BF%D1%8B%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D0%B8%20%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%20%D0%BD%D0%B0%20%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
|
||||
@@ -0,0 +1,63 @@
|
||||
```markdown
|
||||
# Кодирование символов *
|
||||
|
||||
В компьютере все данные хранятся в виде двоичных чисел, и символы `char` не являются исключением. Для представления символов необходимо создать «набор символов», определяющий взаимно однозначное соответствие между каждым символом и двоичным числом. Имея набор символов, компьютер может выполнять преобразование двоичных чисел в символы путем поиска в таблице.
|
||||
|
||||
## Набор символов ASCII
|
||||
|
||||
<u>Код ASCII</u> является самым ранним набором символов, его полное название — American Standard Code for Information Interchange (Американский стандартный код для обмена информацией). Он использует 7-битные двоичные числа (младшие 7 бит одного байта) для представления символа и может представлять максимум 128 различных символов. Как показано на рисунке ниже, код ASCII включает прописные и строчные буквы английского алфавита, цифры 0~9, некоторые знаки препинания, а также управляющие символы (такие как символ новой строки и табуляции).
|
||||
|
||||

|
||||
|
||||
Однако **код ASCII может представлять только английский язык**. С глобализацией компьютеров появился набор символов <u>EASCII</u>, способный представлять больше языков. Он расширил 7-битный ASCII до 8 бит и может представлять 256 различных символов.
|
||||
|
||||
По всему миру последовательно появились различные наборы символов EASCII, подходящие для разных регионов. Первые 128 символов этих наборов унифицированы как код ASCII, а последние 128 символов определены по-разному для удовлетворения потребностей различных языков.
|
||||
|
||||
## Набор символов GBK
|
||||
|
||||
Позже люди обнаружили, что **код EASCII все еще не может удовлетворить требования к количеству символов во многих языках**. Например, существует почти сто тысяч китайских иероглифов, из которых несколько тысяч используются в повседневной жизни. В 1980 году Главное управление стандартизации Китая выпустило набор символов <u>GB2312</u>, который включал 6763 китайских иероглифа и в основном удовлетворял потребности компьютерной обработки китайских иероглифов.
|
||||
|
||||
Однако GB2312 не мог обрабатывать некоторые редкие иероглифы и традиционные китайские символы. Набор символов <u>GBK</u> был получен путем расширения GB2312 и включает в общей сложности 21886 китайских иероглифов. В схеме кодирования GBK символы ASCII представлены одним байтом, а китайские иероглифы — двумя байтами.
|
||||
|
||||
## Набор символов Unicode
|
||||
|
||||
С бурным развитием компьютерных технологий наборы символов и стандарты кодирования расцвели пышным цветом, что привело ко многим проблемам. С одной стороны, эти наборы символов обычно определяли только символы конкретного языка и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов наборов символов, и если два компьютера использовали разные стандарты кодирования, при передаче информации возникала кракозябра.
|
||||
|
||||
Исследователи того времени думали: **если создать достаточно полный набор символов, включающий все языки и символы со всего мира, разве это не решит проблемы многоязычной среды и кракозябры**? Под влиянием этой идеи появился большой и всеобъемлющий набор символов Unicode.
|
||||
|
||||
<u>Unicode</u> на китайском языке называется «统一码» (унифицированный код), теоретически может вместить более 1 миллиона символов. Он стремится включить символы со всего мира в единый набор символов, предоставляя универсальный набор символов для обработки и отображения текстов на различных языках, уменьшая проблемы с кракозяброй, возникающие из-за различий в стандартах кодирования.
|
||||
|
||||
С момента выпуска в 1991 году Unicode постоянно пополняется новыми языками и символами. По состоянию на сентябрь 2022 года Unicode уже включает 149186 символов, включая символы различных языков, знаки и даже эмодзи. В огромном наборе символов Unicode часто используемые символы занимают 2 байта, а некоторые редкие символы занимают 3 или даже 4 байта.
|
||||
|
||||
Unicode является универсальным набором символов, по сути присваивающим каждому символу номер (называемый «кодовой точкой»), **но он не определяет, как эти кодовые точки символов должны храниться в компьютере**. Мы не можем не задаться вопросом: когда кодовые точки Unicode различной длины одновременно появляются в тексте, как система анализирует символы? Например, при заданном коде длиной 2 байта, как система определяет, является ли это одним 2-байтовым символом или двумя 1-байтовыми символами?
|
||||
|
||||
Для вышеуказанных проблем **прямым решением является хранение всех символов в виде кодов одинаковой длины**. Как показано на рисунке ниже, каждый символ в «Hello» занимает 1 байт, каждый символ в «算法» занимает 2 байта. Мы можем закодировать все символы в «Hello 算法» длиной 2 байта, заполнив старшие биты нулями. Таким образом, система может анализировать один символ каждые 2 байта и восстановить содержание этой фразы.
|
||||
|
||||

|
||||
|
||||
Однако код ASCII уже доказал нам, что для кодирования английского языка требуется только 1 байт. Если использовать вышеуказанную схему, размер английского текста будет в два раза больше, чем при кодировании ASCII, что очень расточительно с точки зрения памяти. Поэтому нам нужен более эффективный метод кодирования Unicode.
|
||||
|
||||
## Кодирование UTF-8
|
||||
|
||||
В настоящее время UTF-8 стал наиболее широко используемым методом кодирования Unicode в мире. **Это кодирование переменной длины**, использующее от 1 до 4 байтов для представления символа в зависимости от сложности символа. Символы ASCII требуют только 1 байт, латинские и греческие буквы требуют 2 байта, часто используемые китайские иероглифы требуют 3 байта, а некоторые другие редкие символы требуют 4 байта.
|
||||
|
||||
Правила кодирования UTF-8 не сложны и делятся на следующие два случая.
|
||||
|
||||
- Для символов длиной 1 байт старший бит устанавливается в $0$, остальные 7 бит устанавливаются в кодовую точку Unicode. Стоит отметить, что символы ASCII занимают первые 128 кодовых точек в наборе символов Unicode. Это означает, что **кодирование UTF-8 обратно совместимо с кодом ASCII**. Это означает, что мы можем использовать UTF-8 для анализа старых текстов в кодировке ASCII.
|
||||
- Для символов длиной $n$ байт (где $n > 1$) старшие $n$ бит первого байта устанавливаются в $1$, бит $n + 1$ устанавливается в $0$; начиная со второго байта, старшие 2 бита каждого байта устанавливаются в $10$; все остальные биты используются для заполнения кодовой точки Unicode символа.
|
||||
|
||||
На рисунке ниже показано кодирование UTF-8, соответствующее «Hello算法». Наблюдая, можно обнаружить, что поскольку старшие $n$ бит все установлены в $1$, система может определить длину символа $n$, прочитав количество единиц в старших битах.
|
||||
|
||||
Но почему старшие 2 бита всех остальных байтов устанавливаются в $10$? На самом деле, это $10$ может служить контрольным символом. Предположим, система начинает анализировать текст с неправильного байта, $10$ в начале байта может помочь системе быстро обнаружить аномалию.
|
||||
|
||||
Причина использования $10$ в качестве контрольного символа заключается в том, что согласно правилам кодирования UTF-8 невозможно, чтобы старшие два бита символа были $10$. Этот вывод можно доказать методом от противного: предположим, что старшие два бита символа равны $10$, это означает, что длина символа равна $1$, что соответствует коду ASCII. Но старший бит кода ASCII должен быть $0$, что противоречит предположению.
|
||||
|
||||

|
||||
|
||||
Помимо UTF-8, распространенные методы кодирования включают следующие два.
|
||||
|
||||
- **Кодирование UTF-16**: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представлены 2 байтами; небольшое количество символов требует 4 байта. Для 2-байтовых символов кодирование UTF-16 равно кодовой точке Unicode.
|
||||
- **Кодирование UTF-32**: каждый символ использует 4 байта. Это означает, что UTF-32 занимает больше места, чем UTF-8 и UTF-16, особенно для текстов с высокой долей символов ASCII.
|
||||
|
||||
С точки зрения занимаемого пространства хранения использование UTF-8 для представления английских символов очень эффективно, поскольку требуется только 1 байт;
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# Классификация структур данных
|
||||
|
||||
К распространенным структурам данных относятся массивы, списки, стеки, очереди, хеш-таблицы, деревья, кучи, графы. Их можно классифицировать по двум измерениям: логической структуре и физической структуре.
|
||||
|
||||
## Логическая структура: линейная и нелинейная
|
||||
|
||||
**Логическая структура раскрывает логические отношения между элементами данных**. В массивах и списках данные расположены в определенном порядке, что отражает линейные отношения между данными. В деревьях данные расположены сверху вниз по уровням, что демонстрирует отношения «предок» и «потомок». Графы состоят из узлов и ребер, отражая сложные сетевые отношения.
|
||||
|
||||
Логические структуры делятся на две большие категории: линейные и нелинейные, как показано на рис. 3.1. Линейные структуры более интуитивно понятны, поскольку в них данные расположены линейно и логически связаны. Нелинейные структуры, наоборот, представляют собой нелинейное расположение элементов данных.
|
||||
|
||||
- **Линейные структуры данных**: массивы, списки, стеки, очереди, хеш-таблицы, в которых элементы связаны последовательно один к одному.
|
||||
|
||||
- **Нелинейные структуры данных**: деревья, кучи, графы, хеш-таблицы.
|
||||
|
||||
Нелинейные структуры данных можно дополнительно разделить на древовидные и сетевые.
|
||||
|
||||
- **Древовидные структуры**: деревья, кучи, хеш-таблицы, в которых элементы связаны один ко многим.
|
||||
|
||||
- **Сетевые структуры**: графы, в которых элементы связаны многие ко многим.
|
||||
|
||||

|
||||
|
||||
## Физическая структура: непрерывная и разреженная
|
||||
|
||||
**Во время выполнения программы обрабатываемые данные в основном хранятся в памяти**. На рис. 3.2 изображен модуль оперативной памяти компьютера, где каждый черный чип содержит определенный участок памяти. Память можно представить как огромную таблицу Excel, где каждая ячейка может хранить данные определенного размера.
|
||||
|
||||
**Система обращается к данным в целевой позиции через адреса памяти**. Компьютер присваивает каждой ячейке таблицы номер по определенным правилам, чтобы обеспечить уникальный адрес памяти для каждого участка, как показано на рис. 3.2. Благодаря этим адресам программа может обращаться к данным в памяти.
|
||||
|
||||

|
||||
|
||||
!!! tip
|
||||
|
||||
Память является общим ресурсом для всех программ, и когда участок памяти занят одной программой, он обычно не может быть одновременно использован другими программами.
|
||||
|
||||
**Поэтому в процессе проектирования структур данных и алгоритмов память занимает важное место**. Например, пиковое использование памяти алгоритмом не должно превышать оставшуюся свободную память системы. Если не хватает непрерывных больших участков памяти, выбранная структура данных должна уметь располагаться в разреженных участках памяти.
|
||||
|
||||
**Физическая структура отражает способ хранения данных в памяти компьютера** и делится на хранение в непрерывном пространстве (массивы) и хранение в разреженном пространстве (списки), как показано на рис. 3.3. Физическая структура на низком уровне определяет методы доступа, обновления, добавления и удаления данных. Обе физические структуры демонстрируют взаимодополняющие характеристики в отношении временной и пространственной эффективности.
|
||||
|
||||

|
||||
|
||||
Следует отметить, что **все структуры данных реализуются на основе массивов, связных списков или их комбинации**. Например, стек и очередь можно реализовать как с использованием массивов, так и с использованием связных списков. Реализация хеш-таблицы может включать как массивы, так и связные списки.
|
||||
|
||||
- **На основе массивов можно реализовать**: стек, очередь, хеш-таблицу, дерево, кучу, граф, матрицу, тензор (массив размерностью ≥ 3) и др.
|
||||
|
||||
- **На основе связных списков можно реализовать**: стек, очередь, хеш-таблицу, дерево, кучу, граф и др.
|
||||
|
||||
Связный список после инициализации может изменять свою длину в процессе выполнения программы, поэтому его также называют динамической структурой данных. Массив после инициализации имеет неизменную длину, поэтому его называют статической структурой данных. Следует отметить, что массив может изменять свою длину путем перераспределения памяти, что придает ему определенную динамичность.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Структуры данных
|
||||
|
||||

|
||||
|
||||
!!! abstract
|
||||
|
||||
Структуры данных подобны прочному и разнообразному каркасу.
|
||||
|
||||
Они предоставляют план для упорядоченной организации данных, на основе которого алгоритмы оживают.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Кодирование чисел *
|
||||
|
||||
!!! tip
|
||||
|
||||
В этой книге разделы, отмеченные символом *, являются необязательными для чтения. Если у вас ограничено время или возникают трудности с пониманием, вы можете пропустить их и вернуться к ним после изучения обязательных разделов.
|
||||
|
||||
## Прямой, обратный и дополнительный коды
|
||||
|
||||
В таблице из предыдущего раздела можно заметить, что во всех целочисленных типах отрицательных чисел на одно больше, чем положительных. Например, диапазон значений `byte` составляет $[-128, 127]$. Этот факт кажется не совсем интуитивным, и его внутренняя причина связана с концепциями прямого, обратного и дополнительного кодов.
|
||||
|
||||
Прежде всего необходимо отметить, что **числа хранятся в компьютере в виде дополнительного кода**. Прежде чем проанализировать причины этого, сначала дадим определения всем трем кодам.
|
||||
|
||||
- **Прямой код**: старший бит двоичного представления числа рассматривается как знак, где $0$ обозначает положительное число, $1$ -- отрицательное, остальные биты представляют значение числа.
|
||||
- **Обратный код**: обратный код положительного числа совпадает с его прямым кодом, обратный код отрицательного числа получается инверсией всех битов прямого кода, кроме знакового.
|
||||
- **Дополнительный код**: дополнительный код положительного числа совпадает с его прямым кодом, дополнительный код отрицательного числа получается добавлением $1$ к его обратному коду.
|
||||
|
||||
На рис. 3.4 изображены методы преобразования прямого, обратного и дополнительного кодов между собой.
|
||||
|
||||

|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:<u>原码(sign-magnitude)</u>虽然最直观,但存在一些局限性。一方面,**负数的原码不能直接用于运算**。例如在原码下计算 $1 + (-2)$ ,得到的结果是 $-3$ ,这显然是不对的... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:$$\begin{aligned}& 1 + (-2) \newline& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline& = 1000 \; 0011 \newline& \rightarrow -3\end{aligned}$$... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:为了解决此问题,计算机引入了<u>反码(1's complement)</u>。如果我们先将原码转换为反码,并在反码下计算 $1 + (-2)$ ,最后将结果从反码转换回原码,则可得到正确结果 $-1$... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:$$\begin{aligned}& 1 + (-2) \newline& \rightarrow 0000 \; 0001 \; \text{(原码)} + 1000 \; 0010 \; \text{(原码)} \newline& = 0000 \; 0001 \; \text{(反码)} + 1111 \; 1101 \; \text{(反码)}... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:另一方面,**数字零的原码有 $+0$ 和 $-0$ 两种表示方式**。这意味着数字零对应两个不同的二进制编码,这可能会带来歧义... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:$$\begin{aligned}+0 & \rightarrow 0000 \; 0000 \newline-0 & \rightarrow 1000 \; 0000\end{aligned}$$... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了<u>补码(2's complement)</u>。我们先来观察一下负零的原码、反码、补码的转换过程... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:$$\begin{aligned}-0 \rightarrow \; & 1000 \; 0000 \; \text{(原码)} \newline= \; & 1111 \; 1111 \; \text{(反码)} \newline= 1 \; & 0000 \; 0000 \; \text{(补码)} \newline\end{aligned}$$... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:在负零的反码基础上加 $1$ 会产生进位,但 `byte` 类型的长度只有 8 位,因此溢出到第 9 位的 $1$ 会被舍弃。也就是说,**负零的补码为 $0000 \; 0000$ ,与正零的补码相同**... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:还剩最后一个疑惑:`byte` 类型的取值范围是 $[-128, 127]$ ,多出来的一个负数 $-128$ 是如何得到的呢?我们注意到,区间 $[-127, +127]$ 内的所有整数都有对应的原码、反码和补码... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:然而,**补码 $1000 \; 0000$ 是一个例外,它并没有对应的原码**。根据转换方法,我们得到该补码的原码为 $0000 \; 0000$... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:$$\begin{aligned}& (-127) + (-1) \newline& \rightarrow 1111 \; 1111 \; \text{(原码)} + 1000 \; 0001 \; \text{(原码)}... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:你可能已经发现了,上述所有计算都是加法运算。这暗示着一个重要事实:**计算机内部的硬件电路主要是基于加法运算设计的**... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:请注意,这并不意味着计算机只能做加法。**通过将加法与一些基本逻辑运算结合,计算机能够实现各种其他的数学运算**... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:现在我们可以总结出计算机使用补码的原因:基于补码表示,计算机可以用同样的电路和操作来处理正数和负数的加法... -->
|
||||
|
||||
<!-- 🔴 俄文版缺失此段落 -->
|
||||
<!-- 中文原文:补码的设计非常精妙,因篇幅关系我们就先介绍到这里,建议有兴趣的读者进一步深入了解。 -->
|
||||
|
||||
## Кодирование чисел с плавающей запятой
|
||||
|
||||
<!-- 🔴 俄文版缺失整个"浮点数编码"章节 -->
|
||||
<!-- 中文原文:细心的你可能会发现:`int` 和 `float` 长度相同,都是 4 字节 ,但为什么 `float` 的取值范围远大于 `int`... -->
|
||||
@@ -0,0 +1,66 @@
|
||||
# Резюме
|
||||
|
||||
### Основные моменты
|
||||
|
||||
- Структуры данных можно классифицировать с точки зрения логической структуры и физической структуры. Логическая структура описывает логические отношения между элементами данных, а физическая структура описывает способ хранения данных в памяти компьютера.
|
||||
- К распространенным логическим структурам относятся линейные, древовидные и сетевые. Обычно мы делим структуры данных на линейные (массивы, связные списки, стеки, очереди) и нелинейные (деревья, графы, кучи) в зависимости от логической структуры. Реализация хеш-таблицы может одновременно включать линейные и нелинейные структуры данных.
|
||||
- Во время выполнения программы данные хранятся в памяти компьютера. Каждое пространство памяти имеет соответствующий адрес памяти, программа обращается к данным через эти адреса памяти.
|
||||
- Физическая структура в основном делится на хранение в непрерывном пространстве (массивы) и хранение в разреженном пространстве (связные списки). Все структуры данных реализуются на основе массивов, связных списков или их комбинации.
|
||||
- Основные типы данных в компьютере включают целые числа `byte`, `short`, `int`, `long`, числа с плавающей запятой `float`, `double`, символы `char` и логический тип `bool`. Их диапазон значений зависит от занимаемого объема памяти и способа представления.
|
||||
- Прямой код, обратный код и дополнительный код -- это три метода кодирования чисел в компьютере, которые могут быть преобразованы друг в друга. Старший бит прямого кода целого числа является знаковым битом, остальные биты представляют значение числа.
|
||||
- Целые числа хранятся в компьютере в виде дополнительного кода. При представлении в дополнительном коде компьютер может одинаково обрабатывать сложение положительных и отрицательных чисел, не требуя специальных аппаратных схем для операции вычитания, и не существует проблемы неоднозначности положительного и отрицательного нуля.
|
||||
- Кодирование числа с плавающей запятой состоит из 1 знакового бита, 8 битов экспоненты и 23 битов мантиссы. Благодаря наличию битов экспоненты диапазон значений чисел с плавающей запятой намного больше, чем у целых чисел, ценой чего является потеря точности.
|
||||
- ASCII -- это самая ранняя кодировка английских символов длиной 1 байт, включающая 127 символов. Кодировка GBK -- это часто используемая китайская кодировка, включающая более 20 тысяч иероглифов. 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 \rightarrow 1101 \rightarrow 1110
|
||||
$$
|
||||
|
||||
Мы обнаружим, что сумма прямого и дополнительного кодов равна $0010 + 1110 = 10000$, то есть дополнительный код $1110$ является «дополнением» прямого кода $0010$ до $10000$. **Это означает, что вышеупомянутая операция «сначала инвертировать, затем добавить 1» фактически является процессом вычисления дополнения до $10000$**.
|
||||
|
||||
Тогда каково «дополнение» дополнительного кода $1110$ до $10000$? Мы по-прежнему можем использовать «сначала инвертировать, затем добавить 1», чтобы получить его:
|
||||
|
||||
$$
|
||||
1110 \rightarrow 0001 \rightarrow 0010
|
||||
$$
|
||||
|
||||
Другими словами, прямой и дополнительный коды являются взаимными дополнениями друг друга до $10000$, поэтому «преобразование прямого кода в дополнительный» и «преобразование дополнительного кода в прямой» могут быть выполнены одной и той же операцией (сначала инвертировать, затем добавить 1).
|
||||
|
||||
Конечно, мы также можем использовать обратную операцию для получения прямого кода дополнительного кода $1110$, то есть «сначала вычесть 1, затем инвертировать»:
|
||||
|
||||
$$
|
||||
1110 \rightarrow 1101 \rightarrow 0010
|
||||
$$
|
||||
|
||||
В итоге, обе операции «сначала инвертировать, затем добавить 1» и «сначала вычесть 1, затем инвертировать» вычисляют дополнение до $10000$, они эквивалентны.
|
||||
|
||||
По сути, операция «инвертирования» фактически вычисляет дополнение до $1111$ (поскольку всегда `прямой код + обратный код = 1111`); а дополнительный код, полученный добавлением 1 к обратному коду, является дополнением до $10000$.
|
||||
|
||||
Приведенный выше пример с $n = 4$ может быть обобщен на двоичные числа любой разрядности.
|
||||
Reference in New Issue
Block a user