This commit is contained in:
krahets
2024-03-23 03:04:26 +08:00
parent 9472c8b4e6
commit 22017aa8e5
43 changed files with 0 additions and 2 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
---
comments: true
icon: material/view-list-outline
---
# Chapter 4.   Arrays and Linked Lists
![Arrays and Linked Lists](../assets/covers/chapter_array_and_linkedlist.jpg){ class="cover-image" }
!!! abstract
The world of data structures resembles a sturdy brick wall.
In arrays, envision bricks snugly aligned, each resting seamlessly beside the next, creating a unified formation. Meanwhile, in linked lists, these bricks disperse freely, embraced by vines gracefully knitting connections between them.
## Chapter Contents
- [4.1   Array](https://www.hello-algo.com/en/chapter_array_and_linkedlist/array/)
- [4.2   Linked List](https://www.hello-algo.com/en/chapter_array_and_linkedlist/linked_list/)
- [4.3   List](https://www.hello-algo.com/en/chapter_array_and_linkedlist/list/)
- [4.4   Memory and Cache](https://www.hello-algo.com/en/chapter_array_and_linkedlist/ram_and_cache/)
- [4.5   Summary](https://www.hello-algo.com/en/chapter_array_and_linkedlist/summary/)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,83 @@
---
comments: true
---
# 4.4   Memory and Cache *
In the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures, representing "continuous storage" and "dispersed storage" respectively.
In fact, **the physical structure largely determines the efficiency of a program's use of memory and cache**, which in turn affects the overall performance of the algorithm.
## 4.4.1   Computer Storage Devices
There are three types of storage devices in computers: "hard disk," "random-access memory (RAM)," and "cache memory." The following table shows their different roles and performance characteristics in computer systems.
<p align="center"> Table 4-2 &nbsp; Computer Storage Devices </p>
<div class="center-table" markdown>
| | Hard Disk | Memory | Cache |
| ---------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| Usage | Long-term storage of data, including OS, programs, files, etc. | Temporary storage of currently running programs and data being processed | Stores frequently accessed data and instructions, reducing the number of CPU accesses to memory |
| Volatility | Data is not lost after power off | Data is lost after power off | Data is lost after power off |
| Capacity | Larger, TB level | Smaller, GB level | Very small, MB level |
| Speed | Slower, several hundred to thousands MB/s | Faster, several tens of GB/s | Very fast, several tens to hundreds of GB/s |
| Price | Cheaper, several cents to yuan / GB | More expensive, tens to hundreds of yuan / GB | Very expensive, priced with CPU |
</div>
We can imagine the computer storage system as a pyramid structure shown in the Figure 4-9 . The storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more costly. This multi-level design is not accidental, but the result of careful consideration by computer scientists and engineers.
- **Hard disks are difficult to replace with memory**. Firstly, data in memory is lost after power off, making it unsuitable for long-term data storage; secondly, the cost of memory is dozens of times that of hard disks, making it difficult to popularize in the consumer market.
- **It is difficult for caches to have both large capacity and high speed**. As the capacity of L1, L2, L3 caches gradually increases, their physical size becomes larger, increasing the physical distance from the CPU core, leading to increased data transfer time and higher element access latency. Under current technology, a multi-level cache structure is the best balance between capacity, speed, and cost.
![Computer Storage System](ram_and_cache.assets/storage_pyramid.png){ class="animation-figure" }
<p align="center"> Figure 4-9 &nbsp; Computer Storage System </p>
!!! note
The storage hierarchy of computers reflects a delicate balance between speed, capacity, and cost. In fact, this kind of trade-off is common in all industrial fields, requiring us to find the best balance between different advantages and limitations.
Overall, **hard disks are used for long-term storage of large amounts of data, memory is used for temporary storage of data being processed during program execution, and cache is used to store frequently accessed data and instructions** to improve program execution efficiency. Together, they ensure the efficient operation of computer systems.
As shown in the Figure 4-10 , during program execution, data is read from the hard disk into memory for CPU computation. The cache can be considered a part of the CPU, **smartly loading data from memory** to provide fast data access to the CPU, significantly enhancing program execution efficiency and reducing reliance on slower memory.
![Data Flow Between Hard Disk, Memory, and Cache](ram_and_cache.assets/computer_storage_devices.png){ class="animation-figure" }
<p align="center"> Figure 4-10 &nbsp; Data Flow Between Hard Disk, Memory, and Cache </p>
## 4.4.2 &nbsp; Memory Efficiency of Data Structures
In terms of memory space utilization, arrays and linked lists have their advantages and limitations.
On one hand, **memory is limited and cannot be shared by multiple programs**, so we hope that data structures can use space as efficiently as possible. The elements of an array are tightly packed without extra space for storing references (pointers) between linked list nodes, making them more space-efficient. However, arrays require allocating sufficient continuous memory space at once, which may lead to memory waste, and array expansion also requires additional time and space costs. In contrast, linked lists allocate and reclaim memory dynamically on a per-node basis, providing greater flexibility.
On the other hand, during program execution, **as memory is repeatedly allocated and released, the degree of fragmentation of free memory becomes higher**, leading to reduced memory utilization efficiency. Arrays, due to their continuous storage method, are relatively less likely to cause memory fragmentation. In contrast, the elements of a linked list are dispersedly stored, and frequent insertion and deletion operations make memory fragmentation more likely.
## 4.4.3 &nbsp; Cache Efficiency of Data Structures
Although caches are much smaller in space capacity than memory, they are much faster and play a crucial role in program execution speed. Since the cache's capacity is limited and can only store a small part of frequently accessed data, when the CPU tries to access data not in the cache, a "cache miss" occurs, forcing the CPU to load the needed data from slower memory.
Clearly, **the fewer the cache misses, the higher the CPU's data read-write efficiency**, and the better the program performance. The proportion of successful data retrieval from the cache by the CPU is called the "cache hit rate," a metric often used to measure cache efficiency.
To achieve higher efficiency, caches adopt the following data loading mechanisms.
- **Cache Lines**: Caches don't store and load data byte by byte but in units of cache lines. Compared to byte-by-byte transfer, the transmission of cache lines is more efficient.
- **Prefetch Mechanism**: Processors try to predict data access patterns (such as sequential access, fixed stride jumping access, etc.) and load data into the cache according to specific patterns to improve the hit rate.
- **Spatial Locality**: If data is accessed, data nearby is likely to be accessed in the near future. Therefore, when loading certain data, the cache also loads nearby data to improve the hit rate.
- **Temporal Locality**: If data is accessed, it's likely to be accessed again in the near future. Caches use this principle to retain recently accessed data to improve the hit rate.
In fact, **arrays and linked lists have different cache utilization efficiencies**, mainly reflected in the following aspects.
- **Occupied Space**: Linked list elements occupy more space than array elements, resulting in less effective data volume in the cache.
- **Cache Lines**: Linked list data is scattered throughout memory, and since caches load "by line," the proportion of loading invalid data is higher.
- **Prefetch Mechanism**: The data access pattern of arrays is more "predictable" than that of linked lists, meaning the system is more likely to guess which data will be loaded next.
- **Spatial Locality**: Arrays are stored in concentrated memory spaces, so the data near the loaded data is more likely to be accessed next.
Overall, **arrays have a higher cache hit rate and are generally more efficient in operation than linked lists**. This makes data structures based on arrays more popular in solving algorithmic problems.
It should be noted that **high cache efficiency does not mean that arrays are always better than linked lists**. Which data structure to choose in actual applications should be based on specific requirements. For example, both arrays and linked lists can implement the "stack" data structure (which will be detailed in the next chapter), but they are suitable for different scenarios.
- In algorithm problems, we tend to choose stacks based on arrays because they provide higher operational efficiency and random access capabilities, with the only cost being the need to pre-allocate a certain amount of memory space for the array.
- If the data volume is very large, highly dynamic, and the expected size of the stack is difficult to estimate, then a stack based on a linked list is more appropriate. Linked lists can disperse a large amount of data in different parts of the memory and avoid the additional overhead of array expansion.
@@ -0,0 +1,85 @@
---
comments: true
---
# 4.5 &nbsp; Summary
### 1. &nbsp; Key Review
- Arrays and linked lists are two basic data structures, representing two storage methods in computer memory: contiguous space storage and non-contiguous space storage. Their characteristics complement each other.
- Arrays support random access and use less memory; however, they are inefficient in inserting and deleting elements and have a fixed length after initialization.
- Linked lists implement efficient node insertion and deletion through changing references (pointers) and can flexibly adjust their length; however, they have lower node access efficiency and consume more memory.
- Common types of linked lists include singly linked lists, circular linked lists, and doubly linked lists, each with its own application scenarios.
- Lists are ordered collections of elements that support addition, deletion, and modification, typically implemented based on dynamic arrays, retaining the advantages of arrays while allowing flexible length adjustment.
- The advent of lists significantly enhanced the practicality of arrays but may lead to some memory space wastage.
- During program execution, data is mainly stored in memory. Arrays provide higher memory space efficiency, while linked lists are more flexible in memory usage.
- Caches provide fast data access to CPUs through mechanisms like cache lines, prefetching, spatial locality, and temporal locality, significantly enhancing program execution efficiency.
- Due to higher cache hit rates, arrays are generally more efficient than linked lists. When choosing a data structure, the appropriate choice should be made based on specific needs and scenarios.
### 2. &nbsp; Q & A
**Q**: Does storing arrays on the stack versus the heap affect time and space efficiency?
Arrays stored on both the stack and heap are stored in contiguous memory spaces, and data operation efficiency is essentially the same. However, stacks and heaps have their own characteristics, leading to the following differences.
1. Allocation and release efficiency: The stack is a smaller memory block, allocated automatically by the compiler; the heap memory is relatively larger and can be dynamically allocated in the code, more prone to fragmentation. Therefore, allocation and release operations on the heap are generally slower than on the stack.
2. Size limitation: Stack memory is relatively small, while the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays.
3. Flexibility: The size of arrays on the stack needs to be determined at compile-time, while the size of arrays on the heap can be dynamically determined at runtime.
**Q**: Why do arrays require elements of the same type, while linked lists do not emphasize same-type elements?
Linked lists consist of nodes connected by references (pointers), and each node can store data of different types, such as int, double, string, object, etc.
In contrast, array elements must be of the same type, allowing the calculation of offsets to access the corresponding element positions. For example, an array containing both int and long types, with single elements occupying 4 bytes and 8 bytes respectively, cannot use the following formula to calculate offsets, as the array contains elements of two different lengths.
```shell
# Element memory address = Array memory address + Element length * Element index
```
**Q**: After deleting a node, is it necessary to set `P.next` to `None`?
Not modifying `P.next` is also acceptable. From the perspective of the linked list, traversing from the head node to the tail node will no longer encounter `P`. This means that node `P` has been effectively removed from the list, and where `P` points no longer affects the list.
From a garbage collection perspective, for languages with automatic garbage collection mechanisms like Java, Python, and Go, whether node `P` is collected depends on whether there are still references pointing to it, not on the value of `P.next`. In languages like C and C++, we need to manually free the node's memory.
**Q**: In linked lists, the time complexity for insertion and deletion operations is `O(1)`. But searching for the element before insertion or deletion takes `O(n)` time, so why isn't the time complexity `O(n)`?
If an element is searched first and then deleted, the time complexity is indeed `O(n)`. However, the `O(1)` advantage of linked lists in insertion and deletion can be realized in other applications. For example, in the implementation of double-ended queues using linked lists, we maintain pointers always pointing to the head and tail nodes, making each insertion and deletion operation `O(1)`.
**Q**: In the image "Linked List Definition and Storage Method", do the light blue storage nodes occupy a single memory address, or do they share half with the node value?
The diagram is just a qualitative representation; quantitative analysis depends on specific situations.
- Different types of node values occupy different amounts of space, such as int, long, double, and object instances.
- The memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes.
**Q**: Is adding elements to the end of a list always `O(1)`?
If adding an element exceeds the list length, the list needs to be expanded first. The system will request a new memory block and move all elements of the original list over, in which case the time complexity becomes `O(n)`.
**Q**: The statement "The emergence of lists greatly improves the practicality of arrays, but may lead to some memory space wastage" - does this refer to the memory occupied by additional variables like capacity, length, and expansion multiplier?
The space wastage here mainly refers to two aspects: on the one hand, lists are set with an initial length, which we may not always need; on the other hand, to prevent frequent expansion, expansion usually multiplies by a coefficient, such as $\times 1.5$. This results in many empty slots, which we typically cannot fully fill.
**Q**: In Python, after initializing `n = [1, 2, 3]`, the addresses of these 3 elements are contiguous, but initializing `m = [2, 1, 3]` shows that each element's `id` is not consecutive but identical to those in `n`. If the addresses of these elements are not contiguous, is `m` still an array?
If we replace list elements with linked list nodes `n = [n1, n2, n3, n4, n5]`, these 5 node objects are also typically dispersed throughout memory. However, given a list index, we can still access the node's memory address in `O(1)` time, thereby accessing the corresponding node. This is because the array stores references to the nodes, not the nodes themselves.
Unlike many languages, in Python, numbers are also wrapped as objects, and lists store references to these numbers, not the numbers themselves. Therefore, we find that the same number in two arrays has the same `id`, and these numbers' memory addresses need not be contiguous.
**Q**: The `std::list` in C++ STL has already implemented a doubly linked list, but it seems that some algorithm books don't directly use it. Is there any limitation?
On the one hand, we often prefer to use arrays to implement algorithms, only using linked lists when necessary, mainly for two reasons.
- Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next), `std::list` usually occupies more space than `std::vector`.
- Cache unfriendly: As the data is not stored continuously, `std::list` has a lower cache utilization rate. Generally, `std::vector` performs better.
On the other hand, linked lists are primarily necessary for binary trees and graphs. Stacks and queues are often implemented using the programming language's `stack` and `queue` classes, rather than linked lists.
**Q**: Does initializing a list `res = [0] * self.size()` result in each element of `res` referencing the same address?
No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0] * self.size()]` would reference the same list `[0]` multiple times.
**Q**: In deleting a node, is it necessary to break the reference to its successor node?
From the perspective of data structures and algorithms (problem-solving), it's okay not to break the link, as long as the program's logic is correct. From the perspective of standard libraries, breaking the link is safer and more logically clear. If the link is not broken, and the deleted node is not properly recycled, it could affect the recycling of the successor node's memory.
@@ -0,0 +1,26 @@
---
comments: true
icon: material/timer-sand
---
# Chapter 2. &nbsp; Complexity Analysis
<div class="center-table" markdown>
![complexity_analysis](../assets/covers/chapter_complexity_analysis.jpg){ class="cover-image" }
</div>
!!! abstract
Complexity analysis is like a space-time navigator in the vast universe of algorithms.
It guides us in exploring deeper within the the dimensions of time and space, seeking more elegant solutions.
## Chapter Contents
- [2.1 &nbsp; Algorithm Efficiency Assessment](https://www.hello-algo.com/en/chapter_computational_complexity/performance_evaluation/)
- [2.2 &nbsp; Iteration and Recursion](https://www.hello-algo.com/en/chapter_computational_complexity/iteration_and_recursion/)
- [2.3 &nbsp; Time Complexity](https://www.hello-algo.com/en/chapter_computational_complexity/time_complexity/)
- [2.4 &nbsp; Space Complexity](https://www.hello-algo.com/en/chapter_computational_complexity/space_complexity/)
- [2.5 &nbsp; Summary](https://www.hello-algo.com/en/chapter_computational_complexity/summary/)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
---
comments: true
---
# 2.1 &nbsp; Algorithm Efficiency Assessment
In algorithm design, we pursue the following two objectives in sequence.
1. **Finding a Solution to the Problem**: The algorithm should reliably find the correct solution within the stipulated range of inputs.
2. **Seeking the Optimal Solution**: For the same problem, multiple solutions might exist, and we aim to find the most efficient algorithm possible.
In other words, under the premise of being able to solve the problem, algorithm efficiency has become the main criterion for evaluating the merits of an algorithm, which includes the following two dimensions.
- **Time Efficiency**: The speed at which an algorithm runs.
- **Space Efficiency**: The size of the memory space occupied by an algorithm.
In short, **our goal is to design data structures and algorithms that are both fast and memory-efficient**. Effectively assessing algorithm efficiency is crucial because only then can we compare various algorithms and guide the process of algorithm design and optimization.
There are mainly two methods of efficiency assessment: actual testing and theoretical estimation.
## 2.1.1 &nbsp; Actual Testing
Suppose we have algorithms `A` and `B`, both capable of solving the same problem, and we need to compare their efficiencies. The most direct method is to use a computer to run these two algorithms and monitor and record their runtime and memory usage. This assessment method reflects the actual situation but has significant limitations.
On one hand, **it's difficult to eliminate interference from the testing environment**. Hardware configurations can affect algorithm performance. For example, algorithm `A` might run faster than `B` on one computer, but the opposite result may occur on another computer with different configurations. This means we would need to test on a variety of machines to calculate average efficiency, which is impractical.
On the other hand, **conducting a full test is very resource-intensive**. As the volume of input data changes, the efficiency of the algorithms may vary. For example, with smaller data volumes, algorithm `A` might run faster than `B`, but the opposite might be true with larger data volumes. Therefore, to draw convincing conclusions, we need to test a wide range of input data sizes, which requires significant computational resources.
## 2.1.2 &nbsp; Theoretical Estimation
Due to the significant limitations of actual testing, we can consider evaluating algorithm efficiency solely through calculations. This estimation method is known as "asymptotic complexity analysis," or simply "complexity analysis."
Complexity analysis reflects the relationship between the time and space resources required for algorithm execution and the size of the input data. **It describes the trend of growth in the time and space required by the algorithm as the size of the input data increases**. This definition might sound complex, but we can break it down into three key points to understand it better.
- "Time and space resources" correspond to "time complexity" and "space complexity," respectively.
- "As the size of input data increases" means that complexity reflects the relationship between algorithm efficiency and the volume of input data.
- "The trend of growth in time and space" indicates that complexity analysis focuses not on the specific values of runtime or space occupied but on the "rate" at which time or space grows.
**Complexity analysis overcomes the disadvantages of actual testing methods**, reflected in the following aspects:
- It is independent of the testing environment and applicable to all operating platforms.
- It can reflect algorithm efficiency under different data volumes, especially in the performance of algorithms with large data volumes.
!!! tip
If you're still confused about the concept of complexity, don't worry. We will introduce it in detail in subsequent chapters.
Complexity analysis provides us with a "ruler" to measure the time and space resources needed to execute an algorithm and compare the efficiency between different algorithms.
Complexity is a mathematical concept and may be abstract and challenging for beginners. From this perspective, complexity analysis might not be the best content to introduce first. However, when discussing the characteristics of a particular data structure or algorithm, it's hard to avoid analyzing its speed and space usage.
In summary, it's recommended that you establish a preliminary understanding of complexity analysis before diving deep into data structures and algorithms, **so that you can carry out simple complexity analyses of algorithms**.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
---
comments: true
---
# 2.5 &nbsp; Summary
### 1. &nbsp; Key Review
**Algorithm Efficiency Assessment**
- Time efficiency and space efficiency are the two main criteria for assessing the merits of an algorithm.
- We can assess algorithm efficiency through actual testing, but it's challenging to eliminate the influence of the test environment, and it consumes substantial computational resources.
- Complexity analysis can overcome the disadvantages of actual testing. Its results are applicable across all operating platforms and can reveal the efficiency of algorithms at different data scales.
**Time Complexity**
- Time complexity measures the trend of an algorithm's running time with the increase in data volume, effectively assessing algorithm efficiency. However, it can fail in certain cases, such as with small input data volumes or when time complexities are the same, making it challenging to precisely compare the efficiency of algorithms.
- Worst-case time complexity is denoted using big O notation, representing the asymptotic upper bound, reflecting the growth level of the number of operations $T(n)$ as $n$ approaches infinity.
- Calculating time complexity involves two steps: first counting the number of operations, then determining the asymptotic upper bound.
- Common time complexities, arranged from low to high, include $O(1)$, $O(\log n)$, $O(n)$, $O(n \log n)$, $O(n^2)$, $O(2^n)$, and $O(n!)$, among others.
- The time complexity of some algorithms is not fixed and depends on the distribution of input data. Time complexities are divided into worst, best, and average cases. The best case is rarely used because input data generally needs to meet strict conditions to achieve the best case.
- Average time complexity reflects the efficiency of an algorithm under random data inputs, closely resembling the algorithm's performance in actual applications. Calculating average time complexity requires accounting for the distribution of input data and the subsequent mathematical expectation.
**Space Complexity**
- Space complexity, similar to time complexity, measures the trend of memory space occupied by an algorithm with the increase in data volume.
- The relevant memory space used during the algorithm's execution can be divided into input space, temporary space, and output space. Generally, input space is not included in space complexity calculations. Temporary space can be divided into temporary data, stack frame space, and instruction space, where stack frame space usually affects space complexity only in recursive functions.
- We usually focus only on the worst-case space complexity, which means calculating the space complexity of the algorithm under the worst input data and at the worst moment of operation.
- Common space complexities, arranged from low to high, include $O(1)$, $O(\log n)$, $O(n)$, $O(n^2)$, and $O(2^n)$, among others.
### 2. &nbsp; Q & A
**Q**: Is the space complexity of tail recursion $O(1)$?
Theoretically, the space complexity of a tail-recursive function can be optimized to $O(1)$. However, most programming languages (such as Java, Python, C++, Go, C#) do not support automatic optimization of tail recursion, so it's generally considered to have a space complexity of $O(n)$.
**Q**: What is the difference between the terms "function" and "method"?
A "function" can be executed independently, with all parameters passed explicitly. A "method" is associated with an object and is implicitly passed to the object calling it, able to operate on the data contained within an instance of a class.
Here are some examples from common programming languages:
- C is a procedural programming language without object-oriented concepts, so it only has functions. However, we can simulate object-oriented programming by creating structures (struct), and functions associated with these structures are equivalent to methods in other programming languages.
- Java and C# are object-oriented programming languages where code blocks (methods) are typically part of a class. Static methods behave like functions because they are bound to the class and cannot access specific instance variables.
- C++ and Python support both procedural programming (functions) and object-oriented programming (methods).
**Q**: Does the "Common Types of Space Complexity" figure reflect the absolute size of occupied space?
No, the figure shows space complexities, which reflect growth trends, not the absolute size of the occupied space.
If you take $n = 8$, you might find that the values of each curve don't correspond to their functions. This is because each curve includes a constant term, intended to compress the value range into a visually comfortable range.
In practice, since we usually don't know the "constant term" complexity of each method, it's generally not possible to choose the best solution for $n = 8$ based solely on complexity. However, for $n = 8^5$, it's much easier to choose, as the growth trend becomes dominant.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,172 @@
---
comments: true
---
# 3.2 &nbsp; Basic Data Types
When discussing data in computers, various forms like text, images, videos, voice and 3D models comes to mind. Despite their different organizational forms, they are all composed of various basic data types.
**Basic data types are those that the CPU can directly operate on** and are directly used in algorithms, mainly including the following.
- Integer types: `byte`, `short`, `int`, `long`.
- Floating-point types: `float`, `double`, used to represent decimals.
- Character type: `char`, used to represent letters, punctuation, and even emojis in various languages.
- Boolean type: `bool`, used to represent "yes" or "no" decisions.
**Basic data types are stored in computers in binary form**. One binary digit is 1 bit. In most modern operating systems, 1 byte consists of 8 bits.
The range of values for basic data types depends on the size of the space they occupy. Below, we take Java as an example.
- The integer type `byte` occupies 1 byte = 8 bits and can represent $2^8$ numbers.
- The integer type `int` occupies 4 bytes = 32 bits and can represent $2^{32}$ numbers.
The following table lists the space occupied, value range, and default values of various basic data types in Java. While memorizing this table isn't necessary, having a general understanding of it and referencing it when required is recommended.
<p align="center"> Table 3-1 &nbsp; Space Occupied and Value Range of Basic Data Types </p>
<div class="center-table" markdown>
| Type | Symbol | Space Occupied | Minimum Value | Maximum Value | Default Value |
| ------- | -------- | -------------- | ------------------------ | ----------------------- | -------------- |
| Integer | `byte` | 1 byte | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | 0 |
| | `short` | 2 bytes | $-2^{15}$ | $2^{15} - 1$ | 0 |
| | `int` | 4 bytes | $-2^{31}$ | $2^{31} - 1$ | 0 |
| | `long` | 8 bytes | $-2^{63}$ | $2^{63} - 1$ | 0 |
| Float | `float` | 4 bytes | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ |
| | `double` | 8 bytes | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | 0.0 |
| Char | `char` | 2 bytes | 0 | $2^{16} - 1$ | 0 |
| Boolean | `bool` | 1 byte | $\text{false}$ | $\text{true}$ | $\text{false}$ |
</div>
Please note that the above table is specific to Java's basic data types. Every programming language has its own data type definitions, which might differ in space occupied, value ranges, and default values.
- In Python, the integer type `int` can be of any size, limited only by available memory; the floating-point `float` is double precision 64-bit; there is no `char` type, as a single character is actually a string `str` of length 1.
- C and C++ do not specify the size of basic data types, it varies with implementation and platform. The above table follows the LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), used for Unix 64-bit operating systems including Linux and macOS.
- The size of `char` in C and C++ is 1 byte, while in most programming languages, it depends on the specific character encoding method, as detailed in the "Character Encoding" chapter.
- Even though representing a boolean only requires 1 bit (0 or 1), it is usually stored in memory as 1 byte. This is because modern computer CPUs typically use 1 byte as the smallest addressable memory unit.
So, what is the connection between basic data types and data structures? We know that data structures are ways to organize and store data in computers. The focus here is on "structure" rather than "data".
If we want to represent "a row of numbers", we naturally think of using an array. This is because the linear structure of an array can represent the adjacency and the ordering of the numbers, but whether the stored content is an integer `int`, a decimal `float`, or a character `char`, is irrelevant to the "data structure".
In other words, **basic data types provide the "content type" of data, while data structures provide the "way of organizing" data**. For example, in the following code, we use the same data structure (array) to store and represent different basic data types, including `int`, `float`, `char`, `bool`, etc.
=== "Python"
```python title=""
# Using various basic data types to initialize arrays
numbers: list[int] = [0] * 5
decimals: list[float] = [0.0] * 5
# Python's characters are actually strings of length 1
characters: list[str] = ['0'] * 5
bools: list[bool] = [False] * 5
# Python's lists can freely store various basic data types and object references
data = [0, 0.0, 'a', False, ListNode(0)]
```
=== "C++"
```cpp title=""
// Using various basic data types to initialize arrays
int numbers[5];
float decimals[5];
char characters[5];
bool bools[5];
```
=== "Java"
```java title=""
// Using various basic data types to initialize arrays
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
boolean[] bools = new boolean[5];
```
=== "C#"
```csharp title=""
// Using various basic data types to initialize arrays
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
bool[] bools = new bool[5];
```
=== "Go"
```go title=""
// Using various basic data types to initialize arrays
var numbers = [5]int{}
var decimals = [5]float64{}
var characters = [5]byte{}
var bools = [5]bool{}
```
=== "Swift"
```swift title=""
// Using various basic data types to initialize arrays
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's arrays can freely store various basic data types and objects
const array = [0, 0.0, 'a', false];
```
=== "TS"
```typescript title=""
// Using various basic data types to initialize arrays
const numbers: number[] = [];
const characters: string[] = [];
const bools: boolean[] = [];
```
=== "Dart"
```dart title=""
// Using various basic data types to initialize arrays
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=""
// Using various basic data types to initialize arrays
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=""
// Using various basic data types to initialize arrays
int numbers[10];
float decimals[10];
char characters[10];
bool bools[10];
```
=== "Zig"
```zig title=""
// Using various basic data types to initialize arrays
var numbers: [5]i32 = undefined;
var decimals: [5]f32 = undefined;
var characters: [5]u8 = undefined;
var bools: [5]bool = undefined;
```
@@ -0,0 +1,97 @@
---
comments: true
---
# 3.4 &nbsp; Character Encoding *
In the computer system, all data is stored in binary form, and characters (represented by char) are no exception. To represent characters, we need to develop a "character set" that defines a one-to-one mapping between each character and binary numbers. With the character set, computers can convert binary numbers to characters by looking up the table.
## 3.4.1 &nbsp; ASCII Character Set
The "ASCII code" is one of the earliest character sets, officially known as the American Standard Code for Information Interchange. It uses 7 binary digits (the lower 7 bits of a byte) to represent a character, allowing for a maximum of 128 different characters. As shown in the Figure 3-6 , ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab).
![ASCII Code](character_encoding.assets/ascii_table.png){ class="animation-figure" }
<p align="center"> Figure 3-6 &nbsp; ASCII Code </p>
However, **ASCII can only represent English characters**. With the globalization of computers, a character set called "EASCII" was developed to represent more languages. It expands from the 7-bit structure of ASCII to 8 bits, enabling the representation of 256 characters.
Globally, various region-specific EASCII character sets have been introduced. The first 128 characters of these sets are consistent with the ASCII, while the remaining 128 characters are defined differently to accommodate the requirements of different languages.
## 3.4.2 &nbsp; GBK Character Set
Later, it was found that **EASCII still could not meet the character requirements of many languages**. For instance, there are nearly a hundred thousand Chinese characters, with several thousand used regularly. In 1980, the Standardization Administration of China released the "GB2312" character set, which included 6763 Chinese characters, essentially fulfilling the computer processing needs for the Chinese language.
However, GB2312 could not handle some rare and traditional characters. The "GBK" character set expands GB2312 and includes 21886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented with one byte, while Chinese characters use two bytes.
## 3.4.3 &nbsp; Unicode Character Set
With the rapid evolution of computer technology and a plethora of character sets and encoding standards, numerous problems arose. On the one hand, these character sets generally only defined characters for specific languages and could not function properly in multilingual environments. On the other hand, the existence of multiple character set standards for the same language caused garbled text when information was exchanged between computers using different encoding standards.
Researchers of that era thought: **What if a comprehensive character set encompassing all global languages and symbols was developed? Wouldn't this resolve the issues associated with cross-linguistic environments and garbled text?** Inspired by this idea, the extensive character set, Unicode, was born.
"Unicode" is referred to as "统一码" (Unified Code) in Chinese, theoretically capable of accommodating over a million characters. It aims to incorporate characters from all over the world into a single set, providing a universal character set for processing and displaying various languages and reducing the issues of garbled text due to different encoding standards.
Since its release in 1991, Unicode has continually expanded to include new languages and characters. As of September 2022, Unicode contains 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, while some rare characters may occupy 3 or even 4 bytes.
Unicode is a universal character set that assigns a number (called a "code point") to each character, **but it does not specify how these character code points should be stored in a computer system**. One might ask: How does a system interpret Unicode code points of varying lengths within a text? For example, given a 2-byte code, how does the system determine if it represents a single 2-byte character or two 1-byte characters?
A straightforward solution to this problem is to store all characters as equal-length encodings. As shown in the Figure 3-7 , each character in "Hello" occupies 1 byte, while each character in "算法" (algorithm) occupies 2 bytes. We could encode all characters in "Hello 算法" as 2 bytes by padding the higher bits with zeros. This method would enable the system to interpret a character every 2 bytes, recovering the content of the phrase.
![Unicode Encoding Example](character_encoding.assets/unicode_hello_algo.png){ class="animation-figure" }
<p align="center"> Figure 3-7 &nbsp; Unicode Encoding Example </p>
However, as ASCII has shown us, encoding English only requires 1 byte. Using the above approach would double the space occupied by English text compared to ASCII encoding, which is a waste of memory space. Therefore, a more efficient Unicode encoding method is needed.
## 3.4.4 &nbsp; UTF-8 Encoding
Currently, UTF-8 has become the most widely used Unicode encoding method internationally. **It is a variable-length encoding**, using 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters need only 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters need 3 bytes, and some other rare characters need 4 bytes.
The encoding rules for UTF-8 are not complex and can be divided into two cases:
- For 1-byte characters, set the highest bit to $0$, and the remaining 7 bits to the Unicode code point. Notably, ASCII characters occupy the first 128 code points in the Unicode set. This means that **UTF-8 encoding is backward compatible with ASCII**. This implies that UTF-8 can be used to parse ancient ASCII text.
- For characters of length $n$ bytes (where $n > 1$), set the highest $n$ bits of the first byte to $1$, and the $(n + 1)^{\text{th}}$ bit to $0$; starting from the second byte, set the highest 2 bits of each byte to $10$; the rest of the bits are used to fill the Unicode code point.
The Figure 3-8 shows the UTF-8 encoding for "Hello算法". It can be observed that since the highest $n$ bits are set to $1$, the system can determine the length of the character as $n$ by counting the number of highest bits set to $1$.
But why set the highest 2 bits of the remaining bytes to $10$? Actually, this $10$ serves as a kind of checksum. If the system starts parsing text from an incorrect byte, the $10$ at the beginning of the byte can help the system quickly detect anomalies.
The reason for using $10$ as a checksum is that, under UTF-8 encoding rules, it's impossible for the highest two bits of a character to be $10$. This can be proven by contradiction: If the highest two bits of a character are $10$, it indicates that the character's length is $1$, corresponding to ASCII. However, the highest bit of an ASCII character should be $0$, which contradicts the assumption.
![UTF-8 Encoding Example](character_encoding.assets/utf-8_hello_algo.png){ class="animation-figure" }
<p align="center"> Figure 3-8 &nbsp; UTF-8 Encoding Example </p>
Apart from UTF-8, other common encoding methods include:
- **UTF-16 Encoding**: Uses 2 or 4 bytes to represent a character. All ASCII characters and commonly used non-English characters are represented with 2 bytes; a few characters require 4 bytes. For 2-byte characters, the UTF-16 encoding equals the Unicode code point.
- **UTF-32 Encoding**: Every character uses 4 bytes. This means UTF-32 occupies more space than UTF-8 and UTF-16, especially for texts with a high proportion of ASCII characters.
From the perspective of storage space, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 to encode some non-English characters (such as Chinese) can be more efficient because it only requires 2 bytes, while UTF-8 might need 3 bytes.
From a compatibility perspective, UTF-8 is the most versatile, with many tools and libraries supporting UTF-8 as a priority.
## 3.4.5 &nbsp; Character Encoding in Programming Languages
Historically, many programming languages utilized fixed-length encodings such as UTF-16 or UTF-32 for processing strings during program execution. This allows strings to be handled as arrays, offering several advantages:
- **Random Access**: Strings encoded in UTF-16 can be accessed randomly with ease. For UTF-8, which is a variable-length encoding, locating the $i^{th}$ character requires traversing the string from the start to the $i^{th}$ position, taking $O(n)$ time.
- **Character Counting**: Similar to random access, counting the number of characters in a UTF-16 encoded string is an $O(1)$ operation. However, counting characters in a UTF-8 encoded string requires traversing the entire string.
- **String Operations**: Many string operations like splitting, concatenating, inserting, and deleting are easier on UTF-16 encoded strings. These operations generally require additional computation on UTF-8 encoded strings to ensure the validity of the UTF-8 encoding.
The design of character encoding schemes in programming languages is an interesting topic involving various factors:
- Javas `String` type uses UTF-16 encoding, with each character occupying 2 bytes. This was based on the initial belief that 16 bits were sufficient to represent all possible characters and proven incorrect later. As the Unicode standard expanded beyond 16 bits, characters in Java may now be represented by a pair of 16-bit values, known as “surrogate pairs.”
- JavaScript and TypeScript use UTF-16 encoding for similar reasons as Java. When JavaScript was first introduced by Netscape in 1995, Unicode was still in its early stages, and 16-bit encoding was sufficient to represent all Unicode characters.
- C# uses UTF-16 encoding, largely because the .NET platform, designed by Microsoft, and many Microsoft technologies, including the Windows operating system, extensively use UTF-16 encoding.
Due to the underestimation of character counts, these languages had to use "surrogate pairs" to represent Unicode characters exceeding 16 bits. This approach has its drawbacks: strings containing surrogate pairs may have characters occupying 2 or 4 bytes, losing the advantage of fixed-length encoding. Additionally, handling surrogate pairs adds complexity and debugging difficulty to programming.
Addressing these challenges, some languages have adopted alternative encoding strategies:
- Pythons `str` type uses Unicode encoding with a flexible representation where the storage length of characters depends on the largest Unicode code point in the string. If all characters are ASCII, each character occupies 1 byte, 2 bytes for characters within the Basic Multilingual Plane (BMP), and 4 bytes for characters beyond the BMP.
- Gos `string` type internally uses UTF-8 encoding. Go also provides the `rune` type for representing individual Unicode code points.
- Rusts `str` and `String` types use UTF-8 encoding internally. Rust also offers the `char` type for individual Unicode code points.
Its important to note that the above discussion pertains to how strings are stored in programming languages, **which is different from how strings are stored in files or transmitted over networks**. For file storage or network transmission, strings are usually encoded in UTF-8 format for optimal compatibility and space efficiency.
@@ -0,0 +1,58 @@
---
comments: true
---
# 3.1 &nbsp; Classification of Data Structures
Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified into "logical structure" and "physical structure".
## 3.1.1 &nbsp; Logical Structure: Linear and Non-Linear
**The logical structures reveal the logical relationships between data elements**. In arrays and linked lists, data are arranged in a specific sequence, demonstrating the linear relationship between data; while in trees, data are arranged hierarchically from the top down, showing the derived relationship between "ancestors" and "descendants"; and graphs are composed of nodes and edges, reflecting the intricate network relationship.
As shown in the Figure 3-1 , logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating data is arranged linearly in logical relationships; non-linear structures, conversely, are arranged non-linearly.
- **Linear Data Structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables.
- **Non-Linear Data Structures**: Trees, Heaps, Graphs, Hash Tables.
![Linear and Non-Linear Data Structures](classification_of_data_structure.assets/classification_logic_structure.png){ class="animation-figure" }
<p align="center"> Figure 3-1 &nbsp; Linear and Non-Linear Data Structures </p>
Non-linear data structures can be further divided into tree structures and network structures.
- **Linear Structures**: Arrays, linked lists, queues, stacks, and hash tables, where elements have a one-to-one sequential relationship.
- **Tree Structures**: Trees, Heaps, Hash Tables, where elements have a one-to-many relationship.
- **Network Structures**: Graphs, where elements have a many-to-many relationships.
## 3.1.2 &nbsp; Physical Structure: Contiguous and Dispersed
**During the execution of an algorithm, the data being processed is stored in memory**. The Figure 3-2 shows a computer memory stick where each black square is a physical memory space. We can think of memory as a vast Excel spreadsheet, with each cell capable of storing a certain amount of data.
**The system accesses the data at the target location by means of a memory address**. As shown in the Figure 3-2 , the computer assigns a unique identifier to each cell in the table according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access the data stored in memory.
![Memory Stick, Memory Spaces, Memory Addresses](classification_of_data_structure.assets/computer_memory_location.png){ class="animation-figure" }
<p align="center"> Figure 3-2 &nbsp; Memory Stick, Memory Spaces, Memory Addresses </p>
!!! tip
It's worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is more complex, involving concepts like address space, memory management, cache mechanisms, virtual memory, and physical memory.
Memory is a shared resource for all programs. When a block of memory is occupied by one program, it cannot be simultaneously used by other programs. **Therefore, considering memory resources is crucial in designing data structures and algorithms**. For instance, the algorithm's peak memory usage should not exceed the remaining free memory of the system; if there is a lack of contiguous memory blocks, then the data structure chosen must be able to be stored in non-contiguous memory blocks.
As illustrated in the Figure 3-3 , **the physical structure reflects the way data is stored in computer memory** and it can be divided into contiguous space storage (arrays) and non-contiguous space storage (linked lists). The two types of physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.
![Contiguous Space Storage and Dispersed Space Storage](classification_of_data_structure.assets/classification_phisical_structure.png){ class="animation-figure" }
<p align="center"> Figure 3-3 &nbsp; Contiguous Space Storage and Dispersed Space Storage </p>
**It is worth noting that all data structures are implemented based on arrays, linked lists, or a combination of both**. For example, stacks and queues can be implemented using either arrays or linked lists; while implementations of hash tables may involve both arrays and linked lists.
- **Array-based implementations**: Stacks, Queues, Hash Tables, Trees, Heaps, Graphs, Matrices, Tensors (arrays with dimensions $\geq 3$).
- **Linked-list-based implementations**: Stacks, Queues, Hash Tables, Trees, Heaps, Graphs, etc.
Data structures implemented based on arrays are also called “Static Data Structures,” meaning their length cannot be changed after initialization. Conversely, those based on linked lists are called “Dynamic Data Structures,” which can still adjust their size during program execution.
!!! tip
If you find it challenging to comprehend the physical structure, it is recommended that you read the next chapter, "Arrays and Linked Lists," and revisit this section later.
+26
View File
@@ -0,0 +1,26 @@
---
comments: true
icon: material/shape-outline
---
# Chapter 3. &nbsp; Data Structures
<div class="center-table" markdown>
![Data Structures](../assets/covers/chapter_data_structure.jpg){ class="cover-image" }
</div>
!!! abstract
Data structures serve as a robust and diverse framework.
They offer a blueprint for the orderly organization of data, upon which algorithms come to life.
## Chapter Contents
- [3.1 &nbsp; Classification of Data Structures](https://www.hello-algo.com/en/chapter_data_structure/classification_of_data_structure/)
- [3.2 &nbsp; Fundamental Data Types](https://www.hello-algo.com/en/chapter_data_structure/basic_data_types/)
- [3.3 &nbsp; Number Encoding *](https://www.hello-algo.com/en/chapter_data_structure/number_encoding/)
- [3.4 &nbsp; Character Encoding *](https://www.hello-algo.com/en/chapter_data_structure/character_encoding/)
- [3.5 &nbsp; Summary](https://www.hello-algo.com/en/chapter_data_structure/summary/)
@@ -0,0 +1,162 @@
---
comments: true
---
# 3.3 &nbsp; Number Encoding *
!!! note
In this book, chapters marked with an asterisk '*' are optional readings. If you are short on time or find them challenging, you may skip these initially and return to them after completing the essential chapters.
## 3.3.1 &nbsp; Integer Encoding
In the table from the previous section, we observed that all integer types can represent one more negative number than positive numbers, such as the `byte` range of $[-128, 127]$. This phenomenon seems counterintuitive, and its underlying reason involves knowledge of sign-magnitude, one's complement, and two's complement encoding.
Firstly, it's important to note that **numbers are stored in computers using the two's complement form**. Before analyzing why this is the case, let's define these three encoding methods:
- **Sign-magnitude**: The highest bit of a binary representation of a number is considered the sign bit, where $0$ represents a positive number and $1$ represents a negative number. The remaining bits represent the value of the number.
- **One's complement**: The one's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by inverting all bits except the sign bit.
- **Two's complement**: The two's complement of a positive number is the same as its sign-magnitude. For negative numbers, it's obtained by adding $1$ to their one's complement.
The following diagram illustrates the conversions among sign-magnitude, one's complement, and two's complement:
![Conversions between Sign-Magnitude, One's Complement, and Two's Complement](number_encoding.assets/1s_2s_complement.png){ class="animation-figure" }
<p align="center"> Figure 3-4 &nbsp; Conversions between Sign-Magnitude, One's Complement, and Two's Complement </p>
Although sign-magnitude is the most intuitive, it has limitations. For one, **negative numbers in sign-magnitude cannot be directly used in calculations**. For example, in sign-magnitude, calculating $1 + (-2)$ results in $-3$, which is incorrect.
$$
\begin{aligned}
& 1 + (-2) \newline
& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline
& = 1000 \; 0011 \newline
& \rightarrow -3
\end{aligned}
$$
To address this, computers introduced the **one's complement**. If we convert to one's complement and calculate $1 + (-2)$, then convert the result back to sign-magnitude, we get the correct result of $-1$.
$$
\begin{aligned}
& 1 + (-2) \newline
& \rightarrow 0000 \; 0001 \; \text{(Sign-magnitude)} + 1000 \; 0010 \; \text{(Sign-magnitude)} \newline
& = 0000 \; 0001 \; \text{(One's complement)} + 1111 \; 1101 \; \text{(One's complement)} \newline
& = 1111 \; 1110 \; \text{(One's complement)} \newline
& = 1000 \; 0001 \; \text{(Sign-magnitude)} \newline
& \rightarrow -1
\end{aligned}
$$
Additionally, **there are two representations of zero in sign-magnitude**: $+0$ and $-0$. This means two different binary encodings for zero, which could lead to ambiguity. For example, in conditional checks, not differentiating between positive and negative zero might result in incorrect outcomes. Addressing this ambiguity would require additional checks, potentially reducing computational efficiency.
$$
\begin{aligned}
+0 & \rightarrow 0000 \; 0000 \newline
-0 & \rightarrow 1000 \; 0000
\end{aligned}
$$
Like sign-magnitude, one's complement also suffers from the positive and negative zero ambiguity. Therefore, computers further introduced the **two's complement**. Let's observe the conversion process for negative zero in sign-magnitude, one's complement, and two's complement:
$$
\begin{aligned}
-0 \rightarrow \; & 1000 \; 0000 \; \text{(Sign-magnitude)} \newline
= \; & 1111 \; 1111 \; \text{(One's complement)} \newline
= 1 \; & 0000 \; 0000 \; \text{(Two's complement)} \newline
\end{aligned}
$$
Adding $1$ to the one's complement of negative zero produces a carry, but with `byte` length being only 8 bits, the carried-over $1$ to the 9th bit is discarded. Therefore, **the two's complement of negative zero is $0000 \; 0000$**, the same as positive zero, thus resolving the ambiguity.
One last puzzle is the $[-128, 127]$ range for `byte`, with an additional negative number, $-128$. We observe that for the interval $[-127, +127]$, all integers have corresponding sign-magnitude, one's complement, and two's complement, allowing for mutual conversion between them.
However, **the two's complement $1000 \; 0000$ is an exception without a corresponding sign-magnitude**. According to the conversion method, its sign-magnitude would be $0000 \; 0000$, indicating zero. This presents a contradiction because its two's complement should represent itself. Computers designate this special two's complement $1000 \; 0000$ as representing $-128$. In fact, the calculation of $(-1) + (-127)$ in two's complement results in $-128$.
$$
\begin{aligned}
& (-127) + (-1) \newline
& \rightarrow 1111 \; 1111 \; \text{(Sign-magnitude)} + 1000 \; 0001 \; \text{(Sign-magnitude)} \newline
& = 1000 \; 0000 \; \text{(One's complement)} + 1111 \; 1110 \; \text{(One's complement)} \newline
& = 1000 \; 0001 \; \text{(Two's complement)} + 1111 \; 1111 \; \text{(Two's complement)} \newline
& = 1000 \; 0000 \; \text{(Two's complement)} \newline
& \rightarrow -128
\end{aligned}
$$
As you might have noticed, all these calculations are additions, hinting at an important fact: **computers' internal hardware circuits are primarily designed around addition operations**. This is because addition is simpler to implement in hardware compared to other operations like multiplication, division, and subtraction, allowing for easier parallelization and faster computation.
It's important to note that this doesn't mean computers can only perform addition. **By combining addition with basic logical operations, computers can execute a variety of other mathematical operations**. For example, the subtraction $a - b$ can be translated into $a + (-b)$; multiplication and division can be translated into multiple additions or subtractions.
We can now summarize the reason for using two's complement in computers: with two's complement representation, computers can use the same circuits and operations to handle both positive and negative number addition, eliminating the need for special hardware circuits for subtraction and avoiding the ambiguity of positive and negative zero. This greatly simplifies hardware design and enhances computational efficiency.
The design of two's complement is quite ingenious, and due to space constraints, we'll stop here. Interested readers are encouraged to explore further.
## 3.3.2 &nbsp; Floating-Point Number Encoding
You might have noticed something intriguing: despite having the same length of 4 bytes, why does a `float` have a much larger range of values compared to an `int`? This seems counterintuitive, as one would expect the range to shrink for `float` since it needs to represent fractions.
In fact, **this is due to the different representation method used by floating-point numbers (`float`)**. Let's consider a 32-bit binary number as:
$$
b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
$$
According to the IEEE 754 standard, a 32-bit `float` consists of the following three parts:
- Sign bit $\mathrm{S}$: Occupies 1 bit, corresponding to $b_{31}$.
- Exponent bit $\mathrm{E}$: Occupies 8 bits, corresponding to $b_{30} b_{29} \ldots b_{23}$.
- Fraction bit $\mathrm{N}$: Occupies 23 bits, corresponding to $b_{22} b_{21} \ldots b_0$.
The value of a binary `float` number is calculated as:
$$
\text{val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2 - 127} \times \left(1 . b_{22} b_{21} \ldots b_0\right)_2
$$
Converted to a decimal formula, this becomes:
$$
\text{val} = (-1)^{\mathrm{S}} \times 2^{\mathrm{E} - 127} \times (1 + \mathrm{N})
$$
The range of each component is:
$$
\begin{aligned}
\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline
(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} \times 2^{-i}) \subset [1, 2 - 2^{-23}]
\end{aligned}
$$
![Example Calculation of a float in IEEE 754 Standard](number_encoding.assets/ieee_754_float.png){ class="animation-figure" }
<p align="center"> Figure 3-5 &nbsp; Example Calculation of a float in IEEE 754 Standard </p>
Observing the diagram, given an example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have:
$$
\text{val} = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
$$
Now we can answer the initial question: **The representation of `float` includes an exponent bit, leading to a much larger range than `int`**. Based on the above calculation, the maximum positive number representable by `float` is approximately $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$, and the minimum negative number is obtained by switching the sign bit.
**However, the trade-off for `float`'s expanded range is a sacrifice in precision**. The integer type `int` uses all 32 bits to represent the number, with values evenly distributed; but due to the exponent bit, the larger the value of a `float`, the greater the difference between adjacent numbers.
As shown in the Table 3-2 , exponent bits $E = 0$ and $E = 255$ have special meanings, **used to represent zero, infinity, $\mathrm{NaN}$, etc.**
<p align="center"> Table 3-2 &nbsp; Meaning of Exponent Bits </p>
<div class="center-table" markdown>
| Exponent Bit E | Fraction Bit $\mathrm{N} = 0$ | Fraction Bit $\mathrm{N} \ne 0$ | Calculation Formula |
| ------------------ | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
| $0$ | $\pm 0$ | Subnormal Numbers | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ |
| $1, 2, \dots, 254$ | Normal Numbers | Normal Numbers | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ |
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
</div>
It's worth noting that subnormal numbers significantly improve the precision of floating-point numbers. The smallest positive normal number is $2^{-126}$, and the smallest positive subnormal number is $2^{-126} \times 2^{-23}$.
Double-precision `double` also uses a similar representation method to `float`, which is not elaborated here for brevity.
+37
View File
@@ -0,0 +1,37 @@
---
comments: true
---
# 3.5 &nbsp; Summary
### 1. &nbsp; Key Review
- Data structures can be categorized from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data elements, while physical structure describes how data is stored in computer memory.
- Common logical structures include linear, tree-like, and network structures. We generally classify data structures into linear (arrays, linked lists, stacks, queues) and non-linear (trees, graphs, heaps) based on their logical structure. The implementation of hash tables may involve both linear and non-linear data structures.
- When a program runs, data is stored in computer memory. Each memory space has a corresponding memory address, and the program accesses data through these addresses.
- Physical structures are primarily divided into contiguous space storage (arrays) and dispersed space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both.
- Basic data types in computers include integers (`byte`, `short`, `int`, `long`), floating-point numbers (`float`, `double`), characters (`char`), and booleans (`boolean`). Their range depends on the size of the space occupied and the representation method.
- Original code, complement code, and two's complement code are three methods of encoding numbers in computers, and they can be converted into each other. The highest bit of the original code of an integer is the sign bit, and the remaining bits represent the value of the number.
- Integers are stored in computers in the form of two's complement. In this representation, the computer can treat the addition of positive and negative numbers uniformly, without the need for special hardware circuits for subtraction, and there is no ambiguity of positive and negative zero.
- The encoding of floating-point numbers consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. Due to the presence of the exponent bit, the range of floating-point numbers is much greater than that of integers, but at the cost of sacrificing precision.
- ASCII is the earliest English character set, 1 byte in length, and includes 127 characters. The GBK character set is a commonly used Chinese character set, including more than 20,000 Chinese characters. Unicode strives to provide a complete character set standard, including characters from various languages worldwide, thus solving the problem of garbled characters caused by inconsistent character encoding methods.
- UTF-8 is the most popular Unicode encoding method, with excellent universality. It is a variable-length encoding method with good scalability and effectively improves the efficiency of space usage. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 occupies less space than UTF-8. Programming languages like Java and C# use UTF-16 encoding by default.
### 2. &nbsp; Q & A
**Q**: Why does a hash table contain both linear and non-linear data structures?
The underlying structure of a hash table is an array. To resolve hash collisions, we may use "chaining": each bucket in the array points to a linked list, which, when exceeding a certain threshold, might be transformed into a tree (usually a red-black tree).
From a storage perspective, the foundation of a hash table is an array, where each bucket slot might contain a value, a linked list, or a tree. Therefore, hash tables may contain both linear data structures (arrays, linked lists) and non-linear data structures (trees).
**Q**: Is the length of the `char` type 1 byte?
The length of the `char` type is determined by the encoding method used by the programming language. For example, Java, JavaScript, TypeScript, and C# all use UTF-16 encoding (to save Unicode code points), so the length of the char type is 2 bytes.
**Q**: Is there ambiguity in calling data structures based on arrays "static data structures"? Because operations like push and pop on stacks are "dynamic".
While stacks indeed allow for dynamic data operations, the data structure itself remains "static" (with unchangeable length). Even though data structures based on arrays can dynamically add or remove elements, their capacity is fixed. If the data volume exceeds the pre-allocated size, a new, larger array needs to be created, and the contents of the old array copied into it.
**Q**: When building stacks (queues) without specifying their size, why are they considered "static data structures"?
In high-level programming languages, we don't need to manually specify the initial capacity of stacks (queues); this task is automatically handled internally by the class. For example, the initial capacity of Java's ArrayList is usually 10. Furthermore, the expansion operation is also implemented automatically. See the subsequent "List" chapter for details.
+886
View File
@@ -0,0 +1,886 @@
---
comments: true
---
# 6.3 &nbsp; Hash Algorithms
The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and chaining can **only ensure that the hash table functions normally when collisions occur, but cannot reduce the frequency of hash collisions**.
If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the Figure 6-8 , for a chaining hash table, in the ideal case, the key-value pairs are evenly distributed across the buckets, achieving optimal query efficiency; in the worst case, all key-value pairs are stored in the same bucket, degrading the time complexity to $O(n)$.
![Ideal and Worst Cases of Hash Collisions](hash_algorithm.assets/hash_collision_best_worst_condition.png){ class="animation-figure" }
<p align="center"> Figure 6-8 &nbsp; Ideal and Worst Cases of Hash Collisions </p>
**The distribution of key-value pairs is determined by the hash function**. Recalling the steps of calculating a hash function, first compute the hash value, then modulo it by the array length:
```shell
index = hash(key) % capacity
```
Observing the above formula, when the hash table capacity `capacity` is fixed, **the hash algorithm `hash()` determines the output value**, thereby determining the distribution of key-value pairs in the hash table.
This means that, to reduce the probability of hash collisions, we should focus on the design of the hash algorithm `hash()`.
## 6.3.1 &nbsp; Goals of Hash Algorithms
To achieve a "fast and stable" hash table data structure, hash algorithms should have the following characteristics:
- **Determinism**: For the same input, the hash algorithm should always produce the same output. Only then can the hash table be reliable.
- **High Efficiency**: The process of computing the hash value should be fast enough. The smaller the computational overhead, the more practical the hash table.
- **Uniform Distribution**: The hash algorithm should ensure that key-value pairs are evenly distributed in the hash table. The more uniform the distribution, the lower the probability of hash collisions.
In fact, hash algorithms are not only used to implement hash tables but are also widely applied in other fields.
- **Password Storage**: To protect the security of user passwords, systems usually do not store the plaintext passwords but rather the hash values of the passwords. When a user enters a password, the system calculates the hash value of the input and compares it with the stored hash value. If they match, the password is considered correct.
- **Data Integrity Check**: The data sender can calculate the hash value of the data and send it along; the receiver can recalculate the hash value of the received data and compare it with the received hash value. If they match, the data is considered intact.
For cryptographic applications, to prevent reverse engineering such as deducing the original password from the hash value, hash algorithms need higher-level security features.
- **Unidirectionality**: It should be impossible to deduce any information about the input data from the hash value.
- **Collision Resistance**: It should be extremely difficult to find two different inputs that produce the same hash value.
- **Avalanche Effect**: Minor changes in the input should lead to significant and unpredictable changes in the output.
Note that **"Uniform Distribution" and "Collision Resistance" are two separate concepts**. Satisfying uniform distribution does not necessarily mean collision resistance. For example, under random input `key`, the hash function `key % 100` can produce a uniformly distributed output. However, this hash algorithm is too simple, and all `key` with the same last two digits will have the same output, making it easy to deduce a usable `key` from the hash value, thereby cracking the password.
## 6.3.2 &nbsp; Design of Hash Algorithms
The design of hash algorithms is a complex issue that requires consideration of many factors. However, for some less demanding scenarios, we can also design some simple hash algorithms.
- **Additive Hash**: Add up the ASCII codes of each character in the input and use the total sum as the hash value.
- **Multiplicative Hash**: Utilize the non-correlation of multiplication, multiplying each round by a constant, accumulating the ASCII codes of each character into the hash value.
- **XOR Hash**: Accumulate the hash value by XORing each element of the input data.
- **Rotating Hash**: Accumulate the ASCII code of each character into a hash value, performing a rotation operation on the hash value before each accumulation.
=== "Python"
```python title="simple_hash.py"
def add_hash(key: str) -> int:
"""加法哈希"""
hash = 0
modulus = 1000000007
for c in key:
hash += ord(c)
return hash % modulus
def mul_hash(key: str) -> int:
"""乘法哈希"""
hash = 0
modulus = 1000000007
for c in key:
hash = 31 * hash + ord(c)
return hash % modulus
def xor_hash(key: str) -> int:
"""异或哈希"""
hash = 0
modulus = 1000000007
for c in key:
hash ^= ord(c)
return hash % modulus
def rot_hash(key: str) -> int:
"""旋转哈希"""
hash = 0
modulus = 1000000007
for c in key:
hash = (hash << 4) ^ (hash >> 28) ^ ord(c)
return hash % modulus
```
=== "C++"
```cpp title="simple_hash.cpp"
/* 加法哈希 */
int addHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = (hash + (int)c) % MODULUS;
}
return (int)hash;
}
/* 乘法哈希 */
int mulHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = (31 * hash + (int)c) % MODULUS;
}
return (int)hash;
}
/* 异或哈希 */
int xorHash(string key) {
int hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash ^= (int)c;
}
return hash & MODULUS;
}
/* 旋转哈希 */
int rotHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;
}
return (int)hash;
}
```
=== "Java"
```java title="simple_hash.java"
/* 加法哈希 */
int addHash(String key) {
long hash = 0;
final int MODULUS = 1000000007;
for (char c : key.toCharArray()) {
hash = (hash + (int) c) % MODULUS;
}
return (int) hash;
}
/* 乘法哈希 */
int mulHash(String key) {
long hash = 0;
final int MODULUS = 1000000007;
for (char c : key.toCharArray()) {
hash = (31 * hash + (int) c) % MODULUS;
}
return (int) hash;
}
/* 异或哈希 */
int xorHash(String key) {
int hash = 0;
final int MODULUS = 1000000007;
for (char c : key.toCharArray()) {
hash ^= (int) c;
}
return hash & MODULUS;
}
/* 旋转哈希 */
int rotHash(String key) {
long hash = 0;
final int MODULUS = 1000000007;
for (char c : key.toCharArray()) {
hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS;
}
return (int) hash;
}
```
=== "C#"
```csharp title="simple_hash.cs"
/* 加法哈希 */
int AddHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = (hash + c) % MODULUS;
}
return (int)hash;
}
/* 乘法哈希 */
int MulHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = (31 * hash + c) % MODULUS;
}
return (int)hash;
}
/* 异或哈希 */
int XorHash(string key) {
int hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash ^= c;
}
return hash & MODULUS;
}
/* 旋转哈希 */
int RotHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;
}
return (int)hash;
}
```
=== "Go"
```go title="simple_hash.go"
/* 加法哈希 */
func addHash(key string) int {
var hash int64
var modulus int64
modulus = 1000000007
for _, b := range []byte(key) {
hash = (hash + int64(b)) % modulus
}
return int(hash)
}
/* 乘法哈希 */
func mulHash(key string) int {
var hash int64
var modulus int64
modulus = 1000000007
for _, b := range []byte(key) {
hash = (31*hash + int64(b)) % modulus
}
return int(hash)
}
/* 异或哈希 */
func xorHash(key string) int {
hash := 0
modulus := 1000000007
for _, b := range []byte(key) {
fmt.Println(int(b))
hash ^= int(b)
hash = (31*hash + int(b)) % modulus
}
return hash & modulus
}
/* 旋转哈希 */
func rotHash(key string) int {
var hash int64
var modulus int64
modulus = 1000000007
for _, b := range []byte(key) {
hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus
}
return int(hash)
}
```
=== "Swift"
```swift title="simple_hash.swift"
/* 加法哈希 */
func addHash(key: String) -> Int {
var hash = 0
let MODULUS = 1_000_000_007
for c in key {
for scalar in c.unicodeScalars {
hash = (hash + Int(scalar.value)) % MODULUS
}
}
return hash
}
/* 乘法哈希 */
func mulHash(key: String) -> Int {
var hash = 0
let MODULUS = 1_000_000_007
for c in key {
for scalar in c.unicodeScalars {
hash = (31 * hash + Int(scalar.value)) % MODULUS
}
}
return hash
}
/* 异或哈希 */
func xorHash(key: String) -> Int {
var hash = 0
let MODULUS = 1_000_000_007
for c in key {
for scalar in c.unicodeScalars {
hash ^= Int(scalar.value)
}
}
return hash & MODULUS
}
/* 旋转哈希 */
func rotHash(key: String) -> Int {
var hash = 0
let MODULUS = 1_000_000_007
for c in key {
for scalar in c.unicodeScalars {
hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS
}
}
return hash
}
```
=== "JS"
```javascript title="simple_hash.js"
/* 加法哈希 */
function addHash(key) {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = (hash + c.charCodeAt(0)) % MODULUS;
}
return hash;
}
/* 乘法哈希 */
function mulHash(key) {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = (31 * hash + c.charCodeAt(0)) % MODULUS;
}
return hash;
}
/* 异或哈希 */
function xorHash(key) {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash ^= c.charCodeAt(0);
}
return hash & MODULUS;
}
/* 旋转哈希 */
function rotHash(key) {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;
}
return hash;
}
```
=== "TS"
```typescript title="simple_hash.ts"
/* 加法哈希 */
function addHash(key: string): number {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = (hash + c.charCodeAt(0)) % MODULUS;
}
return hash;
}
/* 乘法哈希 */
function mulHash(key: string): number {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = (31 * hash + c.charCodeAt(0)) % MODULUS;
}
return hash;
}
/* 异或哈希 */
function xorHash(key: string): number {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash ^= c.charCodeAt(0);
}
return hash & MODULUS;
}
/* 旋转哈希 */
function rotHash(key: string): number {
let hash = 0;
const MODULUS = 1000000007;
for (const c of key) {
hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;
}
return hash;
}
```
=== "Dart"
```dart title="simple_hash.dart"
/* 加法哈希 */
int addHash(String key) {
int hash = 0;
final int MODULUS = 1000000007;
for (int i = 0; i < key.length; i++) {
hash = (hash + key.codeUnitAt(i)) % MODULUS;
}
return hash;
}
/* 乘法哈希 */
int mulHash(String key) {
int hash = 0;
final int MODULUS = 1000000007;
for (int i = 0; i < key.length; i++) {
hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;
}
return hash;
}
/* 异或哈希 */
int xorHash(String key) {
int hash = 0;
final int MODULUS = 1000000007;
for (int i = 0; i < key.length; i++) {
hash ^= key.codeUnitAt(i);
}
return hash & MODULUS;
}
/* 旋转哈希 */
int rotHash(String key) {
int hash = 0;
final int MODULUS = 1000000007;
for (int i = 0; i < key.length; i++) {
hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS;
}
return hash;
}
```
=== "Rust"
```rust title="simple_hash.rs"
/* 加法哈希 */
fn add_hash(key: &str) -> i32 {
let mut hash = 0_i64;
const MODULUS: i64 = 1000000007;
for c in key.chars() {
hash = (hash + c as i64) % MODULUS;
}
hash as i32
}
/* 乘法哈希 */
fn mul_hash(key: &str) -> i32 {
let mut hash = 0_i64;
const MODULUS: i64 = 1000000007;
for c in key.chars() {
hash = (31 * hash + c as i64) % MODULUS;
}
hash as i32
}
/* 异或哈希 */
fn xor_hash(key: &str) -> i32 {
let mut hash = 0_i64;
const MODULUS: i64 = 1000000007;
for c in key.chars() {
hash ^= c as i64;
}
(hash & MODULUS) as i32
}
/* 旋转哈希 */
fn rot_hash(key: &str) -> i32 {
let mut hash = 0_i64;
const MODULUS: i64 = 1000000007;
for c in key.chars() {
hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS;
}
hash as i32
}
```
=== "C"
```c title="simple_hash.c"
/* 加法哈希 */
int addHash(char *key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (int i = 0; i < strlen(key); i++) {
hash = (hash + (unsigned char)key[i]) % MODULUS;
}
return (int)hash;
}
/* 乘法哈希 */
int mulHash(char *key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (int i = 0; i < strlen(key); i++) {
hash = (31 * hash + (unsigned char)key[i]) % MODULUS;
}
return (int)hash;
}
/* 异或哈希 */
int xorHash(char *key) {
int hash = 0;
const int MODULUS = 1000000007;
for (int i = 0; i < strlen(key); i++) {
hash ^= (unsigned char)key[i];
}
return hash & MODULUS;
}
/* 旋转哈希 */
int rotHash(char *key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (int i = 0; i < strlen(key); i++) {
hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;
}
return (int)hash;
}
```
=== "Zig"
```zig title="simple_hash.zig"
[class]{}-[func]{addHash}
[class]{}-[func]{mulHash}
[class]{}-[func]{xorHash}
[class]{}-[func]{rotHash}
```
??? pythontutor "Code Visualization"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20*%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20*%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%98%E6%B3%95%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22%E5%BC%82%E6%88%96%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E6%97%8B%E8%BD%AC%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20%7Bhash%7D%22%29%0A&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=6&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Full Screen ></a></div>
It is observed that the last step of each hash algorithm is to take the modulus of the large prime number $1000000007$ to ensure that the hash value is within an appropriate range. It is worth pondering why emphasis is placed on modulo a prime number, or what are the disadvantages of modulo a composite number? This is an interesting question.
To conclude: **Using a large prime number as the modulus can maximize the uniform distribution of hash values**. Since a prime number does not share common factors with other numbers, it can reduce the periodic patterns caused by the modulo operation, thus avoiding hash collisions.
For example, suppose we choose the composite number $9$ as the modulus, which can be divided by $3$, then all `key` divisible by $3$ will be mapped to hash values $0$, $3$, $6$.
$$
\begin{aligned}
\text{modulus} & = 9 \newline
\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline
\text{hash} & = \{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\dots \}
\end{aligned}
$$
If the input `key` happens to have this kind of arithmetic sequence distribution, then the hash values will cluster, thereby exacerbating hash collisions. Now, suppose we replace `modulus` with the prime number $13$, since there are no common factors between `key` and `modulus`, the uniformity of the output hash values will be significantly improved.
$$
\begin{aligned}
\text{modulus} & = 13 \newline
\text{key} & = \{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \dots \} \newline
\text{hash} & = \{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \dots \}
\end{aligned}
$$
It is worth noting that if the `key` is guaranteed to be randomly and uniformly distributed, then choosing a prime number or a composite number as the modulus can both produce uniformly distributed hash values. However, when the distribution of `key` has some periodicity, modulo a composite number is more likely to result in clustering.
In summary, we usually choose a prime number as the modulus, and this prime number should be large enough to eliminate periodic patterns as much as possible, enhancing the robustness of the hash algorithm.
## 6.3.3 &nbsp; Common Hash Algorithms
It is not hard to see that the simple hash algorithms mentioned above are quite "fragile" and far from reaching the design goals of hash algorithms. For example, since addition and XOR obey the commutative law, additive hash and XOR hash cannot distinguish strings with the same content but in different order, which may exacerbate hash collisions and cause security issues.
In practice, we usually use some standard hash algorithms, such as MD5, SHA-1, SHA-2, and SHA-3. They can map input data of any length to a fixed-length hash value.
Over the past century, hash algorithms have been in a continuous process of upgrading and optimization. Some researchers strive to improve the performance of hash algorithms, while others, including hackers, are dedicated to finding security issues in hash algorithms. The Table 6-2 shows hash algorithms commonly used in practical applications.
- MD5 and SHA-1 have been successfully attacked multiple times and are thus abandoned in various security applications.
- SHA-2 series, especially SHA-256, is one of the most secure hash algorithms to date, with no successful attacks reported, hence commonly used in various security applications and protocols.
- SHA-3 has lower implementation costs and higher computational efficiency compared to SHA-2, but its current usage coverage is not as extensive as the SHA-2 series.
<p align="center"> Table 6-2 &nbsp; Common Hash Algorithms </p>
<div class="center-table" markdown>
| | MD5 | SHA-1 | SHA-2 | SHA-3 |
| --------------- | ----------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ---------------------------- |
| Release Year | 1992 | 1995 | 2002 | 2008 |
| Output Length | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit |
| Hash Collisions | Frequent | Frequent | Rare | Rare |
| Security Level | Low, has been successfully attacked | Low, has been successfully attacked | High | High |
| Applications | Abandoned, still used for data integrity checks | Abandoned | Cryptocurrency transaction verification, digital signatures, etc. | Can be used to replace SHA-2 |
</div>
# Hash Values in Data Structures
We know that the keys in a hash table can be of various data types such as integers, decimals, or strings. Programming languages usually provide built-in hash algorithms for these data types to calculate the bucket indices in the hash table. Taking Python as an example, we can use the `hash()` function to compute the hash values for various data types.
- The hash values of integers and booleans are their own values.
- The calculation of hash values for floating-point numbers and strings is more complex, and interested readers are encouraged to study this on their own.
- The hash value of a tuple is a combination of the hash values of each of its elements, resulting in a single hash value.
- The hash value of an object is generated based on its memory address. By overriding the hash method of an object, hash values can be generated based on content.
!!! tip
Be aware that the definition and methods of the built-in hash value calculation functions in different programming languages vary.
=== "Python"
```python title="built_in_hash.py"
num = 3
hash_num = hash(num)
# Hash value of integer 3 is 3
bol = True
hash_bol = hash(bol)
# Hash value of boolean True is 1
dec = 3.14159
hash_dec = hash(dec)
# Hash value of decimal 3.14159 is 326484311674566659
str = "Hello 算法"
hash_str = hash(str)
# Hash value of string "Hello 算法" is 4617003410720528961
tup = (12836, "小哈")
hash_tup = hash(tup)
# Hash value of tuple (12836, '小哈') is 1029005403108185979
obj = ListNode(0)
hash_obj = hash(obj)
# Hash value of ListNode object at 0x1058fd810 is 274267521
```
=== "C++"
```cpp title="built_in_hash.cpp"
int num = 3;
size_t hashNum = hash<int>()(num);
// Hash value of integer 3 is 3
bool bol = true;
size_t hashBol = hash<bool>()(bol);
// Hash value of boolean 1 is 1
double dec = 3.14159;
size_t hashDec = hash<double>()(dec);
// Hash value of decimal 3.14159 is 4614256650576692846
string str = "Hello 算法";
size_t hashStr = hash<string>()(str);
// Hash value of string "Hello 算法" is 15466937326284535026
// In C++, built-in std::hash() only provides hash values for basic data types
// Hash values for arrays and objects need to be implemented separately
```
=== "Java"
```java title="built_in_hash.java"
int num = 3;
int hashNum = Integer.hashCode(num);
// Hash value of integer 3 is 3
boolean bol = true;
int hashBol = Boolean.hashCode(bol);
// Hash value of boolean true is 1231
double dec = 3.14159;
int hashDec = Double.hashCode(dec);
// Hash value of decimal 3.14159 is -1340954729
String str = "Hello 算法";
int hashStr = str.hashCode();
// Hash value of string "Hello 算法" is -727081396
Object[] arr = { 12836, "小哈" };
int hashTup = Arrays.hashCode(arr);
// Hash value of array [12836, 小哈] is 1151158
ListNode obj = new ListNode(0);
int hashObj = obj.hashCode();
// Hash value of ListNode object utils.ListNode@7dc5e7b4 is 2110121908
```
=== "C#"
```csharp title="built_in_hash.cs"
int num = 3;
int hashNum = num.GetHashCode();
// Hash value of integer 3 is 3;
bool bol = true;
int hashBol = bol.GetHashCode();
// Hash value of boolean true is 1;
double dec = 3.14159;
int hashDec = dec.GetHashCode();
// Hash value of decimal 3.14159 is -1340954729;
string str = "Hello 算法";
int hashStr = str.GetHashCode();
// Hash value of string "Hello 算法" is -586107568;
object[] arr = [12836, "小哈"];
int hashTup = arr.GetHashCode();
// Hash value of array [12836, 小哈] is 42931033;
ListNode obj = new(0);
int hashObj = obj.GetHashCode();
// Hash value of ListNode object 0 is 39053774;
```
=== "Go"
```go title="built_in_hash.go"
// Go does not provide built-in hash code functions
```
=== "Swift"
```swift title="built_in_hash.swift"
let num = 3
let hashNum = num.hashValue
// Hash value of integer 3 is 9047044699613009734
let bol = true
let hashBol = bol.hashValue
// Hash value of boolean true is -4431640247352757451
let dec = 3.14159
let hashDec = dec.hashValue
// Hash value of decimal 3.14159 is -2465384235396674631
let str = "Hello 算法"
let hashStr = str.hashValue
// Hash value of string "Hello 算法" is -7850626797806988787
let arr = [AnyHashable(12836), AnyHashable("小哈")]
let hashTup = arr.hashValue
// Hash value of array [AnyHashable(12836), AnyHashable("小哈")] is -2308633508154532996
let obj = ListNode(x: 0)
let hashObj = obj.hashValue
// Hash value of ListNode object utils.ListNode is -2434780518035996159
```
=== "JS"
```javascript title="built_in_hash.js"
// JavaScript does not provide built-in hash code functions
```
=== "TS"
```typescript title="built_in_hash.ts"
// TypeScript does not provide built-in hash code functions
```
=== "Dart"
```dart title="built_in_hash.dart"
int num = 3;
int hashNum = num.hashCode;
// Hash value of integer 3 is 34803
bool bol = true;
int hashBol = bol.hashCode;
// Hash value of boolean true is 1231
double dec = 3.14159;
int hashDec = dec.hashCode;
// Hash value of decimal 3.14159 is 2570631074981783
String str = "Hello 算法";
int hashStr = str.hashCode;
// Hash value of string "Hello 算法" is 468167534
List arr = [12836, "小哈"];
int hashArr = arr.hashCode;
// Hash value of array [12836, 小哈] is 976512528
ListNode obj = new ListNode(0);
int hashObj = obj.hashCode;
// Hash value of ListNode object Instance of 'ListNode' is 1033450432
```
=== "Rust"
```rust title="built_in_hash.rs"
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let num = 3;
let mut num_hasher = DefaultHasher::new();
num.hash(&mut num_hasher);
let hash_num = num_hasher.finish();
// Hash value of integer 3 is 568126464209439262
let bol = true;
let mut bol_hasher = DefaultHasher::new();
bol.hash(&mut bol_hasher);
let hash_bol = bol_hasher.finish();
// Hash value of boolean true is 4952851536318644461
let dec: f32 = 3.14159;
let mut dec_hasher = DefaultHasher::new();
dec.to_bits().hash(&mut dec_hasher);
let hash_dec = dec_hasher.finish();
// Hash value of decimal 3.14159 is 2566941990314602357
let str = "Hello 算法";
let mut str_hasher = DefaultHasher::new();
str.hash(&mut str_hasher);
let hash_str = str_hasher.finish();
// Hash value of string "Hello 算法" is 16092673739211250988
let arr = (&12836, &"小哈");
let mut tup_hasher = DefaultHasher::new();
arr.hash(&mut tup_hasher);
let hash_tup = tup_hasher.finish();
// Hash value of tuple (12836, "小哈") is 1885128010422702749
let node = ListNode::new(42);
let mut hasher = DefaultHasher::new();
node.borrow().val.hash(&mut hasher);
let hash = hasher.finish();
// Hash value of ListNode object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852
```
=== "C"
```c title="built_in_hash.c"
// C does not provide built-in hash code functions
```
=== "Zig"
```zig title="built_in_hash.zig"
```
??? pythontutor "Code Visualization"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%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%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=19&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%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%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=19&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Full Screen ></a></div>
In many programming languages, **only immutable objects can serve as the `key` in a hash table**. If we use a list (dynamic array) as a `key`, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original `value` in the hash table.
Although the member variables of a custom object (such as a linked list node) are mutable, it is hashable. **This is because the hash value of an object is usually generated based on its memory address**, and even if the contents of the object change, the memory address remains the same, so the hash value remains unchanged.
You might have noticed that the hash values output in different consoles are different. **This is because the Python interpreter adds a random salt to the string hash function each time it starts up**. This approach effectively prevents HashDoS attacks and enhances the security of the hash algorithm.
File diff suppressed because one or more lines are too long
+1669
View File
File diff suppressed because one or more lines are too long
+25
View File
@@ -0,0 +1,25 @@
---
comments: true
icon: material/table-search
---
# Chapter 6. &nbsp; Hash Table
<div class="center-table" markdown>
![Hash Table](../assets/covers/chapter_hashing.jpg){ class="cover-image" }
</div>
!!! abstract
In the world of computing, a hash table is akin to an intelligent librarian.
It understands how to compute index numbers, enabling swift retrieval of the desired book.
## Chapter Contents
- [6.1 &nbsp; Hash Table](https://www.hello-algo.com/en/chapter_hashing/hash_map/)
- [6.2 &nbsp; Hash Collision](https://www.hello-algo.com/en/chapter_hashing/hash_collision/)
- [6.3 &nbsp; Hash Algorithm](https://www.hello-algo.com/en/chapter_hashing/hash_algorithm/)
- [6.4 &nbsp; Summary](https://www.hello-algo.com/en/chapter_hashing/summary/)
+51
View File
@@ -0,0 +1,51 @@
---
comments: true
---
# 6.4 &nbsp; Summary
### 1. &nbsp; Key Review
- Given an input `key`, a hash table can retrieve the corresponding `value` in $O(1)$ time, which is highly efficient.
- Common hash table operations include querying, adding key-value pairs, deleting key-value pairs, and traversing the hash table.
- The hash function maps a `key` to an array index, allowing access to the corresponding bucket to retrieve the `value`.
- Two different keys may end up with the same array index after hashing, leading to erroneous query results. This phenomenon is known as hash collision.
- The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table resizing can mitigate hash collisions. Similar to array resizing, hash table resizing is costly.
- Load factor, defined as the ratio of the number of elements to the number of buckets in the hash table, reflects the severity of hash collisions and is often used as a trigger for resizing the hash table.
- Chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same list. However, excessively long lists can reduce query efficiency, which can be improved by converting the lists into red-black trees.
- Open addressing handles hash collisions through multiple probes. Linear probing uses a fixed step size but cannot delete elements and is prone to clustering. Multiple hashing uses several hash functions for probing, making it less susceptible to clustering but increasing computational load.
- Different programming languages adopt various hash table implementations. For example, Java's `HashMap` uses chaining, while Python's `dict` employs open addressing.
- In hash tables, we desire hash algorithms with determinism, high efficiency, and uniform distribution. In cryptography, hash algorithms should also possess collision resistance and the avalanche effect.
- Hash algorithms typically use large prime numbers as moduli to ensure uniform distribution of hash values and reduce hash collisions.
- Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file integrity checks, while SHA-2 is commonly used in secure applications and protocols.
- Programming languages usually provide built-in hash algorithms for data types to calculate bucket indices in hash tables. Generally, only immutable objects are hashable.
### 2. &nbsp; Q & A
**Q**: When does the time complexity of a hash table degrade to $O(n)$?
The time complexity of a hash table can degrade to $O(n)$ when hash collisions are severe. When the hash function is well-designed, the capacity is set appropriately, and collisions are evenly distributed, the time complexity is $O(1)$. We usually consider the time complexity to be $O(1)$ when using built-in hash tables in programming languages.
**Q**: Why not use the hash function $f(x) = x$? This would eliminate collisions.
Under the hash function $f(x) = x$, each element corresponds to a unique bucket index, which is equivalent to an array. However, the input space is usually much larger than the output space (array length), so the last step of a hash function is often to take the modulo of the array length. In other words, the goal of a hash table is to map a larger state space to a smaller one while providing $O(1)$ query efficiency.
**Q**: Why can hash tables be more efficient than arrays, linked lists, or binary trees, even though they are implemented using these structures?
Firstly, hash tables have higher time efficiency but lower space efficiency. A significant portion of memory in hash tables remains unused.
Secondly, they are only more efficient in specific use cases. If a feature can be implemented with the same time complexity using an array or a linked list, it's usually faster than using a hash table. This is because the computation of the hash function incurs overhead, making the constant factor in the time complexity larger.
Lastly, the time complexity of hash tables can degrade. For example, in chaining, we perform search operations in a linked list or red-black tree, which still risks degrading to $O(n)$ time.
**Q**: Does multiple hashing also have the flaw of not being able to delete elements directly? Can space marked as deleted be reused?
Multiple hashing is a form of open addressing, and all open addressing methods have the drawback of not being able to delete elements directly; they require marking elements as deleted. Marked spaces can be reused. When inserting new elements into the hash table, and the hash function points to a position marked as deleted, that position can be used by the new element. This maintains the probing sequence of the hash table while ensuring efficient use of space.
**Q**: Why do hash collisions occur during the search process in linear probing?
During the search process, the hash function points to the corresponding bucket and key-value pair. If the `key` doesn't match, it indicates a hash collision. Therefore, linear probing will search downwards at a predetermined step size until the correct key-value pair is found or the search fails.
**Q**: Why can resizing a hash table alleviate hash collisions?
The last step of a hash function often involves taking the modulo of the array length $n$, to keep the output within the array index range. When resizing, the array length $n$ changes, and the indices corresponding to the keys may also change. Keys that were previously mapped to the same bucket might be distributed across multiple buckets after resizing, thereby mitigating hash collisions.
@@ -0,0 +1,66 @@
---
comments: true
---
# 1.1 &nbsp; Algorithms are Everywhere
When we hear the word "algorithm," we naturally think of mathematics. However, many algorithms do not involve complex mathematics but rely more on basic logic, which can be seen everywhere in our daily lives.
Before formally discussing algorithms, there's an interesting fact worth sharing: **you have already unconsciously learned many algorithms and have become accustomed to applying them in your daily life**. Here, I will give a few specific examples to prove this point.
**Example 1: Looking Up a Dictionary**. In an English dictionary, words are listed alphabetically. Suppose we're searching for a word that starts with the letter $r$. This is typically done in the following way:
1. Open the dictionary to about halfway and check the first letter on the page, let's say the letter is $m$.
2. Since $r$ comes after $m$ in the alphabet, we can ignore the first half of the dictionary and focus on the latter half.
3. Repeat steps `1.` and `2.` until you find the page where the word starts with $r$.
=== "<1>"
![Process of Looking Up a Dictionary](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png){ class="animation-figure" }
=== "<2>"
![Binary Search in Dictionary Step 2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png){ class="animation-figure" }
=== "<3>"
![Binary Search in Dictionary Step 3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png){ class="animation-figure" }
=== "<4>"
![Binary Search in Dictionary Step 4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png){ class="animation-figure" }
=== "<5>"
![Binary Search in Dictionary Step 5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png){ class="animation-figure" }
<p align="center"> Figure 1-1 &nbsp; Process of Looking Up a Dictionary </p>
This essential skill for elementary students, looking up a dictionary, is actually the famous "Binary Search" algorithm. From a data structure perspective, we can consider the dictionary as a sorted "array"; from an algorithmic perspective, the series of actions taken to look up a word in the dictionary can be viewed as "Binary Search."
**Example 2: Organizing Playing Cards**. When playing cards, we need to arrange the cards in our hand in ascending order, as shown in the following process.
1. Divide the playing cards into "ordered" and "unordered" sections, assuming initially the leftmost card is already in order.
2. Take out a card from the unordered section and insert it into the correct position in the ordered section; after this, the leftmost two cards are in order.
3. Continue to repeat step `2.` until all cards are in order.
![Playing Cards Sorting Process](algorithms_are_everywhere.assets/playing_cards_sorting.png){ class="animation-figure" }
<p align="center"> Figure 1-2 &nbsp; Playing Cards Sorting Process </p>
The above method of organizing playing cards is essentially the "Insertion Sort" algorithm, which is very efficient for small datasets. Many programming languages' sorting functions include the insertion sort.
**Example 3: Making Change**. Suppose we buy goods worth $69$ yuan at a supermarket and give the cashier $100$ yuan, then the cashier needs to give us $31$ yuan in change. They would naturally complete the thought process as shown below.
1. The options are currencies smaller than $31$, including $1$, $5$, $10$, and $20$.
2. Take out the largest $20$ from the options, leaving $31 - 20 = 11$.
3. Take out the largest $10$ from the remaining options, leaving $11 - 10 = 1$.
4. Take out the largest $1$ from the remaining options, leaving $1 - 1 = 0$.
5. Complete the change-making, with the solution being $20 + 10 + 1 = 31$.
![Change making process](algorithms_are_everywhere.assets/greedy_change.png){ class="animation-figure" }
<p align="center"> Figure 1-3 &nbsp; Change making process </p>
In the above steps, we make the best choice at each step (using the largest denomination possible), ultimately resulting in a feasible change-making plan. From the perspective of data structures and algorithms, this method is essentially a "Greedy" algorithm.
From cooking a meal to interstellar travel, almost all problem-solving involves algorithms. The advent of computers allows us to store data structures in memory and write code to call the CPU and GPU to execute algorithms. In this way, we can transfer real-life problems to computers, solving various complex issues more efficiently.
!!! tip
If concepts such as data structures, algorithms, arrays, and binary search still seem somewhat obsecure, I encourage you to continue reading. This book will gently guide you into the realm of understanding data structures and algorithms.
+24
View File
@@ -0,0 +1,24 @@
---
comments: true
icon: material/calculator-variant-outline
---
# Chapter 1. &nbsp; Introduction to Algorithms
<div class="center-table" markdown>
![A first look at the algorithm](../assets/covers/chapter_introduction.jpg){ class="cover-image" }
</div>
!!! abstract
A graceful maiden dances, intertwined with the data, her skirt swaying to the melody of algorithms.
She invites you to a dance, follow her steps, and enter the world of algorithms full of logic and beauty.
## Chapter Contents
- [1.1 &nbsp; Algorithms are Everywhere](https://www.hello-algo.com/en/chapter_introduction/algorithms_are_everywhere/)
- [1.2 &nbsp; What is an Algorithm](https://www.hello-algo.com/en/chapter_introduction/what_is_dsa/)
- [1.3 &nbsp; Summary](https://www.hello-algo.com/en/chapter_introduction/summary/)
+13
View File
@@ -0,0 +1,13 @@
---
comments: true
---
# 1.3 &nbsp; Summary
- Algorithms are ubiquitous in daily life and are not as inaccessible and complex as they might seem. In fact, we have already unconsciously learned many algorithms to solve various problems in life.
- The principle of looking up a word in a dictionary is consistent with the binary search algorithm. The binary search algorithm embodies the important algorithmic concept of divide and conquer.
- The process of organizing playing cards is very similar to the insertion sort algorithm. The insertion sort algorithm is suitable for sorting small datasets.
- The steps of making change in currency essentially follow the greedy algorithm, where each step involves making the best possible choice at the moment.
- An algorithm is a set of instructions or steps used to solve a specific problem within a finite amount of time, while a data structure is the way data is organized and stored in a computer.
- Data structures and algorithms are closely linked. Data structures are the foundation of algorithms, and algorithms are the stage to utilize the functions of data structures.
- We can liken data structures and algorithms to building blocks. The blocks represent data, the shape and connection method of the blocks represent data structures, and the steps of assembling the blocks correspond to algorithms.
@@ -0,0 +1,65 @@
---
comments: true
---
# 1.2 &nbsp; What is an Algorithm
## 1.2.1 &nbsp; Definition of an Algorithm
An "algorithm" is a set of instructions or steps to solve a specific problem within a finite amount of time. It has the following characteristics:
- The problem is clearly defined, including unambiguous definitions of input and output.
- The algorithm is feasible, meaning it can be completed within a finite number of steps, time, and memory space.
- Each step has a definitive meaning. The output is consistently the same under the same inputs and conditions.
## 1.2.2 &nbsp; Definition of a Data Structure
A "data structure" is a way of organizing and storing data in a computer, with the following design goals:
- Minimize space occupancy to save computer memory.
- Make data operations as fast as possible, covering data access, addition, deletion, updating, etc.
- Provide concise data representation and logical information to enable efficient algorithm execution.
**Designing data structures is a balancing act, often requiring trade-offs**. If you want to improve in one aspect, you often need to compromise in another. Here are two examples:
- Compared to arrays, linked lists offer more convenience in data addition and deletion but sacrifice data access speed.
- Graphs, compared to linked lists, provide richer logical information but require more memory space.
## 1.2.3 &nbsp; Relationship Between Data Structures and Algorithms
As shown in the Figure 1-4 , data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:
- Data structures are the foundation of algorithms. They provide structured data storage and methods for manipulating data for algorithms.
- Algorithms are the stage where data structures come into play. The data structure alone only stores data information; it is through the application of algorithms that specific problems can be solved.
- Algorithms can often be implemented based on different data structures, but their execution efficiency can vary greatly. Choosing the right data structure is key.
![Relationship between data structures and algorithms](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png){ class="animation-figure" }
<p align="center"> Figure 1-4 &nbsp; Relationship between data structures and algorithms </p>
Data structures and algorithms can be likened to a set of building blocks, as illustrated in the Figure 1-5 . A building block set includes numerous pieces, accompanied by detailed assembly instructions. Following these instructions step by step allows us to construct an intricate block model.
![Assembling blocks](what_is_dsa.assets/assembling_blocks.png){ class="animation-figure" }
<p align="center"> Figure 1-5 &nbsp; Assembling blocks </p>
The detailed correspondence between the two is shown in the Table 1-1 .
<p align="center"> Table 1-1 &nbsp; Comparing Data Structures and Algorithms to Building Blocks </p>
<div class="center-table" markdown>
| Data Structures and Algorithms | Building Blocks |
| ------------------------------ | --------------------------------------------------------------- |
| Input data | Unassembled blocks |
| Data structure | Organization of blocks, including shape, size, connections, etc |
| Algorithm | A series of steps to assemble the blocks into the desired shape |
| Output data | Completed Block model |
</div>
It's worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations in multiple programming languages.
!!! tip "Conventional Abbreviation"
In real-life discussions, we often refer to "Data Structures and Algorithms" simply as "Algorithms". For example, the well-known LeetCode algorithm problems actually test both data structure and algorithm knowledge.
+56
View File
@@ -0,0 +1,56 @@
---
comments: true
---
# 0.1 &nbsp; About This Book
This open-source project aims to create a free, and beginner-friendly crash course on data structures and algorithms.
- Using animated illustrations, it delivers structured insights into data structures and algorithmic concepts, ensuring comprehensibility and a smooth learning curve.
- Run code with just one click, supporting Java, C++, Python, Go, JS, TS, C#, Swift, Rust, Dart, Zig and other languages.
- Readers are encouraged to engage with each other in the discussion area for each section, questions and comments are usually answered within two days.
## 0.1.1 &nbsp; Target Audience
If you are new to algorithms with limited exposure, or you have accumulated some experience in algorithms, but you only have a vague understanding of data structures and algorithms, and you are constantly jumping between "yep" and "hmm", then this book is for you!
If you have already accumulated a certain amount of problem-solving experience, and are familiar with most types of problems, then this book can help you review and organize your algorithm knowledge system. The repository's source code can be used as a "problem-solving toolkit" or an "algorithm cheat sheet".
If you are an algorithm expert, we look forward to receiving your valuable suggestions, or [join us and collaborate](https://www.hello-algo.com/chapter_appendix/contribution/).
!!! success "Prerequisites"
You should know how to write and read simple code in at least one programming language.
## 0.1.2 &nbsp; Content Structure
The main content of the book is shown in the following figure.
- **Complexity Analysis**: explores aspects and methods for evaluating data structures and algorithms. Covers methods of deriving time complexity and space complexity, along with common types and examples.
- **Data Structures**: focuses on fundamental data types, classification methods, definitions, pros and cons, common operations, types, applications, and implementation methods of data structures such as array, linked list, stack, queue, hash table, tree, heap, graph, etc.
- **Algorithms**: defines algorithms, discusses their pros and cons, efficiency, application scenarios, problem-solving steps, and includes sample questions for various algorithms such as search, sorting, divide and conquer, backtracking, dynamic programming, greedy algorithms, and more.
![Main Content of the Book](about_the_book.assets/hello_algo_mindmap.png){ class="animation-figure" }
<p align="center"> Figure 0-1 &nbsp; Main Content of the Book </p>
## 0.1.3 &nbsp; Acknowledgements
This book is continuously improved with the joint efforts of many contributors from the open-source community. Thanks to each writer who invested their time and energy, listed in the order generated by GitHub: krahets, codingonion, nuomi1, Gonglja, Reanon, justin-tse, danielsss, hpstory, S-N-O-R-L-A-X, night-cruise, msk397, gvenusleo, RiverTwilight, gyt95, zhuoqinyue, Zuoxun, Xia-Sang, mingXta, FangYuan33, GN-Yu, IsChristina, xBLACKICEx, guowei-gong, Cathay-Chen, mgisr, JoseHung, qualifier1024, pengchzn, Guanngxu, longsizhuo, L-Super, what-is-me, yuan0221, lhxsm, Slone123c, WSL0809, longranger2, theNefelibatas, xiongsp, JeffersonHuang, hongyun-robot, K3v123, yuelinxin, a16su, gaofer, malone6, Wonderdch, xjr7670, DullSword, Horbin-Magician, NI-SW, reeswell, XC-Zero, XiaChuerwu, yd-j, iron-irax, huawuque404, MolDuM, Nigh, KorsChen, foursevenlove, 52coder, bubble9um, youshaoXG, curly210102, gltianwen, fanchenggang, Transmigration-zhou, FloranceYeh, FreddieLi, ShiMaRing, lipusheng, Javesun99, JackYang-hellobobo, shanghai-Jerry, 0130w, Keynman, psychelzh, logan-qiu, ZnYang2018, MwumLi, 1ch0, Phoenix0415, qingpeng9802, Richard-Zhang1019, QiLOL, Suremotoo, Turing-1024-Lee, Evilrabbit520, GaochaoZhu, ZJKung, linzeyan, hezhizhen, ZongYangL, beintentional, czruby, coderlef, dshlstarr, szu17dmy, fbigm, gledfish, hts0000, boloboloda, iStig, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, liuxjerry, lucaswangdev, lyl625760, chadyi, noobcodemaker, selear, siqyka, syd168, 4yDX3906, tao363, wangwang105, weibk, yabo083, yi427, yishangzhang, zhouLion, baagod, ElaBosak233, xb534, luluxia, yanedie, thomasq0, YangXuanyi and th1nk3r-ing.
The code review work for this book was completed by codingonion, Gonglja, gvenusleo, hpstory, justintse, krahets, night-cruise, nuomi1, and Reanon (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages.
Throughout the creation of this book, numerous individuals provided invaluable assistance, including but not limited to:
- Thanks to my mentor at the company, Dr. Xi Li, who encouraged me in a conversation to "get moving fast," which solidified my determination to write this book;
- Thanks to my girlfriend Bubble, as the first reader of this book, for offering many valuable suggestions from the perspective of a beginner in algorithms, making this book more suitable for newbies;
- Thanks to Tengbao, Qibao, and Feibao for coming up with a creative name for this book, evoking everyone's fond memories of writing their first line of code "Hello World!";
- Thanks to Xiaoquan for providing professional help in intellectual property, which has played a significant role in the development of this open-source book;
- Thanks to Sutong for designing a beautiful cover and logo for this book, and for patiently making multiple revisions under my insistence;
- Thanks to @squidfunk for providing writing and typesetting suggestions, as well as his developed open-source documentation theme [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master).
Throughout the writing journey, I delved into numerous textbooks and articles on data structures and algorithms. These works served as exemplary models, ensuring the accuracy and quality of this book's content. I extend my gratitude to all who preceded me for their invaluable contributions!
This book advocates a combination of hands-on and minds-on learning, inspired in this regard by ["Dive into Deep Learning"](https://github.com/d2l-ai/d2l-zh). I highly recommend this excellent book to all readers.
**Heartfelt thanks to my parents, whose ongoing support and encouragement have allowed me to do this interesting work**.
+24
View File
@@ -0,0 +1,24 @@
---
comments: true
icon: material/book-open-outline
---
# Chapter 0. &nbsp; Preface
<div class="center-table" markdown>
![Preface](../assets/covers/chapter_preface.jpg){ class="cover-image" }
</div>
!!! abstract
Algorithms are like a beautiful symphony, with each line of code flowing like a rhythm.
May this book ring softly in your mind, leaving a unique and profound melody.
## Chapter Contents
- [0.1 &nbsp; About This Book](https://www.hello-algo.com/en/chapter_preface/about_the_book/)
- [0.2 &nbsp; How to Read](https://www.hello-algo.com/en/chapter_preface/suggestions/)
- [0.3 &nbsp; Summary](https://www.hello-algo.com/en/chapter_preface/summary/)
+242
View File
@@ -0,0 +1,242 @@
---
comments: true
---
# 0.2 &nbsp; How to Read
!!! tip
For the best reading experience, it is recommended that you read through this section.
## 0.2.1 &nbsp; Writing Conventions
- Chapters marked with '*' after the title are optional and contain relatively challenging content. If you are short on time, it is advisable to skip them.
- Key technical terms and their English equivalents are enclosed in **Bold** + *italics* brackets, for example, ***array***. It's advisable to familiarize yourself with these for better comprehension of technical texts.
- Proprietary terms and words with specific meanings are indicated with “quotation marks” to avoid ambiguity.
- **Bolded text** indicates key content or summary statements, which deserve special attention.
- When it comes to terms that are inconsistent between programming languages, this book follows Python, for example using $\text{None}$ to mean "null".
- This book partially ignores the comment conventions for programming languages in exchange for a more compact layout of the content. The comments primarily consist of three types: title comments, content comments, and multi-line comments.
=== "Python"
```python title=""
"""Header comments for labeling functions, classes, test samples, etc""""
# Comments for explaining details
"""
Multiline
comments
"""
```
=== "C++"
```cpp title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Java"
```java title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "C#"
```csharp title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Go"
```go title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Swift"
```swift title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "JS"
```javascript title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "TS"
```typescript title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Dart"
```dart title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Rust"
```rust title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "C"
```c title=""
/* Header comments for labeling functions, classes, test samples, etc */
// Comments for explaining details.
/**
* Multiline
* comments
*/
```
=== "Zig"
```zig title=""
// Header comments for labeling functions, classes, test samples, etc
// Comments for explaining details.
// Multiline
// comments
```
## 0.2.2 &nbsp; Efficient Learning via Animated Illustrations
Compared with text, videos and pictures have a higher density of information and are more structured, making them easier to understand. In this book, **key and difficult concepts are mainly presented through animations and illustrations**, with text serving as explanations and supplements.
When encountering content with animations or illustrations as shown in the Figure 0-2 , **prioritize understanding the figure, with text as supplementary**, integrating both for a comprehensive understanding.
![Animated Illustration Example](../index.assets/animation.gif){ class="animation-figure" }
<p align="center"> Figure 0-2 &nbsp; Animated Illustration Example </p>
## 0.2.3 &nbsp; Deepen Understanding through Coding Practice
The source code of this book is hosted on the [GitHub Repository](https://github.com/krahets/hello-algo). As shown in the Figure 0-3 , **the source code comes with test examples and can be executed with just a single click**.
If time permits, **it's recommended to type out the code yourself**. If pressed for time, at least read and run all the codes.
Compared to just reading code, writing code often yields more learning. **Learning by doing is the real way to learn.**
![Running Code Example](../index.assets/running_code.gif){ class="animation-figure" }
<p align="center"> Figure 0-3 &nbsp; Running Code Example </p>
Setting up to run the code involves three main steps.
**Step 1: Install a local programming environment**. Follow the [tutorial](https://www.hello-algo.com/chapter_appendix/installation/) in the appendix for installation, or skip this step if already installed.
**Step 2: Clone or download the code repository**. Visit the [GitHub Repository](https://github.com/krahets/hello-algo).
If [Git](https://git-scm.com/downloads) is installed, use the following command to clone the repository:
```shell
git clone https://github.com/krahets/hello-algo.git
```
Alternatively, you can also click the "Download ZIP" button at the location shown in the Figure 0-4 to directly download the code as a compressed ZIP file. Then, you can simply extract it locally.
![Cloning Repository and Downloading Code](suggestions.assets/download_code.png){ class="animation-figure" }
<p align="center"> Figure 0-4 &nbsp; Cloning Repository and Downloading Code </p>
**Step 3: Run the source code**. As shown in the Figure 0-5 , for the code block labeled with the file name at the top, we can find the corresponding source code file in the `codes` folder of the repository. These files can be executed with a single click, which will help you save unnecessary debugging time and allow you to focus on learning.
![Code Block and Corresponding Source Code File](suggestions.assets/code_md_to_repo.png){ class="animation-figure" }
<p align="center"> Figure 0-5 &nbsp; Code Block and Corresponding Source Code File </p>
## 0.2.4 &nbsp; Learning Together in Discussion
While reading this book, please don't skip over the points that you didn't learn. **Feel free to post your questions in the comment section**. We will be happy to answer them and can usually respond within two days.
As illustrated in the Figure 0-6 , each chapter features a comment section at the bottom. I encourage you to pay attention to these comments. They not only expose you to others' encountered problems, aiding in identifying knowledge gaps and sparking deeper contemplation, but also invite you to generously contribute by answering fellow readers' inquiries, sharing insights, and fostering mutual improvement.
![Comment Section Example](../index.assets/comment.gif){ class="animation-figure" }
<p align="center"> Figure 0-6 &nbsp; Comment Section Example </p>
## 0.2.5 &nbsp; Algorithm Learning Path
Overall, the journey of mastering data structures and algorithms can be divided into three stages:
1. **Stage 1: Introduction to algorithms**. We need to familiarize ourselves with the characteristics and usage of various data structures and learn about the principles, processes, uses, and efficiency of different algorithms.
2. **Stage 2: Practicing algorithm problems**. It is recommended to start from popular problems, such as [Sword for Offer](https://leetcode.cn/studyplan/coding-interviews/) and [LeetCode Hot 100](https://leetcode.cn/studyplan/top-100- liked/), and accumulate at least 100 questions to familiarize yourself with mainstream algorithmic problems. Forgetfulness can be a challenge when you start practicing, but rest assured that this is normal. We can follow the "Ebbinghaus Forgetting Curve" to review the questions, and usually after 3~5 rounds of repetitions, we will be able to memorize them.
3. **Stage 3: Building the knowledge system**. In terms of learning, we can read algorithm column articles, solution frameworks, and algorithm textbooks to continuously enrich the knowledge system. In terms of practicing, we can try advanced strategies, such as categorizing by topic, multiple solutions for a single problem, and one solution for multiple problems, etc. Insights on these strategies can be found in various communities.
As shown in the Figure 0-7 , this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3.
![Algorithm Learning Path](suggestions.assets/learning_route.png){ class="animation-figure" }
<p align="center"> Figure 0-7 &nbsp; Algorithm Learning Path </p>
+12
View File
@@ -0,0 +1,12 @@
---
comments: true
---
# 0.3 &nbsp; Summary
- The main audience of this book is beginners in algorithm. If you already have some basic knowledge, this book can help you systematically review your algorithm knowledge, and the source code in this book can also be used as a "Coding Toolkit".
- The book consists of three main sections, Complexity Analysis, Data Structures, and Algorithms, covering most of the topics in the field.
- For newcomers to algorithms, it is crucial to read an introductory book in the beginning stages to avoid many detours or common pitfalls.
- Animations and figures within the book are usually used to introduce key points and difficult knowledge. These should be given more attention when reading the book.
- Practice is the best way to learn programming. It is highly recommended that you run the source code and type in the code yourself.
- Each chapter in the web version of this book features a discussion section, and you are welcome to share your questions and insights at any time.
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
---
comments: true
icon: material/stack-overflow
---
# Chapter 5. &nbsp; Stack and Queue
<div class="center-table" markdown>
![Stack and Queue](../assets/covers/chapter_stack_and_queue.jpg){ class="cover-image" }
</div>
!!! abstract
A stack is like cats placed on top of each other, while a queue is like cats lined up one by one.
They represent the logical relationships of Last-In-First-Out (LIFO) and First-In-First-Out (FIFO), respectively.
## Chapter Contents
- [5.1 &nbsp; Stack](https://www.hello-algo.com/en/chapter_stack_and_queue/stack/)
- [5.2 &nbsp; Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/queue/)
- [5.3 &nbsp; Double-ended Queue](https://www.hello-algo.com/en/chapter_stack_and_queue/deque/)
- [5.4 &nbsp; Summary](https://www.hello-algo.com/en/chapter_stack_and_queue/summary/)
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
---
comments: true
---
# 5.4 &nbsp; Summary
### 1. &nbsp; Key Review
- Stack is a data structure that follows the Last-In-First-Out (LIFO) principle and can be implemented using arrays or linked lists.
- In terms of time efficiency, the array implementation of the stack has a higher average efficiency. However, during expansion, the time complexity for a single push operation can degrade to $O(n)$. In contrast, the linked list implementation of a stack offers more stable efficiency.
- Regarding space efficiency, the array implementation of the stack may lead to a certain degree of space wastage. However, it's important to note that the memory space occupied by nodes in a linked list is generally larger than that for elements in an array.
- A queue is a data structure that follows the First-In-First-Out (FIFO) principle, and it can also be implemented using arrays or linked lists. The conclusions regarding time and space efficiency for queues are similar to those for stacks.
- A double-ended queue (deque) is a more flexible type of queue that allows adding and removing elements at both ends.
### 2. &nbsp; Q & A
**Q**: Is the browser's forward and backward functionality implemented with a doubly linked list?
A browser's forward and backward navigation is essentially a manifestation of the "stack" concept. When a user visits a new page, the page is added to the top of the stack; when they click the back button, the page is popped from the top of the stack. A double-ended queue (deque) can conveniently implement some additional operations, as mentioned in the "Double-Ended Queue" section.
**Q**: After popping from a stack, is it necessary to free the memory of the popped node?
If the popped node will still be used later, it's not necessary to free its memory. In languages like Java and Python that have automatic garbage collection, manual memory release is not necessary; in C and C++, manual memory release is required.
**Q**: A double-ended queue seems like two stacks joined together. What are its uses?
A double-ended queue, which is a combination of a stack and a queue or two stacks joined together, exhibits both stack and queue logic. Thus, it can implement all applications of stacks and queues while offering more flexibility.
**Q**: How exactly are undo and redo implemented?
Undo and redo operations are implemented using two stacks: Stack A for undo and Stack B for redo.
1. Each time a user performs an operation, it is pushed onto Stack A, and Stack B is cleared.
2. When the user executes an "undo", the most recent operation is popped from Stack A and pushed onto Stack B.
3. When the user executes a "redo", the most recent operation is popped from Stack B and pushed back onto Stack A.
@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><g transform="translate(-3437 -1242)"><g><path d="M3438 1313C3438 1276 3468 1246 3505 1246L3726 1246C3763 1246 3793 1276 3793 1313L3793 1313C3793 1350 3763 1380 3726 1380L3505 1380C3468 1380 3438 1350 3438 1313Z" fill="#55AEA6" fill-rule="evenodd" fill-opacity="1"/><text fill="#FFFFFF" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 3606.76 1330)">中文版</text><g><g><g><path d="M0 17.2C0 12.4566 3.85656 8.59999 8.59999 8.59999L34.4 8.59999 40.85 8.59999 43 8.59999 77.3999 8.59999C82.1434 8.59999 85.9999 12.4566 85.9999 17.2L85.9999 51.6C85.9999 56.3434 82.1434 60.2 77.3999 60.2L43 60.2 40.85 60.2 34.4 60.2 8.59999 60.2C3.85656 60.2 0 56.3434 0 51.6L0 17.2ZM43 17.2 43 51.6 77.3999 51.6 77.3999 17.2 43 17.2ZM23.959 23.6365C23.529 22.669 22.5615 22.0375 21.5 22.0375 20.4384 22.0375 19.4709 22.669 19.0409 23.6365L10.4409 42.9865C9.83624 44.3437 10.4544 45.9293 11.8116 46.534 13.1687 47.1387 14.7544 46.5206 15.3591 45.1634L16.555 42.4625 26.445 42.4625 27.6409 45.1634C28.2456 46.5206 29.8312 47.1253 31.1884 46.534 32.5456 45.9428 33.1503 44.3437 32.559 42.9865L23.959 23.6365ZM21.5 31.3362 24.0531 37.0875 18.9469 37.0875 21.5 31.3362ZM60.2 22.0375C61.6781 22.0375 62.8875 23.2469 62.8875 24.725L62.8875 25.2625 68.7999 25.2625 70.95 25.2625C72.4281 25.2625 73.6374 26.4719 73.6374 27.95 73.6374 29.4281 72.4281 30.6375 70.95 30.6375L70.6812 30.6375 70.4662 31.2422C69.2703 34.5209 67.4562 37.504 65.145 40.0303 65.2659 40.1109 65.3868 40.1781 65.5078 40.2453L68.0474 41.7637C69.324 42.5297 69.7271 44.1825 68.9746 45.4456 68.2221 46.7087 66.5559 47.1253 65.2928 46.3728L62.7531 44.8543C62.1484 44.4915 61.5706 44.1153 60.9928 43.7122 59.5684 44.72 58.05 45.5934 56.424 46.319L55.9403 46.534C54.5831 47.1387 52.9975 46.5206 52.3928 45.1634 51.7881 43.8062 52.4062 42.2206 53.7634 41.6159L54.2472 41.4009C55.1071 41.0112 55.9403 40.5812 56.7331 40.084L55.0937 38.4447C54.0456 37.3965 54.0456 35.69 55.0937 34.6418 56.1418 33.5937 57.8484 33.5937 58.8965 34.6418L60.8584 36.6037 60.9256 36.6709C62.5918 34.9106 63.949 32.8681 64.93 30.624L60.2 30.624 50.525 30.624C49.0468 30.624 47.8375 29.4147 47.8375 27.9365 47.8375 26.4584 49.0468 25.249 50.525 25.249L57.5125 25.249 57.5125 24.7115C57.5125 23.2334 58.7218 22.024 60.2 22.024Z" fill="#FFFFFF" fill-rule="nonzero" fill-opacity="1" transform="matrix(1 0 0 1.00291 3488 1278)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><g transform="translate(-2699 -1242)"><g><path d="M2702 1313C2702 1276 2732 1246 2769 1246L2989 1246C3026 1246 3056 1276 3056 1313 3056 1350 3026 1380 2989 1380L2769 1380C2732 1380 2702 1350 2702 1313Z" fill="#52BBB1" fill-rule="evenodd" fill-opacity="1"/><text fill="#3B3B3B" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 2870.19 1330)">中文版</text><g><g><g><path d="M0 17.25C0 12.4928 3.86778 8.62501 8.62501 8.62501L34.5 8.62501 40.9688 8.62501 43.1251 8.62501 77.6251 8.62501C82.3823 8.62501 86.2501 12.4928 86.2501 17.25L86.2501 51.7501C86.2501 56.5073 82.3823 60.3751 77.6251 60.3751L43.1251 60.3751 40.9688 60.3751 34.5 60.3751 8.62501 60.3751C3.86778 60.3751 0 56.5073 0 51.7501L0 17.25ZM43.1251 17.25 43.1251 51.7501 77.6251 51.7501 77.6251 17.25 43.1251 17.25ZM24.0287 23.7053C23.5975 22.735 22.6272 22.1016 21.5625 22.1016 20.4979 22.1016 19.5276 22.735 19.0963 23.7053L10.4713 43.1116C9.86486 44.4727 10.4848 46.063 11.8459 46.6694 13.2071 47.2758 14.7973 46.6559 15.4037 45.2948L16.6031 42.586 26.5219 42.586 27.7213 45.2948C28.3278 46.6559 29.918 47.2624 31.2791 46.6694 32.6403 46.0764 33.2467 44.4727 32.6538 43.1116L24.0287 23.7053ZM21.5625 31.4274 24.1231 37.1954 19.002 37.1954 21.5625 31.4274ZM60.3751 22.1016C61.8575 22.1016 63.0704 23.3145 63.0704 24.7969L63.0704 25.336 69.0001 25.336 71.1563 25.336C72.6388 25.336 73.8517 26.5489 73.8517 28.0313 73.8517 29.5137 72.6388 30.7266 71.1563 30.7266L70.8868 30.7266 70.6712 31.3331C69.4718 34.6213 67.6524 37.6131 65.3345 40.1467 65.4558 40.2276 65.577 40.295 65.6983 40.3624L68.2454 41.8852C69.5257 42.6534 69.93 44.311 69.1753 45.5778 68.4206 46.8446 66.7495 47.2624 65.4827 46.5077L62.9356 44.9848C62.3292 44.621 61.7497 44.2436 61.1702 43.8393 59.7417 44.8501 58.2188 45.726 56.5882 46.4538L56.103 46.6694C54.7419 47.2758 53.1516 46.6559 52.5452 45.2948 51.9387 43.9337 52.5587 42.3434 53.9198 41.737L54.405 41.5214C55.2675 41.1305 56.103 40.6993 56.8981 40.2006L55.254 38.5565C54.2028 37.5053 54.2028 35.7938 55.254 34.7426 56.3052 33.6915 58.0167 33.6915 59.0679 34.7426L61.0354 36.7102 61.1028 36.7776C62.7739 35.0122 64.1351 32.9637 65.1188 30.7131L60.3751 30.7131 50.6719 30.7131C49.1895 30.7131 47.9766 29.5002 47.9766 28.0178 47.9766 26.5354 49.1895 25.3225 50.6719 25.3225L57.6798 25.3225 57.6798 24.7834C57.6798 23.301 58.8927 22.0881 60.3751 22.0881Z" fill="#3B3B3B" fill-rule="nonzero" fill-opacity="1" transform="matrix(1.00869 0 0 1 2751 1278)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><g transform="translate(-3437 -838)"><g><path d="M3438 907C3438 869.997 3468 840 3505 840L3726 840C3763 840 3793 869.997 3793 907L3793 907C3793 944.004 3763 974.001 3726 974.001L3505 974C3468 974 3438 944.003 3438 907Z" fill="#55AEA6" fill-rule="evenodd" fill-opacity="1"/><text fill="#FFFFFF" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 3630.75 924)">PDF</text><g><g><g><path d="M3515 882.125C3515 877.644 3518.64 874 3523.12 874L3543.44 874 3543.44 890.25C3543.44 892.497 3545.25 894.313 3547.5 894.313L3563.75 894.313 3563.75 912.594 3537.34 912.594C3532.86 912.594 3529.22 916.237 3529.22 920.719L3529.22 939 3523.12 939C3518.64 939 3515 935.357 3515 930.875L3515 882.125ZM3563.75 890.25 3547.5 890.25 3547.5 874 3563.75 890.25ZM3537.34 918.688 3541.41 918.688C3545.33 918.688 3548.52 921.874 3548.52 925.797 3548.52 929.72 3545.33 932.906 3541.41 932.906L3539.38 932.906 3539.38 936.969C3539.38 938.086 3538.46 939 3537.34 939 3536.23 939 3535.31 938.086 3535.31 936.969L3535.31 930.875 3535.31 920.719C3535.31 919.602 3536.23 918.688 3537.34 918.688ZM3541.41 928.844C3543.09 928.844 3544.45 927.486 3544.45 925.797 3544.45 924.109 3543.09 922.75 3541.41 922.75L3539.38 922.75 3539.38 928.844 3541.41 928.844ZM3553.59 918.688 3557.66 918.688C3561.02 918.688 3563.75 921.417 3563.75 924.781L3563.75 932.906C3563.75 936.271 3561.02 939 3557.66 939L3553.59 939C3552.48 939 3551.56 938.086 3551.56 936.969L3551.56 920.719C3551.56 919.602 3552.48 918.688 3553.59 918.688ZM3557.66 934.938C3558.77 934.938 3559.69 934.024 3559.69 932.906L3559.69 924.781C3559.69 923.664 3558.77 922.75 3557.66 922.75L3555.62 922.75 3555.62 934.938 3557.66 934.938ZM3567.81 920.719C3567.81 919.602 3568.73 918.688 3569.84 918.688L3575.94 918.688C3577.05 918.688 3577.97 919.602 3577.97 920.719 3577.97 921.836 3577.05 922.75 3575.94 922.75L3571.88 922.75 3571.88 926.813 3575.94 926.813C3577.05 926.813 3577.97 927.727 3577.97 928.844 3577.97 929.961 3577.05 930.875 3575.94 930.875L3571.88 930.875 3571.88 936.969C3571.88 938.086 3570.96 939 3569.84 939 3568.73 939 3567.81 938.086 3567.81 936.969L3567.81 928.844 3567.81 920.719Z" fill="#FFFFFF" fill-rule="nonzero" fill-opacity="1"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><g transform="translate(-2699 -838)"><g><path d="M2702 907C2702 869.997 2732 840 2769 840L2989 840C3026 840 3056 869.997 3056 907 3056 944.003 3026 974 2989 974L2769 974C2732 974 2702 944.003 2702 907Z" fill="#52BBB1" fill-rule="evenodd" fill-opacity="1"/><text fill="#3B3B3B" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 2894.18 924)">PDF</text><g><g><g><path d="M2779 882.125C2779 877.644 2782.64 874 2787.12 874L2807.44 874 2807.44 890.25C2807.44 892.497 2809.25 894.313 2811.5 894.313L2827.75 894.313 2827.75 912.594 2801.34 912.594C2796.86 912.594 2793.22 916.237 2793.22 920.719L2793.22 939 2787.12 939C2782.64 939 2779 935.357 2779 930.875L2779 882.125ZM2827.75 890.25 2811.5 890.25 2811.5 874 2827.75 890.25ZM2801.34 918.688 2805.41 918.688C2809.33 918.688 2812.52 921.874 2812.52 925.797 2812.52 929.72 2809.33 932.906 2805.41 932.906L2803.37 932.906 2803.37 936.969C2803.37 938.086 2802.46 939 2801.34 939 2800.23 939 2799.31 938.086 2799.31 936.969L2799.31 930.875 2799.31 920.719C2799.31 919.602 2800.23 918.688 2801.34 918.688ZM2805.41 928.844C2807.09 928.844 2808.45 927.486 2808.45 925.797 2808.45 924.109 2807.09 922.75 2805.41 922.75L2803.37 922.75 2803.37 928.844 2805.41 928.844ZM2817.59 918.688 2821.66 918.688C2825.02 918.688 2827.75 921.417 2827.75 924.781L2827.75 932.906C2827.75 936.271 2825.02 939 2821.66 939L2817.59 939C2816.48 939 2815.56 938.086 2815.56 936.969L2815.56 920.719C2815.56 919.602 2816.48 918.688 2817.59 918.688ZM2821.66 934.938C2822.77 934.938 2823.69 934.024 2823.69 932.906L2823.69 924.781C2823.69 923.664 2822.77 922.75 2821.66 922.75L2819.62 922.75 2819.62 934.938 2821.66 934.938ZM2831.81 920.719C2831.81 919.602 2832.73 918.688 2833.84 918.688L2839.94 918.688C2841.05 918.688 2841.97 919.602 2841.97 920.719 2841.97 921.836 2841.05 922.75 2839.94 922.75L2835.87 922.75 2835.87 926.813 2839.94 926.813C2841.05 926.813 2841.97 927.727 2841.97 928.844 2841.97 929.961 2841.05 930.875 2839.94 930.875L2835.87 930.875 2835.87 936.969C2835.87 938.086 2834.96 939 2833.84 939 2832.73 939 2831.81 938.086 2831.81 936.969L2831.81 928.844 2831.81 920.719Z" fill="#3B3B3B" fill-rule="nonzero" fill-opacity="1"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><defs><clipPath id="clip0"><rect x="3501" y="672" width="82" height="65"/></clipPath><clipPath id="clip1"><rect x="3501" y="672" width="82" height="65"/></clipPath><clipPath id="clip2"><rect x="3501" y="672" width="82" height="65"/></clipPath></defs><g transform="translate(-3437 -632)"><g><path d="M3438 704C3438 666.997 3468 637 3505 637L3726 637C3763 637 3793 666.997 3793 704L3793 704C3793 741.003 3763 771 3726 771L3505 771C3468 771 3438 741.003 3438 704Z" fill="#55AEA6" fill-rule="evenodd" fill-opacity="1"/><text fill="#FFFFFF" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 3626.16 721)">Web</text><g clip-path="url(#clip0)"><g clip-path="url(#clip1)"><g clip-path="url(#clip2)"><path d="M16.25 0C11.7685 0 8.125 3.64355 8.125 8.125L8.125 36.5625 2.4375 36.5625C1.0918 36.5625 0 37.6543 0 39 0 44.3828 4.36719 48.75 9.75 48.75L40.625 48.75 40.625 36.5625 16.25 36.5625 16.25 8.125 56.875 8.125 56.875 12.1875 65 12.1875 65 8.125C65 3.64355 61.3564 0 56.875 0L16.25 0ZM65 16.25 50.7812 16.25C47.417 16.25 44.6875 18.9795 44.6875 22.3437L44.6875 58.9062C44.6875 62.2705 47.417 65 50.7812 65L75.1562 65C78.5205 65 81.25 62.2705 81.25 58.9062L81.25 32.5 69.0625 32.5C66.8154 32.5 65 30.6846 65 28.4375L65 16.25ZM69.0625 16.25 69.0625 28.4375 81.25 28.4375 69.0625 16.25Z" fill="#FFFFFF" fill-rule="nonzero" fill-opacity="1" transform="matrix(1.00923 0 0 1 3501 672)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1 @@
<svg width="358" height="142" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><defs><clipPath id="clip0"><rect x="2765" y="672" width="81" height="65"/></clipPath><clipPath id="clip1"><rect x="2765" y="672" width="81" height="65"/></clipPath><clipPath id="clip2"><rect x="2765" y="672" width="81" height="65"/></clipPath></defs><g transform="translate(-2699 -632)"><g><path d="M2702 704C2702 666.997 2732 637 2769 637L2989 637C3026 637 3056 666.997 3056 704 3056 741.003 3026 771 2989 771L2769 771C2732 771 2702 741.003 2702 704Z" fill="#52BBB1" fill-rule="evenodd" fill-opacity="1"/><text fill="#3B3B3B" fill-opacity="1" font-family="Noto Sans SC,Noto Sans SC_MSFontService,sans-serif" font-style="normal" font-variant="normal" font-weight="400" font-stretch="normal" font-size="48" text-anchor="start" direction="ltr" writing-mode="lr-tb" unicode-bidi="normal" text-decoration="none" transform="matrix(1 0 0 1 2889.59 721)">Web</text><g clip-path="url(#clip0)"><g clip-path="url(#clip1)"><g clip-path="url(#clip2)"><path d="M16.2 0C11.7323 0 8.09998 3.63233 8.09998 8.09998L8.09998 36.4499 2.42999 36.4499C1.08843 36.4499 0 37.5383 0 38.8799 0 44.2461 4.35374 48.5999 9.71997 48.5999L40.4999 48.5999 40.4999 36.4499 16.2 36.4499 16.2 8.09998 56.6998 8.09998 56.6998 12.15 64.7998 12.15 64.7998 8.09998C64.7998 3.63233 61.1675 0 56.6998 0L16.2 0ZM64.7998 16.2 50.6249 16.2C47.271 16.2 44.5499 18.921 44.5499 22.2749L44.5499 58.7248C44.5499 62.0787 47.271 64.7998 50.6249 64.7998L74.9248 64.7998C78.2787 64.7998 80.9998 62.0787 80.9998 58.7248L80.9998 32.3999 68.8498 32.3999C66.6097 32.3999 64.7998 30.5901 64.7998 28.3499L64.7998 16.2ZM68.8498 16.2 68.8498 28.3499 80.9998 28.3499 68.8498 16.2Z" fill="#3B3B3B" fill-rule="nonzero" fill-opacity="1" transform="matrix(1 0 0 1.00309 2765 672)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+156
View File
@@ -0,0 +1,156 @@
---
comments: true
glightbox: false
hide:
- footer
- toc
- edit
---
<div class="header-img-div" style="max-width: 600px;">
<img src="index.assets/hello_algo_header.png" style="width: 100%; height: auto; margin-left: 15px; margin-right: 15px;">
</div>
<h2 align="center" style="margin-top: 25px;">Hello Algo</h2>
<p align="center">Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code</p>
<p align="center">
<a href="https://github.com/krahets/hello-algo">
<img alt="GitHub repo stars" src="https://img.shields.io/github/stars/krahets/hello-algo?style=social&link=https%3A%2F%2Fgithub.com%2Fkrahets%2Fhello-algo">
</a>
&nbsp;
<a href="https://github.com/krahets/hello-algo/releases">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/krahets/hello-algo/total?style=social&logo=gitbook&logoColor=black&label=Downloads">
</a>
&nbsp;
<a href="https://github.com/krahets/hello-algo">
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors-anon/krahets/hello-algo?style=social&logo=git&logoColor=%23101010">
</a>
</p>
<p align="center">
<a href="https://www.hello-algo.com/en/chapter_preface/" class="rounded-button">
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M249.6 471.5c10.8 3.8 22.4-4.1 22.4-15.5V78.6c0-4.2-1.6-8.4-5-11C247.4 52 202.4 32 144 32C93.5 32 46.3 45.3 18.1 56.1C6.8 60.5 0 71.7 0 83.8V454.1c0 11.9 12.8 20.2 24.1 16.5C55.6 460.1 105.5 448 144 448c33.9 0 79 14 105.6 23.5zm76.8 0C353 462 398.1 448 432 448c38.5 0 88.4 12.1 119.9 22.6c11.3 3.8 24.1-4.6 24.1-16.5V83.8c0-12.1-6.8-23.3-18.1-27.6C529.7 45.3 482.5 32 432 32c-58.4 0-103.4 20-123 35.6c-3.3 2.6-5 6.8-5 11V456c0 11.4 11.7 19.3 22.4 15.5z"/></svg>
Dive In
</a>
<a href="https://github.com/krahets/hello-algo" class="rounded-button">
<svg xmlns="http://www.w3.org/2000/svg" height="16px" width="15.5px" viewBox="0 0 496 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
Clone Repo
</a>
<a href="https://github.com/krahets/hello-algo/releases" class="rounded-button">
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 144-208 0c-35.3 0-64 28.7-64 64l0 144-48 0c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM176 352l32 0c30.9 0 56 25.1 56 56s-25.1 56-56 56l-16 0 0 32c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-48 0-80c0-8.8 7.2-16 16-16zm32 80c13.3 0 24-10.7 24-24s-10.7-24-24-24l-16 0 0 48 16 0zm96-80l32 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-32 0c-8.8 0-16-7.2-16-16l0-128c0-8.8 7.2-16 16-16zm32 128c8.8 0 16-7.2 16-16l0-64c0-8.8-7.2-16-16-16l-16 0 0 96 16 0zm80-112c0-8.8 7.2-16 16-16l48 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 32 32 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 48c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-64 0-64z"/></svg>
Get PDF
</a>
</p>
<div class="admonition example" style="max-width: 800px;margin: 0 auto;">
<p class="admonition-title">The English edition is brewing...</p>
<p>Feel free to engage in Chinese-to-English translation and pull request review! For guidelines, please see <a href="https://github.com/krahets/hello-algo/issues/914">#914</a>.</p>
</div>
<div style="display: flex;">
<div class="admonition quote" style="flex: 1; margin-right: 0.4rem;">
<p class="admonition-title">Quote</p>
<p>"An easy-to-understand book on data structures and algorithms, which guides readers to learn by minds-on and hands-on. Strongly recommended for algorithm beginners!"</p>
<p><strong>—— Junhui Deng, Professor of Computer Science, Tsinghua University</strong></p>
</div>
<div class="admonition quote" style="flex: 1; margin-left: 0.4rem;">
<p class="admonition-title">Quote</p>
<p>"If I had 'Hello Algo' when I was learning data structures and algorithms, it would have been 10 times easier!"</p>
<p><strong>—— Mu Li, Senior Principal Scientist, Amazon</strong></p>
</div>
</div>
---
<div style="display: flex; align-items: center; margin: 2rem auto;">
<div style="flex: 1; margin: 0 2rem; display: flex; flex-direction: column; align-items: center;">
<div style="display: flex; flex-direction: column; align-items: center;">
<h3>Animated illustrations</h3>
<svg xmlns="http://www.w3.org/2000/svg" height="28" width="28" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="var(--md-primary-bg-color)" d="M256 0H576c35.3 0 64 28.7 64 64V288c0 35.3-28.7 64-64 64H256c-35.3 0-64-28.7-64-64V64c0-35.3 28.7-64 64-64zM476 106.7C471.5 100 464 96 456 96s-15.5 4-20 10.7l-56 84L362.7 169c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h80 48H552c8.9 0 17-4.9 21.2-12.7s3.7-17.3-1.2-24.6l-96-144zM336 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zM64 128h96V384v32c0 17.7 14.3 32 32 32H320c17.7 0 32-14.3 32-32V384H512v64c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V192c0-35.3 28.7-64 64-64zm8 64c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16H88c8.8 0 16-7.2 16-16V208c0-8.8-7.2-16-16-16H72zm0 104c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16H88c8.8 0 16-7.2 16-16V312c0-8.8-7.2-16-16-16H72zm0 104c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16H88c8.8 0 16-7.2 16-16V416c0-8.8-7.2-16-16-16H72zm336 16v16c0 8.8 7.2 16 16 16h16c8.8 0 16-7.2 16-16V416c0-8.8-7.2-16-16-16H424c-8.8 0-16 7.2-16 16z"/></svg>
</div>
<p style="text-align: center;">Easy to understand</br>Smooth learning curve</p>
</div>
<img src="index.assets/animation.gif" class="cover-image" style="flex-shrink: 0; width: auto; max-width: 50%; border-radius: 0.5rem;">
</div>
<div class="admonition quote">
<p align="center">"A picture is worth a thousand words."</p>
</div>
<div style="display: flex; align-items: center; margin: 2rem auto;">
<img src="index.assets/running_code.gif" class="cover-image" style="flex-shrink: 0; width: auto; max-width: 50%; border-radius: 0.5rem;">
<div style="flex: 1; margin: 0 2rem; display: flex; flex-direction: column; align-items: center;">
<div style="display: flex; flex-direction: column; align-items: center;">
<h3>Off-the-Shelf Code</h3>
<svg xmlns="http://www.w3.org/2000/svg" height="28" width="28" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="var(--md-primary-bg-color)" d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>
</div>
<p style="text-align: center;">Multi programming languages</br>Run with one click</p>
</div>
</div>
<div class="admonition quote">
<p align="center">"Talk is cheap. Show me the code."</p>
</div>
<div style="display: flex; align-items: center; margin: 2rem auto;">
<div style="flex: 1; margin: 0 2rem; display: flex; flex-direction: column; align-items: center;">
<div style="display: flex; flex-direction: column; align-items: center;">
<h3>Learning Together</h3>
<svg xmlns="http://www.w3.org/2000/svg" height="28" width="28" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="var(--md-primary-bg-color)" d="M88.2 309.1c9.8-18.3 6.8-40.8-7.5-55.8C59.4 230.9 48 204 48 176c0-63.5 63.8-128 160-128s160 64.5 160 128s-63.8 128-160 128c-13.1 0-25.8-1.3-37.8-3.6c-10.4-2-21.2-.6-30.7 4.2c-4.1 2.1-8.3 4.1-12.6 6c-16 7.2-32.9 13.5-49.9 18c2.8-4.6 5.4-9.1 7.9-13.6c1.1-1.9 2.2-3.9 3.2-5.9zM0 176c0 41.8 17.2 80.1 45.9 110.3c-.9 1.7-1.9 3.5-2.8 5.1c-10.3 18.4-22.3 36.5-36.6 52.1c-6.6 7-8.3 17.2-4.6 25.9C5.8 378.3 14.4 384 24 384c43 0 86.5-13.3 122.7-29.7c4.8-2.2 9.6-4.5 14.2-6.8c15.1 3 30.9 4.5 47.1 4.5c114.9 0 208-78.8 208-176S322.9 0 208 0S0 78.8 0 176zM432 480c16.2 0 31.9-1.6 47.1-4.5c4.6 2.3 9.4 4.6 14.2 6.8C529.5 498.7 573 512 616 512c9.6 0 18.2-5.7 22-14.5c3.8-8.8 2-19-4.6-25.9c-14.2-15.6-26.2-33.7-36.6-52.1c-.9-1.7-1.9-3.4-2.8-5.1C622.8 384.1 640 345.8 640 304c0-94.4-87.9-171.5-198.2-175.8c4.1 15.2 6.2 31.2 6.2 47.8l0 .6c87.2 6.7 144 67.5 144 127.4c0 28-11.4 54.9-32.7 77.2c-14.3 15-17.3 37.6-7.5 55.8c1.1 2 2.2 4 3.2 5.9c2.5 4.5 5.2 9 7.9 13.6c-17-4.5-33.9-10.7-49.9-18c-4.3-1.9-8.5-3.9-12.6-6c-9.5-4.8-20.3-6.2-30.7-4.2c-12.1 2.4-24.7 3.6-37.8 3.6c-61.7 0-110-26.5-136.8-62.3c-16 5.4-32.8 9.4-50 11.8C279 439.8 350 480 432 480z"/></svg>
</div>
<p style="text-align: center;">Discussion and questions welcome</br>Readers progress together</p>
</div>
<img src="index.assets/comment.gif" class="cover-image" style="flex-shrink: 0; width: auto; max-width: 50%; border-radius: 0.5rem;">
</div>
<div class="admonition quote">
<p align="center">"Learning by teaching."</p>
</div>
---
<h3 align="center"> Preface </h3>
Two years ago, I shared the "Sword Offer" series of problem solutions on LeetCode, which received much love and support from many students. During my interactions with readers, the most common question I encountered was "How to get started with algorithms." Gradually, I developed a deep interest in this question.
Blindly solving problems seems to be the most popular method, being simple, direct, and effective. However, problem-solving is like playing a "Minesweeper" game, where students with strong self-learning abilities can successfully clear the mines one by one, but those with insufficient foundations may end up bruised from explosions, retreating step by step in frustration. Thoroughly reading textbooks is also common, but for students aiming for job applications, the energy consumed by graduation, resume submissions, and preparing for written tests and interviews makes tackling thick books a daunting challenge.
If you are facing similar troubles, then you are lucky to have found this book. This book is my answer to this question, not necessarily the best solution, but at least an active attempt. Although this book won't directly land you an Offer, it will guide you through the "knowledge map" of data structures and algorithms, help you understand the shape, size, and distribution of different "mines," and equip you with various "demining methods." With these skills, I believe you can more comfortably solve problems and read literature, gradually building a complete knowledge system.
I deeply agree with Professor Feynman's saying: "Knowledge isn't free. You have to pay attention." In this sense, this book is not entirely "free." To not disappoint the precious "attention" you pay to this book, I will do my utmost, investing the greatest "attention" to complete the creation of this book.
<h3 align="left"> Author </h3>
Yudong Jin([krahets](https://leetcode.cn/u/jyd/)), Senior Algorithm Engineer in a top tech company, Master's degree from Shanghai Jiao Tong University. The highest-read blogger across the entire LeetCode, his published ["Illustration of Algorithm Data Structures"](https://leetcode.cn/leetbook/detail/illustration-of-algorithm/) has been subscribed to by over 300k.
---
<h3 align="center"> Contribution </h3>
This book is continuously improved with the joint efforts of many contributors from the open-source community. Thanks to each writer who invested their time and energy, listed in the order generated by GitHub:
<p align="center">
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
<img width="550" src="https://contrib.rocks/image?repo=krahets/hello-algo">
</a>
</p>
The code review work for this book was completed by codingonion, Gonglja, gvenusleo, hpstory, justintse, krahets, night-cruise, nuomi1, and Reanon (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages.
<div class="center-table">
<table style="border: none;">
<tbody>
<td align="center" style="border: none;"><a href="https://github.com/codingonion"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/99076655?v=4" width="50px;" alt="codingonion"/></br><sub><b>codingonion</b></sub></a></br><sub>Rust, Zig</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/Gonglja"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/39959756?v=4" width="50px;" alt="Gonglja"/></br><sub><b>Gonglja</b></sub></a></br><sub>C, C++</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/gvenusleo"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/79075347?v=4" width="50px;" alt="gvenusleo"/></br><sub><b>gvenusleo</b></sub></a></br><sub>Dart</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/hpstory"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/33348162?v=4" width="50px;" alt="hpstory"/></br><sub><b>hpstory</b></sub></a></br><sub>C#</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/justin-tse"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/24556310?v=4" width="50px;" alt="justin-tse"/></br><sub><b>justin-tse</b></sub></a></br><sub>JS, TS</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/krahets"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/26993056?v=4" width="50px;" alt="krahets"/></br><sub><b>krahets</b></sub></a></br><sub>Java, Python</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/night-cruise"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/77157236?v=4" width="50px;" alt="night-cruise"/></br><sub><b>night-cruise</b></sub></a></br><sub>Rust</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/nuomi1"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/3739017?v=4" width="50px;" alt="nuomi1"/></br><sub><b>nuomi1</b></sub></a></br><sub>Swift</sub></td>
<td align="center" style="border: none;"><a href="https://github.com/Reanon"><img style="border-radius: 50%;" src="https://avatars.githubusercontent.com/u/22005836?v=4" width="50px;" alt="Reanon"/></br><sub><b>Reanon</b></sub></a></br><sub>Go, C</sub></td>
</tbody>
</table>
</div>