diff --git a/en/docs/chapter_appendix/contribution.md b/en/docs/chapter_appendix/contribution.md
index 0852364c5..5a3846cd9 100644
--- a/en/docs/chapter_appendix/contribution.md
+++ b/en/docs/chapter_appendix/contribution.md
@@ -1,40 +1,40 @@
-# Contributing
+# Contributing Together
-Due to the limited abilities of the author, some omissions and errors are inevitable in this book. Please understand. If you discover any typos, broken links, missing content, textual ambiguities, unclear explanations, or unreasonable text structures, please assist us in making corrections to provide readers with better quality learning resources.
+Due to limited capacity, there may be inevitable omissions and errors in this book. We appreciate your understanding and are grateful for your help in correcting them. If you discover typos, broken links, missing content, ambiguous wording, unclear explanations, or structural issues, please help us make corrections to provide readers with higher-quality learning resources.
-The GitHub IDs of all [contributors](https://github.com/krahets/hello-algo/graphs/contributors) will be displayed on the repository, web, and PDF versions of the homepage of this book to thank them for their selfless contributions to the open-source community.
+The GitHub IDs of all [contributors](https://github.com/krahets/hello-algo/graphs/contributors) will be displayed on the homepage of the book repository, the web version, and the PDF version to acknowledge their selfless contributions to the open source community.
-!!! success "The charm of open source"
+!!! success "The Charm of Open Source"
- The interval between two printings of a paper book is often long, making content updates very inconvenient.
-
- In this open-source book, however, the content update cycle is shortened to just a few days or even hours.
+ The interval between two printings of a physical book is often quite long, making content updates very inconvenient.
-### Content fine-tuning
+ In this open source book, the time for content updates has been shortened to just days or even hours.
-As shown in the figure below, there is an "edit icon" in the upper right corner of each page. You can follow these steps to modify text or code.
+### Minor Content Adjustments
-1. Click the "edit icon". If prompted to "fork this repository", please agree to do so.
-2. Modify the Markdown source file content, check the accuracy of the content, and try to keep the formatting consistent.
-3. Fill in the modification description at the bottom of the page, then click the "Propose file change" button. After the page redirects, click the "Create pull request" button to initiate the pull request.
+As shown in the figure below, there is an "edit icon" in the top-right corner of each page. You can modify text or code by following these steps.
-
+1. Click the "edit icon". If you encounter a prompt asking you to "Fork this repository", please approve the operation.
+2. Modify the content of the Markdown source file, verify the correctness of the content, and maintain consistent formatting as much as possible.
+3. Fill in a description of your changes at the bottom of the page, then click the "Propose file change" button. After the page transitions, click the "Create pull request" button to submit your pull request.
-Figures cannot be directly modified and require the creation of a new [Issue](https://github.com/krahets/hello-algo/issues) or a comment to describe the problem. We will redraw and replace the figures as soon as possible.
+
-### Content creation
+Images cannot be directly modified. Please describe the issue by creating a new [Issue](https://github.com/krahets/hello-algo/issues) or leaving a comment. We will promptly redraw and replace the images.
-If you are interested in participating in this open-source project, including translating code into other programming languages or expanding article content, then the following Pull Request workflow needs to be implemented.
+### Content Creation
-1. Log in to GitHub and Fork the [code repository](https://github.com/krahets/hello-algo) of this book to your personal account.
-2. Go to your Forked repository web page and use the `git clone` command to clone the repository to your local machine.
-3. Create content locally and perform complete tests to verify the correctness of the code.
-4. Commit the changes made locally, then push them to the remote repository.
-5. Refresh the repository webpage and click the "Create pull request" button to initiate the pull request.
+If you are interested in contributing to this open source project, including translating code into other programming languages or expanding article content, you will need to follow the Pull Request workflow below.
-### Docker deployment
+1. Log in to GitHub and Fork the book's [code repository](https://github.com/krahets/hello-algo) to your personal account.
+2. Enter your forked repository webpage and use the `git clone` command to clone the repository to your local machine.
+3. Create content locally and conduct comprehensive tests to verify code correctness.
+4. Commit your local changes and push them to the remote repository.
+5. Refresh the repository webpage and click the "Create pull request" button to submit your pull request.
-In the `hello-algo` root directory, execute the following Docker script to access the project at `http://localhost:8000`:
+### Docker Deployment
+
+From the root directory of `hello-algo`, run the following Docker script to access the project at `http://localhost:8000`:
```shell
docker-compose up -d
diff --git a/en/docs/chapter_appendix/installation.md b/en/docs/chapter_appendix/installation.md
index 40e22b62f..d8aebde65 100644
--- a/en/docs/chapter_appendix/installation.md
+++ b/en/docs/chapter_appendix/installation.md
@@ -1,68 +1,68 @@
-# Installation
+# Programming Environment Installation
-## Install IDE
+## Installing IDE
-We recommend using the open-source, lightweight VS Code as your local Integrated Development Environment (IDE). Visit the [VS Code official website](https://code.visualstudio.com/) and choose the version of VS Code appropriate for your operating system to download and install.
+We recommend using the open-source and lightweight VS Code as the local integrated development environment (IDE). Visit the [VS Code official website](https://code.visualstudio.com/), and download and install the appropriate version of VS Code according to your operating system.
-
+
-VS Code has a powerful extension ecosystem, supporting the execution and debugging of most programming languages. For example, after installing the "Python Extension Pack," you can debug Python code. The installation steps are shown in the figure below.
+VS Code has a powerful ecosystem of extensions that supports running and debugging most programming languages. For example, after installing the "Python Extension Pack" extension, you can debug Python code. The installation steps are shown in the following figure.
-
+
-## Install language environments
+## Installing Language Environments
-### Python environment
+### Python Environment
-1. Download and install [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), requiring Python 3.10 or newer.
-2. In the VS Code extension marketplace, search for `python` and install the Python Extension Pack.
-3. (Optional) Enter `pip install black` in the command line to install the code formatting tool.
+1. Download and install [Miniconda3](https://docs.conda.io/en/latest/miniconda.html), which requires Python 3.10 or newer.
+2. Search for `python` in the VS Code extension marketplace and install the Python Extension Pack.
+3. (Optional) Enter `pip install black` on the command line to install the code formatter.
-### C/C++ environment
+### C/C++ Environment
-1. Windows systems need to install [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([Configuration tutorial](https://blog.csdn.net/qq_33698226/article/details/129031241)); MacOS comes with Clang, so no installation is necessary.
-2. In the VS Code extension marketplace, search for `c++` and install the C/C++ Extension Pack.
+1. Windows systems need to install [MinGW](https://sourceforge.net/projects/mingw-w64/files/) ([configuration tutorial](https://blog.csdn.net/qq_33698226/article/details/129031241)); macOS comes with Clang built-in and does not require installation.
+2. Search for `c++` in the VS Code extension marketplace and install the C/C++ Extension Pack.
3. (Optional) Open the Settings page, search for the `Clang_format_fallback Style` code formatting option, and set it to `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`.
-### Java environment
+### Java Environment
1. Download and install [OpenJDK](https://jdk.java.net/18/) (version must be > JDK 9).
-2. In the VS Code extension marketplace, search for `java` and install the Extension Pack for Java.
+2. Search for `java` in the VS Code extension marketplace and install the Extension Pack for Java.
-### C# environment
+### C# Environment
1. Download and install [.Net 8.0](https://dotnet.microsoft.com/en-us/download).
-2. In the VS Code extension marketplace, search for `C# Dev Kit` and install the C# Dev Kit ([Configuration tutorial](https://code.visualstudio.com/docs/csharp/get-started)).
-3. You can also use Visual Studio ([Installation tutorial](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)).
+2. Search for `C# Dev Kit` in the VS Code extension marketplace and install C# Dev Kit ([configuration tutorial](https://code.visualstudio.com/docs/csharp/get-started)).
+3. You can also use Visual Studio ([installation tutorial](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022)).
-### Go environment
+### Go Environment
-1. Download and install [go](https://go.dev/dl/).
-2. In the VS Code extension marketplace, search for `go` and install Go.
-3. Press `Ctrl + Shift + P` to call up the command bar, enter go, choose `Go: Install/Update Tools`, select all and install.
+1. Download and install [Go](https://go.dev/dl/).
+2. Search for `go` in the VS Code extension marketplace and install Go.
+3. Press `Ctrl + Shift + P` to open the command palette, type `go`, select `Go: Install/Update Tools`, check all options and install.
-### Swift environment
+### Swift Environment
1. Download and install [Swift](https://www.swift.org/download/).
-2. In the VS Code extension marketplace, search for `swift` and install [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang).
+2. Search for `swift` in the VS Code extension marketplace and install [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang).
-### JavaScript environment
+### JavaScript Environment
1. Download and install [Node.js](https://nodejs.org/en/).
-2. (Optional) In the VS Code extension marketplace, search for `Prettier` and install the code formatting tool.
+2. (Optional) Search for `Prettier` in the VS Code extension marketplace and install the code formatter.
-### TypeScript environment
+### TypeScript Environment
1. Follow the same installation steps as the JavaScript environment.
2. Install [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation).
-3. In the VS Code extension marketplace, search for `typescript` and install [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors).
+3. Search for `typescript` in the VS Code extension marketplace and install [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors).
-### Dart environment
+### Dart Environment
1. Download and install [Dart](https://dart.dev/get-dart).
-2. In the VS Code extension marketplace, search for `dart` and install [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code).
+2. Search for `dart` in the VS Code extension marketplace and install [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code).
-### Rust environment
+### Rust Environment
1. Download and install [Rust](https://www.rust-lang.org/tools/install).
-2. In the VS Code extension marketplace, search for `rust` and install [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
+2. Search for `rust` in the VS Code extension marketplace and install [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
diff --git a/en/docs/chapter_appendix/terminology.md b/en/docs/chapter_appendix/terminology.md
index f46133c81..eb6febfaf 100644
--- a/en/docs/chapter_appendix/terminology.md
+++ b/en/docs/chapter_appendix/terminology.md
@@ -1,14 +1,14 @@
-# Glossary
+# Terminology Table
-The table below lists the important terms that appear in the book, and it is worth noting the following points.
+The following table lists important terms that appear in this book. It is worth noting the following points:
-- It is recommended to remember the English names of the terms to facilitate reading English literature.
-- Some terms have different names in Simplified and Traditional Chinese.
+- We recommend remembering the English names of terms to help with reading English literature.
+- Some terms have different names in Simplified Chinese and Traditional Chinese.
Table Important Terms in Data Structures and Algorithms
-| English | 简体中文 | 繁体中文 |
-| ------------------------------ | -------------- | -------------- |
+| English | Simplified Chinese | Traditional Chinese |
+| ------------------------------ | ------------------ | ------------------- |
| algorithm | 算法 | 演算法 |
| data structure | 数据结构 | 資料結構 |
| code | 代码 | 程式碼 |
diff --git a/en/docs/chapter_array_and_linkedlist/array.md b/en/docs/chapter_array_and_linkedlist/array.md
index 639f83018..bd389e381 100755
--- a/en/docs/chapter_array_and_linkedlist/array.md
+++ b/en/docs/chapter_array_and_linkedlist/array.md
@@ -1,14 +1,14 @@
# Array
-An array is a linear data structure that operates as a lineup of similar items, stored together in a computer's memory in contiguous spaces. It's like a sequence that maintains organized storage. Each item in this lineup has its unique 'spot' known as an index. Please refer to the figure below to observe how arrays work and grasp these key terms.
+An array is a linear data structure that stores elements of the same type in contiguous memory space. The position of an element in the array is called the element's index. The figure below illustrates the main concepts and storage method of arrays.

-## Common operations on arrays
+## Common Array Operations
-### Initializing arrays
+### Initializing Arrays
-Arrays can be initialized in two ways depending on the needs: either without initial values or with specified initial values. When initial values are not specified, most programming languages will set the array elements to $0$:
+We can choose between two array initialization methods based on our needs: without initial values or with given initial values. When no initial values are specified, most programming languages will initialize array elements to $0$:
=== "Python"
@@ -25,7 +25,7 @@ Arrays can be initialized in two ways depending on the needs: either without ini
// Stored on stack
int arr[5];
int nums[5] = { 1, 3, 2, 5, 4 };
- // Stored on heap (manual memory release needed)
+ // Stored on heap (requires manual memory release)
int* arr1 = new int[5];
int* nums1 = new int[5] { 1, 3, 2, 5, 4 };
```
@@ -51,9 +51,9 @@ Arrays can be initialized in two ways depending on the needs: either without ini
```go title="array.go"
/* Initialize array */
var arr [5]int
- // In Go, specifying the length ([5]int) denotes an array, while not specifying it ([]int) denotes a slice.
- // Since Go's arrays are designed to have compile-time fixed length, only constants can be used to specify the length.
- // For convenience in implementing the extend() method, the Slice will be considered as an Array here.
+ // In Go, specifying length ([5]int) creates an array; not specifying length ([]int) creates a slice
+ // Since Go's arrays are designed to have their length determined at compile time, only constants can be used to specify the length
+ // For convenience in implementing the extend() method, slices are treated as arrays below
nums := []int{1, 3, 2, 5, 4}
```
@@ -95,10 +95,10 @@ Arrays can be initialized in two ways depending on the needs: either without ini
/* Initialize array */
let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]
let slice: &[i32] = &[0; 5];
- // In Rust, specifying the length ([i32; 5]) denotes an array, while not specifying it (&[i32]) denotes a slice.
- // Since Rust's arrays are designed to have compile-time fixed length, only constants can be used to specify the length.
- // Vectors are generally used as dynamic arrays in Rust.
- // For convenience in implementing the extend() method, the vector will be considered as an array here.
+ // In Rust, specifying length ([i32; 5]) creates an array; not specifying length (&[i32]) creates a slice
+ // Since Rust's arrays are designed to have their length determined at compile time, only constants can be used to specify the length
+ // Vector is the type generally used as a dynamic array in Rust
+ // For convenience in implementing the extend() method, vectors are treated as arrays below
let nums: Vec = vec![1, 3, 2, 5, 4];
```
@@ -113,109 +113,123 @@ Arrays can be initialized in two ways depending on the needs: either without ini
=== "Kotlin"
```kotlin title="array.kt"
+ /* Initialize array */
+ var arr = IntArray(5) // { 0, 0, 0, 0, 0 }
+ var nums = intArrayOf(1, 3, 2, 5, 4)
+ ```
+=== "Ruby"
+
+ ```ruby title="array.rb"
+ # Initialize array
+ arr = Array.new(5, 0)
+ nums = [1, 3, 2, 5, 4]
```
=== "Zig"
```zig title="array.zig"
// Initialize array
- var arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }
- var nums = [_]i32{ 1, 3, 2, 5, 4 };
+ const arr = [_]i32{0} ** 5; // { 0, 0, 0, 0, 0 }
+ const nums = [_]i32{ 1, 3, 2, 5, 4 };
```
-### Accessing elements
+??? pythontutor "Visualize code execution"
-Elements in an array are stored in contiguous memory spaces, making it simpler to compute each element's memory address. The formula shown in the Figure below aids in determining an element's memory address, utilizing the array's memory address (specifically, the first element's address) and the element's index. This computation streamlines direct access to the desired element.
+ https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Accessing Elements
+
+Array elements are stored in contiguous memory space, which means calculating the memory address of array elements is very easy. Given the array's memory address (the memory address of the first element) and an element's index, we can use the formula shown in the figure below to calculate the element's memory address and directly access that element.

-As observed in the figure above, array indexing conventionally begins at $0$. While this might appear counterintuitive, considering counting usually starts at $1$, within the address calculation formula, **an index is essentially an offset from the memory address**. For the first element's address, this offset is $0$, validating its index as $0$.
+Observing the figure above, we find that the first element of an array has an index of $0$, which may seem counterintuitive since counting from $1$ would be more natural. However, from the perspective of the address calculation formula, **an index is essentially an offset from the memory address**. The address offset of the first element is $0$, so it is reasonable for its index to be $0$.
-Accessing elements in an array is highly efficient, allowing us to randomly access any element in $O(1)$ time.
+Accessing elements in an array is highly efficient; we can randomly access any element in the array in $O(1)$ time.
```src
[file]{array}-[class]{}-[func]{random_access}
```
-### Inserting elements
+### Inserting Elements
-Array elements are tightly packed in memory, with no space available to accommodate additional data between them. As illustrated in the figure below, inserting an element in the middle of an array requires shifting all subsequent elements back by one position to create room for the new element.
+Array elements are stored "tightly adjacent" in memory, with no space between them to store any additional data. As shown in the figure below, if we want to insert an element in the middle of an array, we need to shift all elements after that position backward by one position, and then assign the value to that index.
-
+
-It's important to note that due to the fixed length of an array, inserting an element will unavoidably result in the loss of the last element in the array. Solutions to address this issue will be explored in the "List" chapter.
+It is worth noting that since the length of an array is fixed, inserting an element will inevitably cause the element at the end of the array to be "lost". We will leave the solution to this problem for discussion in the "List" chapter.
```src
[file]{array}-[class]{}-[func]{insert}
```
-### Deleting elements
+### Removing Elements
-Similarly, as depicted in the figure below, to delete an element at index $i$, all elements following index $i$ must be moved forward by one position.
+Similarly, as shown in the figure below, to delete the element at index $i$, we need to shift all elements after index $i$ forward by one position.
-
+
-Please note that after deletion, the former last element becomes "meaningless," hence requiring no specific modification.
+Note that after the deletion is complete, the original last element becomes "meaningless", so we do not need to specifically modify it.
```src
[file]{array}-[class]{}-[func]{remove}
```
-In summary, the insertion and deletion operations in arrays present the following disadvantages:
+Overall, array insertion and deletion operations have the following drawbacks:
-- **High time complexity**: Both insertion and deletion in an array have an average time complexity of $O(n)$, where $n$ is the length of the array.
-- **Loss of elements**: Due to the fixed length of arrays, elements that exceed the array's capacity are lost during insertion.
-- **Waste of memory**: Initializing a longer array and utilizing only the front part results in "meaningless" end elements during insertion, leading to some wasted memory space.
+- **High time complexity**: The average time complexity for both insertion and deletion in arrays is $O(n)$, where $n$ is the length of the array.
+- **Loss of elements**: Since the length of an array is immutable, after inserting an element, elements that exceed the array's length will be lost.
+- **Memory waste**: We can initialize a relatively long array and only use the front portion, so that when inserting data, the lost elements at the end are "meaningless", but this causes some memory space to be wasted.
-### Traversing arrays
+### Traversing Arrays
-In most programming languages, we can traverse an array either by using indices or by directly iterating over each element:
+In most programming languages, we can traverse an array either by index or by directly iterating through each element in the array:
```src
[file]{array}-[class]{}-[func]{traverse}
```
-### Finding elements
+### Finding Elements
-Locating a specific element within an array involves iterating through the array, checking each element to determine if it matches the desired value.
+Finding a specified element in an array requires traversing the array and checking whether the element value matches in each iteration; if it matches, output the corresponding index.
-Because arrays are linear data structures, this operation is commonly referred to as "linear search."
+Since an array is a linear data structure, the above search operation is called a "linear search".
```src
[file]{array}-[class]{}-[func]{find}
```
-### Expanding arrays
+### Expanding Arrays
-In complex system environments, ensuring the availability of memory space after an array for safe capacity extension becomes challenging. Consequently, in most programming languages, **the length of an array is immutable**.
+In complex system environments, programs cannot guarantee that the memory space after an array is available, making it unsafe to expand the array's capacity. Therefore, in most programming languages, **the length of an array is immutable**.
-To expand an array, it's necessary to create a larger array and then copy the elements from the original array. This operation has a time complexity of $O(n)$ and can be time-consuming for large arrays. The code are as follows:
+If we want to expand an array, we need to create a new, larger array and then copy the original array elements to the new array one by one. This is an $O(n)$ operation, which is very time-consuming when the array is large. The code is shown below:
```src
[file]{array}-[class]{}-[func]{extend}
```
-## Advantages and limitations of arrays
+## Advantages and Limitations of Arrays
-Arrays are stored in contiguous memory spaces and consist of elements of the same type. This approach provides substantial prior information that systems can leverage to optimize the efficiency of data structure operations.
+Arrays are stored in contiguous memory space with elements of the same type. This approach contains rich prior information that the system can use to optimize the efficiency of data structure operations.
-- **High space efficiency**: Arrays allocate a contiguous block of memory for data, eliminating the need for additional structural overhead.
-- **Support for random access**: Arrays allow $O(1)$ time access to any element.
-- **Cache locality**: When accessing array elements, the computer not only loads them but also caches the surrounding data, utilizing high-speed cache to enchance subsequent operation speeds.
+- **High space efficiency**: Arrays allocate contiguous memory blocks for data without additional structural overhead.
+- **Support for random access**: Arrays allow accessing any element in $O(1)$ time.
+- **Cache locality**: When accessing array elements, the computer not only loads the element but also caches the surrounding data, thereby leveraging the cache to improve the execution speed of subsequent operations.
-However, continuous space storage is a double-edged sword, with the following limitations:
+Contiguous space storage is a double-edged sword with the following limitations:
-- **Low efficiency in insertion and deletion**: As arrays accumulate many elements, inserting or deleting elements requires shifting a large number of elements.
-- **Fixed length**: The length of an array is fixed after initialization. Expanding an array requires copying all data to a new array, incurring significant costs.
-- **Space wastage**: If the allocated array size exceeds the what is necessary, the extra space is wasted.
+- **Low insertion and deletion efficiency**: When an array has many elements, insertion and deletion operations require shifting a large number of elements.
+- **Immutable length**: After an array is initialized, its length is fixed. Expanding the array requires copying all data to a new array, which is very costly.
+- **Space waste**: If the allocated size of an array exceeds what is actually needed, the extra space is wasted.
-## Typical applications of arrays
+## Typical Applications of Arrays
-Arrays are fundamental and widely used data structures. They find frequent application in various algorithms and serve in the implementation of complex data structures.
+Arrays are a fundamental and common data structure, frequently used in various algorithms and for implementing various complex data structures.
-- **Random access**: Arrays are ideal for storing data when random sampling is required. By generating a random sequence based on indices, we can achieve random sampling efficiently.
-- **Sorting and searching**: Arrays are the most commonly used data structure for sorting and searching algorithms. Techniques like quick sort, merge sort, binary search, etc., are primarily operate on arrays.
-- **Lookup tables**: Arrays serve as efficient lookup tables for quick element or relationship retrieval. For instance, mapping characters to ASCII codes becomes seamless by using the ASCII code values as indices and storing corresponding elements in the array.
-- **Machine learning**: Within the domain of neural networks, arrays play a pivotal role in executing crucial linear algebra operations involving vectors, matrices, and tensors. Arrays serve as the primary and most extensively used data structure in neural network programming.
-- **Data structure implementation**: Arrays serve as the building blocks for implementing various data structures like stacks, queues, hash tables, heaps, graphs, etc. For instance, the adjacency matrix representation of a graph is essentially a two-dimensional array.
+- **Random access**: If we want to randomly sample some items, we can use an array to store them and generate a random sequence to implement random sampling based on indices.
+- **Sorting and searching**: Arrays are the most commonly used data structure for sorting and searching algorithms. Quick sort, merge sort, binary search, and others are primarily performed on arrays.
+- **Lookup tables**: When we need to quickly find an element or its corresponding relationship, we can use an array as a lookup table. For example, if we want to implement a mapping from characters to ASCII codes, we can use the ASCII code value of a character as an index, with the corresponding element stored at that position in the array.
+- **Machine learning**: Neural networks make extensive use of linear algebra operations between vectors, matrices, and tensors, all of which are constructed in the form of arrays. Arrays are the most commonly used data structure in neural network programming.
+- **Data structure implementation**: Arrays can be used to implement stacks, queues, hash tables, heaps, graphs, and other data structures. For example, the adjacency matrix representation of a graph is essentially a two-dimensional array.
diff --git a/en/docs/chapter_array_and_linkedlist/index.md b/en/docs/chapter_array_and_linkedlist/index.md
index 764b753f2..1a6747c47 100644
--- a/en/docs/chapter_array_and_linkedlist/index.md
+++ b/en/docs/chapter_array_and_linkedlist/index.md
@@ -1,9 +1,9 @@
-# Arrays and linked lists
+# Arrays and Linked Lists
-
+
!!! abstract
- The world of data structures resembles a sturdy brick wall.
+ The world of data structures is like a solid 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.
+ Array bricks are neatly arranged, tightly packed one by one. Linked list bricks are scattered everywhere, with connecting vines freely weaving through the gaps between bricks.
diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.md b/en/docs/chapter_array_and_linkedlist/linked_list.md
index 6292b5284..731884866 100755
--- a/en/docs/chapter_array_and_linkedlist/linked_list.md
+++ b/en/docs/chapter_array_and_linkedlist/linked_list.md
@@ -1,20 +1,20 @@
-# Linked list
+# Linked List
-Memory space is a shared resource among all programs. In a complex system environment, available memory can be dispersed throughout the memory space. We understand that the memory allocated for an array must be continuous. However, for very large arrays, finding a sufficiently large contiguous memory space might be challenging. This is where the flexible advantage of linked lists becomes evident.
+Memory space is a shared resource for all programs. In a complex system runtime environment, available memory space may be scattered throughout the memory. We know that the memory space for storing an array must be contiguous, and when the array is very large, the memory may not be able to provide such a large contiguous space. This is where the flexibility advantage of linked lists becomes apparent.
-A linked list is a linear data structure in which each element is a node object, and the nodes are interconnected through "references". These references hold the memory addresses of subsequent nodes, enabling navigation from one node to the next.
+A linked list is a linear data structure in which each element is a node object, and the nodes are connected through "references". A reference records the memory address of the next node, through which the next node can be accessed from the current node.
-The design of linked lists allows for their nodes to be distributed across memory locations without requiring contiguous memory addresses.
+The design of linked lists allows nodes to be stored scattered throughout the memory, and their memory addresses do not need to be contiguous.

-As shown in the figure above, we see that the basic building block of a linked list is the node object. Each node comprises two key components: the node's "value" and a "reference" to the next node.
+Observing the figure above, the basic unit of a linked list is a node object. Each node contains two pieces of data: the node's "value" and a "reference" to the next node.
-- The first node in a linked list is the "head node", and the final one is the "tail node".
-- The tail node points to "null", designated as `null` in Java, `nullptr` in C++, and `None` in Python.
-- In languages that support pointers, like C, C++, Go, and Rust, this "reference" is typically implemented as a "pointer".
+- The first node of a linked list is called the "head node", and the last node is called the "tail node".
+- The tail node points to "null", which is denoted as `null`, `nullptr`, and `None` in Java, C++, and Python, respectively.
+- In languages that support pointers, such as C, C++, Go, and Rust, the aforementioned "reference" should be replaced with "pointer".
-As the code below illustrates, a `ListNode` in a linked list, besides holding a value, must also maintain an additional reference (or pointer). Therefore, **a linked list occupies more memory space than an array when storing the same quantity of data.**.
+As shown in the following code, a linked list node `ListNode` contains not only a value but also an additional reference (pointer). Therefore, **linked lists occupy more memory space than arrays when storing the same amount of data**.
=== "Python"
@@ -162,7 +162,27 @@ As the code below illustrates, a `ListNode` in a linked list, besides holding a
=== "Kotlin"
```kotlin title=""
+ /* Linked list node class */
+ // Constructor
+ class ListNode(x: Int) {
+ val _val: Int = x // Node value
+ val next: ListNode? = null // Reference to the next node
+ }
+ ```
+=== "Ruby"
+
+ ```ruby title=""
+ # Linked list node class
+ class ListNode
+ attr_accessor :val # Node value
+ attr_accessor :next # Reference to the next node
+
+ def initialize(val=0, next_node=nil)
+ @val = val
+ @next = next_node
+ end
+ end
```
=== "Zig"
@@ -185,16 +205,16 @@ As the code below illustrates, a `ListNode` in a linked list, besides holding a
}
```
-## Common operations on linked lists
+## Common Linked List Operations
-### Initializing a linked list
+### Initializing a Linked List
-Constructing a linked list is a two-step process: first, initializing each node object, and second, forming the reference links between the nodes. After initialization, we can traverse all nodes sequentially from the head node by following the `next` reference.
+Building a linked list involves two steps: first, initializing each node object; second, constructing the reference relationships between nodes. Once initialization is complete, we can traverse all nodes starting from the head node of the linked list through the reference `next`.
=== "Python"
```python title="linked_list.py"
- # Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4
+ # Initialize linked list 1 -> 3 -> 2 -> 5 -> 4
# Initialize each node
n0 = ListNode(1)
n1 = ListNode(3)
@@ -211,7 +231,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "C++"
```cpp title="linked_list.cpp"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
@@ -228,7 +248,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Java"
```java title="linked_list.java"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
@@ -245,7 +265,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "C#"
```csharp title="linked_list.cs"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
ListNode n0 = new(1);
ListNode n1 = new(3);
@@ -262,7 +282,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Go"
```go title="linked_list.go"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
n0 := NewListNode(1)
n1 := NewListNode(3)
@@ -279,7 +299,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Swift"
```swift title="linked_list.swift"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
let n0 = ListNode(x: 1)
let n1 = ListNode(x: 3)
@@ -296,7 +316,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "JS"
```javascript title="linked_list.js"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
const n0 = new ListNode(1);
const n1 = new ListNode(3);
@@ -313,7 +333,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "TS"
```typescript title="linked_list.ts"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
const n0 = new ListNode(1);
const n1 = new ListNode(3);
@@ -330,7 +350,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Dart"
```dart title="linked_list.dart"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */\
// Initialize each node
ListNode n0 = ListNode(1);
ListNode n1 = ListNode(3);
@@ -347,7 +367,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Rust"
```rust title="linked_list.rs"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));
let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));
@@ -365,7 +385,7 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "C"
```c title="linked_list.c"
- /* Initialize linked list: 1 -> 3 -> 2 -> 5 -> 4 */
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
// Initialize each node
ListNode* n0 = newListNode(1);
ListNode* n1 = newListNode(3);
@@ -382,7 +402,35 @@ Constructing a linked list is a two-step process: first, initializing each node
=== "Kotlin"
```kotlin title="linked_list.kt"
+ /* Initialize linked list 1 -> 3 -> 2 -> 5 -> 4 */
+ // Initialize each node
+ val n0 = ListNode(1)
+ val n1 = ListNode(3)
+ val n2 = ListNode(2)
+ val n3 = ListNode(5)
+ val n4 = ListNode(4)
+ // Build references between nodes
+ n0.next = n1;
+ n1.next = n2;
+ n2.next = n3;
+ n3.next = n4;
+ ```
+=== "Ruby"
+
+ ```ruby title="linked_list.rb"
+ # Initialize linked list 1 -> 3 -> 2 -> 5 -> 4
+ # Initialize each node
+ n0 = ListNode.new(1)
+ n1 = ListNode.new(3)
+ n2 = ListNode.new(2)
+ n3 = ListNode.new(5)
+ n4 = ListNode.new(4)
+ # Build references between nodes
+ n0.next = n1
+ n1.next = n2
+ n2.next = n3
+ n3.next = n4
```
=== "Zig"
@@ -402,86 +450,90 @@ Constructing a linked list is a two-step process: first, initializing each node
n3.next = &n4;
```
-The array as a whole is a variable, for instance, the array `nums` includes elements like `nums[0]`, `nums[1]`, and so on, whereas a linked list is made up of several distinct node objects. **We typically refer to a linked list by its head node**, for example, the linked list in the previous code snippet is referred to as `n0`.
+??? pythontutor "Visualize code execution"
-### Inserting nodes
+ https://pythontutor.com/render.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%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-Inserting a node into a linked list is very easy. As shown in the figure below, let's assume we aim to insert a new node `P` between two adjacent nodes `n0` and `n1`. **This can be achieved by simply modifying two node references (pointers)**, with a time complexity of $O(1)$.
+An array is a single variable; for example, an array `nums` contains elements `nums[0]`, `nums[1]`, etc. A linked list, however, is composed of multiple independent node objects. **We typically use the head node as the reference to the linked list**; for example, the linked list in the above code can be referred to as linked list `n0`.
-By comparison, inserting an element into an array has a time complexity of $O(n)$, which becomes less efficient when dealing with large data volumes.
+### Inserting a Node
-
+Inserting a node in a linked list is very easy. As shown in the figure below, suppose we want to insert a new node `P` between two adjacent nodes `n0` and `n1`. **We only need to change two node references (pointers)**, with a time complexity of $O(1)$.
+
+In contrast, the time complexity of inserting an element in an array is $O(n)$, which is inefficient when dealing with large amounts of data.
+
+
```src
[file]{linked_list}-[class]{}-[func]{insert}
```
-### Deleting nodes
+### Removing a Node
-As shown in the figure below, deleting a node from a linked list is also very easy, **involving only the modification of a single node's reference (pointer)**.
+As shown in the figure below, removing a node in a linked list is also very convenient. **We only need to change one node's reference (pointer)**.
-It's important to note that even though node `P` continues to point to `n1` after being deleted, it becomes inaccessible during linked list traversal. This effectively means that `P` is no longer a part of the linked list.
+Note that although node `P` still points to `n1` after the deletion operation is complete, the linked list can no longer access `P` when traversing, which means `P` no longer belongs to this linked list.
-
+
```src
[file]{linked_list}-[class]{}-[func]{remove}
```
-### Accessing nodes
+### Accessing a Node
-**Accessing nodes in a linked list is less efficient**. As previously mentioned, any element in an array can be accessed in $O(1)$ time. In contrast, with a linked list, the program involves starting from the head node and sequentially traversing through the nodes until the desired node is found. In other words, to access the $i$-th node in a linked list, the program must iterate through $i - 1$ nodes, resulting in a time complexity of $O(n)$.
+**Accessing nodes in a linked list is less efficient**. As mentioned in the previous section, we can access any element in an array in $O(1)$ time. This is not the case with linked lists. The program needs to start from the head node and traverse backward one by one until the target node is found. That is, accessing the $i$-th node in a linked list requires $i - 1$ iterations, with a time complexity of $O(n)$.
```src
[file]{linked_list}-[class]{}-[func]{access}
```
-### Finding nodes
+### Finding a Node
-Traverse the linked list to locate a node whose value matches `target`, and then output the index of that node within the linked list. This procedure is also an example of linear search. The corresponding code is provided below:
+Traverse the linked list to find a node with value `target`, and output the index of that node in the linked list. This process is also a linear search. The code is shown below:
```src
[file]{linked_list}-[class]{}-[func]{find}
```
-## Arrays vs. linked lists
+## Arrays vs. Linked Lists
-The table below summarizes the characteristics of arrays and linked lists, and it also compares their efficiencies in various operations. Because they utilize opposing storage strategies, their respective properties and operational efficiencies exhibit distinct contrasts.
+The table below summarizes the characteristics of arrays and linked lists and compares their operational efficiencies. Since they employ two opposite storage strategies, their various properties and operational efficiencies also exhibit contrasting characteristics.
- Table Efficiency comparison of arrays and linked lists
+ Table Comparison of array and linked list efficiencies
-| | Arrays | Linked Lists |
-| ------------------ | ------------------------------------------------ | ----------------------- |
-| Storage | Contiguous Memory Space | Dispersed Memory Space |
-| Capacity Expansion | Fixed Length | Flexible Expansion |
-| Memory Efficiency | Less Memory per Element, Potential Space Wastage | More Memory per Element |
-| Accessing Elements | $O(1)$ | $O(n)$ |
-| Adding Elements | $O(n)$ | $O(1)$ |
-| Deleting Elements | $O(n)$ | $O(1)$ |
+| | Array | Linked List |
+| ---------------------- | --------------------------------------------- | -------------------------- |
+| Storage method | Contiguous memory space | Scattered memory space |
+| Capacity expansion | Immutable length | Flexible expansion |
+| Memory efficiency | Elements occupy less memory, but space may be wasted | Elements occupy more memory |
+| Accessing an element | $O(1)$ | $O(n)$ |
+| Adding an element | $O(n)$ | $O(1)$ |
+| Removing an element | $O(n)$ | $O(1)$ |
-## Common types of linked lists
+## Common Types of Linked Lists
-As shown in the figure below, there are three common types of linked lists.
+As shown in the figure below, there are three common types of linked lists:
-- **Singly linked list**: This is the standard linked list described earlier. Nodes in a singly linked list include a value and a reference to the next node. The first node is known as the head node, and the last node, which points to null (`None`), is the tail node.
-- **Circular linked list**: This is formed when the tail node of a singly linked list points back to the head node, creating a loop. In a circular linked list, any node can function as the head node.
-- **Doubly linked list**: In contrast to a singly linked list, a doubly linked list maintains references in two directions. Each node contains references (pointer) to both its successor (the next node) and predecessor (the previous node). Although doubly linked lists offer more flexibility for traversing in either direction, they also consume more memory space.
+- **Singly linked list**: This is the ordinary linked list introduced earlier. The nodes of a singly linked list contain a value and a reference to the next node. We call the first node the head node and the last node the tail node, which points to null `None`.
+- **Circular linked list**: If we make the tail node of a singly linked list point to the head node (connecting the tail to the head), we get a circular linked list. In a circular linked list, any node can be viewed as the head node.
+- **Doubly linked list**: Compared to a singly linked list, a doubly linked list records references in both directions. The node definition of a doubly linked list includes references to both the successor node (next node) and the predecessor node (previous node). Compared to a singly linked list, a doubly linked list is more flexible and can traverse the linked list in both directions, but it also requires more memory space.
=== "Python"
```python title=""
class ListNode:
- """Bidirectional linked list node class"""
+ """Doubly linked list node class"""
def __init__(self, val: int):
self.val: int = val # Node value
self.next: ListNode | None = None # Reference to the successor node
- self.prev: ListNode | None = None # Reference to a predecessor node
+ self.prev: ListNode | None = None # Reference to the predecessor node
```
=== "C++"
```cpp title=""
- /* Bidirectional linked list node structure */
+ /* Doubly linked list node structure */
struct ListNode {
int val; // Node value
ListNode *next; // Pointer to the successor node
@@ -493,10 +545,10 @@ As shown in the figure below, there are three common types of linked lists.
=== "Java"
```java title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode {
int val; // Node value
- ListNode next; // Reference to the next node
+ ListNode next; // Reference to the successor node
ListNode prev; // Reference to the predecessor node
ListNode(int x) { val = x; } // Constructor
}
@@ -505,10 +557,10 @@ As shown in the figure below, there are three common types of linked lists.
=== "C#"
```csharp title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode(int x) { // Constructor
int val = x; // Node value
- ListNode next; // Reference to the next node
+ ListNode next; // Reference to the successor node
ListNode prev; // Reference to the predecessor node
}
```
@@ -516,14 +568,14 @@ As shown in the figure below, there are three common types of linked lists.
=== "Go"
```go title=""
- /* Bidirectional linked list node structure */
+ /* Doubly linked list node structure */
type DoublyListNode struct {
Val int // Node value
Next *DoublyListNode // Pointer to the successor node
Prev *DoublyListNode // Pointer to the predecessor node
}
- // NewDoublyListNode initialization
+ // NewDoublyListNode Initialization
func NewDoublyListNode(val int) *DoublyListNode {
return &DoublyListNode{
Val: val,
@@ -536,10 +588,10 @@ As shown in the figure below, there are three common types of linked lists.
=== "Swift"
```swift title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode {
var val: Int // Node value
- var next: ListNode? // Reference to the next node
+ var next: ListNode? // Reference to the successor node
var prev: ListNode? // Reference to the predecessor node
init(x: Int) { // Constructor
@@ -551,7 +603,7 @@ As shown in the figure below, there are three common types of linked lists.
=== "JS"
```javascript title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode {
constructor(val, next, prev) {
this.val = val === undefined ? 0 : val; // Node value
@@ -564,7 +616,7 @@ As shown in the figure below, there are three common types of linked lists.
=== "TS"
```typescript title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode {
val: number;
next: ListNode | null;
@@ -580,11 +632,11 @@ As shown in the figure below, there are three common types of linked lists.
=== "Dart"
```dart title=""
- /* Bidirectional linked list node class */
+ /* Doubly linked list node class */
class ListNode {
int val; // Node value
- ListNode next; // Reference to the next node
- ListNode prev; // Reference to the predecessor node
+ ListNode? next; // Reference to the successor node
+ ListNode? prev; // Reference to the predecessor node
ListNode(this.val, [this.next, this.prev]); // Constructor
}
```
@@ -595,15 +647,15 @@ As shown in the figure below, there are three common types of linked lists.
use std::rc::Rc;
use std::cell::RefCell;
- /* Bidirectional linked list node type */
+ /* Doubly linked list node type */
#[derive(Debug)]
struct ListNode {
val: i32, // Node value
- next: Option>>, // Pointer to successor node
- prev: Option>>, // Pointer to predecessor node
+ next: Option>>, // Pointer to the successor node
+ prev: Option>>, // Pointer to the predecessor node
}
- /* Constructors */
+ /* Constructor */
impl ListNode {
fn new(val: i32) -> Self {
ListNode {
@@ -618,16 +670,16 @@ As shown in the figure below, there are three common types of linked lists.
=== "C"
```c title=""
- /* Bidirectional linked list node structure */
+ /* Doubly linked list node structure */
typedef struct ListNode {
int val; // Node value
struct ListNode *next; // Pointer to the successor node
struct ListNode *prev; // Pointer to the predecessor node
} ListNode;
- /* Constructors */
+ /* Constructor */
ListNode *newListNode(int val) {
- ListNode *node, *next;
+ ListNode *node;
node = (ListNode *) malloc(sizeof(ListNode));
node->val = val;
node->next = NULL;
@@ -639,13 +691,36 @@ As shown in the figure below, there are three common types of linked lists.
=== "Kotlin"
```kotlin title=""
+ /* Doubly linked list node class */
+ // Constructor
+ class ListNode(x: Int) {
+ val _val: Int = x // Node value
+ val next: ListNode? = null // Reference to the successor node
+ val prev: ListNode? = null // Reference to the predecessor node
+ }
+ ```
+=== "Ruby"
+
+ ```ruby title=""
+ # Doubly linked list node class
+ class ListNode
+ attr_accessor :val # Node value
+ attr_accessor :next # Reference to the successor node
+ attr_accessor :prev # Reference to the predecessor node
+
+ def initialize(val=0, next_node=nil, prev_node=nil)
+ @val = val
+ @next = next_node
+ @prev = prev_node
+ end
+ end
```
=== "Zig"
```zig title=""
- // Bidirectional linked list node class
+ // Doubly linked list node class
pub fn ListNode(comptime T: type) type {
return struct {
const Self = @This();
@@ -666,21 +741,21 @@ As shown in the figure below, there are three common types of linked lists.

-## Typical applications of linked lists
+## Typical Applications of Linked Lists
-Singly linked lists are frequently utilized in implementing stacks, queues, hash tables, and graphs.
+Singly linked lists are commonly used to implement stacks, queues, hash tables, and graphs.
-- **Stacks and queues**: In singly linked lists, if insertions and deletions occur at the same end, it behaves like a stack (last-in-first-out). Conversely, if insertions are at one end and deletions at the other, it functions like a queue (first-in-first-out).
-- **Hash tables**: Linked lists are used in chaining, a popular method for resolving hash collisions. Here, all collided elements are grouped into a linked list.
-- **Graphs**: Adjacency lists, a standard method for graph representation, associate each graph vertex with a linked list. This list contains elements that represent vertices connected to the corresponding vertex.
+- **Stacks and queues**: When insertion and deletion operations both occur at one end of the linked list, it exhibits last-in-first-out characteristics, corresponding to a stack. When insertion operations occur at one end of the linked list and deletion operations occur at the other end, it exhibits first-in-first-out characteristics, corresponding to a queue.
+- **Hash tables**: Separate chaining is one of the mainstream solutions for resolving hash collisions. In this approach, all colliding elements are placed in a linked list.
+- **Graphs**: An adjacency list is a common way to represent a graph, where each vertex in the graph is associated with a linked list, and each element in the linked list represents another vertex connected to that vertex.
-Doubly linked lists are ideal for scenarios requiring rapid access to preceding and succeeding elements.
+Doubly linked lists are commonly used in scenarios where quick access to the previous and next elements is needed.
-- **Advanced data structures**: In structures like red-black trees and B-trees, accessing a node's parent is essential. This is achieved by incorporating a reference to the parent node in each node, akin to a doubly linked list.
-- **Browser history**: In web browsers, doubly linked lists facilitate navigating the history of visited pages when users click forward or back.
-- **LRU algorithm**: Doubly linked lists are apt for Least Recently Used (LRU) cache eviction algorithms, enabling swift identification of the least recently used data and facilitating fast node addition and removal.
+- **Advanced data structures**: For example, in red-black trees and B-trees, we need to access the parent node of a node, which can be achieved by saving a reference to the parent node in the node, similar to a doubly linked list.
+- **Browser history**: In web browsers, when a user clicks the forward or backward button, the browser needs to know the previous and next web pages the user visited. The characteristics of doubly linked lists make this operation simple.
+- **LRU algorithm**: In cache eviction (LRU) algorithms, we need to quickly find the least recently used data and support quick addition and deletion of nodes. Using a doubly linked list is very suitable for this.
-Circular linked lists are ideal for applications that require periodic operations, such as resource scheduling in operating systems.
+Circular linked lists are commonly used in scenarios that require periodic operations, such as operating system resource scheduling.
-- **Round-robin scheduling algorithm**: In operating systems, the round-robin scheduling algorithm is a common CPU scheduling method, requiring cycling through a group of processes. Each process is assigned a time slice, and upon expiration, the CPU rotates to the next process. This cyclical operation can be efficiently realized using a circular linked list, allowing for a fair and time-shared system among all processes.
-- **Data buffers**: Circular linked lists are also used in data buffers, like in audio and video players, where the data stream is divided into multiple buffer blocks arranged in a circular fashion for seamless playback.
+- **Round-robin scheduling algorithm**: In operating systems, round-robin scheduling is a common CPU scheduling algorithm that needs to cycle through a set of processes. Each process is assigned a time slice, and when the time slice expires, the CPU switches to the next process. This cyclic operation can be implemented using a circular linked list.
+- **Data buffers**: In some data buffer implementations, circular linked lists may also be used. For example, in audio and video players, the data stream may be divided into multiple buffer blocks and placed in a circular linked list to achieve seamless playback.
diff --git a/en/docs/chapter_array_and_linkedlist/list.md b/en/docs/chapter_array_and_linkedlist/list.md
index 6ab106876..7fbf5ab8a 100755
--- a/en/docs/chapter_array_and_linkedlist/list.md
+++ b/en/docs/chapter_array_and_linkedlist/list.md
@@ -1,26 +1,26 @@
# List
-A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, addition, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays.
+A list is an abstract data structure concept that represents an ordered collection of elements, supporting operations such as element access, modification, insertion, deletion, and traversal, without requiring users to consider capacity limitations. Lists can be implemented based on linked lists or arrays.
-- A linked list inherently serves as a list, supporting operations for adding, deleting, searching, and modifying elements, with the flexibility to dynamically adjust its size.
-- Arrays also support these operations, but due to their immutable length, they can be considered as a list with a length limit.
+- A linked list can naturally be viewed as a list, supporting element insertion, deletion, search, and modification operations, and can flexibly expand dynamically.
+- An array also supports element insertion, deletion, search, and modification, but since its length is immutable, it can only be viewed as a list with length limitations.
-When implementing lists using arrays, **the immutability of length reduces the practicality of the list**. This is because predicting the amount of data to be stored in advance is often challenging, making it difficult to choose an appropriate list length. If the length is too small, it may not meet the requirements; if too large, it may waste memory space.
+When implementing lists using arrays, **the immutable length property reduces the practicality of the list**. This is because we usually cannot determine in advance how much data we need to store, making it difficult to choose an appropriate list length. If the length is too small, it may fail to meet usage requirements; if the length is too large, it will waste memory space.
-To solve this problem, we can implement lists using a dynamic array. It inherits the advantages of arrays and can dynamically expand during program execution.
+To solve this problem, we can use a dynamic array to implement a list. It inherits all the advantages of arrays and can dynamically expand during program execution.
-In fact, **many programming languages' standard libraries implement lists using dynamic arrays**, such as Python's `list`, Java's `ArrayList`, C++'s `vector`, and C#'s `List`. In the following discussion, we will consider "list" and "dynamic array" as synonymous concepts.
+In fact, **the lists provided in the standard libraries of many programming languages are implemented based on dynamic arrays**, such as `list` in Python, `ArrayList` in Java, `vector` in C++, and `List` in C#. In the following discussion, we will treat "list" and "dynamic array" as equivalent concepts.
-## Common list operations
+## Common List Operations
-### Initializing a list
+### Initialize a List
-We typically use two initialization methods: "without initial values" and "with initial values".
+We typically use two initialization methods: "without initial values" and "with initial values":
=== "Python"
```python title="list.py"
- # Initialize list
+ # Initialize a list
# Without initial values
nums1: list[int] = []
# With initial values
@@ -30,8 +30,8 @@ We typically use two initialization methods: "without initial values" and "with
=== "C++"
```cpp title="list.cpp"
- /* Initialize list */
- // Note, in C++ the vector is the equivalent of nums described here
+ /* Initialize a list */
+ // Note that vector in C++ is equivalent to nums as described in this article
// Without initial values
vector nums1;
// With initial values
@@ -41,10 +41,10 @@ We typically use two initialization methods: "without initial values" and "with
=== "Java"
```java title="list.java"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
List nums1 = new ArrayList<>();
- // With initial values (note the element type should be the wrapper class Integer[] for int[])
+ // With initial values (note that array elements should use the wrapper class Integer[] instead of int[])
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
List nums = new ArrayList<>(Arrays.asList(numbers));
```
@@ -52,7 +52,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "C#"
```csharp title="list.cs"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
List nums1 = [];
// With initial values
@@ -63,7 +63,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "Go"
```go title="list_test.go"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
nums1 := []int{}
// With initial values
@@ -73,7 +73,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "Swift"
```swift title="list.swift"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
let nums1: [Int] = []
// With initial values
@@ -83,7 +83,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "JS"
```javascript title="list.js"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
const nums1 = [];
// With initial values
@@ -93,7 +93,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "TS"
```typescript title="list.ts"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
const nums1: number[] = [];
// With initial values
@@ -103,7 +103,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "Dart"
```dart title="list.dart"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
List nums1 = [];
// With initial values
@@ -113,7 +113,7 @@ We typically use two initialization methods: "without initial values" and "with
=== "Rust"
```rust title="list.rs"
- /* Initialize list */
+ /* Initialize a list */
// Without initial values
let nums1: Vec = Vec::new();
// With initial values
@@ -129,119 +129,138 @@ We typically use two initialization methods: "without initial values" and "with
=== "Kotlin"
```kotlin title="list.kt"
+ /* Initialize a list */
+ // Without initial values
+ var nums1 = listOf()
+ // With initial values
+ var numbers = arrayOf(1, 3, 2, 5, 4)
+ var nums = numbers.toMutableList()
+ ```
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Initialize a list
+ # Without initial values
+ nums1 = []
+ # With initial values
+ nums = [1, 3, 2, 5, 4]
```
=== "Zig"
```zig title="list.zig"
- // Initialize list
+ // Initialize a list
var nums = std.ArrayList(i32).init(std.heap.page_allocator);
defer nums.deinit();
try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 });
```
-### Accessing elements
+??? pythontutor "Visualize Execution"
-Lists are essentially arrays, thus they can access and update elements in $O(1)$ time, which is very efficient.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Access Elements
+
+Since a list is essentially an array, we can access and update elements in $O(1)$ time complexity, which is very efficient.
=== "Python"
```python title="list.py"
- # Access elements
- num: int = nums[1] # Access the element at index 1
+ # Access an element
+ num: int = nums[1] # Access element at index 1
- # Update elements
- nums[1] = 0 # Update the element at index 1 to 0
+ # Update an element
+ nums[1] = 0 # Update element at index 1 to 0
```
=== "C++"
```cpp title="list.cpp"
- /* Access elements */
- int num = nums[1]; // Access the element at index 1
+ /* Access an element */
+ int num = nums[1]; // Access element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "Java"
```java title="list.java"
- /* Access elements */
- int num = nums.get(1); // Access the element at index 1
+ /* Access an element */
+ int num = nums.get(1); // Access element at index 1
- /* Update elements */
- nums.set(1, 0); // Update the element at index 1 to 0
+ /* Update an element */
+ nums.set(1, 0); // Update element at index 1 to 0
```
=== "C#"
```csharp title="list.cs"
- /* Access elements */
- int num = nums[1]; // Access the element at index 1
+ /* Access an element */
+ int num = nums[1]; // Access element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "Go"
```go title="list_test.go"
- /* Access elements */
- num := nums[1] // Access the element at index 1
+ /* Access an element */
+ num := nums[1] // Access element at index 1
- /* Update elements */
- nums[1] = 0 // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0 // Update element at index 1 to 0
```
=== "Swift"
```swift title="list.swift"
- /* Access elements */
- let num = nums[1] // Access the element at index 1
+ /* Access an element */
+ let num = nums[1] // Access element at index 1
- /* Update elements */
- nums[1] = 0 // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0 // Update element at index 1 to 0
```
=== "JS"
```javascript title="list.js"
- /* Access elements */
- const num = nums[1]; // Access the element at index 1
+ /* Access an element */
+ const num = nums[1]; // Access element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "TS"
```typescript title="list.ts"
- /* Access elements */
- const num: number = nums[1]; // Access the element at index 1
+ /* Access an element */
+ const num: number = nums[1]; // Access element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "Dart"
```dart title="list.dart"
- /* Access elements */
- int num = nums[1]; // Access the element at index 1
+ /* Access an element */
+ int num = nums[1]; // Access element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "Rust"
```rust title="list.rs"
- /* Access elements */
- let num: i32 = nums[1]; // Access the element at index 1
- /* Update elements */
- nums[1] = 0; // Update the element at index 1 to 0
+ /* Access an element */
+ let num: i32 = nums[1]; // Access element at index 1
+ /* Update an element */
+ nums[1] = 0; // Update element at index 1 to 0
```
=== "C"
@@ -253,221 +272,237 @@ Lists are essentially arrays, thus they can access and update elements in $O(1)$
=== "Kotlin"
```kotlin title="list.kt"
+ /* Access an element */
+ val num = nums[1] // Access element at index 1
+ /* Update an element */
+ nums[1] = 0 // Update element at index 1 to 0
+ ```
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Access an element
+ num = nums[1] # Access element at index 1
+ # Update an element
+ nums[1] = 0 # Update element at index 1 to 0
```
=== "Zig"
```zig title="list.zig"
- // Access elements
- var num = nums.items[1]; // Access the element at index 1
+ // Access an element
+ var num = nums.items[1]; // Access element at index 1
- // Update elements
- nums.items[1] = 0; // Update the element at index 1 to 0
+ // Update an element
+ nums.items[1] = 0; // Update element at index 1 to 0
```
-### Inserting and removing elements
+??? pythontutor "Visualize Execution"
-Compared to arrays, lists offer more flexibility in adding and removing elements. While adding elements to the end of a list is an $O(1)$ operation, the efficiency of inserting and removing elements elsewhere in the list remains the same as in arrays, with a time complexity of $O(n)$.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Insert and Delete Elements
+
+Compared to arrays, lists can freely add and delete elements. Adding an element at the end of a list has a time complexity of $O(1)$, but inserting and deleting elements still have the same efficiency as arrays, with a time complexity of $O(n)$.
=== "Python"
```python title="list.py"
- # Clear list
+ # Clear the list
nums.clear()
- # Append elements at the end
+ # Add elements at the end
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
- # Insert element in the middle
+ # Insert an element in the middle
nums.insert(3, 6) # Insert number 6 at index 3
- # Remove elements
- nums.pop(3) # Remove the element at index 3
+ # Delete an element
+ nums.pop(3) # Delete element at index 3
```
=== "C++"
```cpp title="list.cpp"
- /* Clear list */
+ /* Clear the list */
nums.clear();
- /* Append elements at the end */
+ /* Add elements at the end */
nums.push_back(1);
nums.push_back(3);
nums.push_back(2);
nums.push_back(5);
nums.push_back(4);
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums.insert(nums.begin() + 3, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.erase(nums.begin() + 3); // Remove the element at index 3
+ /* Delete an element */
+ nums.erase(nums.begin() + 3); // Delete element at index 3
```
=== "Java"
```java title="list.java"
- /* Clear list */
+ /* Clear the list */
nums.clear();
- /* Append elements at the end */
+ /* Add elements at the end */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums.add(3, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.remove(3); // Remove the element at index 3
+ /* Delete an element */
+ nums.remove(3); // Delete element at index 3
```
=== "C#"
```csharp title="list.cs"
- /* Clear list */
+ /* Clear the list */
nums.Clear();
- /* Append elements at the end */
+ /* Add elements at the end */
nums.Add(1);
nums.Add(3);
nums.Add(2);
nums.Add(5);
nums.Add(4);
- /* Insert element in the middle */
- nums.Insert(3, 6);
+ /* Insert an element in the middle */
+ nums.Insert(3, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.RemoveAt(3);
+ /* Delete an element */
+ nums.RemoveAt(3); // Delete element at index 3
```
=== "Go"
```go title="list_test.go"
- /* Clear list */
+ /* Clear the list */
nums = nil
- /* Append elements at the end */
+ /* Add elements at the end */
nums = append(nums, 1)
nums = append(nums, 3)
nums = append(nums, 2)
nums = append(nums, 5)
nums = append(nums, 4)
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert number 6 at index 3
- /* Remove elements */
- nums = append(nums[:3], nums[4:]...) // Remove the element at index 3
+ /* Delete an element */
+ nums = append(nums[:3], nums[4:]...) // Delete element at index 3
```
=== "Swift"
```swift title="list.swift"
- /* Clear list */
+ /* Clear the list */
nums.removeAll()
- /* Append elements at the end */
+ /* Add elements at the end */
nums.append(1)
nums.append(3)
nums.append(2)
nums.append(5)
nums.append(4)
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums.insert(6, at: 3) // Insert number 6 at index 3
- /* Remove elements */
- nums.remove(at: 3) // Remove the element at index 3
+ /* Delete an element */
+ nums.remove(at: 3) // Delete element at index 3
```
=== "JS"
```javascript title="list.js"
- /* Clear list */
+ /* Clear the list */
nums.length = 0;
- /* Append elements at the end */
+ /* Add elements at the end */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
- /* Insert element in the middle */
- nums.splice(3, 0, 6);
+ /* Insert an element in the middle */
+ nums.splice(3, 0, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.splice(3, 1);
+ /* Delete an element */
+ nums.splice(3, 1); // Delete element at index 3
```
=== "TS"
```typescript title="list.ts"
- /* Clear list */
+ /* Clear the list */
nums.length = 0;
- /* Append elements at the end */
+ /* Add elements at the end */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
- /* Insert element in the middle */
- nums.splice(3, 0, 6);
+ /* Insert an element in the middle */
+ nums.splice(3, 0, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.splice(3, 1);
+ /* Delete an element */
+ nums.splice(3, 1); // Delete element at index 3
```
=== "Dart"
```dart title="list.dart"
- /* Clear list */
+ /* Clear the list */
nums.clear();
- /* Append elements at the end */
+ /* Add elements at the end */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums.insert(3, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.removeAt(3); // Remove the element at index 3
+ /* Delete an element */
+ nums.removeAt(3); // Delete element at index 3
```
=== "Rust"
```rust title="list.rs"
- /* Clear list */
+ /* Clear the list */
nums.clear();
- /* Append elements at the end */
+ /* Add elements at the end */
nums.push(1);
nums.push(3);
nums.push(2);
nums.push(5);
nums.push(4);
- /* Insert element in the middle */
+ /* Insert an element in the middle */
nums.insert(3, 6); // Insert number 6 at index 3
- /* Remove elements */
- nums.remove(3); // Remove the element at index 3
+ /* Delete an element */
+ nums.remove(3); // Delete element at index 3
```
=== "C"
@@ -479,42 +514,80 @@ Compared to arrays, lists offer more flexibility in adding and removing elements
=== "Kotlin"
```kotlin title="list.kt"
+ /* Clear the list */
+ nums.clear();
+ /* Add elements at the end */
+ nums.add(1);
+ nums.add(3);
+ nums.add(2);
+ nums.add(5);
+ nums.add(4);
+
+ /* Insert an element in the middle */
+ nums.add(3, 6); // Insert number 6 at index 3
+
+ /* Delete an element */
+ nums.remove(3); // Delete element at index 3
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Clear the list
+ nums.clear
+
+ # Add elements at the end
+ nums << 1
+ nums << 3
+ nums << 2
+ nums << 5
+ nums << 4
+
+ # Insert an element in the middle
+ nums.insert(3, 6) # Insert number 6 at index 3
+
+ # Delete an element
+ nums.delete_at(3) # Delete element at index 3
```
=== "Zig"
```zig title="list.zig"
- // Clear list
+ // Clear the list
nums.clearRetainingCapacity();
- // Append elements at the end
+ // Add elements at the end
try nums.append(1);
try nums.append(3);
try nums.append(2);
try nums.append(5);
try nums.append(4);
- // Insert element in the middle
+ // Insert an element in the middle
try nums.insert(3, 6); // Insert number 6 at index 3
- // Remove elements
- _ = nums.orderedRemove(3); // Remove the element at index 3
+ // Delete an element
+ _ = nums.orderedRemove(3); // Delete element at index 3
```
-### Iterating the list
+??? pythontutor "Visualize Execution"
-Similar to arrays, lists can be iterated either by using indices or by directly iterating through each element.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Traverse a List
+
+Like arrays, lists can be traversed by index or by directly iterating through elements.
=== "Python"
```python title="list.py"
- # Iterate through the list by index
+ # Traverse the list by index
count = 0
for i in range(len(nums)):
count += nums[i]
- # Iterate directly through list elements
+ # Traverse list elements directly
for num in nums:
count += num
```
@@ -522,13 +595,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "C++"
```cpp title="list.cpp"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0;
for (int num : nums) {
count += num;
@@ -538,13 +611,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Java"
```java title="list.java"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums.get(i);
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
for (int num : nums) {
count += num;
}
@@ -553,13 +626,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "C#"
```csharp title="list.cs"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
int count = 0;
for (int i = 0; i < nums.Count; i++) {
count += nums[i];
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0;
foreach (int num in nums) {
count += num;
@@ -569,13 +642,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Go"
```go title="list_test.go"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
count := 0
for i := 0; i < len(nums); i++ {
count += nums[i]
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0
for _, num := range nums {
count += num
@@ -585,13 +658,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Swift"
```swift title="list.swift"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
var count = 0
for i in nums.indices {
count += nums[i]
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0
for num in nums {
count += num
@@ -601,13 +674,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "JS"
```javascript title="list.js"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
let count = 0;
for (let i = 0; i < nums.length; i++) {
count += nums[i];
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0;
for (const num of nums) {
count += num;
@@ -617,13 +690,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "TS"
```typescript title="list.ts"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
let count = 0;
for (let i = 0; i < nums.length; i++) {
count += nums[i];
}
- /* Iterate directly through list elements */
+ /* Traverse list elements directly */
count = 0;
for (const num of nums) {
count += num;
@@ -633,13 +706,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Dart"
```dart title="list.dart"
- /* Iterate through the list by index */
+ /* Traverse the list by index */
int count = 0;
for (var i = 0; i < nums.length; i++) {
count += nums[i];
}
-
- /* Iterate directly through list elements */
+
+ /* Traverse list elements directly */
count = 0;
for (var num in nums) {
count += num;
@@ -649,13 +722,13 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Rust"
```rust title="list.rs"
- // Iterate through the list by index
+ // Traverse the list by index
let mut _count = 0;
for i in 0..nums.len() {
_count += nums[i];
}
- // Iterate directly through list elements
+ // Traverse list elements directly
_count = 0;
for num in &nums {
_count += num;
@@ -671,36 +744,65 @@ Similar to arrays, lists can be iterated either by using indices or by directly
=== "Kotlin"
```kotlin title="list.kt"
+ /* Traverse the list by index */
+ var count = 0
+ for (i in nums.indices) {
+ count += nums[i]
+ }
+ /* Traverse list elements directly */
+ for (num in nums) {
+ count += num
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Traverse the list by index
+ count = 0
+ for i in 0...nums.length
+ count += nums[i]
+ end
+
+ # Traverse list elements directly
+ count = 0
+ for num in nums
+ count += num
+ end
```
=== "Zig"
```zig title="list.zig"
- // Iterate through the list by index
+ // Traverse the list by index
var count: i32 = 0;
var i: i32 = 0;
while (i < nums.items.len) : (i += 1) {
count += nums[i];
}
- // Iterate directly through list elements
+ // Traverse list elements directly
count = 0;
for (nums.items) |num| {
count += num;
}
```
-### Concatenating lists
+??? pythontutor "Visualize Execution"
-Given a new list `nums1`, we can append it to the end of the original list.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Concatenate Lists
+
+Given a new list `nums1`, we can concatenate it to the end of the original list.
=== "Python"
```python title="list.py"
# Concatenate two lists
nums1: list[int] = [6, 8, 7, 10, 9]
- nums += nums1 # Concatenate nums1 to the end of nums
+ nums += nums1 # Concatenate list nums1 to the end of nums
```
=== "C++"
@@ -708,7 +810,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```cpp title="list.cpp"
/* Concatenate two lists */
vector nums1 = { 6, 8, 7, 10, 9 };
- // Concatenate nums1 to the end of nums
+ // Concatenate list nums1 to the end of nums
nums.insert(nums.end(), nums1.begin(), nums1.end());
```
@@ -717,7 +819,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```java title="list.java"
/* Concatenate two lists */
List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));
- nums.addAll(nums1); // Concatenate nums1 to the end of nums
+ nums.addAll(nums1); // Concatenate list nums1 to the end of nums
```
=== "C#"
@@ -725,7 +827,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```csharp title="list.cs"
/* Concatenate two lists */
List nums1 = [6, 8, 7, 10, 9];
- nums.AddRange(nums1); // Concatenate nums1 to the end of nums
+ nums.AddRange(nums1); // Concatenate list nums1 to the end of nums
```
=== "Go"
@@ -733,7 +835,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```go title="list_test.go"
/* Concatenate two lists */
nums1 := []int{6, 8, 7, 10, 9}
- nums = append(nums, nums1...) // Concatenate nums1 to the end of nums
+ nums = append(nums, nums1...) // Concatenate list nums1 to the end of nums
```
=== "Swift"
@@ -741,7 +843,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```swift title="list.swift"
/* Concatenate two lists */
let nums1 = [6, 8, 7, 10, 9]
- nums.append(contentsOf: nums1) // Concatenate nums1 to the end of nums
+ nums.append(contentsOf: nums1) // Concatenate list nums1 to the end of nums
```
=== "JS"
@@ -749,7 +851,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```javascript title="list.js"
/* Concatenate two lists */
const nums1 = [6, 8, 7, 10, 9];
- nums.push(...nums1); // Concatenate nums1 to the end of nums
+ nums.push(...nums1); // Concatenate list nums1 to the end of nums
```
=== "TS"
@@ -757,7 +859,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```typescript title="list.ts"
/* Concatenate two lists */
const nums1: number[] = [6, 8, 7, 10, 9];
- nums.push(...nums1); // Concatenate nums1 to the end of nums
+ nums.push(...nums1); // Concatenate list nums1 to the end of nums
```
=== "Dart"
@@ -765,7 +867,7 @@ Given a new list `nums1`, we can append it to the end of the original list.
```dart title="list.dart"
/* Concatenate two lists */
List nums1 = [6, 8, 7, 10, 9];
- nums.addAll(nums1); // Concatenate nums1 to the end of nums
+ nums.addAll(nums1); // Concatenate list nums1 to the end of nums
```
=== "Rust"
@@ -785,7 +887,17 @@ Given a new list `nums1`, we can append it to the end of the original list.
=== "Kotlin"
```kotlin title="list.kt"
+ /* Concatenate two lists */
+ val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()
+ nums.addAll(nums1) // Concatenate list nums1 to the end of nums
+ ```
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Concatenate two lists
+ nums1 = [6, 8, 7, 10, 9]
+ nums += nums1
```
=== "Zig"
@@ -795,81 +907,85 @@ Given a new list `nums1`, we can append it to the end of the original list.
var nums1 = std.ArrayList(i32).init(std.heap.page_allocator);
defer nums1.deinit();
try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 });
- try nums.insertSlice(nums.items.len, nums1.items); // Concatenate nums1 to the end of nums
+ try nums.insertSlice(nums.items.len, nums1.items); // Concatenate list nums1 to the end of nums
```
-### Sorting the list
+??? pythontutor "Visualize Execution"
-Once the list is sorted, we can employ algorithms commonly used in array-related algorithm problems, such as "binary search" and "two-pointer" algorithms.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+
+### Sort a List
+
+After sorting a list, we can use "binary search" and "two-pointer" algorithms, which are frequently tested in array algorithm problems.
=== "Python"
```python title="list.py"
- # Sort the list
- nums.sort() # After sorting, the list elements are in ascending order
+ # Sort a list
+ nums.sort() # After sorting, list elements are arranged from smallest to largest
```
=== "C++"
```cpp title="list.cpp"
- /* Sort the list */
- sort(nums.begin(), nums.end()); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ sort(nums.begin(), nums.end()); // After sorting, list elements are arranged from smallest to largest
```
=== "Java"
```java title="list.java"
- /* Sort the list */
- Collections.sort(nums); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ Collections.sort(nums); // After sorting, list elements are arranged from smallest to largest
```
=== "C#"
```csharp title="list.cs"
- /* Sort the list */
- nums.Sort(); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.Sort(); // After sorting, list elements are arranged from smallest to largest
```
=== "Go"
```go title="list_test.go"
- /* Sort the list */
- sort.Ints(nums) // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ sort.Ints(nums) // After sorting, list elements are arranged from smallest to largest
```
=== "Swift"
```swift title="list.swift"
- /* Sort the list */
- nums.sort() // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.sort() // After sorting, list elements are arranged from smallest to largest
```
=== "JS"
```javascript title="list.js"
- /* Sort the list */
- nums.sort((a, b) => a - b); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.sort((a, b) => a - b); // After sorting, list elements are arranged from smallest to largest
```
=== "TS"
```typescript title="list.ts"
- /* Sort the list */
- nums.sort((a, b) => a - b); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.sort((a, b) => a - b); // After sorting, list elements are arranged from smallest to largest
```
=== "Dart"
```dart title="list.dart"
- /* Sort the list */
- nums.sort(); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.sort(); // After sorting, list elements are arranged from smallest to largest
```
=== "Rust"
```rust title="list.rs"
- /* Sort the list */
- nums.sort(); // After sorting, the list elements are in ascending order
+ /* Sort a list */
+ nums.sort(); // After sorting, list elements are arranged from smallest to largest
```
=== "C"
@@ -881,25 +997,37 @@ Once the list is sorted, we can employ algorithms commonly used in array-related
=== "Kotlin"
```kotlin title="list.kt"
+ /* Sort a list */
+ nums.sort() // After sorting, list elements are arranged from smallest to largest
+ ```
+=== "Ruby"
+
+ ```ruby title="list.rb"
+ # Sort a list
+ nums = nums.sort { |a, b| a <=> b } # After sorting, list elements are arranged from smallest to largest
```
=== "Zig"
```zig title="list.zig"
- // Sort the list
+ // Sort a list
std.sort.sort(i32, nums.items, {}, comptime std.sort.asc(i32));
```
-## List implementation
+??? pythontutor "Visualize Execution"
-Many programming languages come with built-in lists, including Java, C++, Python, etc. Their implementations tend to be intricate, featuring carefully considered settings for various parameters, like initial capacity and expansion factors. Readers who are curious can delve into the source code for further learning.
+ https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-To enhance our understanding of how lists work, we will attempt to implement a simplified version of a list, focusing on three crucial design aspects:
+## List Implementation
-- **Initial capacity**: Choose a reasonable initial capacity for the array. In this example, we choose 10 as the initial capacity.
-- **Size recording**: Declare a variable `size` to record the current number of elements in the list, updating in real-time with element insertion and deletion. With this variable, we can locate the end of the list and determine whether expansion is needed.
-- **Expansion mechanism**: If the list reaches full capacity upon an element insertion, an expansion process is required. This involves creating a larger array based on the expansion factor, and then transferring all elements from the current array to the new one. In this example, we stipulate that the array size should double with each expansion.
+Many programming languages have built-in lists, such as Java, C++, and Python. Their implementations are quite complex, and the parameters are carefully considered, such as initial capacity, expansion multiples, and so on. Interested readers can consult the source code to learn more.
+
+To deepen our understanding of how lists work, we attempt to implement a simple list with three key design considerations:
+
+- **Initial capacity**: Select a reasonable initial capacity for the underlying array. In this example, we choose 10 as the initial capacity.
+- **Size tracking**: Declare a variable `size` to record the current number of elements in the list and update it in real-time as elements are inserted and deleted. Based on this variable, we can locate the end of the list and determine whether expansion is needed.
+- **Expansion mechanism**: When the list capacity is full upon inserting an element, we need to expand. We create a larger array based on the expansion multiple and then move all elements from the current array to the new array in order. In this example, we specify that the array should be expanded to 2 times its previous size each time.
```src
[file]{my_list}-[class]{my_list}-[func]{}
diff --git a/en/docs/chapter_array_and_linkedlist/ram_and_cache.md b/en/docs/chapter_array_and_linkedlist/ram_and_cache.md
index 9922891a9..8462197ed 100644
--- a/en/docs/chapter_array_and_linkedlist/ram_and_cache.md
+++ b/en/docs/chapter_array_and_linkedlist/ram_and_cache.md
@@ -1,71 +1,71 @@
-# Memory and cache *
+# Random-Access Memory and Cache *
-In the first two sections of this chapter, we explored arrays and linked lists, two fundamental data structures that represent "continuous storage" and "dispersed storage," respectively.
+In the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures that represent "contiguous storage" and "distributed storage" as two physical structures, respectively.
-In fact, **the physical structure largely determines how efficiently a program utilizes memory and cache**, which in turn affects the overall performance of the algorithm.
+In fact, **physical structure largely determines the efficiency with which programs utilize memory and cache**, which in turn affects the overall performance of algorithmic programs.
-## Computer storage devices
+## 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 respective roles and performance characteristics in computer systems.
+Computers include three types of storage devices: hard disk, random-access memory (RAM), and cache memory. The following table shows their different roles and performance characteristics in a computer system.
- Table Computer storage devices
+ Table Computer Storage Devices
-| | 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 (USD) | Cheaper, a few cents / GB | More expensive, a few dollars / GB | Very expensive, priced with CPU |
+| | Hard Disk | RAM | Cache |
+| -------------- | ------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------- |
+| Purpose | Long-term storage of data, including operating systems, programs, and files | Temporary storage of currently running programs and data being processed | Storage of frequently accessed data and instructions to reduce CPU's accesses to memory |
+| Volatility | Data is not lost after power-off | Data is lost after power-off | Data is lost after power-off |
+| Capacity | Large, on the order of terabytes (TB) | Small, on the order of gigabytes (GB) | Very small, on the order of megabytes (MB) |
+| Speed | Slow, hundreds to thousands of MB/s | Fast, tens of GB/s | Very fast, tens to hundreds of GB/s |
+| Cost (USD/GB) | Inexpensive, fractions of a dollar to a few dollars per GB | Expensive, tens to hundreds of dollars per GB | Very expensive, priced as part of the CPU package |
-The computer storage system can be visualized as a pyramid, as shown in the figure below. The storage devices at the top of the pyramid are faster, have smaller capacities, and are more expensive. This multi-level design is not accidental, but a deliberate outcome of careful consideration by computer scientists and engineers.
+We can imagine the computer storage system as a pyramid structure as shown in the diagram below. Storage devices closer to the top of the pyramid are faster, have smaller capacity, and are more expensive. This multi-layered design is not by accident, but rather the result of careful consideration by computer scientists and engineers.
-- **Replacing hard disks with memory is challenging**. Firstly, data in memory is lost after power off, making it unsuitable for long-term data storage; secondly, memory is significantly more expensive than hard disks, limiting its feasibility for widespread use in the consumer market.
-- **Caches face a trade-off between large capacity and high speed**. As the capacity of L1, L2, and L3 caches increases, their physical size grows, increasing the distance from the CPU core. This results in longer data transfer times and higher access latency. With current technology, a multi-level cache structure provides the optimal balance between capacity, speed, and cost.
+- **Hard disk cannot be easily replaced by RAM**. First, data in memory is lost after power-off, making it unsuitable for long-term data storage. Second, memory is tens of times more expensive than hard disk, which makes it difficult to popularize in the consumer market.
+- **Cache cannot simultaneously achieve large capacity and high speed**. As the capacity of L1, L2, and L3 caches increases, their physical size becomes larger, and the physical distance between them and the CPU core increases, resulting in longer data transmission time and higher element access latency. With current technology, the multi-layered cache structure represents the best balance point between capacity, speed, and cost.
-
+
!!! tip
- The storage hierarchy in computers reflects a careful balance between speed, capacity, and cost. This type of trade-off is common across various industries, where finding the optimal balance between benefits and limitations is essential.
+ The storage hierarchy of computers embodies a delicate balance among speed, capacity, and cost. In fact, such trade-offs are common across all industrial fields, requiring us to find the optimal balance point between different advantages and constraints.
-Overall, **hard disks provide long-term storage for large volumes of data, memory serves as temporary storage for data being processed during program execution, and cache stores frequently accessed data and instructions to enhance execution efficiency**. Together, they ensure the efficient operation of computer systems.
+In summary, **hard disk is used for long-term storage of large amounts of data, RAM is used for temporary storage of data being processed during program execution, and cache is used for storage of frequently accessed data and instructions**, to improve program execution efficiency. The three work together to ensure efficient operation of the computer system.
-As shown in the figure below, during program execution, data is read from the hard disk into memory for CPU computation. The cache, acting as an extension of the CPU, **intelligently preloads data from memory**, enabling faster data access for the CPU. This greatly improves program execution efficiency while reducing reliance on slower memory.
+As shown in the diagram below, during program execution, data is read from the hard disk into RAM for CPU computation. Cache can be viewed as part of the CPU, **it intelligently loads data from RAM**, providing the CPU with high-speed data reading, thereby significantly improving program execution efficiency and reducing reliance on slower RAM.
-
+
-## Memory efficiency of data structures
+## Memory Efficiency of Data Structures
-In terms of memory space utilization, arrays and linked lists have their advantages and limitations.
+In terms of memory space utilization, arrays and linked lists each have advantages and limitations.
-On one hand, **memory is limited and cannot be shared by multiple programs**, so optimizing space usage in data structures is crucial. Arrays are space-efficient because their elements are tightly packed, without requiring extra memory for references (pointers) as in linked lists. However, arrays require pre-allocating a contiguous block of memory, which can lead to waste if the allocated space exceeds the actual need. Expanding an array also incurs additional time and space overhead. In contrast, linked lists allocate and free memory dynamically for each node, offering greater flexibility at the cost of additional memory for pointers.
+On one hand, **memory is limited, and the same memory cannot be shared by multiple programs**, so we hope data structures can utilize space as efficiently as possible. Array elements are tightly packed and do not require additional space to store references (pointers) between linked list nodes, thus having higher space efficiency. However, arrays need to allocate sufficient contiguous memory space at once, which may lead to memory waste, and array expansion requires additional time and space costs. In comparison, linked lists perform dynamic memory allocation and deallocation on a "node" basis, providing greater flexibility.
-On the other hand, during program execution, **repeated memory allocation and deallocation increase memory fragmentation**, reducing memory utilization efficiency. Arrays, due to their continuous storage method, are relatively less likely to cause memory fragmentation. In contrast, linked lists store elements in non-contiguous locations, and frequent insertions and deletions can exacerbate memory fragmentation.
+On the other hand, during program execution, **as memory is repeatedly allocated and freed, the degree of fragmentation of free memory becomes increasingly severe**, leading to reduced memory utilization efficiency. Arrays, due to their contiguous storage approach, are relatively less prone to memory fragmentation. Conversely, linked list elements are distributed in storage, and frequent insertion and deletion operations are more likely to cause memory fragmentation.
-## Cache efficiency of data structures
+## 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. Due to their limited capacity, caches can only store a subset of frequently accessed data. When the CPU attempts to access data not present in the cache, a cache miss occurs, requiring the CPU to retrieve the needed data from slower memory, which can impact performance.
+Although cache has much smaller space capacity than memory, it is much faster than memory and plays a crucial role in program execution speed. Since cache capacity is limited and can only store a small portion of frequently accessed data, when the CPU attempts to access data that is not in the cache, a cache miss occurs, and the CPU must load the required data from the 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.
+Clearly, **the fewer "cache misses," the higher the efficiency of CPU data reads and writes**, and the better the program performance. We call the proportion of data that the CPU successfully obtains from the cache the cache hit rate, a metric typically used to measure cache efficiency.
-To achieve higher efficiency, caches adopt the following data loading mechanisms.
+To achieve the highest efficiency possible, cache employs the following data loading mechanisms.
-- **Cache lines**: Caches operate by storing and loading data in units called cache lines, rather than individual bytes. This approach improves efficiency by transferring larger blocks of data at once.
-- **Prefetch mechanism**: Processors predict data access patterns (e.g., sequential or fixed-stride access) and preload data into the cache based on these patterns to increase the cache hit rate.
-- **Spatial locality**: When a specific piece of data is accessed, nearby data is likely to be accessed soon. To leverage this, caches load adjacent data along with the requested data, improving hit rates.
-- **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.
+- **Cache lines**: The cache does not store and load data on a byte-by-byte basis, but rather as cache lines. Compared to byte-by-byte transmission, cache line transmission is more efficient.
+- **Prefetching mechanism**: The processor attempts to predict data access patterns (e.g., sequential access, fixed-stride jumping access, etc.) and loads data into the cache according to specific patterns, thereby improving hit rate.
+- **Spatial locality**: If a piece of data is accessed, nearby data may also be accessed in the near future. Therefore, when the cache loads a particular piece of data, it also loads nearby data to improve hit rate.
+- **Temporal locality**: If a piece of data is accessed, it is likely to be accessed again in the near future. Cache leverages this principle by retaining recently accessed data to improve hit rate.
-In fact, **arrays and linked lists have different cache utilization efficiencies**, which is mainly reflected in the following aspects.
+In fact, **arrays and linked lists have different efficiencies in utilizing cache**, manifested in the following aspects.
-- **Occupied space**: Linked list elements take up more space than array elements, resulting in less effective data being held in the cache.
-- **Cache lines**: Linked list data is scattered throughout the memory, and cache is "loaded by row", so the proportion of invalid data loaded is higher.
-- **Prefetch mechanism**: The data access pattern of arrays is more "predictable" than that of linked lists, that is, it is easier for the system to guess the data that is about to be loaded.
-- **Spatial locality**: Arrays are stored in a continuous memory space, so data near the data being loaded is more likely to be accessed soon.
+- **Space occupied**: Linked list elements occupy more space than array elements, resulting in fewer effective data in the cache.
+- **Cache lines**: Linked list data are scattered throughout memory, while cache loads "by lines," so the proportion of invalid data loaded is higher.
+- **Prefetching mechanism**: Arrays have more "predictable" data access patterns than linked lists, making it easier for the system to guess which data will be loaded next.
+- **Spatial locality**: Arrays are stored in centralized memory space, so data near loaded data is more likely to be accessed soon.
-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.
+Overall, **arrays have higher cache hit rates, thus they usually outperform linked lists in operation efficiency**. This makes data structures implemented based on arrays more popular when solving algorithmic problems.
-It should be noted that **high cache efficiency does not mean that arrays are always better than linked lists**. The choice of data structure should depend on specific application 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.
+It is important to note that **high cache efficiency does not mean arrays are superior to linked lists in all cases**. In practical applications, which data structure to choose should be determined based on specific requirements. For example, both arrays and linked lists can implement the "stack" data structure (which will be discussed in detail 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 a better choice. Linked lists can distribute a large amount of data in different parts of the memory and avoid the additional overhead of array expansion.
+- When solving algorithm problems, we tend to prefer stack implementations based on arrays, because they provide higher operation efficiency and the ability of random access, at the cost of needing to pre-allocate a certain amount of memory space for the array.
+- If the data volume is very large, the dynamic nature is high, and the expected size of the stack is difficult to estimate, then a stack implementation based on linked lists is more suitable. Linked lists can distribute large amounts of data across different parts of memory and avoid the additional overhead produced by array expansion.
diff --git a/en/docs/chapter_array_and_linkedlist/summary.md b/en/docs/chapter_array_and_linkedlist/summary.md
index 73b021c7a..d856f7889 100644
--- a/en/docs/chapter_array_and_linkedlist/summary.md
+++ b/en/docs/chapter_array_and_linkedlist/summary.md
@@ -1,81 +1,86 @@
# Summary
-### Key review
+### Key Takeaways
-- 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.
+- Arrays and linked lists are two fundamental data structures, representing two different ways data can be stored in computer memory: contiguous memory storage and scattered memory storage. The characteristics of the two complement each other.
+- Arrays support random access and use less memory; however, inserting and deleting elements is inefficient, and the length is immutable after initialization.
+- Linked lists achieve efficient insertion and deletion of nodes by modifying references (pointers), and can flexibly adjust length; however, node access is inefficient and memory consumption is higher. Common linked list types include singly linked lists, circular linked lists, and doubly linked lists.
+- A list is an ordered collection of elements that supports insertion, deletion, search, and modification, typically implemented based on dynamic arrays. It retains the advantages of arrays while allowing flexible adjustment of length.
+- The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space.
+- During program execution, data is primarily stored in memory. Arrays provide higher memory space efficiency, while linked lists offer greater flexibility in memory usage.
+- Caches provide fast data access to the CPU through mechanisms such as cache lines, prefetching, and spatial and temporal locality, significantly improving program execution efficiency.
+- Because arrays have higher cache hit rates, they are generally more efficient than linked lists. When choosing a data structure, appropriate selection should be made based on specific requirements and scenarios.
### Q & A
-**Q**: Does storing arrays on the stack versus the heap affect time and space efficiency?
+**Q**: Does storing an array on the stack versus on the heap affect time efficiency 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.
+Arrays stored on the stack and on the heap are both stored in contiguous memory space, so data operation efficiency is basically the same. However, the stack and heap 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.
+1. Allocation and deallocation efficiency: The stack is a relatively small piece of memory, with allocation automatically handled by the compiler; the heap is relatively larger and can be dynamically allocated in code, more prone to fragmentation. Therefore, allocation and deallocation operations on the heap are usually slower than on the stack.
+2. Size limitations: Stack memory is relatively small, and the heap size is generally limited by available memory. Therefore, the heap is more suitable for storing large arrays.
+3. Flexibility: The size of an array on the stack must be determined at compile time, while the size of an array on the heap can be determined dynamically at runtime.
-**Q**: Why do arrays require elements of the same type, while linked lists do not emphasize same-type elements?
+**Q**: Why do arrays require elements of the same type, while linked lists do not emphasize this requirement?
-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.
+Linked lists are composed of nodes, with nodes connected through references (pointers), and each node can store different types of data, 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.
+In contrast, array elements must be of the same type, so that the corresponding element position can be obtained by calculating the offset. For example, if an array contains both `int` and `long` types, with individual elements occupying 4 bytes and 8 bytes respectively, then the following formula cannot be used to calculate the offset, because the array contains two different "element lengths".
```shell
-# Element memory address = array memory address + element length * element index
+# Element memory address = Array memory address (first element memory address) + Element length * Element index
```
-**Q**: After deleting a node, is it necessary to set `P.next` to `None`?
+**Q**: After deleting node `P`, do we need 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.
+It is not necessary to modify `P.next`. 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 removed from the linked list, and it doesn't matter where node `P` points to at this time—it won't affect the linked 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.
+From a data structures and algorithms perspective (problem-solving), not disconnecting the pointer doesn't matter as long as the program logic is correct. From the perspective of standard libraries, disconnecting is safer and the logic is clearer. If not disconnected, assuming the deleted node is not properly reclaimed, it may affect the memory reclamation of its successor nodes.
-**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)`?
+**Q**: In a linked list, the time complexity of insertion and deletion operations is $O(1)$. However, both insertion and deletion require $O(n)$ time to find the element; 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)`.
+If the element is first found and then deleted, the time complexity is indeed $O(n)$. However, the advantage of $O(1)$ insertion and deletion in linked lists can be demonstrated in other applications. For example, a deque is well-suited for linked list implementation, where we maintain pointer variables always pointing to the head and tail nodes, with each insertion and deletion operation being $O(1)$.
-**Q**: In the figure "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?
+**Q**: In the diagram "Linked List Definition and Storage Methods", does the light blue pointer node occupy a single memory address, or does it share equally with the node value?
-The figure is just a qualitative representation; quantitative analysis depends on specific situations.
+This diagram is a qualitative representation; a quantitative representation requires analysis based on the specific situation.
-- 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.
+- Different types of node values occupy different amounts of space, such as `int`, `long`, `double`, and instance objects, etc.
+- The amount of 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)`?
+**Q**: Is appending an element at 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)`.
+If appending an element exceeds the list length, the list must first be expanded before adding. The system allocates a new block of memory and moves all elements from the original list to it, 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?
+**Q**: "The emergence of lists has greatly improved the practicality of arrays, but may result in some wasted memory space"—does this space waste refer to the memory occupied by additional variables such as capacity, length, and expansion factor?
-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.
+This space waste mainly has two aspects: on one hand, lists typically set an initial length, which we may not need to fully utilize; on the other hand, to prevent frequent expansion, expansion generally multiplies by a coefficient, such as $\times 1.5$. As a result, there will be many empty positions that we typically cannot completely 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?
+**Q**: In Python, after initializing `n = [1, 2, 3]`, the addresses of these 3 elements are contiguous, but initializing `m = [2, 1, 3]` reveals that each element's id is not continuous; rather, they are the same as those in `n`. Since 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.
+If we replace list elements with linked list nodes `n = [n1, n2, n3, n4, n5]`, usually these 5 node objects are also scattered throughout memory. However, given a list index, we can still obtain the node memory address in $O(1)$ time, thereby accessing the corresponding node. This is because the array stores references to 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.
+Unlike many languages, numbers in Python are wrapped as objects, and lists store not the numbers themselves, but references to the numbers. Therefore, we find that the same numbers in two arrays have the same id, and the memory addresses of these numbers 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?
+**Q**: C++ STL has `std::list` which has already implemented a doubly linked list, but it seems that some algorithm books don't use it directly. Is there a limitation?
-On the one hand, we often prefer to use arrays to implement algorithms, only using linked lists when necessary, mainly for two reasons.
+On one hand, we often prefer to use arrays for implementing algorithms and only use 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.
+- Space overhead: Since each element requires two additional pointers (one for the previous element and one for the next element), `std::list` typically consumes more space than `std::vector`.
+- Cache unfriendliness: Since data is not stored contiguously, `std::list` has lower cache utilization. In general, `std::vector` has better performance.
-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.
+On the other hand, cases where linked lists are necessary mainly involve binary trees and graphs. Stacks and queues usually use the `stack` and `queue` provided by the programming language, rather than linked lists.
-**Q**: Does initializing a list `res = [0] * self.size()` result in each element of `res` referencing the same address?
+**Q**: Does the operation `res = [[0]] * n` create a 2D list where each `[0]` is independent?
-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.
+No, they are not independent. In this 2D list, all the `[0]` are actually references to the same object. If we modify one element, we will find that all corresponding elements change accordingly.
-**Q**: In deleting a node, is it necessary to break the reference to its successor node?
+If we want each `[0]` in the 2D list to be independent, we can use `res = [[0] for _ in range(n)]` to achieve this. The principle of this approach is to initialize $n$ independent `[0]` list objects.
-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.
+**Q**: Does the operation `res = [0] * n` create a list where each integer 0 is independent?
+
+In this list, all integer 0s are references to the same object. This is because Python uses a caching mechanism for small integers (typically -5 to 256) to maximize object reuse and improve performance.
+
+Although they point to the same object, we can still independently modify each element in the list. This is because Python integers are "immutable objects". When we modify an element, we are actually switching to a reference of another object, rather than changing the original object itself.
+
+However, when list elements are "mutable objects" (such as lists, dictionaries, or class instances), modifying an element directly changes the object itself, and all elements referencing that object will have the same change.
diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.md b/en/docs/chapter_backtracking/backtracking_algorithm.md
index 7f4bfbb5e..192375550 100644
--- a/en/docs/chapter_backtracking/backtracking_algorithm.md
+++ b/en/docs/chapter_backtracking/backtracking_algorithm.md
@@ -1,45 +1,45 @@
-# Backtracking algorithms
+# Backtracking Algorithm
-Backtracking algorithm is a method to solve problems by exhaustive search. Its core concept is to start from an initial state and brutally search for all possible solutions. The algorithm records the correct ones until a solution is found or all possible solutions have been tried but no solution can be found.
+The backtracking algorithm is a method for solving problems through exhaustive search. Its core idea is to start from an initial state and exhaustively search all possible solutions. When a correct solution is found, it is recorded. This process continues until a solution is found or all possible choices have been tried without finding a solution.
-Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary tree" chapter, we mentioned that pre-order, in-order, and post-order traversals are all depth-first searches. Next, we are going to use pre-order traversal to solve a backtracking problem. This helps us to understand how the algorithm works gradually.
+The backtracking algorithm typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that preorder, inorder, and postorder traversals all belong to depth-first search. Next, we will construct a backtracking problem using preorder traversal to progressively understand how the backtracking algorithm works.
-!!! question "Example One"
+!!! question "Example 1"
- Given a binary tree, search and record all nodes with a value of $7$ and return them in a list.
+ Given a binary tree, search and record all nodes with value $7$, and return a list of these nodes.
-To solve this problem, we traverse this tree in pre-order and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The process is shown in the figure below:
+For this problem, we perform a preorder traversal of the tree and check whether the current node's value is $7$. If it is, we add the node to the result list `res`. The relevant implementation is shown in the following figure and code:
```src
[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order}
```
-
+
-## Trial and retreat
+## Attempt and Backtrack
-**It is called a backtracking algorithm because it uses a "trial" and "retreat" strategy when searching the solution space**. During the search, whenever it encounters a state where it can no longer proceed to obtain a satisfying solution, it undoes the previous choice and reverts to the previous state so that other possible choices can be chosen for the next attempt.
+**The reason it is called a backtracking algorithm is that it employs "attempt" and "backtrack" strategies when searching the solution space**. When the algorithm encounters a state where it cannot continue forward or cannot find a solution that satisfies the constraints, it will undo the previous choice, return to a previous state, and try other possible choices.
-In Example One, visiting each node starts a "trial". And passing a leaf node or the `return` statement to going back to the parent node suggests "retreat".
+For Example 1, visiting each node represents an "attempt", while skipping over a leaf node or a function `return` from the parent node represents a "backtrack".
-It's worth noting that **retreat is not merely about function returns**. We'll expand slightly on Example One question to explain what it means.
+It is worth noting that **backtracking is not limited to function returns alone**. To illustrate this, let's extend Example 1 slightly.
-!!! question "Example Two"
+!!! question "Example 2"
- In a binary tree, search for all nodes with a value of $7$ and for all matching nodes, **please return the paths from the root node to that node**.
+ In a binary tree, search all nodes with value $7$, **and return the paths from the root node to these nodes**.
-Based on the code from Example One, we need to use a list called `path` to record the visited node paths. When a node with a value of $7$ is reached, we copy `path` and add it to the result list `res`. After the traversal, `res` holds all the solutions. The code is as shown:
+Based on the code from Example 1, we need to use a list `path` to record the visited node path. When we reach a node with value $7$, we copy `path` and add it to the result list `res`. After traversal is complete, `res` contains all the solutions. The code is as follows:
```src
[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order}
```
-In each "trial", we record the path by adding the current node to `path`. Whenever we need to "retreat", we pop the node from `path` **to restore the state prior to this failed attempt**.
+In each "attempt", we record the path by adding the current node to `path`; before "backtracking", we need to remove the node from `path`, **to restore the state before this attempt**.
-By observing the process shown in the figure below, **the trial is like "advancing", and retreat is like "undoing"**. The later pairs can be seen as a reverse operation to their counterpart.
+Observing the process shown in the following figure, **we can understand attempt and backtrack as "advance" and "undo"**, two operations that are the reverse of each other.
=== "<1>"
- 
+ 
=== "<2>"

@@ -71,49 +71,49 @@ By observing the process shown in the figure below, **the trial is like "advanci
=== "<11>"

-## Prune
+## Pruning
-Complex backtracking problems usually involve one or more constraints, **which are often used for "pruning"**.
+Complex backtracking problems usually contain one or more constraints. **Constraints can typically be used for "pruning"**.
-!!! question "Example Three"
+!!! question "Example 3"
- In a binary tree, search for all nodes with a value of $7$ and return the paths from the root to these nodes, **with restriction that the paths do not contain nodes with a value of $3$**.
+ In a binary tree, search all nodes with value $7$ and return the paths from the root node to these nodes, **but require that the paths do not contain nodes with value $3$**.
-To meet the above constraints, **we need to add a pruning operation**: during the search process, if a node with a value of $3$ is encountered, it aborts further searching down through the path immediately. The code is as shown:
+To satisfy the above constraints, **we need to add pruning operations**: during the search process, if we encounter a node with value $3$, we return early and do not continue searching. The code is as follows:
```src
[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order}
```
-"Pruning" is a very vivid noun. As shown in the figure below, in the search process, **we "cut off" the search branches that do not meet the constraints**. It avoids further unnecessary attempts, thus enhances the search efficiency.
+"Pruning" is a vivid term. As shown in the following figure, during the search process, **we "prune" search branches that do not satisfy the constraints**, avoiding many meaningless attempts and thus improving search efficiency.
-
+
-## Framework code
+## Framework Code
-Now, let's try to distill the main framework of "trial, retreat, and prune" from backtracking to enhance the code's universality.
+Next, we attempt to extract the main framework of backtracking's "attempt, backtrack, and pruning", to improve code generality.
-In the following framework code, `state` represents the current state of the problem, `choices` represents the choices available under the current state:
+In the following framework code, `state` represents the current state of the problem, and `choices` represents the choices available in the current state:
=== "Python"
```python title=""
def backtrack(state: State, choices: list[choice], res: list[state]):
"""Backtracking algorithm framework"""
- # Check if it's a solution
+ # Check if it is a solution
if is_solution(state):
# Record the solution
record_solution(state, res)
# Stop searching
return
- # Iterate through all choices
+ # Traverse all choices
for choice in choices:
- # Prune: check if the choice is valid
+ # Pruning: check if the choice is valid
if is_valid(state, choice):
- # Trial: make a choice, update the state
+ # Attempt: make a choice and update the state
make_choice(state, choice)
backtrack(state, choices, res)
- # Retreat: undo the choice, revert to the previous state
+ # Backtrack: undo the choice and restore to the previous state
undo_choice(state, choice)
```
@@ -122,21 +122,21 @@ In the following framework code, `state` represents the current state of the pro
```cpp title=""
/* Backtracking algorithm framework */
void backtrack(State *state, vector &choices, vector &res) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (Choice choice : choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice);
}
}
@@ -148,21 +148,21 @@ In the following framework code, `state` represents the current state of the pro
```java title=""
/* Backtracking algorithm framework */
void backtrack(State state, List choices, List res) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (Choice choice : choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice);
}
}
@@ -174,21 +174,21 @@ In the following framework code, `state` represents the current state of the pro
```csharp title=""
/* Backtracking algorithm framework */
void Backtrack(State state, List choices, List res) {
- // Check if it's a solution
+ // Check if it is a solution
if (IsSolution(state)) {
// Record the solution
RecordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
foreach (Choice choice in choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (IsValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
MakeChoice(state, choice);
Backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
UndoChoice(state, choice);
}
}
@@ -200,21 +200,21 @@ In the following framework code, `state` represents the current state of the pro
```go title=""
/* Backtracking algorithm framework */
func backtrack(state *State, choices []Choice, res *[]State) {
- // Check if it's a solution
+ // Check if it is a solution
if isSolution(state) {
// Record the solution
recordSolution(state, res)
// Stop searching
return
}
- // Iterate through all choices
+ // Traverse all choices
for _, choice := range choices {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if isValid(state, choice) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice)
backtrack(state, choices, res)
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice)
}
}
@@ -226,21 +226,21 @@ In the following framework code, `state` represents the current state of the pro
```swift title=""
/* Backtracking algorithm framework */
func backtrack(state: inout State, choices: [Choice], res: inout [State]) {
- // Check if it's a solution
+ // Check if it is a solution
if isSolution(state: state) {
// Record the solution
recordSolution(state: state, res: &res)
// Stop searching
return
}
- // Iterate through all choices
+ // Traverse all choices
for choice in choices {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if isValid(state: state, choice: choice) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state: &state, choice: choice)
backtrack(state: &state, choices: choices, res: &res)
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state: &state, choice: choice)
}
}
@@ -252,21 +252,21 @@ In the following framework code, `state` represents the current state of the pro
```javascript title=""
/* Backtracking algorithm framework */
function backtrack(state, choices, res) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (let choice of choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice);
}
}
@@ -278,21 +278,21 @@ In the following framework code, `state` represents the current state of the pro
```typescript title=""
/* Backtracking algorithm framework */
function backtrack(state: State, choices: Choice[], res: State[]): void {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (let choice of choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice);
}
}
@@ -304,21 +304,21 @@ In the following framework code, `state` represents the current state of the pro
```dart title=""
/* Backtracking algorithm framework */
void backtrack(State state, List, List res) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (Choice choice in choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice);
}
}
@@ -330,21 +330,21 @@ In the following framework code, `state` represents the current state of the pro
```rust title=""
/* Backtracking algorithm framework */
fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) {
- // Check if it's a solution
+ // Check if it is a solution
if is_solution(state) {
// Record the solution
record_solution(state, res);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for choice in choices {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if is_valid(state, choice) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
make_choice(state, choice);
backtrack(state, choices, res);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undo_choice(state, choice);
}
}
@@ -356,21 +356,21 @@ In the following framework code, `state` represents the current state of the pro
```c title=""
/* Backtracking algorithm framework */
void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res, numRes);
// Stop searching
return;
}
- // Iterate through all choices
+ // Traverse all choices
for (int i = 0; i < numChoices; i++) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, &choices[i])) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, &choices[i]);
backtrack(state, choices, numChoices, res, numRes);
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, &choices[i]);
}
}
@@ -382,21 +382,21 @@ In the following framework code, `state` represents the current state of the pro
```kotlin title=""
/* Backtracking algorithm framework */
fun backtrack(state: State?, choices: List, res: List?) {
- // Check if it's a solution
+ // Check if it is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res)
// Stop searching
return
}
- // Iterate through all choices
+ // Traverse all choices
for (choice in choices) {
- // Prune: check if the choice is valid
+ // Pruning: check if the choice is valid
if (isValid(state, choice)) {
- // Trial: make a choice, update the state
+ // Attempt: make a choice and update the state
makeChoice(state, choice)
backtrack(state, choices, res)
- // Retreat: undo the choice, revert to the previous state
+ // Backtrack: undo the choice and restore to the previous state
undoChoice(state, choice)
}
}
@@ -406,7 +406,27 @@ In the following framework code, `state` represents the current state of the pro
=== "Ruby"
```ruby title=""
+ ### Backtracking algorithm framework ###
+ def backtrack(state, choices, res)
+ # Check if it is a solution
+ if is_solution?(state)
+ # Record the solution
+ record_solution(state, res)
+ return
+ end
+ # Traverse all choices
+ for choice in choices
+ # Pruning: check if the choice is valid
+ if is_valid?(state, choice)
+ # Attempt: make a choice and update the state
+ make_choice(state, choice)
+ backtrack(state, choices, res)
+ # Backtrack: undo the choice and restore to the previous state
+ undo_choice(state, choice)
+ end
+ end
+ end
```
=== "Zig"
@@ -415,75 +435,75 @@ In the following framework code, `state` represents the current state of the pro
```
-Now, we are able to solve Example Three using the framework code. The `state` is the node traversal path, `choices` are the current node's left and right children, and the result `res` is the list of paths:
+Next, we solve Example 3 based on the framework code. The state `state` is the node traversal path, the choices `choices` are the left and right child nodes of the current node, and the result `res` is a list of paths:
```src
[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack}
```
-As per the requirements, after finding a node with a value of $7$, the search should continue. **As a result, the `return` statement after recording the solution should be removed**. The figure below compares the search processes with and without retaining the `return` statement.
+As per the problem statement, we should continue searching after finding a node with value $7$. **Therefore, we need to remove the `return` statement after recording the solution**. The following figure compares the search process with and without the `return` statement.
-
+
-Compared to the implementation based on pre-order traversal, the code using the backtracking algorithm framework seems verbose. However, it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework.
+Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but has better generality. In fact, **many backtracking problems can be solved within this framework**. We only need to define `state` and `choices` for the specific problem and implement each method in the framework.
-## Common terminology
+## Common Terminology
-To analyze algorithmic problems more clearly, we summarize the meanings of commonly used terminology in backtracking algorithms and provide corresponding examples from Example Three as shown in the table below.
+To analyze algorithmic problems more clearly, we summarize the meanings of common terminology used in backtracking algorithms and provide corresponding examples from Example 3, as shown in the following table.
- Table Common backtracking algorithm terminology
+ Table Common Backtracking Algorithm Terminology
-| Term | Definition | Example Three |
-| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| Solution | A solution is an answer that satisfies specific conditions of the problem, which may have one or more | All paths from the root node to node $7$ that meet the constraint |
-| Constraint | Constraints are conditions in the problem that limit the feasibility of solutions, often used for pruning | Paths do not contain node $3$ |
-| State | State represents the situation of the problem at a certain moment, including choices made | Current visited node path, i.e., `path` node list |
-| Trial | A trial is the process of exploring the solution space based on available choices, including making choices, updating the state, and checking if it's a solution | Recursively visiting left (right) child nodes, adding nodes to `path`, checking if the node's value is $7$ |
-| Retreat | Retreat refers to the action of undoing previous choices and returning to the previous state when encountering states that do not meet the constraints | When passing leaf nodes, ending node visits, encountering nodes with a value of $3$, terminating the search, and the recursion function returns |
-| Prune | Prune is a method to avoid meaningless search paths based on the characteristics and constraints of the problem, which can enhance search efficiency | When encountering a node with a value of $3$, no further search is required |
+| Term | Definition | Example 3 |
+| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| Solution (solution) | A solution is an answer that satisfies the specific conditions of a problem; there may be one or more solutions | All paths from root to nodes with value $7$ that satisfy the constraint |
+| Constraint (constraint) | A constraint is a condition in the problem that limits the feasibility of solutions, typically used for pruning | Paths do not contain nodes with value $3$ |
+| State (state) | State represents the situation of a problem at a certain moment, including the choices already made | The currently visited node path, i.e., the `path` list of nodes |
+| Attempt (attempt) | An attempt is the process of exploring the solution space according to available choices, including making choices, updating state, and checking if it is a solution | Recursively visit left (right) child nodes, add nodes to `path`, check if node value is $7$ |
+| Backtrack (backtracking) | Backtracking refers to undoing previous choices and returning to a previous state when encountering a state that does not satisfy constraints | Stop searching when passing over leaf nodes, ending node visits, or encountering nodes with value $3$; function returns |
+| Pruning (pruning) | Pruning is a method of avoiding meaningless search paths according to problem characteristics and constraints, which can improve search efficiency | When encountering a node with value $3$, do not continue searching |
!!! tip
- Concepts like problems, solutions, states, etc., are universal, and are involved in divide and conquer, backtracking, dynamic programming, and greedy algorithms, among others.
+ The concepts of problem, solution, state, etc. are universal and are involved in divide-and-conquer, backtracking, dynamic programming, greedy and other algorithms.
-## Advantages and limitations
+## Advantages and Limitations
-The backtracking algorithm is essentially a depth-first search algorithm that attempts all possible solutions until a satisfying solution is found. The advantage of this method is that it can find all possible solutions, and with reasonable pruning operations, it can be highly efficient.
+The backtracking algorithm is essentially a depth-first search algorithm that tries all possible solutions until it finds one that satisfies the conditions. The advantage of this approach is that it can find all possible solutions, and with reasonable pruning operations, it achieves high efficiency.
-However, when dealing with large-scale or complex problems, **the running efficiency of backtracking algorithm may not be acceptable**.
+However, when dealing with large-scale or complex problems, **the running efficiency of the backtracking algorithm may be unacceptable**.
-- **Time complexity**: Backtracking algorithms usually need to traverse all possible states in the state space, which can reach exponential or factorial time complexity.
-- **Space complexity**: In recursive calls, it is necessary to save the current state (such as paths, auxiliary variables for pruning, etc.). When the depth is very large, the space need may become significantly bigger.
+- **Time**: The backtracking algorithm usually needs to traverse all possibilities in the solution space, and the time complexity can reach exponential or factorial order.
+- **Space**: During recursive calls, the current state needs to be saved (such as paths, auxiliary variables used for pruning, etc.), and when the depth is large, the space requirement can become very large.
-Even so, **backtracking remains the best solution for certain search problems and constraint satisfaction problems**. For these problems, there is no way to predict which choices can generate valid solutions. We have to traverse all possible choices. In this case, **the key is about how to optimize the efficiency**. There are two common efficiency optimization methods.
+Nevertheless, **the backtracking algorithm is still the best solution for certain search problems and constraint satisfaction problems**. For these problems, since we cannot predict which choices will generate valid solutions, we must traverse all possible choices. In this case, **the key is how to optimize efficiency**. There are two common efficiency optimization methods.
-- **Prune**: Avoid searching paths that definitely will not produce a solution, thus saving time and space.
-- **Heuristic search**: Introduce some strategies or estimates during the search process to prioritize the paths that are most likely to produce valid solutions.
+- **Pruning**: Avoid searching paths that are guaranteed not to produce solutions, thereby saving time and space.
+- **Heuristic search**: Introduce certain strategies or estimation values during the search process to prioritize searching paths that are most likely to produce valid solutions.
-## Typical backtracking problems
+## Typical Backtracking Examples
-Backtracking algorithms can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems.
+The backtracking algorithm can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems.
-**Search problems**: The goal of these problems is to find solutions that meet specific conditions.
+**Search problems**: The goal of these problems is to find solutions that satisfy specific conditions.
-- Full permutation problem: Given a set, find all possible permutations and combinations of it.
-- Subset sum problem: Given a set and a target sum, find all subsets of the set that sum to the target.
-- Tower of Hanoi problem: Given three rods and a series of different-sized discs, the goal is to move all the discs from one rod to another, moving only one disc at a time, and never placing a larger disc on a smaller one.
+- Permutation problem: Given a set, find all possible permutations and combinations.
+- Subset sum problem: Given a set and a target sum, find all subsets in the set whose elements sum to the target.
+- Tower of Hanoi: Given three pegs and a series of disks of different sizes, move all disks from one peg to another, moving only one disk at a time, and never placing a larger disk on a smaller disk.
-**Constraint satisfaction problems**: The goal of these problems is to find solutions that satisfy all the constraints.
+**Constraint satisfaction problems**: The goal of these problems is to find solutions that satisfy all constraints.
-- $n$ queens: Place $n$ queens on an $n \times n$ chessboard so that they do not attack each other.
-- Sudoku: Fill a $9 \times 9$ grid with the numbers $1$ to $9$, ensuring that the numbers do not repeat in each row, each column, and each $3 \times 3$ subgrid.
-- Graph coloring problem: Given an undirected graph, color each vertex with the fewest possible colors so that adjacent vertices have different colors.
+- N-Queens: Place $n$ queens on an $n \times n$ chessboard such that they do not attack each other.
+- Sudoku: Fill numbers $1$ to $9$ in a $9 \times 9$ grid such that each row, column, and $3 \times 3$ subgrid contains no repeated digits.
+- Graph coloring: Given an undirected graph, color each vertex with the minimum number of colors such that adjacent vertices have different colors.
-**Combinatorial optimization problems**: The goal of these problems is to find the optimal solution within a combination space that meets certain conditions.
+**Combinatorial optimization problems**: The goal of these problems is to find an optimal solution that satisfies certain conditions in a combinatorial space.
-- 0-1 knapsack problem: Given a set of items and a backpack, each item has a certain value and weight. The goal is to choose items to maximize the total value within the backpack's capacity limit.
-- Traveling salesman problem: In a graph, starting from one point, visit all other points exactly once and then return to the starting point, seeking the shortest path.
-- Maximum clique problem: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge.
+- 0-1 Knapsack: Given a set of items and a knapsack, each item has a value and weight. Under the knapsack capacity constraint, select items to maximize total value.
+- Traveling Salesman Problem: Starting from a point in a graph, visit all other points exactly once and return to the starting point, finding the shortest path.
+- Maximum Clique: Given an undirected graph, find the largest complete subgraph, i.e., a subgraph where any two vertices are connected by an edge.
-Please note that for many combinatorial optimization problems, backtracking is not the optimal solution.
+Note that for many combinatorial optimization problems, backtracking is not the optimal solution.
-- The 0-1 knapsack problem is usually solved using dynamic programming to achieve higher time efficiency.
-- The traveling salesman is a well-known NP-Hard problem, commonly solved using genetic algorithms and ant colony algorithms, among others.
-- The maximum clique problem is a classic problem in graph theory, which can be solved using greedy algorithms and other heuristic methods.
+- The 0-1 Knapsack problem is usually solved using dynamic programming to achieve higher time efficiency.
+- The Traveling Salesman Problem is a famous NP-Hard problem; common solutions include genetic algorithms and ant colony algorithms.
+- The Maximum Clique problem is a classical problem in graph theory and can be solved using heuristic algorithms such as greedy algorithms.
diff --git a/en/docs/chapter_backtracking/index.md b/en/docs/chapter_backtracking/index.md
index 862581c93..50f3f5c94 100644
--- a/en/docs/chapter_backtracking/index.md
+++ b/en/docs/chapter_backtracking/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Like explorers in a maze, we may encounter obstacles on our path forward.
+ We are like explorers in a maze, and may encounter difficulties on the path forward.
- The power of backtracking lets us begin anew, keep trying, and eventually find the exit leading to the light.
+ The power of backtracking allows us to start over, keep trying, and eventually find the exit leading to light.
diff --git a/en/docs/chapter_backtracking/n_queens_problem.md b/en/docs/chapter_backtracking/n_queens_problem.md
index 4ec02a6da..4c2fc1275 100644
--- a/en/docs/chapter_backtracking/n_queens_problem.md
+++ b/en/docs/chapter_backtracking/n_queens_problem.md
@@ -1,53 +1,53 @@
-# n queens problem
+# n-queens problem
!!! question
- According to the rules of chess, a queen can attack pieces in the same row, column, or diagonal line. Given $n$ queens and an $n \times n$ chessboard, find arrangements where no two queens can attack each other.
+ According to the rules of chess, a queen can attack pieces that share the same row, column, or diagonal line. Given $n$ queens and an $n \times n$ chessboard, find a placement scheme such that no two queens can attack each other.
-As shown in the figure below, there are two solutions when $n = 4$. From the perspective of the backtracking algorithm, an $n \times n$ chessboard has $n^2$ squares, presenting all possible choices `choices`. The state of the chessboard `state` changes continuously as each queen is placed.
+As shown in the figure below, when $n = 4$, there are two solutions that can be found. From the perspective of the backtracking algorithm, an $n \times n$ chessboard has $n^2$ squares, which provide all the choices `choices`. During the process of placing queens one by one, the chessboard state changes continuously, and the chessboard at each moment represents the state `state`.
-
+
-The figure below shows the three constraints of this problem: **multiple queens cannot occupy the same row, column, or diagonal**. It is important to note that diagonals are divided into the main diagonal `\` and the secondary diagonal `/`.
+The figure below illustrates the three constraints of this problem: **multiple queens cannot be in the same row, the same column, or on the same diagonal**. It is worth noting that diagonals are divided into two types: the main diagonal `\` and the anti-diagonal `/`.
-
+
-### Row-by-row placing strategy
+### Row-by-row placement strategy
-As the number of queens equals the number of rows on the chessboard, both being $n$, it is easy to conclude that **each row on the chessboard allows and only allows one queen to be placed**.
+Since both the number of queens and the number of rows on the chessboard are $n$, we can easily derive a conclusion: **each row of the chessboard allows and only allows exactly one queen to be placed**.
-This means that we can adopt a row-by-row placing strategy: starting from the first row, place one queen per row until the last row is reached.
+This means we can adopt a row-by-row placement strategy: starting from the first row, place one queen in each row until the last row is completed.
-The figure below shows the row-by-row placing process for the 4 queens problem. Due to space limitations, the figure only expands one search branch of the first row, and prunes any placements that do not meet the column and diagonal constraints.
+The figure below shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that do not satisfy the column constraint and diagonal constraints are pruned.
-
+
-Essentially, **the row-by-row placing strategy serves as a pruning function**, eliminating all search branches that would place multiple queens in the same row.
+Essentially, **the row-by-row placement strategy serves a pruning function**, as it avoids all search branches where multiple queens appear in the same row.
### Column and diagonal pruning
-To satisfy column constraints, we can use a boolean array `cols` of length $n$ to track whether a queen occupies each column. Before each placement decision, `cols` is used to prune the columns that already have queens, and it is dynamically updated during backtracking.
+To satisfy the column constraint, we can use a boolean array `cols` of length $n$ to record whether each column has a queen. Before each placement decision, we use `cols` to prune columns that already have queens, and dynamically update the state of `cols` during backtracking.
!!! tip
- Note that the origin of the matrix is located in the upper left corner, where the row index increases from top to bottom, and the column index increases from left to right.
+ Please note that the origin of the matrix is located in the upper-left corner, where the row index increases from top to bottom, and the column index increases from left to right.
-How about the diagonal constraints? Let the row and column indices of a certain cell on the chessboard be $(row, col)$. By selecting a specific main diagonal, we notice that the difference $row - col$ is the same for all cells on that diagonal, **meaning that $row - col$ is a constant value on the main diagonal**.
+So how do we handle diagonal constraints? Consider a square on the chessboard with row and column indices $(row, col)$. If we select a specific main diagonal in the matrix, we find that all squares on that diagonal have the same difference between their row and column indices, **meaning that $row - col$ is a constant value for all squares on the main diagonal**.
-In other words, if two cells satisfy $row_1 - col_1 = row_2 - col_2$, they are definitely on the same main diagonal. Using this pattern, we can utilize the array `diags1` shown in the figure below to track whether a queen is on any main diagonal.
+In other words, if two squares satisfy $row_1 - col_1 = row_2 - col_2$, they must be on the same main diagonal. Using this pattern, we can use the array `diags1` shown in the figure below to record whether there is a queen on each main diagonal.
-Similarly, **the sum of $row + col$ is a constant value for all cells on the secondary diagonal**. We can also use the array `diags2` to handle secondary diagonal constraints.
+Similarly, **for all squares on an anti-diagonal, the sum $row + col$ is a constant value**. We can likewise use the array `diags2` to handle anti-diagonal constraints.

### Code implementation
-Please note, in an $n$-dimensional square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Consequently, the number of both main and secondary diagonals is $2n - 1$, meaning the length of the arrays `diags1` and `diags2` is $2n - 1$.
+Please note that in an $n$-dimensional square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Therefore, the number of both main diagonals and anti-diagonals is $2n - 1$, meaning the length of both arrays `diags1` and `diags2` is $2n - 1$.
```src
[file]{n_queens}-[class]{}-[func]{n_queens}
```
-Placing $n$ queens row-by-row, considering column constraints, from the first row to the last row, there are $n$, $n-1$, $\dots$, $2$, $1$ choices, using $O(n!)$ time. When recording a solution, it is necessary to copy the matrix `state` and add it to `res`, with the copying operation using $O(n^2)$ time. Therefore, **the overall time complexity is $O(n! \cdot n^2)$**. In practice, pruning based on diagonal constraints can significantly reduce the search space, thus often the search efficiency is better than the aforementioned time complexity.
+Placing $n$ queens row by row, considering the column constraint, from the first row to the last row there are $n$, $n-1$, $\dots$, $2$, $1$ choices, using $O(n!)$ time. When recording a solution, it is necessary to copy the matrix `state` and add it to `res`, and the copy operation uses $O(n^2)$ time. Therefore, **the overall time complexity is $O(n! \cdot n^2)$**. In practice, pruning based on diagonal constraints can also significantly reduce the search space, so the search efficiency is often better than the time complexity mentioned above.
-Array `state` uses $O(n^2)$ space, and arrays `cols`, `diags1`, and `diags2` each use $O(n)$ space as well. The maximum recursion depth is $n$, using $O(n)$ stack frame space. Therefore, **the space complexity is $O(n^2)$**.
+The array `state` uses $O(n^2)$ space, and the arrays `cols`, `diags1`, and `diags2` each use $O(n)$ space. The maximum recursion depth is $n$, using $O(n)$ stack frame space. Therefore, **the space complexity is $O(n^2)$**.
diff --git a/en/docs/chapter_backtracking/permutations_problem.md b/en/docs/chapter_backtracking/permutations_problem.md
index ff3b87b64..a390950d0 100644
--- a/en/docs/chapter_backtracking/permutations_problem.md
+++ b/en/docs/chapter_backtracking/permutations_problem.md
@@ -1,95 +1,95 @@
-# Permutation problem
+# Permutations Problem
-The permutation problem is a typical application of the backtracking algorithm. It involves finding all possible arrangements (permutations) of elements from a given set, such as an array or a string.
+The permutations problem is a classic application of backtracking algorithms. It is defined as finding all possible arrangements of elements in a given collection (such as an array or string).
-The table below shows several examples, including input arrays and their corresponding permutations.
+The table below shows several example datasets, including input arrays and their corresponding permutations.
- Table Permutation examples
+ Table Permutations Examples
-| Input array | Permutations |
+| Input Array | All Permutations |
| :---------- | :----------------------------------------------------------------- |
| $[1]$ | $[1]$ |
| $[1, 2]$ | $[1, 2], [2, 1]$ |
| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ |
-## Cases without duplicate elements
+## Case with Distinct Elements
!!! question
Given an integer array with no duplicate elements, return all possible permutations.
-From a backtracking perspective, **we can view the process of generating permutations as a series of choices.** Suppose the input array is $[1, 2, 3]$. If we choose $1$ first, then $3$, and finally $2$, we get the permutation $[1, 3, 2]$. "Backtracking" means undoing a previous choice and exploring alternative options.
+From the perspective of backtracking algorithms, **we can imagine the process of generating permutations as the result of a series of choices**. Suppose the input array is $[1, 2, 3]$. If we first choose $1$, then choose $3$, and finally choose $2$, we obtain the permutation $[1, 3, 2]$. Backtracking means undoing a choice and then trying other choices.
-From a coding perspective, the candidate set `choices` consists of all elements in the input array, while `state` holds the elements selected so far. Since each element can only be chosen once, **all elements in `state` must be unique**.
+From the perspective of backtracking code, the candidate set `choices` consists of all elements in the input array, and the state `state` is the elements that have been chosen so far. Note that each element can only be chosen once, **therefore all elements in `state` should be unique**.
-As illustrated in the figure below, we can expand the search process into a recursive tree, where each node represents the current `state`. Starting from the root node, after three rounds of selections, we reach the leaf nodes—each corresponding to a permutation.
+As shown in the figure below, we can unfold the search process into a recursion tree, where each node in the tree represents the current state `state`. Starting from the root node, after three rounds of choices, we reach a leaf node, and each leaf node corresponds to a permutation.
-
+
-### Repeated-choice pruning
+### Pruning Duplicate Choices
-To ensure each element is selected only once, we introduce a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We then base our pruning steps on this array:
+To ensure that each element is chosen only once, we consider introducing a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We implement the following pruning operation based on it.
-- After choosing `choice[i]`, set `selected[i]` to $\text{True}$ to mark it as chosen.
-- While iterating through `choices`, skip all elements marked as chosen (i.e., prune those branches).
+- After making a choice `choice[i]`, we set `selected[i]` to $\text{True}$, indicating that it has been chosen.
+- When traversing the candidate list `choices`, we skip all nodes that have been chosen, which is pruning.
-As shown in the figure below, suppose we choose 1 in the first round, then 3 in the second round, and finally 2 in the third round. We need to prune the branch for element 1 in the second round and the branches for elements 1 and 3 in the third round.
+As shown in the figure below, suppose we choose $1$ in the first round, $3$ in the second round, and $2$ in the third round. Then we need to prune the branch of element $1$ in the second round and prune the branches of elements $1$ and $3$ in the third round.
-
+
-From the figure, we can see that this pruning process reduces the search space from $O(n^n)$ to $O(n!)$.
+Observing the above figure, we find that this pruning operation reduces the search space size from $O(n^n)$ to $O(n!)$.
-### Code implementation
+### Code Implementation
-With this understanding, we can "fill in the blanks" of our framework code. To keep the overall code concise, we won’t implement each part of the framework separately but instead expand everything in the `backtrack()` function:
+After understanding the above information, we can fill in the blanks in the template code. To shorten the overall code, we do not implement each function in the template separately, but instead unfold them in the `backtrack()` function:
```src
[file]{permutations_i}-[class]{}-[func]{permutations_i}
```
-## Considering duplicate elements
+## Case with Duplicate Elements
!!! question
- Given an integer array**that may contain duplicate elements**, return all unique permutations.
+ Given an integer array that **may contain duplicate elements**, return all unique permutations.
-Suppose the input array is $[1, 1, 2]$. To distinguish between the two identical elements $1$, we label the second one as $\hat{1}$.
+Suppose the input array is $[1, 1, 2]$. To distinguish the two duplicate elements $1$, we denote the second $1$ as $\hat{1}$.
-As shown in the figure below, half of the permutations produced by this method are duplicates:
+As shown in the figure below, the method described above generates permutations where half are duplicates.

-So how can we eliminate these duplicate permutations? One direct approach is to use a hash set to remove duplicates after generating all permutations. However, this is less elegant **because branches that produce duplicates are inherently unnecessary and should be pruned in advance,** thus improving the algorithm’s efficiency.
+So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because **the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early**, which can further improve algorithm efficiency.
-### Equal-element pruning
+### Pruning Duplicate Elements
-Looking at the figure below, in the first round, choosing $1$ or $\hat{1}$ leads to the same permutations, so we prune $\hat{1}$.
+Observe the figure below. In the first round, choosing $1$ or choosing $\hat{1}$ is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune $\hat{1}$.
-Similarly, after choosing $2$ in the first round, choosing $1$ or $\hat{1}$ in the second round also leads to duplicate branches, so we prune $\hat{1}$ then as well.
+Similarly, after choosing $2$ in the first round, the $1$ and $\hat{1}$ in the second round also produce duplicate branches, so the second round's $\hat{1}$ should also be pruned.
-Essentially, **our goal is to ensure that multiple identical elements are only selected once per round of choices.**
+Essentially, **our goal is to ensure that multiple equal elements are chosen only once in a certain round of choices**.
-
+
-### Code implementation
+### Code Implementation
-Based on the code from the previous problem, we introduce a hash set `duplicated` in each round. This set keeps track of elements we have already attempted, so we can prune duplicates:
+Building on the code from the previous problem, we consider opening a hash set `duplicated` in each round of choices to record which elements have been tried in this round, and prune duplicate elements:
```src
[file]{permutations_ii}-[class]{}-[func]{permutations_ii}
```
-Assuming all elements are distinct, there are $n!$ (factorial) permutations of $n$ elements. Recording each result requires copying a list of length $n$, which takes $O(n)$ time. **Hence, the total time complexity is $O(n!n)$.**
+Assuming elements are pairwise distinct, there are $n!$ (factorial) permutations of $n$ elements. When recording results, we need to copy a list of length $n$, using $O(n)$ time. **Therefore, the time complexity is $O(n! \cdot n)$**.
-The maximum recursion depth is $n$, using $O(n)$ stack space. The `selected` array also requires $O(n)$ space. Because there can be up to $n$ separate `duplicated` sets at any one time, they collectively occupy $O(n^2)$ space. **Therefore, the space complexity is $O(n^2)$.**
+The maximum recursion depth is $n$, using $O(n)$ stack frame space. `selected` uses $O(n)$ space. At most $n$ `duplicated` sets exist simultaneously, using $O(n^2)$ space. **Therefore, the space complexity is $O(n^2)$**.
-### Comparing the two pruning methods
+### Comparison of Two Pruning Methods
-Although both `selected` and `duplicated` serve as pruning mechanisms, they target different issues:
+Note that although both `selected` and `duplicated` are used for pruning, they have different objectives.
-- **Repeated-choice pruning**(via `selected`): There is a single `selected` array for the entire search, indicating which elements are already in the current state. This prevents the same element from appearing more than once in `state`.
-- **Equal-element pruning**(via `duplicated`): Each call to the `backtrack` function uses its own `duplicated` set, recording which elements have already been chosen in that specific iteration (`for` loop). This ensures that equal elements are selected only once per round of choices.
+- **Pruning duplicate choices**: There is only one `selected` throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in `state`.
+- **Pruning duplicate elements**: Each round of choices (each `backtrack` function call) contains a `duplicated` set. It records which elements have been chosen in this round's iteration (the `for` loop), and its purpose is to ensure that equal elements are chosen only once.
-The figure below shows the scope of these two pruning strategies. Each node in the tree represents a choice; the path from the root to any leaf corresponds to one complete permutation.
+The figure below shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation.
-
+
diff --git a/en/docs/chapter_backtracking/subset_sum_problem.md b/en/docs/chapter_backtracking/subset_sum_problem.md
index 51638edb9..821d12fa9 100644
--- a/en/docs/chapter_backtracking/subset_sum_problem.md
+++ b/en/docs/chapter_backtracking/subset_sum_problem.md
@@ -1,95 +1,95 @@
-# Subset sum problem
+# Subset-Sum Problem
-## Case without duplicate elements
+## Without Duplicate Elements
!!! question
- Given an array of positive integers `nums` and a target positive integer `target`, find all possible combinations such that the sum of the elements in the combination equals `target`. The given array has no duplicate elements, and each element can be chosen multiple times. Please return these combinations as a list, which should not contain duplicate combinations.
+ Given a positive integer array `nums` and a target positive integer `target`, find all possible combinations where the sum of elements in the combination equals `target`. The given array has no duplicate elements, and each element can be selected multiple times. Return these combinations in list form, where the list should not contain duplicate combinations.
-For example, for the input set $\{3, 4, 5\}$ and target integer $9$, the solutions are $\{3, 3, 3\}, \{4, 5\}$. Note the following two points.
+For example, given the set $\{3, 4, 5\}$ and target integer $9$, the solutions are $\{3, 3, 3\}, \{4, 5\}$. Note the following two points:
-- Elements in the input set can be chosen an unlimited number of times.
-- Subsets do not distinguish the order of elements, for example $\{4, 5\}$ and $\{5, 4\}$ are the same subset.
+- Elements in the input set can be selected repeatedly without limit.
+- Subsets do not distinguish element order; for example, $\{4, 5\}$ and $\{5, 4\}$ are the same subset.
-### Reference permutation solution
+### Reference to Full Permutation Solution
-Similar to the permutation problem, we can imagine the generation of subsets as a series of choices, updating the "element sum" in real-time during the choice process. When the element sum equals `target`, the subset is recorded in the result list.
+Similar to the full permutation problem, we can imagine the process of generating subsets as a series of choices, and update the "sum of elements" in real-time during the selection process. When the sum equals `target`, we record the subset to the result list.
-Unlike the permutation problem, **elements in this problem can be chosen an unlimited number of times**, thus there is no need to use a `selected` boolean list to record whether an element has been chosen. We can make minor modifications to the permutation code to initially solve the problem:
+Unlike the full permutation problem, **elements in this problem's set can be selected unlimited times**, so we do not need to use a `selected` boolean list to track whether an element has been selected. We can make minor modifications to the full permutation code and initially obtain the solution:
```src
[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive}
```
-Inputting the array $[3, 4, 5]$ and target element $9$ into the above code yields the results $[3, 3, 3], [4, 5], [5, 4]$. **Although it successfully finds all subsets with a sum of $9$, it includes the duplicate subset $[4, 5]$ and $[5, 4]$**.
+When we input array $[3, 4, 5]$ and target element $9$ to the above code, the output is $[3, 3, 3], [4, 5], [5, 4]$. **Although we successfully find all subsets that sum to $9$, there are duplicate subsets $[4, 5]$ and $[5, 4]$**.
-This is because the search process distinguishes the order of choices, however, subsets do not distinguish the choice order. As shown in the figure below, choosing $4$ before $5$ and choosing $5$ before $4$ are different branches, but correspond to the same subset.
+This is because the search process distinguishes the order of selections, but subsets do not distinguish selection order. As shown in the figure below, selecting 4 first and then 5 versus selecting 5 first and then 4 are different branches, but they correspond to the same subset.
-
+
-To eliminate duplicate subsets, **a straightforward idea is to deduplicate the result list**. However, this method is very inefficient for two reasons.
+To eliminate duplicate subsets, **one straightforward idea is to deduplicate the result list**. However, this approach is very inefficient for two reasons:
-- When there are many array elements, especially when `target` is large, the search process produces a large number of duplicate subsets.
-- Comparing subsets (arrays) for differences is very time-consuming, requiring arrays to be sorted first, then comparing the differences of each element in the arrays.
+- When there are many array elements, especially when `target` is large, the search process generates many duplicate subsets.
+- Comparing subsets (arrays) is very time-consuming, requiring sorting the arrays first, then comparing each element in them.
-### Duplicate subset pruning
+### Pruning Duplicate Subsets
-**We consider deduplication during the search process through pruning**. Observing the figure below, duplicate subsets are generated when choosing array elements in different orders, for example in the following situations.
+**We consider deduplication through pruning during the search process**. Observing the figure below, duplicate subsets occur when array elements are selected in different orders, as in the following cases:
-1. When choosing $3$ in the first round and $4$ in the second round, all subsets containing these two elements are generated, denoted as $[3, 4, \dots]$.
-2. Later, when $4$ is chosen in the first round, **the second round should skip $3$** because the subset $[4, 3, \dots]$ generated by this choice completely duplicates the subset from step `1.`.
+1. When the first and second rounds select $3$ and $4$ respectively, all subsets containing these two elements are generated, denoted as $[3, 4, \dots]$.
+2. Afterward, when the first round selects $4$, **the second round should skip $3$**, because the subset $[4, 3, \dots]$ generated by this choice is completely duplicate with the subset generated in step `1.`
-In the search process, each layer's choices are tried one by one from left to right, so the more to the right a branch is, the more it is pruned.
+In the search process, each level's choices are tried from left to right, so the rightmost branches are pruned more.
-1. First two rounds choose $3$ and $5$, generating subset $[3, 5, \dots]$.
-2. First two rounds choose $4$ and $5$, generating subset $[4, 5, \dots]$.
-3. If $5$ is chosen in the first round, **then the second round should skip $3$ and $4$** as the subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ completely duplicate the subsets described in steps `1.` and `2.`.
+1. The first two rounds select $3$ and $5$, generating subset $[3, 5, \dots]$.
+2. The first two rounds select $4$ and $5$, generating subset $[4, 5, \dots]$.
+3. If the first round selects $5$, **the second round should skip $3$ and $4$**, because subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ are completely duplicate with the subsets described in steps `1.` and `2.`
-
+
-In summary, given the input array $[x_1, x_2, \dots, x_n]$, the choice sequence in the search process should be $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$, which needs to satisfy $i_1 \leq i_2 \leq \dots \leq i_m$. **Any choice sequence that does not meet this condition will cause duplicates and should be pruned**.
+In summary, given an input array $[x_1, x_2, \dots, x_n]$, let the selection sequence in the search process be $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$. This selection sequence must satisfy $i_1 \leq i_2 \leq \dots \leq i_m$; **any selection sequence that does not satisfy this condition will cause duplicates and should be pruned**.
-### Code implementation
+### Code Implementation
-To implement this pruning, we initialize the variable `start`, which indicates the starting point for traversal. **After making the choice $x_{i}$, set the next round to start from index $i$**. This will ensure the choice sequence satisfies $i_1 \leq i_2 \leq \dots \leq i_m$, thereby ensuring the uniqueness of the subsets.
+To implement this pruning, we initialize a variable `start` to indicate the starting point of traversal. **After making choice $x_{i}$, set the next round to start traversal from index $i$**. This ensures that the selection sequence satisfies $i_1 \leq i_2 \leq \dots \leq i_m$, guaranteeing subset uniqueness.
-Besides, we have made the following two optimizations to the code.
+In addition, we have made the following two optimizations to the code:
-- Before starting the search, sort the array `nums`. In the traversal of all choices, **end the loop directly when the subset sum exceeds `target`** as subsequent elements are larger and their subset sum will definitely exceed `target`.
-- Eliminate the element sum variable `total`, **by performing subtraction on `target` to count the element sum**. When `target` equals $0$, record the solution.
+- Before starting the search, first sort the array `nums`. When traversing all choices, **end the loop immediately when the subset sum exceeds `target`**, because subsequent elements are larger, and their subset sums must exceed `target`.
+- Omit the element sum variable `total` and **use subtraction on `target` to track the sum of elements**. Record the solution when `target` equals $0$.
```src
[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i}
```
-The figure below shows the overall backtracking process after inputting the array $[3, 4, 5]$ and target element $9$ into the above code.
+The figure below shows the complete backtracking process when array $[3, 4, 5]$ and target element $9$ are input to the above code.
-
+
-## Considering cases with duplicate elements
+## With Duplicate Elements in Array
!!! question
- Given an array of positive integers `nums` and a target positive integer `target`, find all possible combinations such that the sum of the elements in the combination equals `target`. **The given array may contain duplicate elements, and each element can only be chosen once**. Please return these combinations as a list, which should not contain duplicate combinations.
+ Given a positive integer array `nums` and a target positive integer `target`, find all possible combinations where the sum of elements in the combination equals `target`. **The given array may contain duplicate elements, and each element can be selected at most once**. Return these combinations in list form, where the list should not contain duplicate combinations.
-Compared to the previous question, **this question's input array may contain duplicate elements**, introducing new problems. For example, given the array $[4, \hat{4}, 5]$ and target element $9$, the existing code's output results in $[4, 5], [\hat{4}, 5]$, resulting in duplicate subsets.
+Compared to the previous problem, **the input array in this problem may contain duplicate elements**, which introduces new challenges. For example, given array $[4, \hat{4}, 5]$ and target element $9$, the output of the existing code is $[4, 5], [\hat{4}, 5]$, which contains duplicate subsets.
-**The reason for this duplication is that equal elements are chosen multiple times in a certain round**. In the figure below, the first round has three choices, two of which are $4$, generating two duplicate search branches, thus outputting duplicate subsets; similarly, the two $4$s in the second round also produce duplicate subsets.
+**The reason for this duplication is that equal elements are selected multiple times in a certain round**. In the figure below, the first round has three choices, two of which are $4$, creating two duplicate search branches that output duplicate subsets. Similarly, the two $4$'s in the second round also produce duplicate subsets.

-### Equal element pruning
+### Pruning Equal Elements
-To solve this issue, **we need to limit equal elements to being chosen only once per round**. The implementation is quite clever: since the array is sorted, equal elements are adjacent. This means that in a certain round of choices, if the current element is equal to its left-hand element, it means it has already been chosen, so skip the current element directly.
+To solve this problem, **we need to limit equal elements to be selected only once in each round**. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a certain round of selection, if the current element equals the element to its left, it means this element has already been selected, so we skip the current element directly.
-At the same time, **this question stipulates that each array element can only be chosen once**. Fortunately, we can also use the variable `start` to meet this constraint: after making the choice $x_{i}$, set the next round to start from index $i + 1$ going forward. This not only eliminates duplicate subsets but also avoids repeated selection of elements.
+At the same time, **this problem specifies that each array element can only be selected once**. Fortunately, we can also use the variable `start` to satisfy this constraint: after making choice $x_{i}$, set the next round to start traversal from index $i + 1$ onwards. This both eliminates duplicate subsets and avoids selecting elements multiple times.
-### Code implementation
+### Code Implementation
```src
[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii}
```
-The figure below shows the backtracking process for the array $[4, 4, 5]$ and target element $9$, including four types of pruning operations. Please combine the illustration with the code comments to understand the entire search process and how each type of pruning operation works.
+The figure below shows the backtracking process for array $[4, 4, 5]$ and target element $9$, which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works.
-
+
diff --git a/en/docs/chapter_backtracking/summary.md b/en/docs/chapter_backtracking/summary.md
index 4631b4c1e..e13bd8c1f 100644
--- a/en/docs/chapter_backtracking/summary.md
+++ b/en/docs/chapter_backtracking/summary.md
@@ -1,23 +1,23 @@
# Summary
-### Key review
+### Key Review
-- The essence of the backtracking algorithm is exhaustive search. It seeks solutions that meet the conditions by performing a depth-first traversal of the solution space. During the search, if a satisfying solution is found, it is recorded, until all solutions are found or the traversal is completed.
-- The search process of the backtracking algorithm includes trying and backtracking. It uses depth-first search to explore various choices, and when a choice does not meet the constraints, the previous choice is undone. Then it reverts to the previous state and continues to try other options. Trying and backtracking are operations in opposite directions.
-- Backtracking problems usually contain multiple constraints. These constraints can be used to perform pruning operations. Pruning can terminate unnecessary search branches in advance, greatly enhancing search efficiency.
-- The backtracking algorithm is mainly used to solve search problems and constraint satisfaction problems. Although combinatorial optimization problems can be solved using backtracking, there are often more efficient or effective solutions available.
-- The permutation problem aims to search for all possible permutations of the elements in a given set. We use an array to record whether each element has been chosen, avoiding repeated selection of the same element. This ensures that each element is chosen only once.
-- In permutation problems, if the set contains duplicate elements, the final result will include duplicate permutations. We need to restrict that identical elements can only be selected once in each round, which is usually implemented using a hash set.
-- The subset-sum problem aims to find all subsets in a given set that sum to a target value. The set does not distinguish the order of elements, but the search process may generate duplicate subsets. This occurs because the algorithm explores different element orders as unique paths. Before backtracking, we sort the data and set a variable to indicate the starting point of the traversal for each round. This allows us to prune the search branches that generate duplicate subsets.
-- For the subset-sum problem, equal elements in the array can produce duplicate sets. Using the precondition that the array is already sorted, we prune by determining if adjacent elements are equal. This ensures that equal elements are only selected once per round.
-- The $n$ queens problem aims to find schemes to place $n$ queens on an $n \times n$ chessboard such that no two queens can attack each other. The constraints of the problem include row constraints, column constraints, and constraints on the main and secondary diagonals. To meet the row constraint, we adopt a strategy of placing one queen per row, ensuring each row has one queen placed.
-- The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether there is a queen in each column, thereby indicating whether the selected cell is legal. For diagonal constraints, we use two arrays to respectively record the presence of queens on the main and secondary diagonals. The challenge is to determine the relationship between row and column indices for cells on the same main or secondary diagonal.
+- The backtracking algorithm is fundamentally an exhaustive search method. It finds solutions that meet specified conditions by performing a depth-first traversal of the solution space. During the search process, when a solution satisfying the conditions is found, it is recorded. The search ends either after finding all solutions or when the traversal is complete.
+- The backtracking algorithm search process consists of two parts: attempting and backtracking. It tries various choices through depth-first search. When encountering situations that violate constraints, it reverts the previous choice, returns to the previous state, and continues exploring other options. Attempting and backtracking are operations in opposite directions.
+- Backtracking problems typically contain multiple constraints, which can be utilized to implement pruning operations. Pruning can terminate unnecessary search branches early, significantly improving search efficiency.
+- The backtracking algorithm is primarily used to solve search problems and constraint satisfaction problems. While combinatorial optimization problems can be solved with backtracking, there are often more efficient or better-performing solutions available.
+- The permutation problem aims to find all possible permutations of elements in a given set. We use an array to record whether each element has been selected, thereby pruning search branches that attempt to select the same element repeatedly, ensuring each element is selected exactly once.
+- In the permutation problem, if the set contains duplicate elements, the final result will contain duplicate permutations. We need to impose a constraint so that equal elements can only be selected once per round, which is typically achieved using a hash set.
+- The subset-sum problem aims to find all subsets of a given set that sum to a target value. Since the set is unordered but the search process outputs results in all orders, duplicate subsets are generated. We sort the data before backtracking and use a variable to indicate the starting point of each round's traversal, thereby pruning search branches that generate duplicate subsets.
+- For the subset-sum problem, equal elements in the array produce duplicate sets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round.
+- The $n$ queens problem aims to find placements of $n$ queens on an $n \times n$ chessboard such that no two queens can attack each other. The constraints of this problem include row constraints, column constraints, and main and anti-diagonal constraints. To satisfy row constraints, we adopt a row-by-row placement strategy, ensuring exactly one queen is placed in each row.
+- The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether each column has a queen, thereby indicating whether a selected cell is valid. For diagonal constraints, we use two arrays to separately record whether queens exist on each main or anti-diagonal. The challenge lies in finding the row-column index pattern that characterizes cells on the same main (anti-)diagonal.
### Q & A
-**Q**: How can we understand the relationship between backtracking and recursion?
+**Q**: How should we understand the relationship between backtracking and recursion?
-Overall, backtracking is an "algorithmic strategy," while recursion is more of a "tool."
+Overall, backtracking is an "algorithm strategy", while recursion is more like a "tool".
-- Backtracking algorithms are typically based on recursion. However, backtracking is one of the application scenarios of recursion, specifically in search problems.
-- The structure of recursion reflects the problem-solving paradigm of "sub-problem decomposition." It is commonly used in solving problems involving divide and conquer, backtracking, and dynamic programming (memoized recursion).
+- The backtracking algorithm is typically implemented based on recursion. However, backtracking is one application scenario of recursion and represents the application of recursion in search problems.
+- The structure of recursion embodies the "subproblem decomposition" problem-solving paradigm, commonly used to solve problems involving divide-and-conquer, backtracking, and dynamic programming (memoized recursion).
diff --git a/en/docs/chapter_computational_complexity/index.md b/en/docs/chapter_computational_complexity/index.md
index 1fbbaefe0..b68d5fd29 100644
--- a/en/docs/chapter_computational_complexity/index.md
+++ b/en/docs/chapter_computational_complexity/index.md
@@ -1,9 +1,9 @@
-# Complexity analysis
+# Complexity Analysis

!!! abstract
- Complexity analysis is like a space-time navigator in the vast universe of algorithms.
+ Complexity analysis is like a space-time guide in the vast universe of algorithms.
- It guides us in exploring deeper within the dimensions of time and space, seeking more elegant solutions.
+ It leads us to explore deeply within the two dimensions of time and space, seeking more elegant solutions.
diff --git a/en/docs/chapter_computational_complexity/iteration_and_recursion.md b/en/docs/chapter_computational_complexity/iteration_and_recursion.md
index f873eb5fa..232eb9c82 100644
--- a/en/docs/chapter_computational_complexity/iteration_and_recursion.md
+++ b/en/docs/chapter_computational_complexity/iteration_and_recursion.md
@@ -1,77 +1,77 @@
-# Iteration and recursion
+# Iteration and Recursion
-In algorithms, the repeated execution of a task is quite common and is closely related to the analysis of complexity. Therefore, before delving into the concepts of time complexity and space complexity, let's first explore how to implement repetitive tasks in programming. This involves understanding two fundamental programming control structures: iteration and recursion.
+In algorithms, repeatedly executing a task is very common and closely related to complexity analysis. Therefore, before introducing time complexity and space complexity, let's first understand how to implement repeated task execution in programs, namely the two basic program control structures: iteration and recursion.
## Iteration
-Iteration is a control structure for repeatedly performing a task. In iteration, a program repeats a block of code as long as a certain condition is met until this condition is no longer satisfied.
+Iteration is a control structure for repeatedly executing a task. In iteration, a program repeatedly executes a segment of code under certain conditions until those conditions are no longer satisfied.
-### For loops
+### for Loop
-The `for` loop is one of the most common forms of iteration, and **it's particularly suitable when the number of iterations is known in advance**.
+The `for` loop is one of the most common forms of iteration, **suitable for use when the number of iterations is known in advance**.
-The following function uses a `for` loop to perform a summation of $1 + 2 + \dots + n$, with the sum being stored in the variable `res`. It's important to note that in Python, `range(a, b)` creates an interval that is inclusive of `a` but exclusive of `b`, meaning it iterates over the range from $a$ up to $b−1$.
+The following function implements the summation $1 + 2 + \dots + n$ based on a `for` loop, with the sum result recorded using the variable `res`. Note that in Python, `range(a, b)` corresponds to a "left-closed, right-open" interval, with the traversal range being $a, a + 1, \dots, b-1$:
```src
[file]{iteration}-[class]{}-[func]{for_loop}
```
-The figure below represents this sum function.
+The figure below shows the flowchart of this summation function.
-
+
-The number of operations in this summation function is proportional to the size of the input data $n$, or in other words, it has a linear relationship. **This "linear relationship" is what time complexity describes**. This topic will be discussed in more detail in the next section.
+The number of operations in this summation function is proportional to the input data size $n$, or has a "linear relationship". In fact, **time complexity describes precisely this "linear relationship"**. Related content will be introduced in detail in the next section.
-### While loops
+### while Loop
-Similar to `for` loops, `while` loops are another approach for implementing iteration. In a `while` loop, the program checks a condition at the beginning of each iteration; if the condition is true, the execution continues, otherwise, the loop ends.
+Similar to the `for` loop, the `while` loop is also a method for implementing iteration. In a `while` loop, the program first checks the condition in each round; if the condition is true, it continues execution, otherwise it ends the loop.
-Below we use a `while` loop to implement the sum $1 + 2 + \dots + n$.
+Below we use a `while` loop to implement the summation $1 + 2 + \dots + n$:
```src
[file]{iteration}-[class]{}-[func]{while_loop}
```
-**`while` loops provide more flexibility than `for` loops**, especially since they allow for custom initialization and modification of the condition variable at each step.
+**The `while` loop has greater flexibility than the `for` loop**. In a `while` loop, we can freely design the initialization and update steps of the condition variable.
-For example, in the following code, the condition variable $i$ is updated twice each round, which would be inconvenient to implement with a `for` loop.
+For example, in the following code, the condition variable $i$ is updated twice per round, which is not convenient to implement using a `for` loop:
```src
[file]{iteration}-[class]{}-[func]{while_loop_ii}
```
-Overall, **`for` loops are more concise, while `while` loops are more flexible**. Both can implement iterative structures. Which one to use should be determined based on the specific requirements of the problem.
+Overall, **`for` loops have more compact code, while `while` loops are more flexible**; both can implement iterative structures. The choice of which to use should be determined based on the requirements of the specific problem.
-### Nested loops
+### Nested Loops
-We can nest one loop structure within another. Below is an example using `for` loops:
+We can nest one loop structure inside another. Below is an example using `for` loops:
```src
[file]{iteration}-[class]{}-[func]{nested_for_loop}
```
-The figure below represents this nested loop.
+The figure below shows the flowchart of this nested loop.
-
+
-In such cases, the number of operations of the function is proportional to $n^2$, meaning the algorithm's runtime and the size of the input data $n$ has a 'quadratic relationship.'
+In this case, the number of operations of the function is proportional to $n^2$, or the algorithm's running time has a "quadratic relationship" with the input data size $n$.
-We can further increase the complexity by adding more nested loops, each level of nesting effectively "increasing the dimension," which raises the time complexity to "cubic," "quartic," and so on.
+We can continue adding nested loops, where each nesting is a "dimension increase", raising the time complexity to "cubic relationship", "quartic relationship", and so on.
## Recursion
-Recursion is an algorithmic strategy where a function solves a problem by calling itself. It primarily involves two phases:
+Recursion is an algorithmic strategy that solves problems by having a function call itself. It mainly consists of two phases.
-1. **Calling**: This is where the program repeatedly calls itself, often with progressively smaller or simpler arguments, moving towards the "termination condition."
-2. **Returning**: Upon triggering the "termination condition," the program begins to return from the deepest recursive function, aggregating the results of each layer.
+1. **Descend**: The program continuously calls itself deeper, usually passing in smaller or more simplified parameters, until reaching a "termination condition".
+2. **Ascend**: After triggering the "termination condition", the program returns layer by layer from the deepest recursive function, aggregating the result of each layer.
-From an implementation perspective, recursive code mainly includes three elements.
+From an implementation perspective, recursive code mainly consists of three elements.
-1. **Termination Condition**: Determines when to switch from "calling" to "returning."
-2. **Recursive Call**: Corresponds to "calling," where the function calls itself, usually with smaller or more simplified parameters.
-3. **Return Result**: Corresponds to "returning," where the result of the current recursion level is returned to the previous layer.
+1. **Termination condition**: Used to determine when to switch from "descending" to "ascending".
+2. **Recursive call**: Corresponds to "descending", where the function calls itself, usually with smaller or more simplified parameters.
+3. **Return result**: Corresponds to "ascending", returning the result of the current recursion level to the previous layer.
-Observe the following code, where simply calling the function `recur(n)` can compute the sum of $1 + 2 + \dots + n$:
+Observe the following code. We only need to call the function `recur(n)` to complete the calculation of $1 + 2 + \dots + n$:
```src
[file]{recursion}-[class]{}-[func]{recur}
@@ -79,116 +79,116 @@ Observe the following code, where simply calling the function `recur(n)` can com
The figure below shows the recursive process of this function.
-
+
-Although iteration and recursion can achieve the same results from a computational standpoint, **they represent two entirely different paradigms of thinking and problem-solving**.
+Although from a computational perspective, iteration and recursion can achieve the same results, **they represent two completely different paradigms for thinking about and solving problems**.
-- **Iteration**: Solves problems "from the bottom up." It starts with the most basic steps, and then repeatedly adds or accumulates these steps until the task is complete.
-- **Recursion**: Solves problems "from the top down." It breaks down the original problem into smaller sub-problems, each of which has the same form as the original problem. These sub-problems are then further decomposed into even smaller sub-problems, stopping at the base case whose solution is known.
+- **Iteration**: Solves problems "bottom-up". Starting from the most basic steps, these steps are then repeatedly executed or accumulated until the task is complete.
+- **Recursion**: Solves problems "top-down". The original problem is decomposed into smaller subproblems that have the same form as the original problem. These subproblems continue to be decomposed into even smaller subproblems until reaching the base case (where the solution is known).
-Let's take the earlier example of the summation function, defined as $f(n) = 1 + 2 + \dots + n$.
+Taking the above summation function as an example, let the problem be $f(n) = 1 + 2 + \dots + n$.
-- **Iteration**: In this approach, we simulate the summation process within a loop. Starting from $1$ and traversing to $n$, we perform the summation operation in each iteration to eventually compute $f(n)$.
-- **Recursion**: Here, the problem is broken down into a sub-problem: $f(n) = n + f(n-1)$. This decomposition continues recursively until reaching the base case, $f(1) = 1$, at which point the recursion terminates.
+- **Iteration**: Simulates the summation process in a loop, traversing from $1$ to $n$, performing the summation operation in each round to obtain $f(n)$.
+- **Recursion**: Decomposes the problem into the subproblem $f(n) = n + f(n-1)$, continuously decomposing (recursively) until terminating at the base case $f(1) = 1$.
-### Call stack
+### Call Stack
-Every time a recursive function calls itself, the system allocates memory for the newly initiated function to store local variables, the return address, and other relevant information. This leads to two primary outcomes.
+Each time a recursive function calls itself, the system allocates memory for the newly opened function to store local variables, call addresses, and other information. This leads to two consequences.
-- The function's context data is stored in a memory area called "stack frame space" and is only released after the function returns. Therefore, **recursion generally consumes more memory space than iteration**.
-- Recursive calls introduce additional overhead. **Hence, recursion is usually less time-efficient than loops.**
+- The function's context data is stored in a memory area called "stack frame space", which is not released until the function returns. Therefore, **recursion usually consumes more memory space than iteration**.
+- Recursive function calls incur additional overhead. **Therefore, recursion is usually less time-efficient than loops**.
-As shown in the figure below, there are $n$ unreturned recursive functions before triggering the termination condition, indicating a **recursion depth of $n$**.
+As shown in the figure below, before the termination condition is triggered, there are $n$ unreturned recursive functions existing simultaneously, with a **recursion depth of $n$**.

-In practice, the depth of recursion allowed by programming languages is usually limited, and excessively deep recursion can lead to stack overflow errors.
+In practice, the recursion depth allowed by programming languages is usually limited, and excessively deep recursion may lead to stack overflow errors.
-### Tail recursion
+### Tail Recursion
-Interestingly, **if a function performs its recursive call as the very last step before returning,** it can be optimized by the compiler or interpreter to be as space-efficient as iteration. This scenario is known as tail recursion.
+Interestingly, **if a function makes the recursive call as the very last step before returning**, the function can be optimized by the compiler or interpreter to have space efficiency comparable to iteration. This case is called tail recursion.
-- **Regular recursion**: In standard recursion, when the function returns to the previous level, it continues to execute more code, requiring the system to save the context of the previous call.
-- **Tail recursion**: Here, the recursive call is the final operation before the function returns. This means that upon returning to the previous level, no further actions are needed, so the system does not need to save the context of the previous level.
+- **Regular recursion**: When a function returns to the previous level, it needs to continue executing code, so the system needs to save the context of the previous layer's call.
+- **Tail recursion**: The recursive call is the last operation before the function returns, meaning that after returning to the previous level, there is no need to continue executing other operations, so the system does not need to save the context of the previous layer's function.
-For example, in calculating $1 + 2 + \dots + n$, we can make the result variable `res` a parameter of the function, thereby achieving tail recursion:
+Taking the calculation of $1 + 2 + \dots + n$ as an example, we can set the result variable `res` as a function parameter to implement tail recursion:
```src
[file]{recursion}-[class]{}-[func]{tail_recur}
```
-The execution process of tail recursion is shown in the figure below. Comparing regular recursion and tail recursion, the point of the summation operation is different.
+The execution process of tail recursion is shown in the figure below. Comparing regular recursion and tail recursion, the execution point of the summation operation is different.
-- **Regular recursion**: The summation operation occurs during the "returning" phase, requiring another summation after each layer returns.
-- **Tail recursion**: The summation operation occurs during the "calling" phase, and the "returning" phase only involves returning through each layer.
+- **Regular recursion**: The summation operation is performed during the "ascending" process, requiring an additional summation operation after each layer returns.
+- **Tail recursion**: The summation operation is performed during the "descending" process; the "ascending" process only needs to return layer by layer.

!!! tip
- Note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if the function is in the form of tail recursion, it may still encounter stack overflow issues.
+ Please note that many compilers or interpreters do not support tail recursion optimization. For example, Python does not support tail recursion optimization by default, so even if a function is in tail recursive form, it may still encounter stack overflow issues.
-### Recursion tree
+### Recursion Tree
-When dealing with algorithms related to "divide and conquer", recursion often offers a more intuitive approach and more readable code than iteration. Take the "Fibonacci sequence" as an example.
+When dealing with algorithmic problems related to "divide and conquer", recursion often provides a more intuitive approach and more readable code than iteration. Taking the "Fibonacci sequence" as an example.
!!! question
- Given a Fibonacci sequence $0, 1, 1, 2, 3, 5, 8, 13, \dots$, find the $n$th number in the sequence.
+ Given a Fibonacci sequence $0, 1, 1, 2, 3, 5, 8, 13, \dots$, find the $n$-th number in the sequence.
-Let the $n$th number of the Fibonacci sequence be $f(n)$, it's easy to deduce two conclusions:
+Let the $n$-th number of the Fibonacci sequence be $f(n)$. Two conclusions can be easily obtained.
- The first two numbers of the sequence are $f(1) = 0$ and $f(2) = 1$.
-- Each number in the sequence is the sum of the two preceding ones, that is, $f(n) = f(n - 1) + f(n - 2)$.
+- Each number in the sequence is the sum of the previous two numbers, i.e., $f(n) = f(n - 1) + f(n - 2)$.
-Using the recursive relation, and considering the first two numbers as termination conditions, we can write the recursive code. Calling `fib(n)` will yield the $n$th number of the Fibonacci sequence:
+Following the recurrence relation to make recursive calls, with the first two numbers as termination conditions, we can write the recursive code. Calling `fib(n)` will give us the $n$-th number of the Fibonacci sequence:
```src
[file]{recursion}-[class]{}-[func]{fib}
```
-Observing the above code, we see that it recursively calls two functions within itself, **meaning that one call generates two branching calls**. As illustrated in the figure below, this continuous recursive calling eventually creates a recursion tree with a depth of $n$.
+Observing the above code, we recursively call two functions within the function, **meaning that one call produces two call branches**. As shown in the figure below, such continuous recursive calling will eventually produce a recursion tree with $n$ levels.
-
+
-Fundamentally, recursion embodies the paradigm of "breaking down a problem into smaller sub-problems." This divide-and-conquer strategy is crucial.
+Fundamentally, recursion embodies the paradigm of "decomposing a problem into smaller subproblems", and this divide-and-conquer strategy is crucial.
-- From an algorithmic perspective, many important strategies like searching, sorting, backtracking, divide-and-conquer, and dynamic programming directly or indirectly use this way of thinking.
-- From a data structure perspective, recursion is naturally suited for dealing with linked lists, trees, and graphs, as they are well suited for analysis using the divide-and-conquer approach.
+- From an algorithmic perspective, many important algorithmic strategies such as searching, sorting, backtracking, divide and conquer, and dynamic programming directly or indirectly apply this way of thinking.
+- From a data structure perspective, recursion is naturally suited for handling problems related to linked lists, trees, and graphs, because they are well-suited for analysis using divide-and-conquer thinking.
-## Comparison
+## Comparison of the Two
-Summarizing the above content, the following table shows the differences between iteration and recursion in terms of implementation, performance, and applicability.
+Summarizing the above content, as shown in the table below, iteration and recursion differ in implementation, performance, and applicability.
- Table: Comparison of iteration and recursion characteristics
+ Table Comparison of iteration and recursion characteristics
-| | Iteration | Recursion |
-| ----------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| Approach | Loop structure | Function calls itself |
-| Time Efficiency | Generally higher efficiency, no function call overhead | Each function call generates overhead |
-| Memory Usage | Typically uses a fixed size of memory space | Accumulative function calls can use a substantial amount of stack frame space |
-| Suitable Problems | Suitable for simple loop tasks, intuitive and readable code | Suitable for problem decomposition, like trees, graphs, divide-and-conquer, backtracking, etc., concise and clear code structure |
+| | Iteration | Recursion |
+| -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- |
+| Implementation | Loop structure | Function calls itself |
+| Time efficiency | Generally more efficient, no function call overhead | Each function call incurs overhead |
+| Memory usage | Usually uses a fixed amount of memory space | Accumulated function calls may use a large amount of stack frame space |
+| Suitable problems | Suitable for simple loop tasks, with intuitive and readable code | Suitable for subproblem decomposition, such as trees, graphs, divide and conquer, backtracking, etc., with concise and clear code structure |
!!! tip
- If you find the following content difficult to understand, consider revisiting it after reading the "Stack" chapter.
+ If you find the following content difficult to understand, you can review it after reading the "Stack" chapter.
-So, what is the intrinsic connection between iteration and recursion? Taking the above recursive function as an example, the summation operation occurs during the recursion's "return" phase. This means that the initially called function is the last to complete its summation operation, **mirroring the "last in, first out" principle of a stack**.
+What is the intrinsic relationship between iteration and recursion? Taking the above recursive function as an example, the summation operation is performed during the "ascending" phase of recursion. This means that the function called first actually completes its summation operation last, **and this working mechanism is similar to the "last-in, first-out" principle of stacks**.
-Recursive terms like "call stack" and "stack frame space" hint at the close relationship between recursion and stacks.
+In fact, recursive terminology such as "call stack" and "stack frame space" already hints at the close relationship between recursion and stacks.
-1. **Calling**: When a function is called, the system allocates a new stack frame on the "call stack" for that function, storing local variables, parameters, return addresses, and other data.
-2. **Returning**: When a function completes execution and returns, the corresponding stack frame is removed from the "call stack," restoring the execution environment of the previous function.
+1. **Descend**: When a function is called, the system allocates a new stack frame on the "call stack" for that function to store the function's local variables, parameters, return address, and other data.
+2. **Ascend**: When the function completes execution and returns, the corresponding stack frame is removed from the "call stack", restoring the execution environment of the previous function.
-Therefore, **we can use an explicit stack to simulate the behavior of the call stack**, thus transforming recursion into an iterative form:
+Therefore, **we can use an explicit stack to simulate the behavior of the call stack**, thus transforming recursion into iterative form:
```src
[file]{recursion}-[class]{}-[func]{for_loop_recur}
```
-Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can often be transformed into each other, it's not always advisable to do so for two reasons:
+Observing the above code, when recursion is transformed into iteration, the code becomes more complex. Although iteration and recursion can be converted into each other in many cases, it may not be worthwhile to do so for the following two reasons.
-- The transformed code may become more challenging to understand and less readable.
-- For some complex problems, simulating the behavior of the system's call stack can be quite challenging.
+- The transformed code may be more difficult to understand and less readable.
+- For some complex problems, simulating the behavior of the system call stack can be very difficult.
-In conclusion, **whether to choose iteration or recursion depends on the specific nature of the problem**. In programming practice, it's crucial to weigh the pros and cons of both and choose the most suitable approach for the situation at hand.
+In summary, **choosing between iteration and recursion depends on the nature of the specific problem**. In programming practice, it is crucial to weigh the pros and cons of both and choose the appropriate method based on the context.
diff --git a/en/docs/chapter_computational_complexity/performance_evaluation.md b/en/docs/chapter_computational_complexity/performance_evaluation.md
index 445a5f455..83893114a 100644
--- a/en/docs/chapter_computational_complexity/performance_evaluation.md
+++ b/en/docs/chapter_computational_complexity/performance_evaluation.md
@@ -1,49 +1,49 @@
-# Algorithm efficiency assessment
+# Algorithm Efficiency Evaluation
-In algorithm design, we pursue the following two objectives in sequence.
+In algorithm design, we pursue the following two levels of objectives sequentially.
-1. **Finding a Solution to the Problem**: The algorithm should reliably find the correct solution within the specified 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.
+1. **Finding a solution to the problem**: The algorithm must reliably obtain the correct solution within the specified input range.
+2. **Seeking the optimal solution**: Multiple solutions may exist for the same problem, and we hope to find an algorithm that is as efficient as possible.
-In other words, under the premise of being able to solve the problem, algorithm efficiency has become the main criterion for evaluating an algorithm, which includes the following two dimensions.
+In other words, under the premise of being able to solve the problem, algorithm efficiency has become the primary evaluation criterion for measuring the quality of algorithms. It 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.
+- **Time efficiency**: The length of time the algorithm runs.
+- **Space efficiency**: The size of memory space the algorithm occupies.
-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.
+In short, **our goal is to design data structures and algorithms that are "both fast and memory-efficient"**. Effectively evaluating algorithm efficiency is crucial, because only in this way can we compare various algorithms and guide the algorithm design and optimization process.
-There are mainly two methods of efficiency assessment: actual testing and theoretical estimation.
+Efficiency evaluation methods are mainly divided into two types: actual testing and theoretical estimation.
-## Actual testing
+## 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, monitor and record their runtime and memory usage. This assessment method reflects the actual situation, but it has significant limitations.
+Suppose we now have algorithm `A` and algorithm `B`, both of which can solve the same problem, and we need to compare the efficiency of these two algorithms. The most direct method is to find a computer, run these two algorithms, and monitor and record their running time and memory usage. This evaluation approach can reflect the real situation, but it also has considerable limitations.
-On one hand, **it's difficult to eliminate interference from the testing environment**. Hardware configurations can affect algorithm performance. For example, an algorithm with a high degree of parallelism is better suited for running on multi-core CPUs, while an algorithm that involves intensive memory operations performs better with high-performance memory. The test results of an algorithm may vary across different machines. This means testing across multiple machines to calculate average efficiency becomes impractical.
+On one hand, **it is difficult to eliminate interference factors from the testing environment**. Hardware configuration affects the performance of algorithms. For example, if an algorithm has a high degree of parallelism, it is more suitable for running on multi-core CPUs; if an algorithm has intensive memory operations, it will perform better on high-performance memory. In other words, the test results of an algorithm on different machines may be inconsistent. This means we need to test on various machines and calculate average efficiency, which is impractical.
-On the other hand, **conducting a full test is very resource-intensive**. Algorithm efficiency varies with input data size. For example, with smaller data volumes, algorithm `A` might run faster than `B`, but with larger data volumes, the test results may be the opposite. Therefore, to draw convincing conclusions, we need to test a wide range of input data sizes, which requires excessive computational resources.
+On the other hand, **conducting complete testing is very resource-intensive**. As the input data volume changes, the algorithm will exhibit different efficiencies. For example, when the input data volume is small, the running time of algorithm `A` is shorter than algorithm `B`; but when the input data volume is large, the test results may be exactly the opposite. Therefore, to obtain convincing conclusions, we need to test input data of various scales, which requires a large amount of computational resources.
-## Theoretical estimation
+## 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.
+Since actual testing has considerable limitations, we can consider evaluating algorithm efficiency through calculations alone. This estimation method is called asymptotic complexity analysis, or complexity analysis for short.
-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.
+Complexity analysis can reflect the relationship between the time and space resources required for algorithm execution and the input data scale. **It describes the growth trend of the time and space required for algorithm execution as the input data scale increases**. This definition is somewhat convoluted, so we can break it down into three key points to understand.
- "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 increases.
+- "As the input data scale increases" means that complexity reflects the relationship between algorithm running efficiency and input data scale.
+- "Growth trend of time and space" indicates that complexity analysis focuses not on the specific values of running time or occupied space, but on how "fast" time or space grows.
-**Complexity analysis overcomes the disadvantages of actual testing methods**, reflected in the following aspects:
+**Complexity analysis overcomes the drawbacks of the actual testing method**, reflected in the following aspects.
-- It does not require actually running the code, making it more environmentally friendly and energy efficient.
-- 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.
+- It does not need to actually run the code, making it more environmentally friendly and energy-efficient.
+- It is independent of the testing environment, and the analysis results are applicable to all running platforms.
+- It can reflect algorithm efficiency at different data volumes, especially algorithm performance at large data volumes.
!!! tip
- If you're still confused about the concept of complexity, don't worry. We will cover it in detail in subsequent chapters.
+ If you are 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 evaluate the efficiency of an algorithm, enabling us to measure the time and space resources required to execute it and compare the efficiency of different algorithms.
+Complexity analysis provides us with a "ruler" for evaluating algorithm efficiency, allowing us to measure the time and space resources required to execute a certain algorithm and compare the efficiency between different algorithms.
-Complexity is a mathematical concept that might be abstract and challenging for beginners. From this perspective, complexity analysis might not be the most suitable topic 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.
+Complexity is a mathematical concept that may be relatively abstract for beginners, with a relatively high learning difficulty. From this perspective, complexity analysis may not be very suitable as the first content to be introduced. However, when we discuss the characteristics of a certain data structure or algorithm, it is difficult to avoid analyzing its running speed and space usage.
-In summary, it is recommended to develop a basic understanding of complexity analysis before diving deep into data structures and algorithms, **so that you can perform complexity analysis on simple algorithms**.
\ No newline at end of file
+In summary, it is recommended that before diving deep into data structures and algorithms, **you first establish a preliminary understanding of complexity analysis so that you can complete complexity analysis of simple algorithms**.
diff --git a/en/docs/chapter_computational_complexity/space_complexity.md b/en/docs/chapter_computational_complexity/space_complexity.md
index 33b16f089..93062d24c 100644
--- a/en/docs/chapter_computational_complexity/space_complexity.md
+++ b/en/docs/chapter_computational_complexity/space_complexity.md
@@ -1,130 +1,129 @@
-# Space complexity
+# Space Complexity
-Space complexity is used to measure the growth trend of the memory space occupied by an algorithm as the amount of data increases. This concept is very similar to time complexity, except that "running time" is replaced with "occupied memory space".
+Space complexity measures the growth trend of memory space occupied by an algorithm as the data size increases. This concept is very similar to time complexity, except that "running time" is replaced with "occupied memory space".
-## Space related to algorithms
+## Algorithm-Related Space
-The memory space used by an algorithm during its execution mainly includes the following types.
+The memory space used by an algorithm during execution mainly includes the following types.
- **Input space**: Used to store the input data of the algorithm.
- **Temporary space**: Used to store variables, objects, function contexts, and other data during the algorithm's execution.
- **Output space**: Used to store the output data of the algorithm.
-Generally, the scope of space complexity statistics includes both "Temporary Space" and "Output Space".
+In general, the scope of space complexity statistics is "temporary space" plus "output space".
Temporary space can be further divided into three parts.
- **Temporary data**: Used to save various constants, variables, objects, etc., during the algorithm's execution.
-- **Stack frame space**: Used to save the context data of the called function. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns.
-- **Instruction space**: Used to store compiled program instructions, which are usually negligible in actual statistics.
+- **Stack frame space**: Used to save the context data of called functions. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns.
+- **Instruction space**: Used to save compiled program instructions, which are usually ignored in actual statistics.
-When analyzing the space complexity of a program, **we typically count the Temporary Data, Stack Frame Space, and Output Data**, as shown in the figure below.
+When analyzing the space complexity of a program, **we usually count three parts: temporary data, stack frame space, and output data**, as shown in the following figure.
-
+
-The relevant code is as follows:
+The related code is as follows:
=== "Python"
```python title=""
class Node:
- """Classes"""
+ """Class"""
def __init__(self, x: int):
- self.val: int = x # node value
- self.next: Node | None = None # reference to the next node
+ self.val: int = x # Node value
+ self.next: Node | None = None # Reference to the next node
def function() -> int:
- """Functions"""
- # Perform certain operations...
+ """Function"""
+ # Perform some operations...
return 0
- def algorithm(n) -> int: # input data
- A = 0 # temporary data (constant, usually in uppercase)
- b = 0 # temporary data (variable)
- node = Node(0) # temporary data (object)
- c = function() # Stack frame space (call function)
- return A + b + c # output data
+ def algorithm(n) -> int: # Input data
+ A = 0 # Temporary data (constant, usually represented by uppercase letters)
+ b = 0 # Temporary data (variable)
+ node = Node(0) # Temporary data (object)
+ c = function() # Stack frame space (function call)
+ return A + b + c # Output data
```
=== "C++"
```cpp title=""
- /* Structures */
+ /* Structure */
struct Node {
int val;
Node *next;
Node(int x) : val(x), next(nullptr) {}
};
- /* Functions */
+ /* Function */
int func() {
- // Perform certain operations...
+ // Perform some operations...
return 0;
}
- int algorithm(int n) { // input data
- const int a = 0; // temporary data (constant)
- int b = 0; // temporary data (variable)
- Node* node = new Node(0); // temporary data (object)
- int c = func(); // stack frame space (call function)
- return a + b + c; // output data
+ int algorithm(int n) { // Input data
+ const int a = 0; // Temporary data (constant)
+ int b = 0; // Temporary data (variable)
+ Node* node = new Node(0); // Temporary data (object)
+ int c = func(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "Java"
```java title=""
- /* Classes */
+ /* Class */
class Node {
int val;
Node next;
Node(int x) { val = x; }
}
-
- /* Functions */
+
+ /* Function */
int function() {
- // Perform certain operations...
+ // Perform some operations...
return 0;
}
-
- int algorithm(int n) { // input data
- final int a = 0; // temporary data (constant)
- int b = 0; // temporary data (variable)
- Node node = new Node(0); // temporary data (object)
- int c = function(); // stack frame space (call function)
- return a + b + c; // output data
+
+ int algorithm(int n) { // Input data
+ final int a = 0; // Temporary data (constant)
+ int b = 0; // Temporary data (variable)
+ Node node = new Node(0); // Temporary data (object)
+ int c = function(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "C#"
```csharp title=""
- /* Classes */
- class Node {
- int val;
+ /* Class */
+ class Node(int x) {
+ int val = x;
Node next;
- Node(int x) { val = x; }
}
- /* Functions */
+ /* Function */
int Function() {
- // Perform certain operations...
+ // Perform some operations...
return 0;
}
- int Algorithm(int n) { // input data
- const int a = 0; // temporary data (constant)
- int b = 0; // temporary data (variable)
- Node node = new(0); // temporary data (object)
- int c = Function(); // stack frame space (call function)
- return a + b + c; // output data
+ int Algorithm(int n) { // Input data
+ const int a = 0; // Temporary data (constant)
+ int b = 0; // Temporary data (variable)
+ Node node = new(0); // Temporary data (object)
+ int c = Function(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "Go"
```go title=""
- /* Structures */
+ /* Structure */
type node struct {
val int
next *node
@@ -134,26 +133,26 @@ The relevant code is as follows:
func newNode(val int) *node {
return &node{val: val}
}
-
- /* Functions */
+
+ /* Function */
func function() int {
- // Perform certain operations...
+ // Perform some operations...
return 0
}
- func algorithm(n int) int { // input data
- const a = 0 // temporary data (constant)
- b := 0 // temporary storage of data (variable)
- newNode(0) // temporary data (object)
- c := function() // stack frame space (call function)
- return a + b + c // output data
+ func algorithm(n int) int { // Input data
+ const a = 0 // Temporary data (constant)
+ b := 0 // Temporary data (variable)
+ newNode(0) // Temporary data (object)
+ c := function() // Stack frame space (function call)
+ return a + b + c // Output data
}
```
=== "Swift"
```swift title=""
- /* Classes */
+ /* Class */
class Node {
var val: Int
var next: Node?
@@ -163,99 +162,99 @@ The relevant code is as follows:
}
}
- /* Functions */
+ /* Function */
func function() -> Int {
- // Perform certain operations...
+ // Perform some operations...
return 0
}
- func algorithm(n: Int) -> Int { // input data
- let a = 0 // temporary data (constant)
- var b = 0 // temporary data (variable)
- let node = Node(x: 0) // temporary data (object)
- let c = function() // stack frame space (call function)
- return a + b + c // output data
+ func algorithm(n: Int) -> Int { // Input data
+ let a = 0 // Temporary data (constant)
+ var b = 0 // Temporary data (variable)
+ let node = Node(x: 0) // Temporary data (object)
+ let c = function() // Stack frame space (function call)
+ return a + b + c // Output data
}
```
=== "JS"
```javascript title=""
- /* Classes */
+ /* Class */
class Node {
val;
next;
constructor(val) {
- this.val = val === undefined ? 0 : val; // node value
- this.next = null; // reference to the next node
+ this.val = val === undefined ? 0 : val; // Node value
+ this.next = null; // Reference to the next node
}
}
- /* Functions */
+ /* Function */
function constFunc() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- function algorithm(n) { // input data
- const a = 0; // temporary data (constant)
- let b = 0; // temporary data (variable)
- const node = new Node(0); // temporary data (object)
- const c = constFunc(); // Stack frame space (calling function)
- return a + b + c; // output data
+ function algorithm(n) { // Input data
+ const a = 0; // Temporary data (constant)
+ let b = 0; // Temporary data (variable)
+ const node = new Node(0); // Temporary data (object)
+ const c = constFunc(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "TS"
```typescript title=""
- /* Classes */
+ /* Class */
class Node {
val: number;
next: Node | null;
constructor(val?: number) {
- this.val = val === undefined ? 0 : val; // node value
- this.next = null; // reference to the next node
+ this.val = val === undefined ? 0 : val; // Node value
+ this.next = null; // Reference to the next node
}
}
- /* Functions */
+ /* Function */
function constFunc(): number {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- function algorithm(n: number): number { // input data
- const a = 0; // temporary data (constant)
- let b = 0; // temporary data (variable)
- const node = new Node(0); // temporary data (object)
- const c = constFunc(); // Stack frame space (calling function)
- return a + b + c; // output data
+ function algorithm(n: number): number { // Input data
+ const a = 0; // Temporary data (constant)
+ let b = 0; // Temporary data (variable)
+ const node = new Node(0); // Temporary data (object)
+ const c = constFunc(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "Dart"
```dart title=""
- /* Classes */
+ /* Class */
class Node {
int val;
Node next;
Node(this.val, [this.next]);
}
- /* Functions */
+ /* Function */
int function() {
- // Perform certain operations...
+ // Perform some operations...
return 0;
}
- int algorithm(int n) { // input data
- const int a = 0; // temporary data (constant)
- int b = 0; // temporary data (variable)
- Node node = Node(0); // temporary data (object)
- int c = function(); // stack frame space (call function)
- return a + b + c; // output data
+ int algorithm(int n) { // Input data
+ const int a = 0; // Temporary data (constant)
+ int b = 0; // Temporary data (variable)
+ Node node = Node(0); // Temporary data (object)
+ int c = function(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
@@ -264,56 +263,102 @@ The relevant code is as follows:
```rust title=""
use std::rc::Rc;
use std::cell::RefCell;
-
- /* Structures */
+
+ /* Structure */
struct Node {
val: i32,
next: Option>>,
}
- /* Constructor */
+ /* Create Node structure */
impl Node {
fn new(val: i32) -> Self {
Self { val: val, next: None }
}
}
- /* Functions */
- fn function() -> i32 {
- // Perform certain operations...
+ /* Function */
+ fn function() -> i32 {
+ // Perform some operations...
return 0;
}
- fn algorithm(n: i32) -> i32 { // input data
- const a: i32 = 0; // temporary data (constant)
- let mut b = 0; // temporary data (variable)
- let node = Node::new(0); // temporary data (object)
- let c = function(); // stack frame space (call function)
- return a + b + c; // output data
+ fn algorithm(n: i32) -> i32 { // Input data
+ const a: i32 = 0; // Temporary data (constant)
+ let mut b = 0; // Temporary data (variable)
+ let node = Node::new(0); // Temporary data (object)
+ let c = function(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "C"
```c title=""
- /* Functions */
+ /* Function */
int func() {
- // Perform certain operations...
+ // Perform some operations...
return 0;
}
- int algorithm(int n) { // input data
- const int a = 0; // temporary data (constant)
- int b = 0; // temporary data (variable)
- int c = func(); // stack frame space (call function)
- return a + b + c; // output data
+ int algorithm(int n) { // Input data
+ const int a = 0; // Temporary data (constant)
+ int b = 0; // Temporary data (variable)
+ int c = func(); // Stack frame space (function call)
+ return a + b + c; // Output data
}
```
=== "Kotlin"
```kotlin title=""
+ /* Class */
+ class Node(var _val: Int) {
+ var next: Node? = null
+ }
+ /* Function */
+ fun function(): Int {
+ // Perform some operations...
+ return 0
+ }
+
+ fun algorithm(n: Int): Int { // Input data
+ val a = 0 // Temporary data (constant)
+ var b = 0 // Temporary data (variable)
+ val node = Node(0) // Temporary data (object)
+ val c = function() // Stack frame space (function call)
+ return a + b + c // Output data
+ }
+ ```
+
+=== "Ruby"
+
+ ```ruby title=""
+ ### Class ###
+ class Node
+ attr_accessor :val # Node value
+ attr_accessor :next # Reference to the next node
+
+ def initialize(x)
+ @val = x
+ end
+ end
+
+ ### Function ###
+ def function
+ # Perform some operations...
+ 0
+ end
+
+ ### Algorithm ###
+ def algorithm(n) # Input data
+ a = 0 # Temporary data (constant)
+ b = 0 # Temporary data (variable)
+ node = Node.new(0) # Temporary data (object)
+ c = function # Stack frame space (function call)
+ a + b + c # Output data
+ end
```
=== "Zig"
@@ -322,16 +367,16 @@ The relevant code is as follows:
```
-## Calculation method
+## Calculation Method
-The method for calculating space complexity is roughly similar to that of time complexity, with the only change being the shift of the statistical object from "number of operations" to "size of used space".
+The calculation method for space complexity is roughly the same as for time complexity, except that the statistical object is changed from "number of operations" to "size of space used".
-However, unlike time complexity, **we usually only focus on the worst-case space complexity**. This is because memory space is a hard requirement, and we must ensure that there is enough memory space reserved under all input data.
+Unlike time complexity, **we usually only focus on the worst-case space complexity**. This is because memory space is a hard requirement, and we must ensure that sufficient memory space is reserved for all input data.
-Consider the following code, the term "worst-case" in worst-case space complexity has two meanings.
+Observe the following code. The "worst case" in worst-case space complexity has two meanings.
-1. **Based on the worst input data**: When $n < 10$, the space complexity is $O(1)$; but when $n > 10$, the initialized array `nums` occupies $O(n)$ space, thus the worst-case space complexity is $O(n)$.
-2. **Based on the peak memory used during the algorithm's execution**: For example, before executing the last line, the program occupies $O(1)$ space; when initializing the array `nums`, the program occupies $O(n)$ space, hence the worst-case space complexity is $O(n)$.
+1. **Based on the worst input data**: When $n < 10$, the space complexity is $O(1)$; but when $n > 10$, the initialized array `nums` occupies $O(n)$ space, so the worst-case space complexity is $O(n)$.
+2. **Based on the peak memory during algorithm execution**: For example, before executing the last line, the program occupies $O(1)$ space; when initializing the array `nums`, the program occupies $O(n)$ space, so the worst-case space complexity is $O(n)$.
=== "Python"
@@ -443,10 +488,10 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```rust title=""
fn algorithm(n: i32) {
- let a = 0; // O(1)
- let b = [0; 10000]; // O(1)
+ let a = 0; // O(1)
+ let b = [0; 10000]; // O(1)
if n > 10 {
- let nums = vec![0; n as usize]; // O(n)
+ let nums = vec![0; n as usize]; // O(n)
}
}
```
@@ -465,7 +510,23 @@ Consider the following code, the term "worst-case" in worst-case space complexit
=== "Kotlin"
```kotlin title=""
+ fun algorithm(n: Int) {
+ val a = 0 // O(1)
+ val b = IntArray(10000) // O(1)
+ if (n > 10) {
+ val nums = IntArray(n) // O(n)
+ }
+ }
+ ```
+=== "Ruby"
+
+ ```ruby title=""
+ def algorithm(n)
+ a = 0 # O(1)
+ b = Array.new(10000) # O(1)
+ nums = Array.new(n) if n > 10 # O(n)
+ end
```
=== "Zig"
@@ -474,22 +535,22 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```
-**In recursive functions, stack frame space must be taken into count**. Consider the following code:
+**In recursive functions, it is necessary to count the stack frame space**. Observe the following code:
=== "Python"
```python title=""
def function() -> int:
- # Perform certain operations
+ # Perform some operations
return 0
def loop(n: int):
- """Loop O(1)"""
+ """Loop has space complexity of O(1)"""
for _ in range(n):
function()
def recur(n: int):
- """Recursion O(n)"""
+ """Recursion has space complexity of O(n)"""
if n == 1:
return
return recur(n - 1)
@@ -499,16 +560,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```cpp title=""
int func() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
void loop(int n) {
for (int i = 0; i < n; i++) {
func();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
void recur(int n) {
if (n == 1) return;
recur(n - 1);
@@ -519,16 +580,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```java title=""
int function() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
void loop(int n) {
for (int i = 0; i < n; i++) {
function();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
void recur(int n) {
if (n == 1) return;
recur(n - 1);
@@ -539,16 +600,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```csharp title=""
int Function() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
void Loop(int n) {
for (int i = 0; i < n; i++) {
Function();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
int Recur(int n) {
if (n == 1) return 1;
return Recur(n - 1);
@@ -559,18 +620,18 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```go title=""
func function() int {
- // Perform certain operations
+ // Perform some operations
return 0
}
-
- /* Cycle O(1) */
+
+ /* Loop has space complexity of O(1) */
func loop(n int) {
for i := 0; i < n; i++ {
function()
}
}
-
- /* Recursion O(n) */
+
+ /* Recursion has space complexity of O(n) */
func recur(n int) {
if n == 1 {
return
@@ -584,18 +645,18 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```swift title=""
@discardableResult
func function() -> Int {
- // Perform certain operations
+ // Perform some operations
return 0
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
func loop(n: Int) {
for _ in 0 ..< n {
function()
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
func recur(n: Int) {
if n == 1 {
return
@@ -608,16 +669,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```javascript title=""
function constFunc() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
function loop(n) {
for (let i = 0; i < n; i++) {
constFunc();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
function recur(n) {
if (n === 1) return;
return recur(n - 1);
@@ -628,16 +689,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```typescript title=""
function constFunc(): number {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
function loop(n: number): void {
for (let i = 0; i < n; i++) {
constFunc();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
function recur(n: number): void {
if (n === 1) return;
return recur(n - 1);
@@ -648,16 +709,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```dart title=""
int function() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
void loop(int n) {
for (int i = 0; i < n; i++) {
function();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
void recur(int n) {
if (n == 1) return;
recur(n - 1);
@@ -668,17 +729,17 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```rust title=""
fn function() -> i32 {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
fn loop(n: i32) {
for i in 0..n {
function();
}
}
- /* Recursion O(n) */
- void recur(n: i32) {
+ /* Recursion has space complexity of O(n) */
+ fn recur(n: i32) {
if n == 1 {
return;
}
@@ -690,16 +751,16 @@ Consider the following code, the term "worst-case" in worst-case space complexit
```c title=""
int func() {
- // Perform certain operations
+ // Perform some operations
return 0;
}
- /* Cycle O(1) */
+ /* Loop has space complexity of O(1) */
void loop(int n) {
for (int i = 0; i < n; i++) {
func();
}
}
- /* Recursion O(n) */
+ /* Recursion has space complexity of O(n) */
void recur(int n) {
if (n == 1) return;
recur(n - 1);
@@ -709,7 +770,41 @@ Consider the following code, the term "worst-case" in worst-case space complexit
=== "Kotlin"
```kotlin title=""
+ fun function(): Int {
+ // Perform some operations
+ return 0
+ }
+ /* Loop has space complexity of O(1) */
+ fun loop(n: Int) {
+ for (i in 0..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.
+A function can be executed independently, with all parameters passed explicitly. A method is associated with an object, is implicitly passed to the object that invokes it, and can operate on data contained in class instances.
-Here are some examples from common programming languages:
+The following examples use several common programming languages for illustration.
-- 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.
+- 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 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?
+**Q**: Does the diagram for "common space complexity types" 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.
+No, the diagram shows space complexity, which reflects growth trends rather than the absolute size of 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.
+Assuming $n = 8$, you might find that the values of each curve do not correspond to the functions. This is because each curve contains a constant term used 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.
+In practice, because we generally do not know what the "constant term" complexity of each method is, we usually cannot select the optimal solution for $n = 8$ based on complexity alone. But for $n = 8^5$, the choice is straightforward, as the growth trend already dominates.
+
+**Q**: Are there situations where algorithms are designed to sacrifice time (or space) based on actual use cases?
+
+In practical applications, most situations choose to sacrifice space for time. For example, with database indexes, we typically choose to build B+ trees or hash indexes, occupying substantial memory space in exchange for efficient queries of $O(\log n)$ or even $O(1)$.
+
+In scenarios where space resources are precious, time may be sacrificed for space. For example, in embedded development, device memory is precious, and engineers may forgo using hash tables and choose to use array sequential search to save memory usage, at the cost of slower searches.
diff --git a/en/docs/chapter_computational_complexity/time_complexity.md b/en/docs/chapter_computational_complexity/time_complexity.md
index 83882debd..7d8419eeb 100644
--- a/en/docs/chapter_computational_complexity/time_complexity.md
+++ b/en/docs/chapter_computational_complexity/time_complexity.md
@@ -1,22 +1,22 @@
# Time complexity
-The runtime can intuitively assess the efficiency of an algorithm. How can we accurately estimate the runtime of a piece of an algorithm?
+Runtime can intuitively and accurately reflect the efficiency of an algorithm. If we want to accurately estimate the runtime of a piece of code, how should we proceed?
-1. **Determining the Running Platform**: This includes hardware configuration, programming language, system environment, etc., all of which can affect the efficiency of code execution.
-2. **Evaluating the Run Time for Various Computational Operations**: For instance, an addition operation `+` might take 1 ns, a multiplication operation `*` might take 10 ns, a print operation `print()` might take 5 ns, etc.
-3. **Counting All the Computational Operations in the Code**: Summing the execution times of all these operations gives the total run time.
+1. **Determine the running platform**, including hardware configuration, programming language, system environment, etc., as these factors all affect code execution efficiency.
+2. **Evaluate the runtime required for various computational operations**, for example, an addition operation `+` requires 1 ns, a multiplication operation `*` requires 10 ns, a print operation `print()` requires 5 ns, etc.
+3. **Count all computational operations in the code**, and sum the execution times of all operations to obtain the runtime.
-For example, consider the following code with an input size of $n$:
+For example, in the following code, the input data size is $n$:
=== "Python"
```python title=""
- # Under an operating platform
+ # On a certain running platform
def algorithm(n: int):
a = 2 # 1 ns
a = a + 1 # 1 ns
a = a * 2 # 10 ns
- # Cycle n times
+ # Loop n times
for _ in range(n): # 1 ns
print(0) # 5 ns
```
@@ -24,13 +24,13 @@ For example, consider the following code with an input size of $n$:
=== "C++"
```cpp title=""
- // Under a particular operating platform
+ // On a certain running platform
void algorithm(int n) {
int a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for (int i = 0; i < n; i++) { // 1 ns
cout << 0 << endl; // 5 ns
}
}
@@ -39,13 +39,13 @@ For example, consider the following code with an input size of $n$:
=== "Java"
```java title=""
- // Under a particular operating platform
+ // On a certain running platform
void algorithm(int n) {
int a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for (int i = 0; i < n; i++) { // 1 ns
System.out.println(0); // 5 ns
}
}
@@ -54,13 +54,13 @@ For example, consider the following code with an input size of $n$:
=== "C#"
```csharp title=""
- // Under a particular operating platform
+ // On a certain running platform
void Algorithm(int n) {
int a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for (int i = 0; i < n; i++) { // 1 ns
Console.WriteLine(0); // 5 ns
}
}
@@ -69,7 +69,7 @@ For example, consider the following code with an input size of $n$:
=== "Go"
```go title=""
- // Under a particular operating platform
+ // On a certain running platform
func algorithm(n int) {
a := 2 // 1 ns
a = a + 1 // 1 ns
@@ -84,7 +84,7 @@ For example, consider the following code with an input size of $n$:
=== "Swift"
```swift title=""
- // Under a particular operating platform
+ // On a certain running platform
func algorithm(n: Int) {
var a = 2 // 1 ns
a = a + 1 // 1 ns
@@ -99,13 +99,13 @@ For example, consider the following code with an input size of $n$:
=== "JS"
```javascript title=""
- // Under a particular operating platform
+ // On a certain running platform
function algorithm(n) {
var a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for(let i = 0; i < n; i++) { // 1 ns
console.log(0); // 5 ns
}
}
@@ -114,13 +114,13 @@ For example, consider the following code with an input size of $n$:
=== "TS"
```typescript title=""
- // Under a particular operating platform
+ // On a certain running platform
function algorithm(n: number): void {
var a: number = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for(let i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for(let i = 0; i < n; i++) { // 1 ns
console.log(0); // 5 ns
}
}
@@ -129,13 +129,13 @@ For example, consider the following code with an input size of $n$:
=== "Dart"
```dart title=""
- // Under a particular operating platform
+ // On a certain running platform
void algorithm(int n) {
int a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for (int i = 0; i < n; i++) { // 1 ns
print(0); // 5 ns
}
}
@@ -144,13 +144,13 @@ For example, consider the following code with an input size of $n$:
=== "Rust"
```rust title=""
- // Under a particular operating platform
+ // On a certain running platform
fn algorithm(n: i32) {
let mut a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for _ in 0..n { // 1 ns for each round i++
+ for _ in 0..n { // 1 ns
println!("{}", 0); // 5 ns
}
}
@@ -159,13 +159,13 @@ For example, consider the following code with an input size of $n$:
=== "C"
```c title=""
- // Under a particular operating platform
+ // On a certain running platform
void algorithm(int n) {
int a = 2; // 1 ns
a = a + 1; // 1 ns
a = a * 2; // 10 ns
// Loop n times
- for (int i = 0; i < n; i++) { // 1 ns , every round i++ is executed
+ for (int i = 0; i < n; i++) { // 1 ns
printf("%d", 0); // 5 ns
}
}
@@ -174,13 +174,37 @@ For example, consider the following code with an input size of $n$:
=== "Kotlin"
```kotlin title=""
+ // On a certain running platform
+ fun algorithm(n: Int) {
+ var a = 2 // 1 ns
+ a = a + 1 // 1 ns
+ a = a * 2 // 10 ns
+ // Loop n times
+ for (i in 0.. 1$ and slower than `C` when $n > 1,000,000$. In fact, as long as the input data size $n$ is sufficiently large, a "constant order" complexity algorithm will always be better than a "linear order" one, demonstrating the essence of time growth trend.
-- **Time complexity analysis is more straightforward**. Obviously, the running platform and the types of computational operations are irrelevant to the trend of run time growth. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same "unit time," simplifying the "computational operation run time count" to a "computational operation count." This significantly reduces the complexity of estimation.
-- **Time complexity has its limitations**. For example, although algorithms `A` and `C` have the same time complexity, their actual run times can be quite different. Similarly, even though algorithm `B` has a higher time complexity than `C`, it is clearly superior when the input data size $n$ is small. In these cases, it's difficult to judge the efficiency of algorithms based solely on time complexity. Nonetheless, despite these issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency.
+- **Time complexity can effectively evaluate algorithm efficiency**. For example, the runtime of algorithm `B` grows linearly; when $n > 1$ it is slower than algorithm `A`, and when $n > 1000000$ it is slower than algorithm `C`. In fact, as long as the input data size $n$ is sufficiently large, an algorithm with "constant order" complexity will always be superior to one with "linear order" complexity, which is precisely the meaning of time growth trend.
+- **The derivation method for time complexity is simpler**. Obviously, the running platform and the types of computational operations are both unrelated to the growth trend of the algorithm's runtime. Therefore, in time complexity analysis, we can simply treat the execution time of all computational operations as the same "unit time", thus simplifying "counting computational operation runtime" to "counting the number of computational operations", which greatly reduces the difficulty of estimation.
+- **Time complexity also has certain limitations**. For example, although algorithms `A` and `C` have the same time complexity, their actual runtimes differ significantly. Similarly, although algorithm `B` has a higher time complexity than `C`, when the input data size $n$ is small, algorithm `B` is clearly superior to algorithm `C`. In such cases, it is often difficult to judge the efficiency of algorithms based solely on time complexity. Of course, despite the above issues, complexity analysis remains the most effective and commonly used method for evaluating algorithm efficiency.
-## Asymptotic upper bound
+## Asymptotic upper bound of functions
-Consider a function with an input size of $n$:
+Given a function with input size $n$:
=== "Python"
@@ -489,7 +547,7 @@ Consider a function with an input size of $n$:
a = 1 # +1
a = a + 1 # +1
a = a * 2 # +1
- # Cycle n times
+ # Loop n times
for i in range(n): # +1
print(0) # +1
```
@@ -502,7 +560,7 @@ Consider a function with an input size of $n$:
a = a + 1; // +1
a = a * 2; // +1
// Loop n times
- for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)
+ for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)
cout << 0 << endl; // +1
}
}
@@ -516,7 +574,7 @@ Consider a function with an input size of $n$:
a = a + 1; // +1
a = a * 2; // +1
// Loop n times
- for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)
+ for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)
System.out.println(0); // +1
}
}
@@ -530,7 +588,7 @@ Consider a function with an input size of $n$:
a = a + 1; // +1
a = a * 2; // +1
// Loop n times
- for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)
+ for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)
Console.WriteLine(0); // +1
}
}
@@ -572,7 +630,7 @@ Consider a function with an input size of $n$:
a += 1; // +1
a *= 2; // +1
// Loop n times
- for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)
+ for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)
console.log(0); // +1
}
}
@@ -586,7 +644,7 @@ Consider a function with an input size of $n$:
a += 1; // +1
a *= 2; // +1
// Loop n times
- for(let i = 0; i < n; i++){ // +1 (execute i ++ every round)
+ for(let i = 0; i < n; i++){ // +1 (i++ is executed each round)
console.log(0); // +1
}
}
@@ -600,7 +658,7 @@ Consider a function with an input size of $n$:
a = a + 1; // +1
a = a * 2; // +1
// Loop n times
- for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)
+ for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)
print(0); // +1
}
}
@@ -615,7 +673,7 @@ Consider a function with an input size of $n$:
a = a * 2; // +1
// Loop n times
- for _ in 0..n { // +1 (execute i ++ every round)
+ for _ in 0..n { // +1 (i++ is executed each round)
println!("{}", 0); // +1
}
}
@@ -629,16 +687,38 @@ Consider a function with an input size of $n$:
a = a + 1; // +1
a = a * 2; // +1
// Loop n times
- for (int i = 0; i < n; i++) { // +1 (execute i ++ every round)
+ for (int i = 0; i < n; i++) { // +1 (i++ is executed each round)
printf("%d", 0); // +1
}
- }
+ }
```
=== "Kotlin"
```kotlin title=""
+ fun algorithm(n: Int) {
+ var a = 1 // +1
+ a = a + 1 // +1
+ a = a * 2 // +1
+ // Loop n times
+ for (i in 0..big-O notation, represents the asymptotic upper bound of the function $T(n)$.
+$T(n)$ is a linear function, indicating that its runtime growth trend is linear, and therefore its time complexity is linear order.
-In essence, time complexity analysis is about finding the asymptotic upper bound of the "number of operations $T(n)$". It has a precise mathematical definition.
+We denote the time complexity of linear order as $O(n)$. This mathematical symbol is called big-$O$ notation, representing the asymptotic upper bound of the function $T(n)$.
-!!! note "Asymptotic Upper Bound"
+Time complexity analysis essentially calculates the asymptotic upper bound of "the number of operations $T(n)$", which has a clear mathematical definition.
- If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, $T(n) \leq c \cdot f(n)$, then $f(n)$ is considered an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$.
+!!! note "Asymptotic upper bound of functions"
-As shown in the figure below, calculating the asymptotic upper bound involves finding a function $f(n)$ such that, as $n$ approaches infinity, $T(n)$ and $f(n)$ have the same growth order, differing only by a constant factor $c$.
+ If there exist positive real numbers $c$ and $n_0$ such that for all $n > n_0$, we have $T(n) \leq c \cdot f(n)$, then $f(n)$ can be considered as an asymptotic upper bound of $T(n)$, denoted as $T(n) = O(f(n))$.
+
+As shown in the figure below, calculating the asymptotic upper bound is to find a function $f(n)$ such that when $n$ tends to infinity, $T(n)$ and $f(n)$ are at the same growth level, differing only by a constant coefficient $c$.

-## Calculation method
+## Derivation method
-While the concept of asymptotic upper bound might seem mathematically dense, you don't need to fully grasp it right away. Let's first understand the method of calculation, which can be practiced and comprehended over time.
+The asymptotic upper bound has a bit of mathematical flavor. If you feel you haven't fully understood it, don't worry. We can first master the derivation method, and gradually grasp its mathematical meaning through continuous practice.
-Once $f(n)$ is determined, we obtain the time complexity $O(f(n))$. But how do we determine the asymptotic upper bound $f(n)$? This process generally involves two steps: counting the number of operations and determining the asymptotic upper bound.
+According to the definition, after determining $f(n)$, we can obtain the time complexity $O(f(n))$. So how do we determine the asymptotic upper bound $f(n)$? Overall, it is divided into two steps: first count the number of operations, then determine the asymptotic upper bound.
-### Step 1: counting the number of operations
+### Step 1: Count the number of operations
-This step involves going through the code line by line. However, due to the presence of the constant $c$ in $c \cdot f(n)$, **all coefficients and constant terms in $T(n)$ can be ignored**. This principle allows for simplification techniques in counting operations.
+For code, count from top to bottom line by line. However, since the constant coefficient $c$ in $c \cdot f(n)$ above can be of any size, **coefficients and constant terms in the number of operations $T(n)$ can all be ignored**. According to this principle, the following counting simplification techniques can be summarized.
-1. **Ignore constant terms in $T(n)$**, as they do not affect the time complexity being independent of $n$.
-2. **Omit all coefficients**. For example, looping $2n$, $5n + 1$ times, etc., can be simplified to $n$ times since the coefficient before $n$ does not impact the time complexity.
-3. **Use multiplication for nested loops**. The total number of operations equals the product of the number of operations in each loop, applying the simplification techniques from points 1 and 2 for each loop level.
+1. **Ignore constants in $T(n)$**. Because they are all independent of $n$, they do not affect time complexity.
+2. **Omit all coefficients**. For example, looping $2n$ times, $5n + 1$ times, etc., can all be simplified as $n$ times, because the coefficient before $n$ does not affect time complexity.
+3. **Use multiplication for nested loops**. The total number of operations equals the product of the number of operations in the outer and inner loops, with each layer of loop still able to apply techniques `1.` and `2.` separately.
-Given a function, we can use these techniques to count operations:
+Given a function, we can use the above techniques to count the number of operations:
=== "Python"
```python title=""
def algorithm(n: int):
- a = 1 # +0 (trick 1)
- a = a + n # +0 (trick 1)
- # +n (technique 2)
+ a = 1 # +0 (Technique 1)
+ a = a + n # +0 (Technique 1)
+ # +n (Technique 2)
for i in range(5 * n + 1):
print(0)
- # +n*n (technique 3)
+ # +n*n (Technique 3)
for i in range(2 * n):
for j in range(n + 1):
print(0)
@@ -708,13 +790,13 @@ Given a function, we can use these techniques to count operations:
```cpp title=""
void algorithm(int n) {
- int a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ int a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (int i = 0; i < 5 * n + 1; i++) {
cout << 0 << endl;
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
cout << 0 << endl;
@@ -727,13 +809,13 @@ Given a function, we can use these techniques to count operations:
```java title=""
void algorithm(int n) {
- int a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ int a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (int i = 0; i < 5 * n + 1; i++) {
System.out.println(0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
System.out.println(0);
@@ -746,13 +828,13 @@ Given a function, we can use these techniques to count operations:
```csharp title=""
void Algorithm(int n) {
- int a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ int a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (int i = 0; i < 5 * n + 1; i++) {
Console.WriteLine(0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
Console.WriteLine(0);
@@ -765,13 +847,13 @@ Given a function, we can use these techniques to count operations:
```go title=""
func algorithm(n int) {
- a := 1 // +0 (trick 1)
- a = a + n // +0 (trick 1)
- // +n (technique 2)
+ a := 1 // +0 (Technique 1)
+ a = a + n // +0 (Technique 1)
+ // +n (Technique 2)
for i := 0; i < 5 * n + 1; i++ {
fmt.Println(0)
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for i := 0; i < 2 * n; i++ {
for j := 0; j < n + 1; j++ {
fmt.Println(0)
@@ -784,13 +866,13 @@ Given a function, we can use these techniques to count operations:
```swift title=""
func algorithm(n: Int) {
- var a = 1 // +0 (trick 1)
- a = a + n // +0 (trick 1)
- // +n (technique 2)
+ var a = 1 // +0 (Technique 1)
+ a = a + n // +0 (Technique 1)
+ // +n (Technique 2)
for _ in 0 ..< (5 * n + 1) {
print(0)
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for _ in 0 ..< (2 * n) {
for _ in 0 ..< (n + 1) {
print(0)
@@ -803,13 +885,13 @@ Given a function, we can use these techniques to count operations:
```javascript title=""
function algorithm(n) {
- let a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ let a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (let i = 0; i < 5 * n + 1; i++) {
console.log(0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (let i = 0; i < 2 * n; i++) {
for (let j = 0; j < n + 1; j++) {
console.log(0);
@@ -822,13 +904,13 @@ Given a function, we can use these techniques to count operations:
```typescript title=""
function algorithm(n: number): void {
- let a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ let a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (let i = 0; i < 5 * n + 1; i++) {
console.log(0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (let i = 0; i < 2 * n; i++) {
for (let j = 0; j < n + 1; j++) {
console.log(0);
@@ -841,13 +923,13 @@ Given a function, we can use these techniques to count operations:
```dart title=""
void algorithm(int n) {
- int a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ int a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (int i = 0; i < 5 * n + 1; i++) {
print(0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
print(0);
@@ -860,15 +942,15 @@ Given a function, we can use these techniques to count operations:
```rust title=""
fn algorithm(n: i32) {
- let mut a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
+ let mut a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
- // +n (technique 2)
+ // +n (Technique 2)
for i in 0..(5 * n + 1) {
println!("{}", 0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for i in 0..(2 * n) {
for j in 0..(n + 1) {
println!("{}", 0);
@@ -881,13 +963,13 @@ Given a function, we can use these techniques to count operations:
```c title=""
void algorithm(int n) {
- int a = 1; // +0 (trick 1)
- a = a + n; // +0 (trick 1)
- // +n (technique 2)
+ int a = 1; // +0 (Technique 1)
+ a = a + n; // +0 (Technique 1)
+ // +n (Technique 2)
for (int i = 0; i < 5 * n + 1; i++) {
printf("%d", 0);
}
- // +n*n (technique 3)
+ // +n*n (Technique 3)
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
printf("%d", 0);
@@ -899,22 +981,50 @@ Given a function, we can use these techniques to count operations:
=== "Kotlin"
```kotlin title=""
+ fun algorithm(n: Int) {
+ var a = 1 // +0 (Technique 1)
+ a = a + n // +0 (Technique 1)
+ // +n (Technique 2)
+ for (i in 0..<5 * n + 1) {
+ println(0)
+ }
+ // +n*n (Technique 3)
+ for (i in 0..<2 * n) {
+ for (j in 0.. Table: Time complexity for different operation counts
+ Table Time complexities corresponding to different numbers of operations
-| Operation Count $T(n)$ | Time Complexity $O(f(n))$ |
-| ---------------------- | ------------------------- |
-| $100000$ | $O(1)$ |
-| $3n + 2$ | $O(n)$ |
-| $2n^2 + 3n + 2$ | $O(n^2)$ |
-| $n^3 + 10000n^2$ | $O(n^3)$ |
-| $2^n + 10000n^{10000}$ | $O(2^n)$ |
+| Number of Operations $T(n)$ | Time Complexity $O(f(n))$ |
+| ---------------------- | -------------------- |
+| $100000$ | $O(1)$ |
+| $3n + 2$ | $O(n)$ |
+| $2n^2 + 3n + 2$ | $O(n^2)$ |
+| $n^3 + 10000n^2$ | $O(n^3)$ |
+| $2^n + 10000n^{10000}$ | $O(2^n)$ |
-## Common types of time complexity
+## Common types
-Let's consider the input data size as $n$. The common types of time complexities are shown in the figure below, arranged from lowest to highest:
+Let the input data size be $n$. Common time complexity types are shown in the figure below (arranged in order from low to high).
$$
\begin{aligned}
-& O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
-& \text{Constant} < \text{Log} < \text{Linear} < \text{Linear-Log} < \text{Quadratic} < \text{Exp} < \text{Factorial}
+O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
+\text{Constant order} < \text{Logarithmic order} < \text{Linear order} < \text{Linearithmic order} < \text{Quadratic order} < \text{Exponential order} < \text{Factorial order}
\end{aligned}
$$
-
+
### Constant order $O(1)$
-Constant order means the number of operations is independent of the input data size $n$. In the following function, although the number of operations `size` might be large, the time complexity remains $O(1)$ as it's unrelated to $n$:
+The number of operations in constant order is independent of the input data size $n$, meaning it does not change as $n$ changes.
+
+In the following function, although the number of operations `size` may be large, since it is independent of the input data size $n$, the time complexity remains $O(1)$:
```src
[file]{time_complexity}-[class]{}-[func]{constant}
@@ -972,23 +1084,23 @@ Constant order means the number of operations is independent of the input data s
### Linear order $O(n)$
-Linear order indicates the number of operations grows linearly with the input data size $n$. Linear order commonly appears in single-loop structures:
+The number of operations in linear order grows linearly relative to the input data size $n$. Linear order typically appears in single-layer loops:
```src
[file]{time_complexity}-[class]{}-[func]{linear}
```
-Operations like array traversal and linked list traversal have a time complexity of $O(n)$, where $n$ is the length of the array or list:
+Operations such as traversing arrays and traversing linked lists have a time complexity of $O(n)$, where $n$ is the length of the array or linked list:
```src
[file]{time_complexity}-[class]{}-[func]{array_traversal}
```
-It's important to note that **the input data size $n$ should be determined based on the type of input data**. For example, in the first example, $n$ represents the input data size, while in the second example, the length of the array $n$ is the data size.
+It is worth noting that **the input data size $n$ should be determined according to the type of input data**. For example, in the first example, the variable $n$ is the input data size; in the second example, the array length $n$ is the data size.
### Quadratic order $O(n^2)$
-Quadratic order means the number of operations grows quadratically with the input data size $n$. Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of $O(n)$, resulting in an overall complexity of $O(n^2)$:
+The number of operations in quadratic order grows quadratically relative to the input data size $n$. Quadratic order typically appears in nested loops, where both the outer and inner loops have a time complexity of $O(n)$, resulting in an overall time complexity of $O(n^2)$:
```src
[file]{time_complexity}-[class]{}-[func]{quadratic}
@@ -996,9 +1108,9 @@ Quadratic order means the number of operations grows quadratically with the inpu
The figure below compares constant order, linear order, and quadratic order time complexities.
-
+
-For instance, in bubble sort, the outer loop runs $n - 1$ times, and the inner loop runs $n-1$, $n-2$, ..., $2$, $1$ times, averaging $n / 2$ times, resulting in a time complexity of $O((n - 1) n / 2) = O(n^2)$:
+Taking bubble sort as an example, the outer loop executes $n - 1$ times, and the inner loop executes $n-1$, $n-2$, $\dots$, $2$, $1$ times, averaging $n / 2$ times, resulting in a time complexity of $O((n - 1) n / 2) = O(n^2)$:
```src
[file]{time_complexity}-[class]{}-[func]{bubble_sort}
@@ -1006,107 +1118,107 @@ For instance, in bubble sort, the outer loop runs $n - 1$ times, and the inner l
### Exponential order $O(2^n)$
-Biological "cell division" is a classic example of exponential order growth: starting with one cell, it becomes two after one division, four after two divisions, and so on, resulting in $2^n$ cells after $n$ divisions.
+Biological "cell division" is a typical example of exponential order growth: the initial state is $1$ cell, after one round of division it becomes $2$, after two rounds it becomes $4$, and so on; after $n$ rounds of division there are $2^n$ cells.
-The figure below and code simulate the cell division process, with a time complexity of $O(2^n)$:
+The figure below and the following code simulate the cell division process, with a time complexity of $O(2^n)$. Note that the input $n$ represents the number of division rounds, and the return value `count` represents the total number of divisions.
```src
[file]{time_complexity}-[class]{}-[func]{exponential}
```
-
+
-In practice, exponential order often appears in recursive functions. For example, in the code below, it recursively splits into two halves, stopping after $n$ divisions:
+In actual algorithms, exponential order often appears in recursive functions. For example, in the following code, it recursively splits in two, stopping after $n$ splits:
```src
[file]{time_complexity}-[class]{}-[func]{exp_recur}
```
-Exponential order growth is extremely rapid and is commonly seen in exhaustive search methods (brute force, backtracking, etc.). For large-scale problems, exponential order is unacceptable, often requiring dynamic programming or greedy algorithms as solutions.
+Exponential order growth is very rapid and is common in exhaustive methods (brute force search, backtracking, etc.). For problems with large data scales, exponential order is unacceptable and typically requires dynamic programming or greedy algorithms to solve.
### Logarithmic order $O(\log n)$
-In contrast to exponential order, logarithmic order reflects situations where "the size is halved each round." Given an input data size $n$, since the size is halved each round, the number of iterations is $\log_2 n$, the inverse function of $2^n$.
+In contrast to exponential order, logarithmic order reflects the situation of "reducing to half each round". Let the input data size be $n$. Since it is reduced to half each round, the number of loops is $\log_2 n$, which is the inverse function of $2^n$.
-The figure below and code simulate the "halving each round" process, with a time complexity of $O(\log_2 n)$, commonly abbreviated as $O(\log n)$:
+The figure below and the following code simulate the process of "reducing to half each round", with a time complexity of $O(\log_2 n)$, abbreviated as $O(\log n)$:
```src
[file]{time_complexity}-[class]{}-[func]{logarithmic}
```
-
+
-Like exponential order, logarithmic order also frequently appears in recursive functions. The code below forms a recursive tree of height $\log_2 n$:
+Like exponential order, logarithmic order also commonly appears in recursive functions. The following code forms a recursion tree of height $\log_2 n$:
```src
[file]{time_complexity}-[class]{}-[func]{log_recur}
```
-Logarithmic order is typical in algorithms based on the divide-and-conquer strategy, embodying the "split into many" and "simplify complex problems" approach. It's slow-growing and is the most ideal time complexity after constant order.
+Logarithmic order commonly appears in algorithms based on the divide-and-conquer strategy, embodying the algorithmic thinking of "dividing into many" and "simplifying complexity". It grows slowly and is the ideal time complexity second only to constant order.
!!! tip "What is the base of $O(\log n)$?"
- Technically, "splitting into $m$" corresponds to a time complexity of $O(\log_m n)$. Using the logarithm base change formula, we can equate different logarithmic complexities:
+ To be precise, "dividing into $m$" corresponds to a time complexity of $O(\log_m n)$. And through the logarithmic base change formula, we can obtain time complexities with different bases that are equal:
$$
O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n)
$$
- This means the base $m$ can be changed without affecting the complexity. Therefore, we often omit the base $m$ and simply denote logarithmic order as $O(\log n)$.
+ That is to say, the base $m$ can be converted without affecting the complexity. Therefore, we usually omit the base $m$ and denote logarithmic order simply as $O(\log n)$.
-### Linear-logarithmic order $O(n \log n)$
+### Linearithmic order $O(n \log n)$
-Linear-logarithmic order often appears in nested loops, with the complexities of the two loops being $O(\log n)$ and $O(n)$ respectively. The related code is as follows:
+Linearithmic order commonly appears in nested loops, where the time complexities of the two layers of loops are $O(\log n)$ and $O(n)$ respectively. The relevant code is as follows:
```src
[file]{time_complexity}-[class]{}-[func]{linear_log_recur}
```
-The figure below demonstrates how linear-logarithmic order is generated. Each level of a binary tree has $n$ operations, and the tree has $\log_2 n + 1$ levels, resulting in a time complexity of $O(n \log n)$.
+The figure below shows how linearithmic order is generated. Each level of the binary tree has a total of $n$ operations, and the tree has $\log_2 n + 1$ levels, resulting in a time complexity of $O(n \log n)$.
-
+
-Mainstream sorting algorithms typically have a time complexity of $O(n \log n)$, such as quicksort, mergesort, and heapsort.
+Mainstream sorting algorithms typically have a time complexity of $O(n \log n)$, such as quicksort, merge sort, and heap sort.
### Factorial order $O(n!)$
-Factorial order corresponds to the mathematical problem of "full permutation." Given $n$ distinct elements, the total number of possible permutations is:
+Factorial order corresponds to the mathematical "permutation" problem. Given $n$ distinct elements, find all possible permutation schemes; the number of schemes is:
$$
n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1
$$
-Factorials are typically implemented using recursion. As shown in the code and the figure below, the first level splits into $n$ branches, the second level into $n - 1$ branches, and so on, stopping after the $n$th level:
+Factorials are typically implemented using recursion. As shown in the figure below and the following code, the first level splits into $n$ branches, the second level splits into $n - 1$ branches, and so on, until the $n$-th level when splitting stops:
```src
[file]{time_complexity}-[class]{}-[func]{factorial_recur}
```
-
+
-Note that factorial order grows even faster than exponential order; it's unacceptable for larger $n$ values.
+Note that because when $n \geq 4$ we always have $n! > 2^n$, factorial order grows faster than exponential order, and is also unacceptable for large $n$.
## Worst, best, and average time complexities
-**The time efficiency of an algorithm is often not fixed but depends on the distribution of the input data**. Assume we have an array `nums` of length $n$, consisting of numbers from $1$ to $n$, each appearing only once, but in a randomly shuffled order. The task is to return the index of the element $1$. We can draw the following conclusions:
+**The time efficiency of an algorithm is often not fixed, but is related to the distribution of the input data**. Suppose we input an array `nums` of length $n$, where `nums` consists of numbers from $1$ to $n$, with each number appearing only once, but the element order is randomly shuffled. The task is to return the index of element $1$. We can draw the following conclusions.
-- When `nums = [?, ?, ..., 1]`, that is, when the last element is $1$, it requires a complete traversal of the array, **achieving the worst-case time complexity of $O(n)$**.
-- When `nums = [1, ?, ?, ...]`, that is, when the first element is $1$, no matter the length of the array, no further traversal is needed, **achieving the best-case time complexity of $\Omega(1)$**.
+- When `nums = [?, ?, ..., 1]`, i.e., when the last element is $1$, it requires a complete traversal of the array, **reaching worst-case time complexity $O(n)$**.
+- When `nums = [1, ?, ?, ...]`, i.e., when the first element is $1$, no matter how long the array is, there is no need to continue traversing, **reaching best-case time complexity $\Omega(1)$**.
-The "worst-case time complexity" corresponds to the asymptotic upper bound, denoted by the big $O$ notation. Correspondingly, the "best-case time complexity" corresponds to the asymptotic lower bound, denoted by $\Omega$:
+The "worst-case time complexity" corresponds to the function's asymptotic upper bound, denoted using big-$O$ notation. Correspondingly, the "best-case time complexity" corresponds to the function's asymptotic lower bound, denoted using $\Omega$ notation:
```src
[file]{worst_best_time_complexity}-[class]{}-[func]{find_one}
```
-It's important to note that the best-case time complexity is rarely used in practice, as it is usually only achievable under very low probabilities and might be misleading. **The worst-case time complexity is more practical as it provides a safety value for efficiency**, allowing us to confidently use the algorithm.
+It is worth noting that we rarely use best-case time complexity in practice, because it can usually only be achieved with a very small probability and may be somewhat misleading. **The worst-case time complexity is more practical because it gives a safety value for efficiency**, allowing us to use the algorithm with confidence.
-From the above example, it's clear that both the worst-case and best-case time complexities only occur under "special data distributions," which may have a small probability of occurrence and may not accurately reflect the algorithm's run efficiency. In contrast, **the average time complexity can reflect the algorithm's efficiency under random input data**, denoted by the $\Theta$ notation.
+From the above example, we can see that both worst-case and best-case time complexities only occur under "special data distributions", which may have a very small probability of occurrence and may not truly reflect the algorithm's running efficiency. In contrast, **average time complexity can reflect the algorithm's running efficiency under random input data**, denoted using the $\Theta$ notation.
-For some algorithms, we can simply estimate the average case under a random data distribution. For example, in the aforementioned example, since the input array is shuffled, the probability of element $1$ appearing at any index is equal. Therefore, the average number of loops for the algorithm is half the length of the array $n / 2$, giving an average time complexity of $\Theta(n / 2) = \Theta(n)$.
+For some algorithms, we can simply derive the average case under random data distribution. For example, in the above example, since the input array is shuffled, the probability of element $1$ appearing at any index is equal, so the algorithm's average number of loops is half the array length $n / 2$, giving an average time complexity of $\Theta(n / 2) = \Theta(n)$.
-However, calculating the average time complexity for more complex algorithms can be quite difficult, as it's challenging to analyze the overall mathematical expectation under the data distribution. In such cases, we usually use the worst-case time complexity as the standard for judging the efficiency of the algorithm.
+But for more complex algorithms, calculating average time complexity is often quite difficult, because it is hard to analyze the overall mathematical expectation under data distribution. In this case, we usually use worst-case time complexity as the criterion for judging algorithm efficiency.
!!! question "Why is the $\Theta$ symbol rarely seen?"
- Possibly because the $O$ notation is more commonly spoken, it is often used to represent the average time complexity. However, strictly speaking, this practice is not accurate. In this book and other materials, if you encounter statements like "average time complexity $O(n)$", please understand it directly as $\Theta(n)$.
+ This may be because the $O$ symbol is too catchy, so we often use it to represent average time complexity. But strictly speaking, this practice is not standard. In this book and other materials, if you encounter expressions like "average time complexity $O(n)$", please understand it directly as $\Theta(n)$.
diff --git a/en/docs/chapter_data_structure/basic_data_types.md b/en/docs/chapter_data_structure/basic_data_types.md
index 126666852..c75b2c040 100644
--- a/en/docs/chapter_data_structure/basic_data_types.md
+++ b/en/docs/chapter_data_structure/basic_data_types.md
@@ -1,66 +1,66 @@
-# Basic data types
+# 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.
+When we talk about data in computers, we think of various forms such as text, images, videos, audio, 3D models, and more. Although these data are organized in different ways, 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.
+**Basic data types are types that the CPU can directly operate on**, and they 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.
+- Integer types `byte`, `short`, `int`, `long`.
+- Floating-point types `float`, `double`, used to represent decimal numbers.
+- Character type `char`, used to represent letters, punctuation marks, and even emojis in various languages.
+- Boolean type `bool`, used to represent "yes" and "no" judgments.
-**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.
+**Basic data types are stored in binary form in computers**. One binary bit 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 range of values for basic data types depends on the size of the space they occupy. Below is an example using Java.
-- 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.
+- Integer type `byte` occupies $1$ byte = $8$ bits, and can represent $2^{8}$ numbers.
+- 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.
+The following table lists the space occupied, value ranges, and default values of various basic data types in Java. You don't need to memorize this table; a general understanding is sufficient, and you can refer to it when needed.
- Table Space occupied and value range of basic data types
+ Table Space occupied and value ranges of basic data types
-| 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}$ |
+| 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$ |
+| Character | `char` | 2 bytes | $0$ | $2^{16} - 1$ | $0$ |
+| Boolean | `bool` | 1 byte | $\text{false}$ | $\text{true}$ | $\text{false}$ |
-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.
+Please note that the above table is specific to Java's basic data types. Each programming language has its own data type definitions, and their space occupied, value ranges, and default values may vary.
-- 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.
+- In Python, the integer type `int` can be of any size, limited only by available memory; the floating-point type `float` is double-precision 64-bit; there is no `char` type, a single character is actually a string `str` of length 1.
+- C and C++ do not explicitly specify the size of basic data types, which varies by implementation and platform. The above table follows the LP64 [data model](https://en.cppreference.com/w/cpp/language/types#Properties), which is used in Unix 64-bit operating systems including Linux and macOS.
+- The size of character `char` is 1 byte in C and C++, and in most programming languages it depends on the specific character encoding method, as detailed in the "Character Encoding" section.
+- Even though representing a boolean value requires only 1 bit ($0$ or $1$), it is usually stored as 1 byte in memory. This is because modern computer CPUs typically use 1 byte as the minimum 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".
+So, what is the relationship between basic data types and data structures? We know that data structures are ways of organizing and storing data in computers. The subject of this statement is "structure", not "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".
+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 order relationships of numbers, but the content stored—whether integer `int`, floating-point `float`, or character `char`—is unrelated 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.
+In other words, **basic data types provide the "content type" of data, while data structures provide the "organization method" of 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
+ # Initialize arrays using various basic data types
numbers: list[int] = [0] * 5
decimals: list[float] = [0.0] * 5
- # Python's characters are actually strings of length 1
+ # In Python, 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
+ # Python 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
+ // Initialize arrays using various basic data types
int numbers[5];
float decimals[5];
char characters[5];
@@ -70,7 +70,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Java"
```java title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
@@ -80,7 +80,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "C#"
```csharp title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
@@ -90,7 +90,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Go"
```go title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
var numbers = [5]int{}
var decimals = [5]float64{}
var characters = [5]byte{}
@@ -100,7 +100,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Swift"
```swift title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
let numbers = Array(repeating: 0, count: 5)
let decimals = Array(repeating: 0.0, count: 5)
let characters: [Character] = Array(repeating: "a", count: 5)
@@ -110,14 +110,14 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "JS"
```javascript title=""
- // JavaScript's arrays can freely store various basic data types and objects
+ // JavaScript 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
+ // Initialize arrays using various basic data types
const numbers: number[] = [];
const characters: string[] = [];
const bools: boolean[] = [];
@@ -126,7 +126,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Dart"
```dart title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
List numbers = List.filled(5, 0);
List decimals = List.filled(5, 0.0);
List characters = List.filled(5, 'a');
@@ -136,9 +136,9 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Rust"
```rust title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
let numbers: Vec = vec![0; 5];
- let decimals: Vec = vec![0.0, 5];
+ let decimals: Vec = vec![0.0; 5];
let characters: Vec = vec!['0'; 5];
let bools: Vec = vec![false; 5];
```
@@ -146,7 +146,7 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "C"
```c title=""
- // Using various basic data types to initialize arrays
+ // Initialize arrays using various basic data types
int numbers[10];
float decimals[10];
char characters[10];
@@ -156,15 +156,38 @@ In other words, **basic data types provide the "content type" of data, while dat
=== "Kotlin"
```kotlin title=""
+ // Initialize arrays using various basic data types
+ val numbers = IntArray(5)
+ val decinals = FloatArray(5)
+ val characters = CharArray(5)
+ val bools = BooleanArray(5)
+ ```
+=== "Ruby"
+
+ ```ruby title=""
+ # Ruby lists can freely store various basic data types and object references
+ data = [0, 0.0, 'a', false, ListNode(0)]
```
=== "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;
+ const hello = [5]u8{ 'h', 'e', 'l', 'l', 'o' };
+ // The above code demonstrates how to define a literal array, where you can choose to specify the array size or use _ instead. When using _, Zig will attempt to automatically calculate the array length
+
+ const matrix_4x4 = [4][4]f32{
+ [_]f32{ 1.0, 0.0, 0.0, 0.0 },
+ [_]f32{ 0.0, 1.0, 0.0, 1.0 },
+ [_]f32{ 0.0, 0.0, 1.0, 0.0 },
+ [_]f32{ 0.0, 0.0, 0.0, 1.0 },
+ };
+ // Multidimensional arrays (matrices) are actually nested arrays, and we can easily create a multidimensional array
+
+ const array = [_:0]u8{ 1, 2, 3, 4 };
+ // Define a sentinel-terminated array. Essentially, this is to be compatible with C's specified string terminator \0. We use the syntax [N:x]T to describe an array with elements of type T and length N, where the value at index N should be x
```
+
+??? pythontutor "Visualized Execution"
+
+ https://pythontutor.com/render.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%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
diff --git a/en/docs/chapter_data_structure/character_encoding.md b/en/docs/chapter_data_structure/character_encoding.md
index 8ba4bd1ca..36cd6cec3 100644
--- a/en/docs/chapter_data_structure/character_encoding.md
+++ b/en/docs/chapter_data_structure/character_encoding.md
@@ -1,87 +1,87 @@
-# Character encoding *
+# Character Encoding *
-In the computer system, all data is stored in binary form, and `char` is 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.
+In computers, all data is stored in binary form, and character `char` is no exception. To represent characters, we need to establish a "character set" that defines a one-to-one correspondence between each character and binary numbers. With a character set, computers can convert binary numbers to characters by looking up the table.
-## ASCII character set
+## 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 below, ASCII includes uppercase and lowercase English letters, numbers 0 ~ 9, various punctuation marks, and certain control characters (such as newline and tab).
+ASCII code is the earliest character set, with the full name American Standard Code for Information Interchange. It uses 7 binary bits (the lower 7 bits of one byte) to represent a character, and can represent a maximum of 128 different characters. As shown in the figure below, ASCII code includes uppercase and lowercase English letters, numbers 0 ~ 9, some punctuation marks, and some control characters (such as newline and tab).

-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.
+However, **ASCII code can only represent English**. With the globalization of computers, a character set called EASCII that can represent more languages emerged. It expands from the 7-bit basis of ASCII to 8 bits, and can represent 256 different 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.
+Worldwide, a batch of EASCII character sets suitable for different regions have appeared successively. The first 128 characters of these character sets are unified as ASCII code, and the last 128 characters are defined differently to adapt to the needs of different languages.
-## GBK character set
+## 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.
+Later, people found that **EASCII code still cannot meet the character quantity requirements of many languages**. For example, there are nearly one hundred thousand Chinese characters, and several thousand are used daily. In 1980, the China National Standardization Administration released the GB2312 character set, which included 6,763 Chinese characters, basically meeting the needs for computer processing of Chinese characters.
-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.
+However, GB2312 cannot handle some rare characters and traditional Chinese characters. The GBK character set is an extension based on GB2312, which includes a total of 21,886 Chinese characters. In the GBK encoding scheme, ASCII characters are represented using one byte, and Chinese characters are represented using two bytes.
-## Unicode character set
+## 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.
+With the vigorous development of computer technology, character sets and encoding standards flourished, which brought many problems. On the one hand, these character sets generally only define characters for specific languages and cannot work normally in multilingual environments. On the other hand, multiple character set standards exist for the same language, and if two computers use different encoding standards, garbled characters will appear during information transmission.
-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.
+Researchers of that era thought: **If a sufficiently complete character set is released that includes all languages and symbols in the world, wouldn't it be possible to solve cross-language environment and garbled character problems**? Driven by this idea, a large and comprehensive 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.
+Unicode is called "统一码" (Unified Code) in Chinese and can theoretically accommodate over one million characters. It is committed to including characters from around the world into a unified character set, providing a universal character set to handle and display various language texts, reducing garbled character problems caused by 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.
+Since its release in 1991, Unicode has continuously expanded to include new languages and characters. As of September 2022, Unicode has included 149,186 characters, including characters, symbols, and even emojis from various languages. In the vast Unicode character set, commonly used characters occupy 2 bytes, and some rare characters occupy 3 bytes 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?
+Unicode is a universal character set that essentially assigns a number (called a "code point") to each character, **but it does not specify how to store these character code points in computers**. We can't help but ask: when Unicode code points of multiple lengths appear simultaneously in a text, how does the system parse the characters? For example, given an encoding with a length of 2 bytes, how does the system determine whether it is one 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 below, 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.
+For the above problem, **a straightforward solution is to store all characters as equal-length encodings**. As shown in the figure below, each character in "Hello" occupies 1 byte, and each character in "算法" (algorithm) occupies 2 bytes. We can encode all characters in "Hello 算法" as 2 bytes in length by padding the high bits with 0. In this way, the system can parse one character every 2 bytes and restore the content of this phrase.

-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.
+However, ASCII code has already proven to us that encoding English only requires 1 byte. If the above scheme is adopted, the size of English text will be twice that under ASCII encoding, which is very wasteful of memory space. Therefore, we need a more efficient Unicode encoding method.
-## UTF-8 encoding
+## 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.
+Currently, UTF-8 has become the most widely used Unicode encoding method internationally. **It is a variable-length encoding** that uses 1 to 4 bytes to represent a character, depending on the complexity of the character. ASCII characters only require 1 byte, Latin and Greek letters require 2 bytes, commonly used Chinese characters require 3 bytes, and some other rare characters require 4 bytes.
-The encoding rules for UTF-8 are not complex and can be divided into two cases:
+The encoding rules of UTF-8 are not complicated and can be divided into the following 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.
+- For 1-byte characters, set the highest bit to $0$, and set the remaining 7 bits to the Unicode code point. It is worth noting that ASCII characters occupy the first 128 code points in the Unicode character set. That is to say, **UTF-8 encoding is backward compatible with ASCII code**. This means we can use UTF-8 to parse very old ASCII code text.
+- For characters with a length of $n$ bytes (where $n > 1$), set the highest $n$ bits of the first byte to $1$, and set the $(n + 1)$-th bit to $0$; starting from the second byte, set the highest 2 bits of each byte to $10$; use all remaining bits to fill in the Unicode code point of the character.
-The figure below 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$.
+The figure below shows the UTF-8 encoding corresponding to "Hello算法". It can be observed that since the highest $n$ bits are all set to $1$, the system can parse the length of the character as $n$ by reading the number of highest bits that are $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.
+But why set the highest 2 bits of all other bytes to $10$? In fact, this $10$ can serve as a check symbol. Assuming the system starts parsing text from an incorrect byte, the $10$ at the beginning of the byte can help the system quickly determine an anomaly.
-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.
+The reason for using $10$ as a check symbol is that under UTF-8 encoding rules, it is impossible for a character's highest two bits to be $10$. This conclusion can be proven by contradiction: assuming the highest two bits of a character are $10$, it means the length of the character is $1$, corresponding to ASCII code. However, the highest bit of ASCII code should be $0$, which contradicts the assumption.

-Apart from UTF-8, other common encoding methods include:
+In addition to UTF-8, common encoding methods also include the following two.
-- **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.
+- **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 need to use 4 bytes. For 2-byte characters, UTF-16 encoding is equal to the Unicode code point.
+- **UTF-32 encoding**: Every character uses 4 bytes. This means that UTF-32 takes up more space than UTF-8 and UTF-16, especially for text 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 the perspective of storage space occupation, using UTF-8 to represent English characters is very efficient because it only requires 1 byte; using UTF-16 encoding for some non-English characters (such as Chinese) will be more efficient because it only requires 2 bytes, while UTF-8 may require 3 bytes.
-From a compatibility perspective, UTF-8 is the most versatile, with many tools and libraries supporting UTF-8 as a priority.
+From a compatibility perspective, UTF-8 has the best universality, and many tools and libraries support UTF-8 first.
-## Character encoding in programming languages
+## 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:
+For most past programming languages, strings during program execution use fixed-length encodings such as UTF-16 or UTF-32. Under fixed-length encoding, we can treat strings as arrays for processing, and this approach has the following 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.
+- **Random access**: UTF-16 encoded strings can be easily accessed randomly. UTF-8 is a variable-length encoding. To find the $i$-th character, we need to traverse from the beginning of the string to the $i$-th character, which requires $O(n)$ time.
+- **Character counting**: Similar to random access, calculating the length of a UTF-16 encoded string is also an $O(1)$ operation. However, calculating the length of a UTF-8 encoded string requires traversing the entire string.
+- **String operations**: Many string operations (such as splitting, joining, inserting, deleting, etc.) on UTF-16 encoded strings are easier to perform. Performing these operations on UTF-8 encoded strings usually requires additional calculations to ensure that invalid UTF-8 encoding is not generated.
-The design of character encoding schemes in programming languages is an interesting topic involving various factors:
+In fact, the design of character encoding schemes for programming languages is a very interesting topic involving many factors.
-- Java’s `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.
+- Java's `String` type uses UTF-16 encoding, with each character occupying 2 bytes. This is because at the beginning of Java language design, people believed that 16 bits were sufficient to represent all possible characters. However, this was an incorrect judgment. Later, the Unicode specification expanded beyond 16 bits, so characters in Java may now be represented by a pair of 16-bit values (called "surrogate pairs").
+- The strings of JavaScript and TypeScript use UTF-16 encoding for reasons similar to Java. When Netscape first introduced the JavaScript language in 1995, Unicode was still in its early stages of development, and at that time, using 16-bit encoding was sufficient to represent all Unicode characters.
+- C# uses UTF-16 encoding mainly because the .NET platform was designed by Microsoft, and many of Microsoft's 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.
+Due to the underestimation of character quantities by the above programming languages, they had to adopt the "surrogate pair" method to represent Unicode characters with lengths exceeding 16 bits. This is a reluctant compromise. On the one hand, in strings containing surrogate pairs, one character may occupy 2 bytes or 4 bytes, thus losing the advantage of fixed-length encoding. On the other hand, handling surrogate pairs requires additional code, which increases the complexity and difficulty of debugging in programming.
-Addressing these challenges, some languages have adopted alternative encoding strategies:
+For the above reasons, some programming languages have proposed different encoding schemes.
-- Python’s `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.
-- Go’s `string` type internally uses UTF-8 encoding. Go also provides the `rune` type for representing individual Unicode code points.
-- Rust’s `str` and `String` types use UTF-8 encoding internally. Rust also offers the `char` type for individual Unicode code points.
+- Python's `str` uses Unicode encoding and adopts a flexible string representation where the stored character length depends on the largest Unicode code point in the string. If all characters in the string are ASCII characters, each character occupies 1 byte; if there are characters exceeding the ASCII range but all within the Basic Multilingual Plane (BMP), each character occupies 2 bytes; if there are characters exceeding the BMP, each character occupies 4 bytes.
+- Go language's `string` type uses UTF-8 encoding internally. Go language also provides the `rune` type, which is used to represent a single Unicode code point.
+- Rust language's `str` and `String` types use UTF-8 encoding internally. Rust also provides the `char` type for representing a single Unicode code point.
-It’s 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.
+It should be noted that the above discussion is about how strings are stored in programming languages, **which is different from how strings are stored in files or transmitted over networks**. In file storage or network transmission, we usually encode strings into UTF-8 format to achieve optimal compatibility and space efficiency.
diff --git a/en/docs/chapter_data_structure/classification_of_data_structure.md b/en/docs/chapter_data_structure/classification_of_data_structure.md
index 548e08c64..e260d9146 100644
--- a/en/docs/chapter_data_structure/classification_of_data_structure.md
+++ b/en/docs/chapter_data_structure/classification_of_data_structure.md
@@ -1,48 +1,48 @@
-# Classification of data structures
+# 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".
+Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified from two dimensions: "logical structure" and "physical structure".
-## Logical structure: linear and non-linear
+## 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.
+**Logical structure reveals the logical relationships between data elements**. In arrays and linked lists, data is arranged in a certain order, embodying the linear relationship between data; while in trees, data is arranged hierarchically from top to bottom, showing the derived relationship between "ancestors" and "descendants"; graphs are composed of nodes and edges, reflecting complex network relationships.
-As shown in the figure below, 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.
+As shown in the figure below, logical structures can be divided into two major categories: "linear" and "non-linear". Linear structures are more intuitive, indicating that data is linearly arranged in logical relationships; non-linear structures are the opposite, arranged non-linearly.
-- **Linear data structures**: Arrays, Linked Lists, Stacks, Queues, Hash Tables, where elements have a one-to-one sequential relationship.
-- **Non-linear data structures**: Trees, Heaps, Graphs, Hash Tables.
+- **Linear data structures**: Arrays, linked lists, stacks, queues, hash tables, where elements have a one-to-one sequential relationship.
+- **Non-linear data structures**: Trees, heaps, graphs, hash tables.
Non-linear data structures can be further divided into tree structures and network structures.
-- **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.
+- **Tree structures**: Trees, heaps, hash tables, where elements have a one-to-many relationship.
+- **Network structures**: Graphs, where elements have a many-to-many relationship.

-## Physical structure: contiguous and dispersed
+## Physical Structure: Contiguous and Dispersed
-**During the execution of an algorithm, the data being processed is stored in memory**. The figure below 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.
+**When an algorithm program runs, the data being processed is mainly stored in memory**. The figure below shows a computer memory stick, where each black square contains a memory space. We can imagine memory as a huge Excel spreadsheet, where each cell can store 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 below, 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.
+**The system accesses data at the target location through memory addresses**. As shown in the figure below, the computer assigns a number to each cell in the spreadsheet according to specific rules, ensuring that each memory space has a unique memory address. With these addresses, the program can access data in memory.
-
+
!!! 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.
+ It is worth noting that comparing memory to an Excel spreadsheet is a simplified analogy. The actual working mechanism of memory is quite complex, involving concepts such as 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, memory resources are an important consideration in the design of 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.
+Memory is a shared resource for all programs. When a block of memory is occupied by a program, it usually cannot be used by other programs at the same time. **Therefore, in the design of data structures and algorithms, memory resources are an important consideration**. For example, the peak memory occupied by an algorithm should not exceed the remaining free memory of the system; if there is a lack of contiguous large memory blocks, then the data structure chosen must be able to be stored in dispersed memory spaces.
-As illustrated in the figure below, **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.
+As shown in the figure below, **physical structure reflects the way data is stored in computer memory**, and can be divided into contiguous space storage (arrays) and dispersed space storage (linked lists). The two physical structures exhibit complementary characteristics in terms of time efficiency and space efficiency.

-**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.
+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 the implementation of hash tables may include 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.
+- **Can be implemented based on arrays**: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimensions $\geq 3$), etc.
+- **Can be implemented based on linked lists**: 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.
+After initialization, linked lists can still adjust their length during program execution, so they are also called "dynamic data structures". After initialization, the length of arrays cannot be changed, so they are also called "static data structures". It is worth noting that arrays can achieve length changes by reallocating memory, thus possessing a certain degree of "dynamism".
!!! 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.
+ If you find it difficult to understand physical structure, it is recommended to read the next chapter first, and then review this section.
diff --git a/en/docs/chapter_data_structure/index.md b/en/docs/chapter_data_structure/index.md
index a417f14e2..c05957eb3 100644
--- a/en/docs/chapter_data_structure/index.md
+++ b/en/docs/chapter_data_structure/index.md
@@ -1,9 +1,9 @@
-# Data structures
+# Data Structure
-
+
!!! abstract
- Data structures serve as a robust and diverse framework.
+ Data structure is like a sturdy and diverse framework.
- They offer a blueprint for the orderly organization of data, upon which algorithms come to life.
+ It provides a blueprint for the orderly organization of data, upon which algorithms come to life.
diff --git a/en/docs/chapter_data_structure/number_encoding.md b/en/docs/chapter_data_structure/number_encoding.md
index 41703f756..c1a74376f 100644
--- a/en/docs/chapter_data_structure/number_encoding.md
+++ b/en/docs/chapter_data_structure/number_encoding.md
@@ -1,24 +1,24 @@
-# Number encoding *
+# Number Encoding *
!!! tip
- 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.
+ 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.
-## Integer encoding
+## Sign-Magnitude, 1's Complement, and 2's Complement
-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.
+In the table from the previous section, we found that all integer types can represent one more negative number than positive numbers. For example, the `byte` range is $[-128, 127]$. This phenomenon is counterintuitive, and its underlying reason involves knowledge of sign-magnitude, 1's complement, and 2's complement.
-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:
+First, it should be noted that **numbers are stored in computers in the form of "2's complement"**. Before analyzing the reasons for this, let's first define these three concepts.
-- **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.
+- **Sign-magnitude**: We treat the highest bit of the binary representation of a number as the sign bit, where $0$ represents a positive number and $1$ represents a negative number, and the remaining bits represent the value of the number.
+- **1's complement**: The 1's complement of a positive number is the same as its sign-magnitude. For a negative number, the 1's complement is obtained by inverting all bits except the sign bit of its sign-magnitude.
+- **2's complement**: The 2's complement of a positive number is the same as its sign-magnitude. For a negative number, the 2's complement is obtained by adding $1$ to its 1's complement.
-The figure below illustrates the conversions among sign-magnitude, one's complement, and two's complement:
+The figure below shows the conversion methods among sign-magnitude, 1's complement, and 2's complement.
-
+
-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.
+Sign-magnitude, although the most intuitive, has some limitations. On one hand, **the sign-magnitude of negative numbers cannot be directly used in operations**. For example, calculating $1 + (-2)$ in sign-magnitude yields $-3$, which is clearly incorrect.
$$
\begin{aligned}
@@ -29,20 +29,20 @@ $$
\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$.
+To solve this problem, computers introduced 1's complement. If we first convert sign-magnitude to 1's complement and calculate $1 + (-2)$ in 1's complement, then convert the result back to sign-magnitude, we can obtain 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
+& = 0000 \; 0001 \; \text{(1's complement)} + 1111 \; 1101 \; \text{(1's complement)} \newline
+& = 1111 \; 1110 \; \text{(1'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.
+On the other hand, **the sign-magnitude of the number zero has two representations, $+0$ and $-0$**. This means that the number zero corresponds to two different binary encodings, which may cause ambiguity. For example, in conditional judgments, if we don't distinguish between positive zero and negative zero, it may lead to incorrect judgment results. If we want to handle the ambiguity of positive and negative zero, we need to introduce additional judgment operations, which may reduce the computational efficiency of the computer.
$$
\begin{aligned}
@@ -51,67 +51,67 @@ $$
\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:
+Like sign-magnitude, 1's complement also has the problem of positive and negative zero ambiguity. Therefore, computers further introduced 2's complement. Let's first observe the conversion process of negative zero from sign-magnitude to 1's complement to 2'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
+= \; & 1111 \; 1111 \; \text{(1's complement)} \newline
+= 1 \; & 0000 \; 0000 \; \text{(2'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.
+Adding $1$ to the 1's complement of negative zero produces a carry, but since the `byte` type has a length of only 8 bits, the $1$ that overflows to the 9th bit is discarded. That is to say, **the 2's complement of negative zero is $0000 \; 0000$, which is the same as the 2's complement of positive zero**. This means that in 2's complement representation, there is only one zero, and the positive and negative zero ambiguity is thus resolved.
-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.
+One last question remains: the range of the `byte` type is $[-128, 127]$, and how is the extra negative number $-128$ obtained? We notice that all integers in the interval $[-127, +127]$ have corresponding sign-magnitude, 1's complement, and 2's complement, and sign-magnitude and 2's complement can be converted to each other.
-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$.
+However, **the 2's complement $1000 \; 0000$ is an exception, and it does not have a corresponding sign-magnitude**. According to the conversion method, we get that the sign-magnitude of this 2's complement is $0000 \; 0000$. This is clearly contradictory because this sign-magnitude represents the number $0$, and its 2's complement should be itself. The computer specifies that this special 2's complement $1000 \; 0000$ represents $-128$. In fact, the result of calculating $(-1) + (-127)$ in 2's complement is $-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
+& = 1000 \; 0000 \; \text{(1's complement)} + 1111 \; 1110 \; \text{(1's complement)} \newline
+& = 1000 \; 0001 \; \text{(2's complement)} + 1111 \; 1111 \; \text{(2's complement)} \newline
+& = 1000 \; 0000 \; \text{(2'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.
+You may have noticed that all the above calculations are addition operations. This hints at an important fact: **the hardware circuits inside computers are mainly designed based on addition operations**. This is because addition operations are simpler to implement in hardware compared to other operations (such as multiplication, division, and subtraction), easier to parallelize, and have faster operation speeds.
-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.
+Please note that this does not mean that computers can only perform addition. **By combining addition with some basic logical operations, computers can implement various other mathematical operations**. For example, calculating the subtraction $a - b$ can be converted to calculating the addition $a + (-b)$; calculating multiplication and division can be converted to calculating 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.
+Now we can summarize the reasons why computers use 2's complement: based on 2's complement representation, computers can use the same circuits and operations to handle the addition of positive and negative numbers, without the need to design special hardware circuits to handle subtraction, and without the need to specially handle the ambiguity problem of positive and negative zero. This greatly simplifies hardware design and improves operational 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.
+The design of 2's complement is very ingenious. Due to space limitations, we will stop here. Interested readers are encouraged to explore further.
-## Floating-point number encoding
+## 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.
+Careful readers may have noticed: `int` and `float` have the same length, both are 4 bytes, but why does `float` have a much larger range than `int`? This is very counterintuitive because it stands to reason that `float` needs to represent decimals, so the range should be smaller.
-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:
+In fact, **this is because floating-point number `float` uses a different representation method**. Let's denote 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:
+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$.
+- 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:
+The calculation method for the value corresponding to the binary `float` is:
$$
-\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
+\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:
+Converted to decimal, the calculation formula is:
$$
-\text{val} = (-1)^{\mathrm{S}} \times 2^{\mathrm{E} - 127} \times (1 + \mathrm{N})
+\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N})
$$
The range of each component is:
@@ -119,21 +119,21 @@ 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}]
+(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}]
\end{aligned}
$$
-
+
-Observing the figure above, given an example data $\mathrm{S} = 0$, $\mathrm{E} = 124$, $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$, we have:
+Observing the figure above, given 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
+\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.
+Now we can answer the initial question: **the representation of `float` includes an exponent bit, resulting in a range far greater than `int`**. According to the above calculation, the maximum positive number that `float` can represent is $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$, and the minimum negative number can be 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.
+**Although floating-point number `float` expands the range, its side effect is sacrificing precision**. The integer type `int` uses all 32 bits to represent numbers, and the numbers are evenly distributed; however, due to the existence of the exponent bit, the larger the value of floating-point number `float`, the larger the difference between two adjacent numbers tends to be.
As shown in the table below, exponent bits $\mathrm{E} = 0$ and $\mathrm{E} = 255$ have special meanings, **used to represent zero, infinity, $\mathrm{NaN}$, etc.**
@@ -141,10 +141,10 @@ As shown in the table below, exponent bits $\mathrm{E} = 0$ and $\mathrm{E} = 25
| 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})$ |
+| $0$ | $\pm 0$ | Subnormal Number | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ |
+| $1, 2, \dots, 254$ | Normal Number | Normal Number | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ |
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
-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}$.
+It is 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.
+Double-precision `double` also uses a representation method similar to `float`, which will not be elaborated here.
diff --git a/en/docs/chapter_data_structure/summary.md b/en/docs/chapter_data_structure/summary.md
index 1defdf79b..28bcbd815 100644
--- a/en/docs/chapter_data_structure/summary.md
+++ b/en/docs/chapter_data_structure/summary.md
@@ -2,65 +2,65 @@
### Key review
-- Data structures can be categorized from two perspectives: logical structure and physical structure. Logical structure describes the logical relationships between data, while physical structure describes how data is stored in memory.
-- Frequently used logical structures include linear structures, trees, and networks. We usually divide 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 is running, data is stored in memory. Each memory space has a corresponding address, and the program accesses data through these addresses.
-- Physical structures can be divided into continuous space storage (arrays) and discrete space storage (linked lists). All data structures are implemented using arrays, linked lists, or a combination of both.
-- The basic data types in computers include integers (`byte`, `short`, `int`, `long`), floating-point numbers (`float`, `double`), characters (`char`), and booleans (`bool`). The value range of a data type depends on its size and representation.
-- Sign-magnitude, 1's complement, 2's complement are three methods of encoding integers in computers, and they can be converted into each other. The most significant bit of the sign-magnitude is the sign bit, and the remaining bits represent the value of the number.
-- Integers are encoded by 2's complement in computers. The benefits of this representation include (i) the computer can unify the addition of positive and negative integers, (ii) no need to design special hardware circuits for subtraction, and (iii) 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 exponent bit, the range of floating-point numbers is much greater than that of integers, but at the cost of precision.
-- ASCII is the earliest English character set, with 1 byte in length and a total of 127 characters. GBK is a popular Chinese character set, which includes more than 20,000 Chinese characters. Unicode aims to provide a complete character set standard that includes characters from various languages in the world, thus solving the garbled character problem caused by inconsistent character encoding methods.
-- UTF-8 is the most popular and general Unicode encoding method. It is a variable-length encoding method with good scalability and space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 takes up less space than UTF-8. Programming languages like Java and C# use UTF-16 encoding by default.
+- Data structures can be classified 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, and network structures. We typically classify data structures as 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 memory 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 `bool`. Their value ranges depend on the size of space they occupy and their representation method.
+- Sign-magnitude, 1's complement, and 2's complement are three methods for encoding numbers in computers, and they can be converted into each other. The most significant bit of sign-magnitude is the sign bit, and the remaining bits represent the value of the number.
+- Integers are stored in computers in 2's complement form. Under 2's complement representation, computers can treat the addition of positive and negative numbers uniformly, without needing to design 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 exponent bits, the range of floating-point numbers is much larger than that of integers, at the cost of sacrificing precision.
+- ASCII is the earliest English character set, with a length of 1 byte, containing a total of 127 characters. GBK is a commonly used Chinese character set, containing over 20,000 Chinese characters. Unicode is committed to providing a complete character set standard, collecting characters from various languages around the world, thereby solving the garbled text problem 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, effectively improving storage space efficiency. UTF-16 and UTF-32 are fixed-length encoding methods. When encoding Chinese characters, UTF-16 occupies less space than UTF-8. Programming languages such as Java and C# use UTF-16 encoding by default.
### Q & A
-**Q**: Why does a hash table contain both linear and non-linear data structures?
+**Q**: Why do hash tables 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" (discussed in a later section, "Hash collision"): each bucket in the array points to a linked list, which may transform into a tree (usually a red-black tree) when its length is larger than a certain threshold.
-From a storage perspective, the underlying structure of a hash table is an array, where each bucket 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).
+The underlying structure of a hash table is an array. To resolve hash collisions, we may use "chaining" (discussed in the subsequent "Hash Collision" section): each bucket in the array points to a linked list, which may be converted to a tree (usually a red-black tree) when the list length exceeds a certain threshold.
+
+From a storage perspective, the underlying structure of a hash table is an array, where each bucket slot may 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 of 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.
+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 store Unicode code points), so the `char` type has a length of 2 bytes.
-**Q**: Is there any ambiguity when we refer to array-based data structures as "static data structures"? The stack can also perform "dynamic" operations such as popping and pushing.
+**Q**: Is there ambiguity in referring to array-based data structures as "static data structures"? Stacks can also perform "dynamic" operations such as push and pop.
-The stack can implement dynamic data operations, but the data structure is still "static" (the length is fixed). Although array-based data structures can dynamically add or remove elements, their capacity is fixed. If the stack size exceeds the pre-allocated size, then the old array will be copied into a newly created and larger array.
+Stacks can indeed implement dynamic data operations, but the data structure is still "static" (fixed length). Although array-based data structures 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 must be copied to the new array.
-**Q**: When building a stack (queue), its size is not specified, so why are they "static data structures"?
+**Q**: When constructing a stack (queue), its size is not specified. Why are they "static data structures"?
-In high-level programming languages, we do not need to manually specify the initial capacity of stacks (queues); this task is automatically completed within the class. For example, the initial capacity of Java's `ArrayList` is usually 10. Furthermore, the expansion operation is also completed automatically. See the subsequent "List" chapter for details.
+In high-level programming languages, we do not need to manually specify the initial capacity of a stack (queue); this work is automatically completed within the class. For example, the initial capacity of Java's `ArrayList` is typically 10. Additionally, the expansion operation is also automatically implemented. See the subsequent "List" section for details.
-**Q**:The method of converting the sign-magnitude to the 2's complement is "first negate and then add 1", so converting the 2's complement to the sign-magnitude should be its inverse operation "first subtract 1 and then negate".
-However, the 2's complement can also be converted to the sign-magnitude through "first negate and then add 1", why is this?
+**Q**: The method of converting sign-magnitude to 2's complement is "first negate then add 1". So converting 2's complement to sign-magnitude should be the inverse operation "first subtract 1 then negate". However, 2's complement can also be converted to sign-magnitude through "first negate then add 1". Why is this?
-**A**:This is because the mutual conversion between the sign-magnitude and the 2's complement is equivalent to computing the "complement". We first define the complement: assuming $a + b = c$, then we say that $a$ is the complement of $b$ to $c$, and vice versa, $b$ is the complement of $a$ to $c$.
+This is because the mutual conversion between sign-magnitude and 2's complement is actually the process of computing the "complement". Let us first define the complement: assuming $a + b = c$, then we say that $a$ is the complement of $b$ to $c$, and conversely, $b$ is the complement of $a$ to $c$.
-Given a binary number $0010$ with length $n = 4$, if this number is the sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained by "first negating and then adding 1":
+Given an $n = 4$ bit binary number $0010$, if we treat this number as sign-magnitude (ignoring the sign bit), then its 2's complement can be obtained through "first negate then add 1":
$$
0010 \rightarrow 1101 \rightarrow 1110
$$
-Observe that the sum of the sign-magnitude and the 2's complement is $0010 + 1110 = 10000$, i.e., the 2's complement $1110$ is the "complement" of the sign-magnitude $0010$ to $10000$. **This means that the above "first negate and then add 1" is equivalent to computing the complement to $10000$**.
+We find that the sum of sign-magnitude and 2's complement is $0010 + 1110 = 10000$, which means the 2's complement $1110$ is the "complement" of sign-magnitude $0010$ to $10000$. **This means the above "first negate then add 1" is actually the process of computing the complement to $10000$**.
-So, what is the "complement" of $1110$ to $10000$? We can still compute it by "negating first and then adding 1":
+So, what is the "complement" of 2's complement $1110$ to $10000$? We can still use "first negate then add 1" to obtain it:
$$
1110 \rightarrow 0001 \rightarrow 0010
$$
-In other words, the sign-magnitude and the 2's complement are each other's "complement" to $10000$, so "sign-magnitude to 2's complement" and "2's complement to sign-magnitude" can be implemented with the same operation (first negate and then add 1).
+In other words, sign-magnitude and 2's complement are each other's "complement" to $10000$, so "sign-magnitude to 2's complement" and "2's complement to sign-magnitude" can be implemented using the same operation (first negate then add 1).
-Of course, we can also use the inverse operation of "first negate and then add 1" to find the sign-magnitude of the 2's complement $1110$, that is, "first subtract 1 and then negate":
+Of course, we can also use the inverse operation to find the sign-magnitude of 2's complement $1110$, that is, "first subtract 1 then negate":
$$
1110 \rightarrow 1101 \rightarrow 0010
$$
-To sum up, "first negate and then add 1" and "first subtract 1 and then negate" are both computing the complement to $10000$, and they are equivalent.
+In summary, both "first negate then add 1" and "first subtract 1 then negate" are computing the complement to $10000$, and they are equivalent.
-Essentially, the "negate" operation is actually to find the complement to $1111$ (because `sign-magnitude + 1's complement = 1111` always holds); and the 1's complement plus 1 is equal to the 2's complement to $10000$.
+Essentially, the "negate" operation is actually finding the complement to $1111$ (because `sign-magnitude + 1's complement = 1111` always holds); and adding 1 to the 1's complement yields the 2's complement, which is the complement to $10000$.
-We take $n = 4$ as an example in the above, and it can be generalized to any binary number with any number of digits.
\ No newline at end of file
+The above uses $n = 4$ as an example, and it can be generalized to binary numbers of any number of bits.
diff --git a/en/docs/chapter_divide_and_conquer/binary_search_recur.md b/en/docs/chapter_divide_and_conquer/binary_search_recur.md
index 2f5fb674c..e54ac1b8f 100644
--- a/en/docs/chapter_divide_and_conquer/binary_search_recur.md
+++ b/en/docs/chapter_divide_and_conquer/binary_search_recur.md
@@ -1,42 +1,42 @@
# Divide and conquer search strategy
-We have learned that search algorithms fall into two main categories.
+We have already learned that search algorithms are divided into two major categories.
-- **Brute-force search**: It is implemented by traversing the data structure, with a time complexity of $O(n)$.
-- **Adaptive search**: It utilizes a unique data organization form or prior information, and its time complexity can reach $O(\log n)$ or even $O(1)$.
+- **Brute-force search**: Implemented by traversing the data structure, with a time complexity of $O(n)$.
+- **Adaptive search**: Utilizes unique data organization forms or prior information, with time complexity reaching $O(\log n)$ or even $O(1)$.
-In fact, **search algorithms with a time complexity of $O(\log n)$ are usually based on the divide-and-conquer strategy**, such as binary search and trees.
+In fact, **search algorithms with time complexity of $O(\log n)$ are typically implemented based on the divide and conquer strategy**, such as binary search and trees.
- Each step of binary search divides the problem (searching for a target element in an array) into a smaller problem (searching for the target element in half of the array), continuing until the array is empty or the target element is found.
-- Trees represent the divide-and-conquer idea, where in data structures like binary search trees, AVL trees, and heaps, the time complexity of various operations is $O(\log n)$.
+- Trees are representative of the divide and conquer idea. In data structures such as binary search trees, AVL trees, and heaps, the time complexity of various operations is $O(\log n)$.
-The divide-and-conquer strategy of binary search is as follows.
+The divide and conquer strategy of binary search is as follows.
-- **The problem can be divided**: Binary search recursively divides the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element.
-- **Subproblems are independent**: In binary search, each round handles one subproblem, unaffected by other subproblems.
-- **The solutions of subproblems do not need to be merged**: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved.
+- **The problem can be decomposed**: Binary search recursively decomposes the original problem (searching in an array) into subproblems (searching in half of the array), achieved by comparing the middle element with the target element.
+- **Subproblems are independent**: In binary search, each round only processes one subproblem, which is not affected by other subproblems.
+- **Solutions of subproblems do not need to be merged**: Binary search aims to find a specific element, so there is no need to merge the solutions of subproblems. When a subproblem is solved, the original problem is also solved.
-Divide-and-conquer can enhance search efficiency because brute-force search can only eliminate one option per round, **whereas divide-and-conquer can eliminate half of the options**.
+Divide and conquer can improve search efficiency because brute-force search can only eliminate one option per round, **while divide and conquer search can eliminate half of the options per round**.
-### Implementing binary search based on divide-and-conquer
+### Implementing binary search based on divide and conquer
-In previous chapters, binary search was implemented based on iteration. Now, we implement it based on divide-and-conquer (recursion).
+In previous sections, binary search was implemented based on iteration. Now we implement it based on divide and conquer (recursion).
!!! question
- Given an ordered array `nums` of length $n$, where all elements are unique, please find the element `target`.
+ Given a sorted array `nums` of length $n$, where all elements are unique, find the element `target`.
-From a divide-and-conquer perspective, we denote the subproblem corresponding to the search interval $[i, j]$ as $f(i, j)$.
+From a divide and conquer perspective, we denote the subproblem corresponding to the search interval $[i, j]$ as $f(i, j)$.
-Starting from the original problem $f(0, n-1)$, perform the binary search through the following steps.
+Starting from the original problem $f(0, n-1)$, perform binary search through the following steps.
1. Calculate the midpoint $m$ of the search interval $[i, j]$, and use it to eliminate half of the search interval.
2. Recursively solve the subproblem reduced by half in size, which could be $f(i, m-1)$ or $f(m+1, j)$.
-3. Repeat steps `1.` and `2.`, until `target` is found or the interval is empty and returns.
+3. Repeat steps `1.` and `2.` until `target` is found or the interval is empty and return.
-The figure below shows the divide-and-conquer process of binary search for element $6$ in an array.
+The figure below shows the divide and conquer process of binary search for element $6$ in an array.
-
+
In the implementation code, we declare a recursive function `dfs()` to solve the problem $f(i, j)$:
diff --git a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
index ee1bbb2b4..bec390f9f 100644
--- a/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
+++ b/en/docs/chapter_divide_and_conquer/build_binary_tree_problem.md
@@ -2,66 +2,66 @@
!!! question
- Given the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence of a binary tree, construct the binary tree and return its root node. Assume there are no duplicate node values in the binary tree (as shown in the figure below).
+ Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume there are no duplicate node values in the binary tree (as shown in the figure below).

-### Determining if it is a divide-and-conquer problem
+### Determining if it is a divide and conquer problem
-The original problem of building a binary tree from the `preorder` and the `inorder` sequences is a typical divide-and-conquer problem.
+The original problem is defined as constructing a binary tree from `preorder` and `inorder`, which is a typical divide and conquer problem.
-- **The problem can be decomposed**: From the perspective of divide-and-conquer, we can divide the original problem into two subproblems—building the left subtree and building the right subtree—plus one operation of initializing the root node. For each subtree (subproblem), we continue applying the same approach, partitioning it into smaller subtrees (subproblems), until reaching the smallest subproblem (an empty subtree).
-- **The subproblems are independent**: The left and right subtrees do not overlap. When building the left subtree, we only need the segments of the in-order and pre-order traversals that correspond to the left subtree. The same approach applies to the right subtree.
-- **Solutions to subproblems can be combined**: Once we have constructed the left and right subtrees (the subproblem solutions), we can attach them to the root node to obtain the solution to the original problem.
+- **The problem can be decomposed**: From a divide and conquer perspective, we can divide the original problem into two subproblems: constructing the left subtree and constructing the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still reuse the above division method, dividing it into smaller subtrees (subproblems) until the smallest subproblem (empty subtree) is reached.
+- **Subproblems are independent**: The left and right subtrees are independent of each other; there is no overlap between them. When constructing the left subtree, we only need to focus on the parts of the inorder and preorder traversals corresponding to the left subtree. The same applies to the right subtree.
+- **Solutions of subproblems can be merged**: Once we have the left and right subtrees (solutions of subproblems), we can link them to the root node to obtain the solution to the original problem.
-### How to divide the subtrees
+### How to divide subtrees
-Based on the above analysis, this problem can be solved using divide-and-conquer. **However, how do we use the pre-order traversal `preorder` sequence and the in-order traversal `inorder` sequence to divide the left and right subtrees?**
+Based on the above analysis, this problem can be solved using divide and conquer, **but how do we divide the left and right subtrees through the preorder traversal `preorder` and inorder traversal `inorder`**?
-By definition, both the `preorder` and `inorder` sequences can be divided into three parts:
+According to the definition, both `preorder` and `inorder` can be divided into three parts.
-- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 3 | 9 | 2 1 7 ]`.
-- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`. For example, in the figure, the tree corresponds to `[ 9 | 3 | 1 2 7 ]`.
+- Preorder traversal: `[ Root Node | Left Subtree | Right Subtree ]`, for example, the tree in the figure above corresponds to `[ 3 | 9 | 2 1 7 ]`.
+- Inorder traversal: `[ Left Subtree | Root Node | Right Subtree ]`, for example, the tree in the figure above corresponds to `[ 9 | 3 | 1 2 7 ]`.
-Using the data from the preceding figure, we can follow the steps shown in the next figure to obtain the division results:
+Using the data from the figure above as an example, we can obtain the division results through the steps shown in the figure below.
-1. The first element 3 in the pre-order traversal is the value of the root node.
-2. Find the index of the root node 3 in the `inorder` sequence, and use this index to split `inorder` into `[ 9 | 3 | 1 2 7 ]`.
-3. According to the split of the `inorder` sequence, it is straightforward to determine that the left and right subtrees contain 1 and 3 nodes, respectively, so we can split the `preorder` sequence into `[ 3 | 9 | 2 1 7 ]` accordingly.
+1. The first element 3 in the preorder traversal is the value of the root node.
+2. Find the index of root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 | 1 2 7 ]`.
+3. Based on the division result of `inorder`, it is easy to determine that the left and right subtrees have 1 and 3 nodes respectively, allowing us to divide `preorder` into `[ 3 | 9 | 2 1 7 ]`.
-
+
-### Describing subtree ranges based on variables
+### Describing subtree intervals based on variables
-Based on the above division method, **we have now obtained the index ranges of the root, left subtree, and right subtree in the `preorder` and `inorder` sequences**. To describe these index ranges, we use several pointer variables.
+Based on the above division method, **we have obtained the index intervals of the root node, left subtree, and right subtree in `preorder` and `inorder`**. To describe these index intervals, we need to use several pointer variables.
-- Let the index of the current tree's root node in the `preorder` sequence be denoted as $i$.
-- Let the index of the current tree's root node in the `inorder` sequence be denoted as $m$.
-- Let the index range of the current tree in the `inorder` sequence be denoted as $[l, r]$.
+- Denote the index of the current tree's root node in `preorder` as $i$.
+- Denote the index of the current tree's root node in `inorder` as $m$.
+- Denote the index interval of the current tree in `inorder` as $[l, r]$.
-As shown in the table below, these variables represent the root node’s index in the `preorder` sequence and the index ranges of the subtrees in the `inorder` sequence.
+As shown in the table below, through these variables we can represent the index of the root node in `preorder` and the index intervals of the subtrees in `inorder`.
- Table Indexes of the root node and subtrees in pre-order and in-order traversals
+ Table Indices of root node and subtrees in preorder and inorder traversals
-| | Root node index in `preorder` | Subtree index range in `inorder` |
-| ------------- | ----------------------------- | ----------------------------------- |
-| Current tree | $i$ | $[l, r]$ |
-| Left subtree | $i + 1$ | $[l, m-1]$ |
-| Right subtree | $i + 1 + (m - l)$ | $[m+1, r]$ |
+| | Root node index in `preorder` | Subtree index interval in `inorder` |
+| ------------ | ----------------------------- | ----------------------------------- |
+| Current tree | $i$ | $[l, r]$ |
+| Left subtree | $i + 1$ | $[l, m-1]$ |
+| Right subtree| $i + 1 + (m - l)$ | $[m+1, r]$ |
-Please note that $(m-l)$ in the right subtree root index represents "the number of nodes in the left subtree." It may help to consult the figure below for a clearer understanding.
+Please note that $(m-l)$ in the right subtree root node index means "the number of nodes in the left subtree". It is recommended to understand this in conjunction with the figure below.
-
+
### Code implementation
-To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping from elements in the `inorder` sequence to their indexes:
+To improve the efficiency of querying $m$, we use a hash table `hmap` to store the mapping from elements in the `inorder` array to their indices:
```src
[file]{build_tree}-[class]{}-[func]{build_tree}
```
-The figure below shows the recursive process of building the binary tree. Each node is created during the "descending" phase of the recursion, and each edge (reference) is formed during the "ascending" phase.
+The figure below shows the recursive process of building the binary tree. Each node is established during the downward "recursion" process, while each edge (reference) is established during the upward "return" process.
=== "<1>"

@@ -90,10 +90,10 @@ The figure below shows the recursive process of building the binary tree. Each n
=== "<9>"

-Each recursive function's division of the `preorder` and `inorder` sequences is illustrated in the figure below.
+The division results of the preorder traversal `preorder` and inorder traversal `inorder` within each recursive function are shown in the figure below.
-
+
-Assuming the binary tree has $n$ nodes, initializing each node (calling the recursive function `dfs()`) takes $O(1)$ time. **Therefore, the overall time complexity is $O(n)$**.
+Let the number of nodes in the tree be $n$. Initializing each node (executing one recursive function `dfs()`) takes $O(1)$ time. **Therefore, the overall time complexity is $O(n)$**.
-Because the hash table stores the mapping from `inorder` elements to their indexes, it requires $O(n)$ space. In the worst case, if the binary tree degenerates into a linked list, the recursive depth can reach $n$, consuming $O(n)$ stack space. **Hence, the overall space complexity is $O(n)$**.
+The hash table stores the mapping from `inorder` elements to their indices, with a space complexity of $O(n)$. In the worst case, when the binary tree degenerates into a linked list, the recursion depth reaches $n$, using $O(n)$ stack frame space. **Therefore, the overall space complexity is $O(n)$**.
diff --git a/en/docs/chapter_divide_and_conquer/divide_and_conquer.md b/en/docs/chapter_divide_and_conquer/divide_and_conquer.md
index 012b10103..70872021a 100644
--- a/en/docs/chapter_divide_and_conquer/divide_and_conquer.md
+++ b/en/docs/chapter_divide_and_conquer/divide_and_conquer.md
@@ -1,48 +1,48 @@
# Divide and conquer algorithms
-Divide and conquer is an important and popular algorithm strategy. As the name suggests, the algorithm is typically implemented recursively and consists of two steps: "divide" and "conquer".
+Divide and conquer is a very important and common algorithm strategy. Divide and conquer is typically implemented based on recursion, consisting of two steps: "divide" and "conquer".
-1. **Divide (partition phase)**: Recursively break down the original problem into two or more smaller sub-problems until the smallest sub-problem is reached.
-2. **Conquer (merge phase)**: Starting from the smallest sub-problem with known solution, we construct the solution to the original problem by merging the solutions of sub-problems in a bottom-up manner.
+1. **Divide (partition phase)**: Recursively divide the original problem into two or more subproblems until the smallest subproblem is reached.
+2. **Conquer (merge phase)**: Starting from the smallest subproblems with known solutions, merge the solutions of subproblems from bottom to top to construct the solution to the original problem.
As shown in the figure below, "merge sort" is one of the typical applications of the divide and conquer strategy.
-1. **Divide**: Recursively divide the original array (original problem) into two sub-arrays (sub-problems), until the sub-array has only one element (smallest sub-problem).
-2. **Conquer**: Merge the ordered sub-arrays (solutions to the sub-problems) from bottom to top to obtain an ordered original array (solution to the original problem).
+1. **Divide**: Recursively divide the original array (original problem) into two subarrays (subproblems) until the subarray has only one element (smallest subproblem).
+2. **Conquer**: Merge the sorted subarrays (solutions to subproblems) from bottom to top to obtain a sorted original array (solution to the original problem).
-
+
-## How to identify divide and conquer problems
+## How to determine divide and conquer problems
-Whether a problem is suitable for a divide-and-conquer solution can usually be decided based on the following criteria.
+Whether a problem is suitable for solving with divide and conquer can usually be determined based on the following criteria.
-1. **The problem can be broken down into smaller ones**: The original problem can be divided into smaller, similar sub-problems and such process can be recursively done in the same manner.
-2. **Sub-problems are independent**: There is no overlap between sub-problems, and they are independent and can be solved separately.
-3. **Solutions to sub-problems can be merged**: The solution to the original problem is derived by combining the solutions of the sub-problems.
+1. **The problem can be decomposed**: The original problem can be divided into smaller, similar subproblems, and can be recursively divided in the same way.
+2. **Subproblems are independent**: There is no overlap between subproblems, they are independent of each other and can be solved independently.
+3. **Solutions of subproblems can be merged**: The solution to the original problem is obtained by merging the solutions of subproblems.
-Clearly, merge sort meets these three criteria.
+Clearly, merge sort satisfies these three criteria.
-1. **The problem can be broken down into smaller ones**: Recursively divide the array (original problem) into two sub-arrays (sub-problems).
-2. **Sub-problems are independent**: Each sub-array can be sorted independently (sub-problems can be solved independently).
-3. **Solutions to sub-problems can be merged**: Two ordered sub-arrays (solutions to the sub-problems) can be merged into one ordered array (solution to the original problem).
+1. **The problem can be decomposed**: Recursively divide the array (original problem) into two subarrays (subproblems).
+2. **Subproblems are independent**: Each subarray can be sorted independently (subproblems can be solved independently).
+3. **Solutions of subproblems can be merged**: Two sorted subarrays (solutions of subproblems) can be merged into one sorted array (solution of the original problem).
-## Improve efficiency through divide and conquer
+## Improving efficiency through divide and conquer
-The **divide-and-conquer strategy not only effectively solves algorithm problems but also often enhances efficiency**. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection sort, bubble sort, and insertion sort because they apply the divide-and-conquer strategy.
+**Divide and conquer can not only effectively solve algorithmic problems but often also improve algorithm efficiency**. In sorting algorithms, quick sort, merge sort, and heap sort are faster than selection, bubble, and insertion sort because they apply the divide and conquer strategy.
-We may have a question in mind: **Why can divide and conquer improve algorithm efficiency, and what is the underlying logic?** In other words, why is breaking a problem into sub-problems, solving them, and combining their solutions to address the original problem offer more efficiency than directly solving the original problem? This question can be analyzed from two aspects: operation count and parallel computation.
+This raises the question: **Why can divide and conquer improve algorithm efficiency, and what is the underlying logic**? In other words, why is dividing a large problem into multiple subproblems, solving the subproblems, and merging their solutions more efficient than directly solving the original problem? This question can be discussed from two aspects: operation count and parallel computation.
-### Optimization of operation count
+### Operation count optimization
-Taking "bubble sort" as an example, it requires $O(n^2)$ time to process an array of length $n$. Suppose we divide the array from the midpoint into two sub-arrays as shown in the figure below, such division requires $O(n)$ time. Sorting each sub-array requires $O((n / 2)^2)$ time. And merging the two sub-arrays requires $O(n)$ time. Thus, the overall time complexity is:
+Taking "bubble sort" as an example, processing an array of length $n$ requires $O(n^2)$ time. Suppose we divide the array into two subarrays from the midpoint as shown in the figure below, the division requires $O(n)$ time, sorting each subarray requires $O((n / 2)^2)$ time, and merging the two subarrays requires $O(n)$ time, resulting in an overall time complexity of:
$$
O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n)
$$
-
+
-Let's calculate the following inequality, where the left side represents the total number of operations before division and the right side represents the total number of operations after division, respectively:
+Next, we compute the following inequality, where the left and right sides represent the total number of operations before and after division, respectively:
$$
\begin{aligned}
@@ -52,40 +52,40 @@ n(n - 4) & > 0
\end{aligned}
$$
-**This means that when $n > 4$, the number of operations after partitioning is fewer, leading to better performance**. Please note that the time complexity after partitioning is still quadratic $O(n^2)$, but the constant factor in the complexity has decreased.
+**This means that when $n > 4$, the number of operations after division is smaller, and sorting efficiency should be higher**. Note that the time complexity after division is still quadratic $O(n^2)$, but the constant term in the complexity has become smaller.
-We can go even further. **How about keeping dividing the sub-arrays from their midpoints into two sub-arrays** until the sub-arrays have only one element left? This idea is actually "merge sort," with a time complexity of $O(n \log n)$.
+Going further, **what if we continuously divide the subarrays from their midpoints into two subarrays** until the subarrays have only one element? This approach is actually "merge sort", with a time complexity of $O(n \log n)$.
-Let's try something a bit different again. **How about splitting into more partitions instead of just two?** For example, we evenly divide the original array into $k$ sub-arrays? This approach is very similar to "bucket sort," which is very suitable for sorting massive data. Theoretically, the time complexity can reach $O(n + k)$.
+Thinking further, **what if we set multiple division points** and evenly divide the original array into $k$ subarrays? This situation is very similar to "bucket sort", which is well-suited for sorting massive amounts of data, with a theoretical time complexity of $O(n + k)$.
-### Optimization through parallel computation
+### Parallel computation optimization
-We know that the sub-problems generated by divide and conquer are independent of each other, **which means that they can be solved in parallel.** As a result, divide and conquer not only reduces the algorithm's time complexity, **but also facilitates parallel optimization by modern operating systems.**
+We know that the subproblems generated by divide and conquer are independent of each other, **so they can typically be solved in parallel**. This means divide and conquer can not only reduce the time complexity of algorithms, **but also benefits from parallel optimization by operating systems**.
-Parallel optimization is particularly effective in environments with multiple cores or processors. As the system can process multiple sub-problems simultaneously, fully utilizing computing resources, the overall runtime is significantly reduced.
+Parallel optimization is particularly effective in multi-core or multi-processor environments, as the system can simultaneously handle multiple subproblems, making fuller use of computing resources and significantly reducing overall runtime.
-For example, in the "bucket sort" shown in the figure below, we break massive data evenly into various buckets. The jobs of sorting each bucket can be allocated to available computing units. Once all jobs are done, all sorted buckets are merged to produce the final result.
+For example, in the "bucket sort" shown in the figure below, we evenly distribute massive data into various buckets, and the sorting tasks for all buckets can be distributed to various computing units. After completion, the results are merged.
-
+
## Common applications of divide and conquer
-Divide and conquer can be used to solve many classic algorithm problems.
+On one hand, divide and conquer can be used to solve many classic algorithmic problems.
-- **Finding the closest pair of points**: This algorithm works by dividing the set of points into two halves. Then it recursively finds the closest pair in each half. Finally it considers pairs that span the two halves to find the overall closest pair.
-- **Large integer multiplication**: One algorithm is called Karatsuba. It breaks down large integer multiplication into several smaller integer multiplications and additions.
-- **Matrix multiplication**: One example is the Strassen algorithm. It breaks down a large matrix multiplication into multiple small matrix multiplications and additions.
-- **Tower of Hanoi problem**: The Tower of Hanoi problem can be solved recursively, a typical application of the divide-and-conquer strategy.
-- **Solving inversion pairs**: In a sequence, if a preceding number is greater than a following number, then these two numbers constitute an inversion pair. Solving inversion pair problem can utilize the idea of divide and conquer, with the aid of merge sort.
+- **Finding the closest pair of points**: This algorithm first divides the point set into two parts, then finds the closest pair of points in each part separately, and finally finds the closest pair of points that spans both parts.
+- **Large integer multiplication**: For example, the Karatsuba algorithm, which decomposes large integer multiplication into several smaller integer multiplications and additions.
+- **Matrix multiplication**: For example, the Strassen algorithm, which decomposes large matrix multiplication into multiple small matrix multiplications and additions.
+- **Hanota problem**: The hanota problem can be solved through recursion, which is a typical application of the divide and conquer strategy.
+- **Solving inversion pairs**: In a sequence, if a preceding number is greater than a following number, these two numbers form an inversion pair. Solving the inversion pair problem can utilize the divide and conquer approach with the help of merge sort.
-Divide and conquer is also widely applied in the design of algorithms and data structures.
+On the other hand, divide and conquer is widely applied in the design of algorithms and data structures.
-- **Binary search**: Binary search divides a sorted array into two halves from the midpoint index. And then based on the comparison result between the target value and the middle element value, one half is discarded. The search continues on the remaining half with the same process until the target is found or there is no remaining element.
-- **Merge sort**: Already introduced at the beginning of this section, no further elaboration is needed.
-- **Quicksort**: Quicksort picks a pivot value to divide the array into two sub-arrays, one with elements smaller than the pivot and the other with elements larger than the pivot. Such process goes on against each of these two sub-arrays until they hold only one element.
-- **Bucket sort**: The basic idea of bucket sort is to distribute data to multiple buckets. After sorting the elements within each bucket, retrieve the elements from the buckets in order to obtain an ordered array.
-- **Trees**: For example, binary search trees, AVL trees, red-black trees, B-trees, and B+ trees, etc. Their operations, such as search, insertion, and deletion, can all be regarded as applications of the divide-and-conquer strategy.
-- **Heap**: A heap is a special type of complete binary tree. Its various operations, such as insertion, deletion, and heapify, actually imply the idea of divide and conquer.
-- **Hash table**: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the strategy. For example, long lists in chained addressing may be converted to red-black trees to improve query efficiency.
+- **Binary search**: Binary search divides a sorted array into two parts from the midpoint index, then decides which half to eliminate based on the comparison result between the target value and the middle element value, and performs the same binary operation on the remaining interval.
+- **Merge sort**: Already introduced at the beginning of this section, no further elaboration needed.
+- **Quick sort**: Quick sort selects a pivot value, then divides the array into two subarrays, one with elements smaller than the pivot and the other with elements larger than the pivot, then performs the same division operation on these two parts until the subarrays have only one element.
+- **Bucket sort**: The basic idea of bucket sort is to scatter data into multiple buckets, then sort the elements within each bucket, and finally extract the elements from each bucket in sequence to obtain a sorted array.
+- **Trees**: For example, binary search trees, AVL trees, red-black trees, B-trees, B+ trees, etc. Their search, insertion, and deletion operations can all be viewed as applications of the divide and conquer strategy.
+- **Heaps**: A heap is a special complete binary tree, and its various operations, such as insertion, deletion, and heapify, actually imply the divide and conquer idea.
+- **Hash tables**: Although hash tables do not directly apply divide and conquer, some hash collision resolution solutions indirectly apply the divide and conquer strategy. For example, long linked lists in chaining may be converted to red-black trees to improve query efficiency.
-It can be seen that **divide and conquer is a subtly pervasive algorithmic idea**, embedded within various algorithms and data structures.
+It can be seen that **divide and conquer is a "subtly pervasive" algorithmic idea**, embedded in various algorithms and data structures.
diff --git a/en/docs/chapter_divide_and_conquer/hanota_problem.md b/en/docs/chapter_divide_and_conquer/hanota_problem.md
index c6fc8b636..b6797c73d 100644
--- a/en/docs/chapter_divide_and_conquer/hanota_problem.md
+++ b/en/docs/chapter_divide_and_conquer/hanota_problem.md
@@ -1,22 +1,22 @@
-# Tower of Hanoi Problem
+# Hanota problem
-In both merge sort and binary tree construction, we break the original problem into two subproblems, each half the size of the original problem. However, for the Tower of Hanoi, we adopt a different decomposition strategy.
+In merge sort and building binary trees, we decompose the original problem into two subproblems, each half the size of the original problem. However, for the hanota problem, we adopt a different decomposition strategy.
!!! question
- We are given three pillars, denoted as `A`, `B`, and `C`. Initially, pillar `A` has $n$ discs, arranged from top to bottom in ascending size. Our task is to move these $n$ discs to pillar `C`, maintaining their original order (as shown in the figure below). The following rules apply during the movement:
-
- 1. A disc can be removed only from the top of a pillar and must be placed on the top of another pillar.
+ Given three pillars, denoted as `A`, `B`, and `C`. Initially, pillar `A` has $n$ discs stacked on it, arranged from top to bottom in ascending order of size. Our task is to move these $n$ discs to pillar `C` while maintaining their original order (as shown in the figure below). The following rules must be followed when moving the discs.
+
+ 1. A disc can only be taken from the top of one pillar and placed on top of another pillar.
2. Only one disc can be moved at a time.
3. A smaller disc must always be on top of a larger disc.
-
+
-**We denote the Tower of Hanoi problem of size $i$ as $f(i)$**. For example, $f(3)$ represents moving $3$ discs from pillar `A` to pillar `C`.
+**We denote the hanota problem of size $i$ as $f(i)$**. For example, $f(3)$ represents moving $3$ discs from `A` to `C`.
-### Consider the base cases
+### Considering the base cases
-As shown in the figure below, for the problem $f(1)$—which has only one disc—we can directly move it from `A` to `C`.
+As shown in the figure below, for problem $f(1)$, when there is only one disc, we can move it directly from `A` to `C`.
=== "<1>"

@@ -24,7 +24,7 @@ As shown in the figure below, for the problem $f(1)$—which has only one disc
=== "<2>"

-For $f(2)$—which has two discs—**we rely on pillar `B` to help keep the smaller disc above the larger disc**, as illustrated in the following figure:
+As shown in the figure below, for problem $f(2)$, when there are two discs, **since we must always keep the smaller disc on top of the larger disc, we need to use `B` to assist in the move**.
1. First, move the smaller disc from `A` to `B`.
2. Then move the larger disc from `A` to `C`.
@@ -42,17 +42,17 @@ For $f(2)$—which has two discs—**we rely on pillar `B` to help keep the smal
=== "<4>"

-The process of solving $f(2)$ can be summarized as: **moving two discs from `A` to `C` with the help of `B`**. Here, `C` is called the target pillar, and `B` is called the buffer pillar.
+The process of solving problem $f(2)$ can be summarized as: **moving two discs from `A` to `C` with the help of `B`**. Here, `C` is called the target pillar, and `B` is called the buffer pillar.
-### Decomposition of subproblems
+### Subproblem decomposition
-For the problem $f(3)$—that is, when there are three discs—the situation becomes slightly more complicated.
+For problem $f(3)$, when there are three discs, the situation becomes slightly more complex.
-Since we already know the solutions to $f(1)$ and $f(2)$, we can adopt a divide-and-conquer perspective and **treat the top two discs on `A` as a single unit**, performing the steps shown in the figure below. This allows the three discs to be successfully moved from `A` to `C`.
+Since we already know the solutions to $f(1)$ and $f(2)$, we can think from a divide and conquer perspective, **treating the top two discs on `A` as a whole**, and execute the steps shown in the figure below. This successfully moves the three discs from `A` to `C`.
-1. Let `B` be the target pillar and `C` the buffer pillar, then move the two discs from `A` to `B`.
+1. Let `B` be the target pillar and `C` be the buffer pillar, and move two discs from `A` to `B`.
2. Move the remaining disc from `A` directly to `C`.
-3. Let `C` be the target pillar and `A` the buffer pillar, then move the two discs from `B` to `C`.
+3. Let `C` be the target pillar and `A` be the buffer pillar, and move two discs from `B` to `C`.
=== "<1>"

@@ -66,32 +66,32 @@ Since we already know the solutions to $f(1)$ and $f(2)$, we can adopt a divide-
=== "<4>"

-Essentially, **we decompose $f(3)$ into two $f(2)$ subproblems and one $f(1)$ subproblem**. By solving these three subproblems in sequence, the original problem is solved, indicating that the subproblems are independent and their solutions can be merged.
+Essentially, **we divide problem $f(3)$ into two subproblems $f(2)$ and one subproblem $f(1)$**. By solving these three subproblems in order, the original problem is solved. This shows that the subproblems are independent and their solutions can be merged.
-From this, we can summarize the divide-and-conquer strategy for the Tower of Hanoi, illustrated in the figure below. We divide the original problem $f(n)$ into two subproblems $f(n-1)$ and one subproblem $f(1)$, and solve these three subproblems in the following order:
+From this, we can summarize the divide and conquer strategy for solving the hanota problem shown in the figure below: divide the original problem $f(n)$ into two subproblems $f(n-1)$ and one subproblem $f(1)$, and solve these three subproblems in the following order.
-1. Move $n-1$ discs from `A` to `B`, using `C` as a buffer.
-2. Move the remaining disc directly from `A` to `C`.
-3. Move $n-1$ discs from `B` to `C`, using `A` as a buffer.
+1. Move $n-1$ discs from `A` to `B` with the help of `C`.
+2. Move the remaining $1$ disc directly from `A` to `C`.
+3. Move $n-1$ discs from `B` to `C` with the help of `A`.
-For each $f(n-1)$ subproblem, **we can apply the same recursive partition** until we reach the smallest subproblem $f(1)$. Because $f(1)$ is already known to require just a single move, it is trivial to solve.
+For these two subproblems $f(n-1)$, **we can recursively divide them in the same way** until reaching the smallest subproblem $f(1)$. The solution to $f(1)$ is known and requires only one move operation.
-
+
### Code implementation
-In the code, we define a recursive function `dfs(i, src, buf, tar)` which moves the top $i$ discs from pillar `src` to pillar `tar`, using pillar `buf` as a buffer:
+In the code, we declare a recursive function `dfs(i, src, buf, tar)`, whose purpose is to move the top $i$ discs from pillar `src` to target pillar `tar` with the help of buffer pillar `buf`:
```src
[file]{hanota}-[class]{}-[func]{solve_hanota}
```
-As shown in the figure below, the Tower of Hanoi problem can be visualized as a recursive tree of height $n$. Each node represents a subproblem, corresponding to a call to `dfs()`, **Hence, the time complexity is $O(2^n)$, and the space complexity is $O(n)$.**
+As shown in the figure below, the hanota problem forms a recursion tree of height $n$, where each node represents a subproblem corresponding to an invocation of the `dfs()` function, **therefore the time complexity is $O(2^n)$ and the space complexity is $O(n)$**.
-
+
!!! quote
- The Tower of Hanoi originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and $64$ differently sized golden discs. They believed that when the last disc was correctly placed, the world would end.
+ The hanota problem originates from an ancient legend. In a temple in ancient India, monks had three tall diamond pillars and $64$ golden discs of different sizes. The monks continuously moved the discs, believing that when the last disc was correctly placed, the world would come to an end.
- However, even if the monks moved one disc every second, it would take about $2^{64} \approx 1.84×10^{19}$ —approximately 585 billion years—far exceeding current estimates of the age of the universe. Thus, if the legend is true, we probably do not need to worry about the world ending.
+ However, even if the monks moved one disc per second, it would take approximately $2^{64} \approx 1.84×10^{19}$ seconds, which is about $5850$ billion years, far exceeding current estimates of the age of the universe. Therefore, if this legend is true, we should not need to worry about the end of the world.
diff --git a/en/docs/chapter_divide_and_conquer/index.md b/en/docs/chapter_divide_and_conquer/index.md
index ed20fcdac..7fea57852 100644
--- a/en/docs/chapter_divide_and_conquer/index.md
+++ b/en/docs/chapter_divide_and_conquer/index.md
@@ -1,9 +1,9 @@
# Divide and conquer
-
+
!!! abstract
Difficult problems are decomposed layer by layer, with each decomposition making them simpler.
- Divide and conquer unveils a profound truth: begin with simplicity, and complexity dissolves.
+ Divide and conquer reveals an important truth: start with simplicity, and nothing remains complex.
diff --git a/en/docs/chapter_divide_and_conquer/summary.md b/en/docs/chapter_divide_and_conquer/summary.md
index 3fd75beff..d9150b731 100644
--- a/en/docs/chapter_divide_and_conquer/summary.md
+++ b/en/docs/chapter_divide_and_conquer/summary.md
@@ -1,11 +1,11 @@
# Summary
-- Divide and conquer is a common algorithm design strategy that consists of two stages—divide (partition) and conquer (merge)—and is generally implemented using recursion.
-- To determine whether a problem is suited for a divide and conquer approach, we check if the problem can be decomposed, whether the subproblems are independent, and whether the subproblems can be merged.
-- Merge sort is a typical example of the divide and conquer strategy. It recursively splits an array into two equal-length subarrays until only one element remains, and then merges these subarrays layer by layer to complete the sorting.
-- Introducing the divide and conquer strategy often improves algorithm efficiency. On one hand, it reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division.
-- Divide and conquer can be applied to numerous algorithmic problems and is widely used in data structures and algorithm design, appearing in many scenarios.
-- Compared to brute force search, adaptive search is more efficient. Search algorithms with a time complexity of $O(\log n)$ are typically based on the divide and conquer strategy.
-- Binary search is another classic application of the divide-and-conquer strategy. It does not involve merging subproblem solutions and can be implemented via a recursive divide-and-conquer approach.
-- In the problem of constructing binary trees, building the tree (the original problem) can be divided into building the left subtree and right subtree (the subproblems). This can be achieved by partitioning the index ranges of the preorder and inorder traversals.
-- In the Tower of Hanoi problem, a problem of size $n$ can be broken down into two subproblems of size $n-1$ and one subproblem of size $1$. By solving these three subproblems in sequence, the original problem is resolved.
+- Divide and conquer is a common algorithm design strategy, consisting of two phases: divide (partition) and conquer (merge), typically implemented based on recursion.
+- The criteria for determining whether a problem is a divide and conquer problem include: whether the problem can be decomposed, whether subproblems are independent, and whether subproblems can be merged.
+- Merge sort is a typical application of the divide and conquer strategy. It recursively divides an array into two equal-length subarrays until only one element remains, then merges them layer by layer to complete the sorting.
+- Introducing the divide and conquer strategy can often improve algorithm efficiency. On one hand, the divide and conquer strategy reduces the number of operations; on the other hand, it facilitates parallel optimization of the system after division.
+- Divide and conquer can both solve many algorithmic problems and is widely applied in data structure and algorithm design, appearing everywhere.
+- Compared to brute-force search, adaptive search is more efficient. Search algorithms with time complexity of $O(\log n)$ are typically implemented based on the divide and conquer strategy.
+- Binary search is another typical application of divide and conquer. It does not include the step of merging solutions of subproblems. We can implement binary search through recursive divide and conquer.
+- In the problem of building a binary tree, building the tree (original problem) can be divided into building the left subtree and right subtree (subproblems), which can be achieved by dividing the index intervals of the preorder and inorder traversals.
+- In the hanota problem, a problem of size $n$ can be divided into two subproblems of size $n-1$ and one subproblem of size $1$. After solving these three subproblems in order, the original problem is solved.
diff --git a/en/docs/chapter_dynamic_programming/dp_problem_features.md b/en/docs/chapter_dynamic_programming/dp_problem_features.md
index 858b7f8dc..5dd013da9 100644
--- a/en/docs/chapter_dynamic_programming/dp_problem_features.md
+++ b/en/docs/chapter_dynamic_programming/dp_problem_features.md
@@ -2,37 +2,37 @@
In the previous section, we learned how dynamic programming solves the original problem by decomposing it into subproblems. In fact, subproblem decomposition is a general algorithmic approach, with different emphases in divide and conquer, dynamic programming, and backtracking.
-- Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and combine the solutions of the subproblems during backtracking to ultimately obtain the solution to the original problem.
-- Dynamic programming also decomposes the problem recursively, but the main difference from divide and conquer algorithms is that the subproblems in dynamic programming are interdependent, and many overlapping subproblems will appear during the decomposition process.
-- Backtracking algorithms exhaust all possible solutions through trial and error and avoid unnecessary search branches by pruning. The solution to the original problem consists of a series of decision steps, and we can consider each sub-sequence before each decision step as a subproblem.
+- Divide and conquer algorithms recursively divide the original problem into multiple independent subproblems until the smallest subproblems are reached, and merge the solutions to the subproblems during backtracking to ultimately obtain the solution to the original problem.
+- Dynamic programming also recursively decomposes problems, but the main difference from divide and conquer algorithms is that subproblems in dynamic programming are interdependent, and many overlapping subproblems appear during the decomposition process.
+- Backtracking algorithms enumerate all possible solutions through trial and error, and avoid unnecessary search branches through pruning. The solution to the original problem consists of a series of decision steps, and we can regard the subsequence before each decision step as a subproblem.
-In fact, dynamic programming is commonly used to solve optimization problems, which not only include overlapping subproblems but also have two other major characteristics: optimal substructure and statelessness.
+In fact, dynamic programming is commonly used to solve optimization problems, which not only contain overlapping subproblems but also have two other major characteristics: optimal substructure and no aftereffects.
## Optimal substructure
-We make a slight modification to the stair climbing problem to make it more suitable to demonstrate the concept of optimal substructure.
+We make a slight modification to the stair climbing problem to make it more suitable for demonstrating the concept of optimal substructure.
-!!! question "Minimum cost of climbing stairs"
+!!! question "Climbing stairs with minimum cost"
- Given a staircase, you can step up 1 or 2 steps at a time, and each step on the staircase has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array $cost$, where $cost[i]$ represents the cost you need to pay at the $i$-th step, $cost[0]$ is the ground (starting point). What is the minimum cost required to reach the top?
+ Given a staircase, where you can climb $1$ or $2$ steps at a time, and each step has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array $cost$, where $cost[i]$ represents the cost at the $i$-th step, and $cost[0]$ is the ground (starting point). What is the minimum cost required to reach the top?
-As shown in the figure below, if the costs of the 1st, 2nd, and 3rd steps are $1$, $10$, and $1$ respectively, then the minimum cost to climb to the 3rd step from the ground is $2$.
+As shown in the figure below, if the costs of the $1$st, $2$nd, and $3$rd steps are $1$, $10$, and $1$ respectively, then climbing from the ground to the $3$rd step requires a minimum cost of $2$.

-Let $dp[i]$ be the cumulative cost of climbing to the $i$-th step. Since the $i$-th step can only come from the $i-1$ or $i-2$ step, $dp[i]$ can only be either $dp[i-1] + cost[i]$ or $dp[i-2] + cost[i]$. To minimize the cost, we should choose the smaller of the two:
+Let $dp[i]$ be the accumulated cost of climbing to the $i$-th step. Since the $i$-th step can only come from the $i-1$-th or $i-2$-th step, $dp[i]$ can only equal $dp[i-1] + cost[i]$ or $dp[i-2] + cost[i]$. To minimize the cost, we should choose the smaller of the two:
$$
dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]
$$
-This leads us to the meaning of optimal substructure: **The optimal solution to the original problem is constructed from the optimal solutions of subproblems**.
+This leads us to the meaning of optimal substructure: **the optimal solution to the original problem is constructed from the optimal solutions to the subproblems**.
-This problem obviously has optimal substructure: we select the better one from the optimal solutions of the two subproblems, $dp[i-1]$ and $dp[i-2]$, and use it to construct the optimal solution for the original problem $dp[i]$.
+This problem clearly has optimal substructure: we select the better one from the optimal solutions to the two subproblems $dp[i-1]$ and $dp[i-2]$, and use it to construct the optimal solution to the original problem $dp[i]$.
-So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to solve for the number of solutions, which seems to be a counting problem, but if we ask in another way: "Solve for the maximum number of solutions". We surprisingly find that **although the problem has changed, the optimal substructure has emerged**: the maximum number of solutions at the $n$-th step equals the sum of the maximum number of solutions at the $n-1$ and $n-2$ steps. Thus, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems.
+So, does the stair climbing problem from the previous section have optimal substructure? Its goal is to find the number of ways, which seems to be a counting problem, but if we change the question: "Find the maximum number of ways". We surprisingly discover that **although the problem before and after modification are equivalent, the optimal substructure has emerged**: the maximum number of ways for the $n$-th step equals the sum of the maximum number of ways for the $n-1$-th and $n-2$-th steps. Therefore, the interpretation of optimal substructure is quite flexible and will have different meanings in different problems.
-According to the state transition equation, and the initial states $dp[1] = cost[1]$ and $dp[2] = cost[2]$, we can obtain the dynamic programming code:
+According to the state transition equation and the initial states $dp[1] = cost[1]$ and $dp[2] = cost[2]$, we can obtain the dynamic programming code:
```src
[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp}
@@ -40,40 +40,40 @@ According to the state transition equation, and the initial states $dp[1] = cost
The figure below shows the dynamic programming process for the above code.
-
+
-This problem can also be space-optimized, compressing one dimension to zero, reducing the space complexity from $O(n)$ to $O(1)$:
+This problem can also be space-optimized, compressing from one dimension to zero, reducing the space complexity from $O(n)$ to $O(1)$:
```src
[file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp}
```
-## Statelessness
+## No aftereffects
-Statelessness is one of the important characteristics that make dynamic programming effective in solving problems. Its definition is: **Given a certain state, its future development is only related to the current state and unrelated to all past states experienced**.
+No aftereffects is one of the important characteristics that enable dynamic programming to solve problems effectively. Its definition is: **given a certain state, its future development is only related to the current state and has nothing to do with all past states**.
-Taking the stair climbing problem as an example, given state $i$, it will develop into states $i+1$ and $i+2$, corresponding to jumping 1 step and 2 steps respectively. When making these two choices, we do not need to consider the states before state $i$, as they do not affect the future of state $i$.
+Taking the stair climbing problem as an example, given state $i$, it will develop into states $i+1$ and $i+2$, corresponding to jumping $1$ step and jumping $2$ steps, respectively. When making these two choices, we do not need to consider the states before state $i$, as they have no effect on the future of state $i$.
However, if we add a constraint to the stair climbing problem, the situation changes.
-!!! question "Stair climbing with constraints"
+!!! question "Climbing stairs with constraint"
- Given a staircase with $n$ steps, you can go up 1 or 2 steps each time, **but you cannot jump 1 step twice in a row**. How many ways are there to climb to the top?
+ Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time, **but you cannot jump $1$ step in two consecutive rounds**. How many ways are there to climb to the top?
-As shown in the figure below, there are only 2 feasible options for climbing to the 3rd step, among which the option of jumping 1 step three times in a row does not meet the constraint condition and is therefore discarded.
+As shown in the figure below, there are only $2$ feasible ways to climb to the $3$rd step. The way of jumping $1$ step three consecutive times does not satisfy the constraint and is therefore discarded.
-
+
-In this problem, if the last round was a jump of 1 step, then the next round must be a jump of 2 steps. This means that **the next step choice cannot be independently determined by the current state (current stair step), but also depends on the previous state (last round's stair step)**.
+In this problem, if the previous round was a jump of $1$ step, then the next round must jump $2$ steps. This means that **the next choice cannot be determined solely by the current state (current stair step number), but also depends on the previous state (the stair step number from the previous round)**.
-It is not difficult to find that this problem no longer satisfies statelessness, and the state transition equation $dp[i] = dp[i-1] + dp[i-2]$ also fails, because $dp[i-1]$ represents this round's jump of 1 step, but it includes many "last round was a jump of 1 step" options, which, to meet the constraint, cannot be directly included in $dp[i]$.
+It is not difficult to see that this problem no longer satisfies no aftereffects, and the state transition equation $dp[i] = dp[i-1] + dp[i-2]$ also fails, because $dp[i-1]$ represents jumping $1$ step in this round, but it includes many solutions where "the previous round was a jump of $1$ step", which cannot be directly counted in $dp[i]$ to satisfy the constraint.
-For this, we need to expand the state definition: **State $[i, j]$ represents being on the $i$-th step and the last round was a jump of $j$ steps**, where $j \in \{1, 2\}$. This state definition effectively distinguishes whether the last round was a jump of 1 step or 2 steps, and we can judge accordingly where the current state came from.
+For this reason, we need to expand the state definition: **state $[i, j]$ represents being on the $i$-th step with the previous round having jumped $j$ steps**, where $j \in \{1, 2\}$. This state definition effectively distinguishes whether the previous round was a jump of $1$ step or $2$ steps, allowing us to determine where the current state came from.
-- When the last round was a jump of 1 step, the round before last could only choose to jump 2 steps, that is, $dp[i, 1]$ can only be transferred from $dp[i-1, 2]$.
-- When the last round was a jump of 2 steps, the round before last could choose to jump 1 step or 2 steps, that is, $dp[i, 2]$ can be transferred from $dp[i-2, 1]$ or $dp[i-2, 2]$.
+- When the previous round jumped $1$ step, the round before that could only choose to jump $2$ steps, i.e., $dp[i, 1]$ can only be transferred from $dp[i-1, 2]$.
+- When the previous round jumped $2$ steps, the round before that could choose to jump $1$ step or $2$ steps, i.e., $dp[i, 2]$ can be transferred from $dp[i-2, 1]$ or $dp[i-2, 2]$.
-As shown in the figure below, $dp[i, j]$ represents the number of solutions for state $[i, j]$. At this point, the state transition equation is:
+As shown in the figure below, under this definition, $dp[i, j]$ represents the number of ways for state $[i, j]$. The state transition equation is then:
$$
\begin{cases}
@@ -82,20 +82,20 @@ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2]
\end{cases}
$$
-
+
-In the end, returning $dp[n, 1] + dp[n, 2]$ will do, the sum of the two representing the total number of solutions for climbing to the $n$-th step:
+Finally, return $dp[n, 1] + dp[n, 2]$, where the sum of the two represents the total number of ways to climb to the $n$-th step:
```src
[file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp}
```
-In the above cases, since we only need to consider the previous state, we can still meet the statelessness by expanding the state definition. However, some problems have very serious "state effects".
+In the above case, since we only need to consider one more preceding state, we can still make the problem satisfy no aftereffects by expanding the state definition. However, some problems have very severe "aftereffects".
-!!! question "Stair climbing with obstacle generation"
+!!! question "Climbing stairs with obstacle generation"
- Given a staircase with $n$ steps, you can go up 1 or 2 steps each time. **It is stipulated that when climbing to the $i$-th step, the system automatically places an obstacle on the $2i$-th step, and thereafter all rounds are not allowed to jump to the $2i$-th step**. For example, if the first two rounds jump to the 2nd and 3rd steps, then later you cannot jump to the 4th and 6th steps. How many ways are there to climb to the top?
+ Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time. **It is stipulated that when climbing to the $i$-th step, the system will automatically place an obstacle on the $2i$-th step, and thereafter no round is allowed to jump to the $2i$-th step**. For example, if the first two rounds jump to the $2$nd and $3$rd steps, then afterwards you cannot jump to the $4$th and $6$th steps. How many ways are there to climb to the top?
-In this problem, the next jump depends on all past states, as each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming often struggles to solve.
+In this problem, the next jump depends on all past states, because each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming is often difficult to solve.
-In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy statelessness. For these kinds of problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time.
+In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time.
diff --git a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
index 1c3b465c2..344c76211 100644
--- a/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
+++ b/en/docs/chapter_dynamic_programming/dp_solution_pipeline.md
@@ -1,63 +1,63 @@
# Dynamic programming problem-solving approach
-The last two sections introduced the main characteristics of dynamic programming problems. Next, let's explore two more practical issues together.
+The previous two sections introduced the main characteristics of dynamic programming problems. Next, let us explore two more practical issues together.
1. How to determine whether a problem is a dynamic programming problem?
-2. What are the complete steps to solve a dynamic programming problem?
+2. What is the complete process for solving a dynamic programming problem, and where should we start?
## Problem determination
-Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and exhibits no aftereffects, it is usually suitable for dynamic programming solutions. However, it is often difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and **first observe whether the problem is suitable for resolution using backtracking (exhaustive search)**.
+Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and satisfies no aftereffects, then it is usually suitable for solving with dynamic programming. However, it is difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and **first observe whether the problem is suitable for solving with backtracking (exhaustive search)**.
-**Problems suitable for backtracking usually fit the "decision tree model"**, which can be described using a tree structure, where each node represents a decision, and each path represents a sequence of decisions.
+**Problems suitable for solving with backtracking usually satisfy the "decision tree model"**, which means the problem can be described using a tree structure, where each node represents a decision and each path represents a sequence of decisions.
-In other words, if the problem contains explicit decision concepts, and the solution is produced through a series of decisions, then it fits the decision tree model and can usually be solved using backtracking.
+In other words, if a problem contains an explicit concept of decisions, and the solution is generated through a series of decisions, then it satisfies the decision tree model and can usually be solved using backtracking.
-On this basis, there are some "bonus points" for determining dynamic programming problems.
+On this basis, dynamic programming problems also have some "bonus points" for determination.
-- The problem contains descriptions of maximization (minimization) or finding the most (least) optimal solution.
-- The problem's states can be represented using a list, multi-dimensional matrix, or tree, and a state has a recursive relationship with its surrounding states.
+- The problem contains descriptions such as maximum (minimum) or most (least), indicating optimization.
+- The problem's state can be represented using a list, multi-dimensional matrix, or tree, and a state has a recurrence relation with its surrounding states.
Correspondingly, there are also some "penalty points".
-- The goal of the problem is to find all possible solutions, not just the optimal solution.
-- The problem description has obvious characteristics of permutations and combinations, requiring the return of specific multiple solutions.
+- The goal of the problem is to find all possible solutions, rather than finding the optimal solution.
+- The problem description has obvious permutation and combination characteristics, requiring the return of specific multiple solutions.
-If a problem fits the decision tree model and has relatively obvious "bonus points", we can assume it is a dynamic programming problem and verify it during the solution process.
+If a problem satisfies the decision tree model and has relatively obvious "bonus points", we can assume it is a dynamic programming problem and verify it during the solving process.
## Problem-solving steps
-The dynamic programming problem-solving process varies with the nature and difficulty of the problem but generally follows these steps: describe decisions, define states, establish a $dp$ table, derive state transition equations, and determine boundary conditions, etc.
+The problem-solving process for dynamic programming varies depending on the nature and difficulty of the problem, but generally follows these steps: describe decisions, define states, establish the $dp$ table, derive state transition equations, determine boundary conditions, etc.
-To illustrate the problem-solving steps more vividly, we use a classic problem, "Minimum Path Sum", as an example.
+To illustrate the problem-solving steps more vividly, we use a classic problem "minimum path sum" as an example.
!!! question
- Given an $n \times m$ two-dimensional grid `grid`, each cell in the grid contains a non-negative integer representing the cost of that cell. The robot starts from the top-left cell and can only move down or right at each step until it reaches the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.
+ Given an $n \times m$ two-dimensional grid `grid`, where each cell in the grid contains a non-negative integer representing the cost of that cell. A robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.
-The figure below shows an example, where the given grid's minimum path sum is $13$.
+The figure below shows an example where the minimum path sum for the given grid is $13$.
-
+
-**First step: Think about each round of decisions, define the state, and thereby obtain the $dp$ table**
+**Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table**
-Each round of decisions in this problem is to move one step down or right from the current cell. Suppose the row and column indices of the current cell are $[i, j]$, then after moving down or right, the indices become $[i+1, j]$ or $[i, j+1]$. Therefore, the state should include two variables: the row index and the column index, denoted as $[i, j]$.
+The decision in each round of this problem is to move one step down or right from the current cell. Let the row and column indices of the current cell be $[i, j]$. After moving down or right, the indices become $[i+1, j]$ or $[i, j+1]$. Therefore, the state should include two variables, the row index and column index, denoted as $[i, j]$.
-The state $[i, j]$ corresponds to the subproblem: the minimum path sum from the starting point $[0, 0]$ to $[i, j]$, denoted as $dp[i, j]$.
+State $[i, j]$ corresponds to the subproblem: **the minimum path sum from the starting point $[0, 0]$ to $[i, j]$**, denoted as $dp[i, j]$.
-Thus, we obtain the two-dimensional $dp$ matrix shown in the figure below, whose size is the same as the input grid $grid$.
+From this, we obtain the two-dimensional $dp$ matrix shown in the figure below, whose size is the same as the input grid $grid$.
-
+
!!! note
- Dynamic programming and backtracking can be described as a sequence of decisions, while a state consists of all decision variables. It should include all variables that describe the progress of solving the problem, containing enough information to derive the next state.
+ The dynamic programming and backtracking processes can be described as a sequence of decisions, and the state consists of all decision variables. It should contain all variables describing the progress of problem-solving, and should contain sufficient information to derive the next state.
Each state corresponds to a subproblem, and we define a $dp$ table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the $dp$ table. Essentially, the $dp$ table is a mapping between states and solutions to subproblems.
-**Second step: Identify the optimal substructure, then derive the state transition equation**
+**Step 2: Identify the optimal substructure, and then derive the state transition equation**
-For the state $[i, j]$, it can only be derived from the cell above $[i-1, j]$ or the cell to the left $[i, j-1]$. Therefore, the optimal substructure is: the minimum path sum to reach $[i, j]$ is determined by the smaller of the minimum path sums of $[i, j-1]$ and $[i-1, j]$.
+For state $[i, j]$, it can only be transferred from the cell above $[i-1, j]$ or the cell to the left $[i, j-1]$. Therefore, the optimal substructure is: the minimum path sum to reach $[i, j]$ is determined by the smaller of the minimum path sums of $[i, j-1]$ and $[i-1, j]$.
Based on the above analysis, the state transition equation shown in the figure below can be derived:
@@ -69,75 +69,75 @@ $$
!!! note
- Based on the defined $dp$ table, think about the relationship between the original problem and the subproblems, and find out how to construct the optimal solution to the original problem from the optimal solutions to the subproblems, i.e., the optimal substructure.
+ Based on the defined $dp$ table, think about the relationship between the original problem and subproblems, and find the method to construct the optimal solution to the original problem from the optimal solutions to the subproblems, which is the optimal substructure.
- Once we have identified the optimal substructure, we can use it to build the state transition equation.
+ Once we identify the optimal substructure, we can use it to construct the state transition equation.
-**Third step: Determine boundary conditions and state transition order**
+**Step 3: Determine boundary conditions and state transition order**
-In this problem, the states in the first row can only come from the states to their left, and the states in the first column can only come from the states above them, so the first row $i = 0$ and the first column $j = 0$ are the boundary conditions.
+In this problem, states in the first row can only come from the state to their left, and states in the first column can only come from the state above them. Therefore, the first row $i = 0$ and first column $j = 0$ are boundary conditions.
-As shown in the figure below, since each cell is derived from the cell to its left and the cell above it, we use loops to traverse the matrix, the outer loop iterating over the rows and the inner loop iterating over the columns.
+As shown in the figure below, since each cell is transferred from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns.

!!! note
- Boundary conditions are used in dynamic programming to initialize the $dp$ table, and in search to prune.
-
- The core of the state transition order is to ensure that when calculating the solution to the current problem, all the smaller subproblems it depends on have already been correctly calculated.
+ Boundary conditions in dynamic programming are used to initialize the $dp$ table, and in search are used for pruning.
-Based on the above analysis, we can directly write the dynamic programming code. However, the decomposition of subproblems is a top-down approach, so implementing it in the order of "brute-force search → memoized search → dynamic programming" is more in line with habitual thinking.
+ The core of state transition order is to ensure that when computing the solution to the current problem, all the smaller subproblems it depends on have already been computed correctly.
-### Method 1: Brute-force search
+Based on the above analysis, we can directly write the dynamic programming code. However, subproblem decomposition is a top-down approach, so implementing in the order "brute force search $\rightarrow$ memoization $\rightarrow$ dynamic programming" is more aligned with thinking habits.
-Start searching from the state $[i, j]$, constantly decomposing it into smaller states $[i-1, j]$ and $[i, j-1]$. The recursive function includes the following elements.
+### Method 1: Brute force search
-- **Recursive parameter**: state $[i, j]$.
-- **Return value**: the minimum path sum from $[0, 0]$ to $[i, j]$ $dp[i, j]$.
-- **Termination condition**: when $i = 0$ and $j = 0$, return the cost $grid[0, 0]$.
-- **Pruning**: when $i < 0$ or $j < 0$ index out of bounds, return the cost $+\infty$, representing infeasibility.
+Starting from state $[i, j]$, continuously decompose into smaller states $[i-1, j]$ and $[i, j-1]$. The recursive function includes the following elements.
-Implementation code as follows:
+- **Recursive parameters**: state $[i, j]$.
+- **Return value**: minimum path sum from $[0, 0]$ to $[i, j]$, which is $dp[i, j]$.
+- **Termination condition**: when $i = 0$ and $j = 0$, return cost $grid[0, 0]$.
+- **Pruning**: when $i < 0$ or $j < 0$, the index is out of bounds, return cost $+\infty$, representing infeasibility.
+
+The implementation code is as follows:
```src
[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs}
```
-The figure below shows the recursive tree rooted at $dp[2, 1]$, which includes some overlapping subproblems, the number of which increases sharply as the size of the grid `grid` increases.
+The figure below shows the recursion tree rooted at $dp[2, 1]$, which includes some overlapping subproblems whose number will increase sharply as the size of grid `grid` grows.
-Essentially, the reason for overlapping subproblems is: **there are multiple paths to reach a certain cell from the top-left corner**.
+Essentially, the reason for overlapping subproblems is: **there are multiple paths from the top-left corner to reach a certain cell**.
-
+
-Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is $m + n - 2$, so the worst-case time complexity is $O(2^{m + n})$. Please note that this calculation method does not consider the situation near the grid edge, where there is only one choice left when reaching the network edge, so the actual number of paths will be less.
+Each state has two choices, down and right, so the total number of steps from the top-left corner to the bottom-right corner is $m + n - 2$, giving a worst-case time complexity of $O(2^{m + n})$, where $n$ and $m$ are the number of rows and columns of the grid, respectively. Note that this calculation does not account for situations near the grid boundaries, where only one choice remains when reaching the grid boundary, so the actual number of paths will be somewhat less.
-### Method 2: Memoized search
+### Method 2: Memoization
-We introduce a memo list `mem` of the same size as the grid `grid`, used to record the solutions to various subproblems, and prune overlapping subproblems:
+We introduce a memo list `mem` of the same size as grid `grid` to record the solutions to subproblems and prune overlapping subproblems:
```src
[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem}
```
-As shown in the figure below, after introducing memoization, all subproblem solutions only need to be calculated once, so the time complexity depends on the total number of states, i.e., the grid size $O(nm)$.
+As shown in the figure below, after introducing memoization, all subproblem solutions only need to be computed once, so the time complexity depends on the total number of states, which is the grid size $O(nm)$.
-
+
### Method 3: Dynamic programming
-Implement the dynamic programming solution iteratively, code as shown below:
+Implement the dynamic programming solution based on iteration, as shown in the code below:
```src
[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp}
```
-The figure below show the state transition process of the minimum path sum, traversing the entire grid, **thus the time complexity is $O(nm)$**.
+The figure below shows the state transition process for minimum path sum, which traverses the entire grid, **thus the time complexity is $O(nm)$**.
-The array `dp` is of size $n \times m$, **therefore the space complexity is $O(nm)$**.
+The array `dp` has size $n \times m$, **thus the space complexity is $O(nm)$**.
=== "<1>"
- 
+ 
=== "<2>"

@@ -174,9 +174,9 @@ The array `dp` is of size $n \times m$, **therefore the space complexity is $O(n
### Space optimization
-Since each cell is only related to the cell to its left and above, we can use a single-row array to implement the $dp$ table.
+Since each cell is only related to the cell to its left and the cell above it, we can use a single-row array to implement the $dp$ table.
-Please note, since the array `dp` can only represent the state of one row, we cannot initialize the first column state in advance, but update it as we traverse each row:
+Note that since the array `dp` can only represent the state of one row, we cannot initialize the first column state in advance, but rather update it when traversing each row:
```src
[file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp}
diff --git a/en/docs/chapter_dynamic_programming/edit_distance_problem.md b/en/docs/chapter_dynamic_programming/edit_distance_problem.md
index 34dad3865..d360ebc89 100644
--- a/en/docs/chapter_dynamic_programming/edit_distance_problem.md
+++ b/en/docs/chapter_dynamic_programming/edit_distance_problem.md
@@ -1,69 +1,69 @@
# Edit distance problem
-Edit distance, also known as Levenshtein distance, refers to the minimum number of modifications required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences.
+Edit distance, also known as Levenshtein distance, refers to the minimum number of edits required to transform one string into another, commonly used in information retrieval and natural language processing to measure the similarity between two sequences.
!!! question
Given two strings $s$ and $t$, return the minimum number of edits required to transform $s$ into $t$.
- You can perform three types of edits on a string: insert a character, delete a character, or replace a character with any other character.
+ You can perform three types of edit operations on a string: insert a character, delete a character, or replace a character with any other character.
As shown in the figure below, transforming `kitten` into `sitting` requires 3 edits, including 2 replacements and 1 insertion; transforming `hello` into `algo` requires 3 steps, including 2 replacements and 1 deletion.
-
+
-**The edit distance problem can naturally be explained with a decision tree model**. Strings correspond to tree nodes, and a round of decision (an edit operation) corresponds to an edge of the tree.
+**The edit distance problem can be naturally explained using the decision tree model**. Strings correspond to tree nodes, and a round of decision (one edit operation) corresponds to an edge of the tree.
-As shown in the figure below, with unrestricted operations, each node can derive many edges, each corresponding to one operation, meaning there are many possible paths to transform `hello` into `algo`.
+As shown in the figure below, without restricting operations, each node can branch into many edges, with each edge corresponding to one operation, meaning there are many possible paths to transform `hello` into `algo`.
-From the perspective of the decision tree, the goal of this problem is to find the shortest path between the node `hello` and the node `algo`.
+From the perspective of the decision tree, the goal of this problem is to find the shortest path between node `hello` and node `algo`.
-
+
### Dynamic programming approach
-**Step one: Think about each round of decision, define the state, thus obtaining the $dp$ table**
+**Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table**
Each round of decision involves performing one edit operation on string $s$.
-We aim to gradually reduce the problem size during the edit process, which enables us to construct subproblems. Let the lengths of strings $s$ and $t$ be $n$ and $m$, respectively. We first consider the tail characters of both strings $s[n-1]$ and $t[m-1]$.
+We want the problem scale to gradually decrease during the editing process, which allows us to construct subproblems. Let the lengths of strings $s$ and $t$ be $n$ and $m$ respectively. We first consider the tail characters of the two strings, $s[n-1]$ and $t[m-1]$.
- If $s[n-1]$ and $t[m-1]$ are the same, we can skip them and directly consider $s[n-2]$ and $t[m-2]$.
-- If $s[n-1]$ and $t[m-1]$ are different, we need to perform one edit on $s$ (insert, delete, replace) so that the tail characters of the two strings match, allowing us to skip them and consider a smaller-scale problem.
+- If $s[n-1]$ and $t[m-1]$ are different, we need to perform one edit on $s$ (insert, delete, or replace) to make the tail characters of the two strings the same, allowing us to skip them and consider a smaller-scale problem.
-Thus, each round of decision (edit operation) in string $s$ changes the remaining characters in $s$ and $t$ to be matched. Therefore, the state is the $i$-th and $j$-th characters currently considered in $s$ and $t$, denoted as $[i, j]$.
+In other words, each round of decision (edit operation) we make on string $s$ will change the remaining characters to be matched in $s$ and $t$. Therefore, the state is the $i$-th and $j$-th characters currently being considered in $s$ and $t$, denoted as $[i, j]$.
-State $[i, j]$ corresponds to the subproblem: **The minimum number of edits required to change the first $i$ characters of $s$ into the first $j$ characters of $t$**.
+State $[i, j]$ corresponds to the subproblem: **the minimum number of edits required to change the first $i$ characters of $s$ into the first $j$ characters of $t$**.
From this, we obtain a two-dimensional $dp$ table of size $(i+1) \times (j+1)$.
-**Step two: Identify the optimal substructure and then derive the state transition equation**
+**Step 2: Identify the optimal substructure, and then derive the state transition equation**
-Consider the subproblem $dp[i, j]$, whose corresponding tail characters of the two strings are $s[i-1]$ and $t[j-1]$, which can be divided into three scenarios as shown in the figure below.
+Consider subproblem $dp[i, j]$, where the tail characters of the corresponding two strings are $s[i-1]$ and $t[j-1]$, which can be divided into the three cases shown in the figure below based on different edit operations.
-1. Add $t[j-1]$ after $s[i-1]$, then the remaining subproblem is $dp[i, j-1]$.
+1. Insert $t[j-1]$ after $s[i-1]$, then the remaining subproblem is $dp[i, j-1]$.
2. Delete $s[i-1]$, then the remaining subproblem is $dp[i-1, j]$.
3. Replace $s[i-1]$ with $t[j-1]$, then the remaining subproblem is $dp[i-1, j-1]$.
-
+
-Based on the analysis above, we can determine the optimal substructure: The minimum number of edits for $dp[i, j]$ is the minimum among $dp[i, j-1]$, $dp[i-1, j]$, and $dp[i-1, j-1]$, plus the edit step $1$. The corresponding state transition equation is:
+Based on the above analysis, the optimal substructure can be obtained: the minimum number of edits for $dp[i, j]$ equals the minimum among the minimum edit steps of $dp[i, j-1]$, $dp[i-1, j]$, and $dp[i-1, j-1]$, plus the edit step $1$ for this time. The corresponding state transition equation is:
$$
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
$$
-Please note, **when $s[i-1]$ and $t[j-1]$ are the same, no edit is required for the current character**, in which case the state transition equation is:
+Please note that **when $s[i-1]$ and $t[j-1]$ are the same, no edit is required for the current character**, in which case the state transition equation is:
$$
dp[i, j] = dp[i-1, j-1]
$$
-**Step three: Determine the boundary conditions and the order of state transitions**
+**Step 3: Determine boundary conditions and state transition order**
-When both strings are empty, the number of edits is $0$, i.e., $dp[0, 0] = 0$. When $s$ is empty but $t$ is not, the minimum number of edits equals the length of $t$, that is, the first row $dp[0, j] = j$. When $s$ is not empty but $t$ is, the minimum number of edits equals the length of $s$, that is, the first column $dp[i, 0] = i$.
+When both strings are empty, the number of edit steps is $0$, i.e., $dp[0, 0] = 0$. When $s$ is empty but $t$ is not, the minimum number of edit steps equals the length of $t$, i.e., the first row $dp[0, j] = j$. When $s$ is not empty but $t$ is empty, the minimum number of edit steps equals the length of $s$, i.e., the first column $dp[i, 0] = i$.
-Observing the state transition equation, solving $dp[i, j]$ depends on the solutions to the left, above, and upper left, so a double loop can be used to traverse the entire $dp$ table in the correct order.
+Observing the state transition equation, the solution $dp[i, j]$ depends on solutions to the left, above, and upper-left, so the entire $dp$ table can be traversed in order through two nested loops.
### Code implementation
@@ -71,10 +71,10 @@ Observing the state transition equation, solving $dp[i, j]$ depends on the solut
[file]{edit_distance}-[class]{}-[func]{edit_distance_dp}
```
-As shown in the figure below, the process of state transition in the edit distance problem is very similar to that in the knapsack problem, which can be seen as filling a two-dimensional grid.
+As shown in the figure below, the state transition process for the edit distance problem is very similar to the knapsack problem and can both be viewed as the process of filling a two-dimensional grid.
=== "<1>"
- 
+ 
=== "<2>"

@@ -120,9 +120,9 @@ As shown in the figure below, the process of state transition in the edit distan
### Space optimization
-Since $dp[i, j]$ is derived from the solutions above $dp[i-1, j]$, to the left $dp[i, j-1]$, and to the upper left $dp[i-1, j-1]$, and direct traversal will lose the upper left solution $dp[i-1, j-1]$, and reverse traversal cannot build $dp[i, j-1]$ in advance, therefore, both traversal orders are not feasible.
+Since $dp[i, j]$ is transferred from the solutions above $dp[i-1, j]$, to the left $dp[i, j-1]$, and to the upper-left $dp[i-1, j-1]$, forward traversal will lose the upper-left solution $dp[i-1, j-1]$, and reverse traversal cannot build $dp[i, j-1]$ in advance, so neither traversal order is feasible.
-For this reason, we can use a variable `leftup` to temporarily store the solution from the upper left $dp[i-1, j-1]$, thus only needing to consider the solutions to the left and above. This situation is similar to the unbounded knapsack problem, allowing for direct traversal. The code is as follows:
+For this reason, we can use a variable `leftup` to temporarily store the upper-left solution $dp[i-1, j-1]$, so we only need to consider the solutions to the left and above. This situation is the same as the unbounded knapsack problem, allowing for forward traversal. The code is as follows:
```src
[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp}
diff --git a/en/docs/chapter_dynamic_programming/index.md b/en/docs/chapter_dynamic_programming/index.md
index f949566f1..29219fab3 100644
--- a/en/docs/chapter_dynamic_programming/index.md
+++ b/en/docs/chapter_dynamic_programming/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Streams merge into rivers, and rivers merge into the sea.
-
- Dynamic programming weaves smaller problems’ solutions into larger ones, guiding us step by step toward the far shore—where the ultimate answer awaits.
+ Streams converge into rivers, rivers converge into the sea.
+
+ Dynamic programming gathers solutions to small problems into answers to large problems, step by step guiding us to the shore of problem-solving.
diff --git a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
index 6a77c8e9e..a3bb8cbd6 100644
--- a/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
+++ b/en/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md
@@ -1,18 +1,18 @@
# Introduction to dynamic programming
-Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems, and stores the solutions of these subproblems to avoid redundant computations, thereby significantly improving time efficiency.
+Dynamic programming is an important algorithmic paradigm that decomposes a problem into a series of smaller subproblems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving time efficiency.
-In this section, we start with a classic problem, first presenting its brute force backtracking solution, identifying the overlapping subproblems, and then gradually deriving a more efficient dynamic programming solution.
+In this section, we start with a classic example, first presenting its brute force backtracking solution, observing the overlapping subproblems within it, and then gradually deriving a more efficient dynamic programming solution.
!!! question "Climbing stairs"
Given a staircase with $n$ steps, where you can climb $1$ or $2$ steps at a time, how many different ways are there to reach the top?
-As shown in the figure below, there are $3$ ways to reach the top of a $3$-step staircase.
+As shown in the figure below, for a $3$-step staircase, there are $3$ different ways to reach the top.

-This problem aims to calculate the number of ways by **using backtracking to exhaust all possibilities**. Specifically, it considers the problem of climbing stairs as a multi-round choice process: starting from the ground, choosing to move up either $1$ or $2$ steps each round, incrementing the count of ways upon reaching the top of the stairs, and pruning the process when it exceeds the top. The code is as follows:
+The goal of this problem is to find the number of ways, **we can consider using backtracking to enumerate all possibilities**. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up $1$ or $2$ steps in each round, incrementing the count by $1$ whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows:
```src
[file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack}
@@ -20,27 +20,27 @@ This problem aims to calculate the number of ways by **using backtracking to exh
## Method 1: Brute force search
-Backtracking algorithms do not explicitly decompose the problem into subproblems. Instead, they treat the problem as a sequence of decision steps, exploring all possibilities through trial and pruning.
+Backtracking algorithms typically do not explicitly decompose problems, but rather treat solving the problem as a series of decision steps, searching for all possible solutions through trial and pruning.
-We can analyze this problem using a decomposition approach. Let $dp[i]$ represent the number of ways to reach the $i^{th}$ step. In this case, $dp[i]$ is the original problem, and its subproblems are:
+We can try to analyze this problem from the perspective of problem decomposition. Let the number of ways to climb to the $i$-th step be $dp[i]$, then $dp[i]$ is the original problem, and its subproblems include:
$$
dp[i-1], dp[i-2], \dots, dp[2], dp[1]
$$
-Since each move can only advance $1$ or $2$ steps, when we stand on the $i^{th}$ step, the previous step must have been either on the $i-1^{th}$ or the $i-2^{th}$ step. In other words, we can only reach the $i^{th}$ from the $i-1^{th}$ or $i-2^{th}$ step.
+Since we can only go up $1$ or $2$ steps in each round, when we stand on the $i$-th step, we could only have been on the $i-1$-th or $i-2$-th step in the previous round. In other words, we can only reach the $i$-th step from the $i-1$-th or $i-2$-th step.
-This leads to an important conclusion: **the number of ways to reach the $i-1^{th}$ step plus the number of ways to reach the $i-2^{th}$ step equals the number of ways to reach the $i^{th}$ step**. The formula is as follows:
+This leads to an important conclusion: **the number of ways to climb to the $i-1$-th step plus the number of ways to climb to the $i-2$-th step equals the number of ways to climb to the $i$-th step**. The formula is as follows:
$$
dp[i] = dp[i-1] + dp[i-2]
$$
-This means that in the stair climbing problem, there is a recursive relationship between the subproblems, **the solution to the original problem can be constructed from the solutions to the subproblems**. The figure below shows this recursive relationship.
+This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, **the solution to the original problem can be constructed from the solutions to the subproblems**. The figure below illustrates this recurrence relation.
-
+
-We can obtain the brute force search solution according to the recursive formula. Starting with $dp[n]$, **we recursively break a larger problem into the sum of two smaller subproblems**, until reaching the smallest subproblems $dp[1]$ and $dp[2]$ where the solutions are known, with $dp[1] = 1$ and $dp[2] = 2$, representing $1$ and $2$ ways to climb to the first and second steps, respectively.
+We can obtain a brute force search solution based on the recurrence formula. Starting from $dp[n]$, **recursively decompose a larger problem into the sum of two smaller problems**, until reaching the smallest subproblems $dp[1]$ and $dp[2]$ and returning. Among them, the solutions to the smallest subproblems are known, namely $dp[1] = 1$ and $dp[2] = 2$, representing $1$ and $2$ ways to climb to the $1$st and $2$nd steps, respectively.
Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise:
@@ -48,20 +48,20 @@ Observe the following code, which, like standard backtracking code, belongs to d
[file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs}
```
-The figure below shows the recursive tree formed by brute force search. For the problem $dp[n]$, the depth of its recursive tree is $n$, with a time complexity of $O(2^n)$. This exponential growth causes the program to run much more slowly when $n$ is large, leading to long wait times.
+The figure below shows the recursion tree formed by brute force search. For the problem $dp[n]$, the depth of its recursion tree is $n$, with a time complexity of $O(2^n)$. Exponential order represents explosive growth; if we input a relatively large $n$, we will fall into a long wait.
-
+
-Observing the figure above, **the exponential time complexity is caused by 'overlapping subproblems'**. For example, $dp[9]$ is broken down into $dp[8]$ and $dp[7]$, and $dp[8]$ is further broken into $dp[7]$ and $dp[6]$, both containing the subproblem $dp[7]$.
+Observing the above figure, **the exponential time complexity is caused by "overlapping subproblems"**. For example, $dp[9]$ is decomposed into $dp[8]$ and $dp[7]$, and $dp[8]$ is decomposed into $dp[7]$ and $dp[6]$, both of which contain the subproblem $dp[7]$.
-Thus, subproblems include even smaller overlapping subproblems, endlessly. A vast majority of computational resources are wasted on these overlapping subproblems.
+And so on, subproblems contain smaller overlapping subproblems, ad infinitum. The vast majority of computational resources are wasted on these overlapping subproblems.
-## Method 2: Memoized search
+## Method 2: Memoization
-To enhance algorithm efficiency, **we hope that all overlapping subproblems are calculated only once**. For this purpose, we declare an array `mem` to record the solution of each subproblem, and prune overlapping subproblems during the search process.
+To improve algorithm efficiency, **we want all overlapping subproblems to be computed only once**. For this purpose, we declare an array `mem` to record the solution to each subproblem and prune overlapping subproblems during the search process.
-1. When $dp[i]$ is calculated for the first time, we record it in `mem[i]` for later use.
-2. When $dp[i]$ needs to be calculated again, we can directly retrieve the result from `mem[i]`, thus avoiding redundant calculations of that subproblem.
+1. When computing $dp[i]$ for the first time, we record it in `mem[i]` for later use.
+2. When we need to compute $dp[i]$ again, we can directly retrieve the result from `mem[i]`, thereby avoiding redundant computation of that subproblem.
The code is as follows:
@@ -69,17 +69,17 @@ The code is as follows:
[file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem}
```
-Observe the figure below, **after memoization, all overlapping subproblems need to be calculated only once, optimizing the time complexity to $O(n)$**, which is a significant leap.
+Observe the figure below, **after memoization, all overlapping subproblems only need to be computed once, optimizing the time complexity to $O(n)$**, which is a tremendous leap.
-
+
## Method 3: Dynamic programming
-**Memoized search is a 'top-down' method**: we start with the original problem (root node), recursively break larger subproblems into smaller ones until the solutions to the smallest known subproblems (leaf nodes) are reached. Subsequently, by backtracking, we collect the solutions of the subproblems, constructing the solution to the original problem.
+**Memoization is a "top-down" method**: we start from the original problem (root node), recursively decompose larger subproblems into smaller ones, until reaching the smallest known subproblems (leaf nodes). Afterward, by backtracking, we collect the solutions to the subproblems layer by layer to construct the solution to the original problem.
-On the contrary, **dynamic programming is a 'bottom-up' method**: starting with the solutions to the smallest subproblems, it iteratively constructs the solutions to larger subproblems until the original problem is solved.
+In contrast, **dynamic programming is a "bottom-up" method**: starting from the solutions to the smallest subproblems, iteratively constructing solutions to larger subproblems until obtaining the solution to the original problem.
-Since dynamic programming does not involve backtracking, it only requires iteration using loops and does not need recursion. In the following code, we initialize an array `dp` to store the solutions to subproblems, serving the same recording function as the array `mem` in memoized search:
+Since dynamic programming does not include a backtracking process, it only requires loop iteration for implementation and does not need recursion. In the following code, we initialize an array `dp` to store the solutions to subproblems, which serves the same recording function as the array `mem` in memoization:
```src
[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp}
@@ -89,22 +89,22 @@ The figure below simulates the execution process of the above code.

-Like the backtracking algorithm, dynamic programming also uses the concept of "states" to represent specific stages in problem solving, each state corresponding to a subproblem and its local optimal solution. For example, the state of the climbing stairs problem is defined as the current step number $i$.
+Like backtracking algorithms, dynamic programming also uses the "state" concept to represent specific stages of problem solving, with each state corresponding to a subproblem and its corresponding local optimal solution. For example, the state in the stair climbing problem is defined as the current stair step number $i$.
Based on the above content, we can summarize the commonly used terminology in dynamic programming.
-- The array `dp` is referred to as the DP table, with $dp[i]$ representing the solution to the subproblem corresponding to state $i$.
-- The states corresponding to the smallest subproblems (steps $1$ and $2$) are called initial states.
-- The recursive formula $dp[i] = dp[i-1] + dp[i-2]$ is called the state transition equation.
+- The array `dp` is called the dp table, where $dp[i]$ represents the solution to the subproblem corresponding to state $i$.
+- The states corresponding to the smallest subproblems (the $1$st and $2$nd steps) are called initial states.
+- The recurrence formula $dp[i] = dp[i-1] + dp[i-2]$ is called the state transition equation.
## Space optimization
-Observant readers may have noticed that **since $dp[i]$ is only related to $dp[i-1]$ and $dp[i-2]$, we do not need to use an array `dp` to store the solutions to all subproblems**, but can simply use two variables to progress iteratively. The code is as follows:
+Observant readers may have noticed that **since $dp[i]$ is only related to $dp[i-1]$ and $dp[i-2]$, we do not need to use an array `dp` to store the solutions to all subproblems**, but can simply use two variables to roll forward. The code is as follows:
```src
[file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp}
```
-Observing the above code, since the space occupied by the array `dp` is eliminated, the space complexity is reduced from $O(n)$ to $O(1)$.
+Observing the above code, since the space occupied by the array `dp` is saved, the space complexity is reduced from $O(n)$ to $O(1)$.
-In many dynamic programming problems, the current state depends only on a limited number of previous states, allowing us to retain only the necessary states and save memory space by "dimension reduction". **This space optimization technique is known as 'rolling variable' or 'rolling array'**.
+In dynamic programming problems, the current state often depends only on a limited number of preceding states, allowing us to retain only the necessary states and save memory space through "dimension reduction". **This space optimization technique is called "rolling variable" or "rolling array"**.
diff --git a/en/docs/chapter_dynamic_programming/knapsack_problem.md b/en/docs/chapter_dynamic_programming/knapsack_problem.md
index 6536c3669..cd8e8ed1c 100644
--- a/en/docs/chapter_dynamic_programming/knapsack_problem.md
+++ b/en/docs/chapter_dynamic_programming/knapsack_problem.md
@@ -1,60 +1,60 @@
-# 0-1 Knapsack problem
+# 0-1 knapsack problem
-The knapsack problem is an excellent introductory problem for dynamic programming and is the most common type of problem in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem, etc.
+The knapsack problem is an excellent introductory problem for dynamic programming and is one of the most common problem forms in dynamic programming. It has many variants, such as the 0-1 knapsack problem, the unbounded knapsack problem, and the multiple knapsack problem.
In this section, we will first solve the most common 0-1 knapsack problem.
!!! question
- Given $n$ items, the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with a capacity of $cap$. Each item can be chosen only once. What is the maximum value of items that can be placed in the knapsack under the capacity limit?
+ Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. Each item can only be selected once. What is the maximum value that can be placed in the knapsack within the capacity limit?
-Observe the figure below, since the item number $i$ starts counting from 1, and the array index starts from 0, thus the weight of item $i$ corresponds to $wgt[i-1]$ and the value corresponds to $val[i-1]$.
+Observe the figure below. Since item number $i$ starts counting from $1$ and array indices start from $0$, item $i$ corresponds to weight $wgt[i-1]$ and value $val[i-1]$.
-
+
-We can consider the 0-1 knapsack problem as a process consisting of $n$ rounds of decisions, where for each item there are two decisions: not to put it in or to put it in, thus the problem fits the decision tree model.
+We can view the 0-1 knapsack problem as a process consisting of $n$ rounds of decisions, where for each item there are two decisions: not putting it in and putting it in, thus the problem satisfies the decision tree model.
-The objective of this problem is to "maximize the value of the items that can be put in the knapsack under the limited capacity," thus it is more likely a dynamic programming problem.
+The goal of this problem is to find "the maximum value that can be placed in the knapsack within the capacity limit", so it is more likely to be a dynamic programming problem.
-**First step: Think about each round of decisions, define states, thereby obtaining the $dp$ table**
+**Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table**
-For each item, if not put into the knapsack, the capacity remains unchanged; if put in, the capacity is reduced. From this, the state definition can be obtained: the current item number $i$ and knapsack capacity $c$, denoted as $[i, c]$.
+For each item, if not placed in the knapsack, the knapsack capacity remains unchanged; if placed in, the knapsack capacity decreases. From this, we can derive the state definition: current item number $i$ and knapsack capacity $c$, denoted as $[i, c]$.
-State $[i, c]$ corresponds to the sub-problem: **the maximum value of the first $i$ items in a knapsack of capacity $c$**, denoted as $dp[i, c]$.
+State $[i, c]$ corresponds to the subproblem: **the maximum value among the first $i$ items in a knapsack of capacity $c$**, denoted as $dp[i, c]$.
-The solution we are looking for is $dp[n, cap]$, so we need a two-dimensional $dp$ table of size $(n+1) \times (cap+1)$.
+What we need to find is $dp[n, cap]$, so we need a two-dimensional $dp$ table of size $(n+1) \times (cap+1)$.
-**Second step: Identify the optimal substructure, then derive the state transition equation**
+**Step 2: Identify the optimal substructure, and then derive the state transition equation**
-After making the decision for item $i$, what remains is the sub-problem of decisions for the first $i-1$ items, which can be divided into two cases.
+After making the decision for item $i$, what remains is the subproblem of the first $i-1$ items, which can be divided into the following two cases.
-- **Not putting item $i$**: The knapsack capacity remains unchanged, state changes to $[i-1, c]$.
-- **Putting item $i$**: The knapsack capacity decreases by $wgt[i-1]$, and the value increases by $val[i-1]$, state changes to $[i-1, c-wgt[i-1]]$.
+- **Not putting item $i$**: The knapsack capacity remains unchanged, and the state changes to $[i-1, c]$.
+- **Putting item $i$**: The knapsack capacity decreases by $wgt[i-1]$, the value increases by $val[i-1]$, and the state changes to $[i-1, c-wgt[i-1]]$.
-The above analysis reveals the optimal substructure of this problem: **the maximum value $dp[i, c]$ is equal to the larger value of the two schemes of not putting item $i$ and putting item $i$**. From this, the state transition equation can be derived:
+The above analysis reveals the optimal substructure of this problem: **the maximum value $dp[i, c]$ equals the larger value between not putting item $i$ and putting item $i$**. From this, the state transition equation can be derived:
$$
dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
$$
-It is important to note that if the current item's weight $wgt[i - 1]$ exceeds the remaining knapsack capacity $c$, then the only option is not to put it in the knapsack.
+Note that if the weight of the current item $wgt[i - 1]$ exceeds the remaining knapsack capacity $c$, then the only option is not to put it in the knapsack.
-**Third step: Determine the boundary conditions and the order of state transitions**
+**Step 3: Determine boundary conditions and state transition order**
When there are no items or the knapsack capacity is $0$, the maximum value is $0$, i.e., the first column $dp[i, 0]$ and the first row $dp[0, c]$ are both equal to $0$.
-The current state $[i, c]$ transitions from the state directly above $[i-1, c]$ and the state to the upper left $[i-1, c-wgt[i-1]]$, thus, the entire $dp$ table is traversed in order through two layers of loops.
+The current state $[i, c]$ is transferred from the state above $[i-1, c]$ and the state in the upper-left $[i-1, c-wgt[i-1]]$, so the entire $dp$ table is traversed in order through two nested loops.
-Following the above analysis, we will next implement the solutions in the order of brute force search, memoized search, and dynamic programming.
+Based on the above analysis, we will next implement the brute force search, memoization, and dynamic programming solutions in order.
-### Method one: Brute force search
+### Method 1: Brute force search
The search code includes the following elements.
-- **Recursive parameters**: State $[i, c]$.
-- **Return value**: Solution to the sub-problem $dp[i, c]$.
-- **Termination condition**: When the item number is out of bounds $i = 0$ or the remaining capacity of the knapsack is $0$, terminate the recursion and return the value $0$.
-- **Pruning**: If the current item's weight exceeds the remaining capacity of the knapsack, the only option is not to put it in the knapsack.
+- **Recursive parameters**: state $[i, c]$.
+- **Return value**: solution to the subproblem $dp[i, c]$.
+- **Termination condition**: when the item number is out of bounds $i = 0$ or the remaining knapsack capacity is $0$, terminate recursion and return value $0$.
+- **Pruning**: if the weight of the current item exceeds the remaining knapsack capacity, only the option of not putting it in is available.
```src
[file]{knapsack}-[class]{}-[func]{knapsack_dfs}
@@ -62,36 +62,36 @@ The search code includes the following elements.
As shown in the figure below, since each item generates two search branches of not selecting and selecting, the time complexity is $O(2^n)$.
-Observing the recursive tree, it is easy to see that there are overlapping sub-problems, such as $dp[1, 10]$, etc. When there are many items and the knapsack capacity is large, especially when there are many items of the same weight, the number of overlapping sub-problems will increase significantly.
+Observing the recursion tree, it is easy to see overlapping subproblems, such as $dp[1, 10]$. When there are many items, large knapsack capacity, and especially many items with the same weight, the number of overlapping subproblems will increase significantly.
-
+
-### Method two: Memoized search
+### Method 2: Memoization
-To ensure that overlapping sub-problems are only calculated once, we use a memoization list `mem` to record the solutions to sub-problems, where `mem[i][c]` corresponds to $dp[i, c]$.
+To ensure that overlapping subproblems are only computed once, we use a memo list `mem` to record the solutions to subproblems, where `mem[i][c]` corresponds to $dp[i, c]$.
-After introducing memoization, **the time complexity depends on the number of sub-problems**, which is $O(n \times cap)$. The implementation code is as follows:
+After introducing memoization, **the time complexity depends on the number of subproblems**, which is $O(n \times cap)$. The implementation code is as follows:
```src
[file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem}
```
-The figure below shows the search branches that are pruned in memoized search.
+The figure below shows the search branches pruned in memoization.
-
+
-### Method three: Dynamic programming
+### Method 3: Dynamic programming
-Dynamic programming essentially involves filling the $dp$ table during the state transition, the code is shown in the figure below:
+Dynamic programming is essentially the process of filling the $dp$ table during state transitions. The code is as follows:
```src
[file]{knapsack}-[class]{}-[func]{knapsack_dp}
```
-As shown in the figure below, both the time complexity and space complexity are determined by the size of the array `dp`, i.e., $O(n \times cap)$.
+As shown in the figure below, both time complexity and space complexity are determined by the size of the array `dp`, which is $O(n \times cap)$.
=== "<1>"
- 
+ 
=== "<2>"

@@ -134,17 +134,17 @@ As shown in the figure below, both the time complexity and space complexity are
### Space optimization
-Since each state is only related to the state in the row above it, we can use two arrays to roll forward, reducing the space complexity from $O(n^2)$ to $O(n)$.
+Since each state is only related to the state in the row above it, we can use two arrays rolling forward to reduce the space complexity from $O(n^2)$ to $O(n)$.
-Further thinking, can we use just one array to achieve space optimization? It can be observed that each state is transferred from the cell directly above or from the upper left cell. If there is only one array, when starting to traverse the $i$-th row, that array still stores the state of row $i-1$.
+Further thinking, can we achieve space optimization using just one array? Observing, we can see that each state is transferred from the cell directly above or the cell in the upper-left. If there is only one array, when we start traversing row $i$, that array still stores the state of row $i-1$.
-- If using normal order traversal, then when traversing to $dp[i, j]$, the values from the upper left $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ may have already been overwritten, thus the correct state transition result cannot be obtained.
-- If using reverse order traversal, there will be no overwriting problem, and the state transition can be conducted correctly.
+- If using forward traversal, then when traversing to $dp[i, j]$, the values in the upper-left $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ may have already been overwritten, thus preventing correct state transition.
+- If using reverse traversal, there will be no overwriting issue, and state transition can proceed correctly.
-The figures below show the transition process from row $i = 1$ to row $i = 2$ in a single array. Please think about the differences between normal order traversal and reverse order traversal.
+The figure below shows the transition process from row $i = 1$ to row $i = 2$ using a single array. Please consider the difference between forward and reverse traversal.
=== "<1>"
- 
+ 
=== "<2>"

@@ -161,7 +161,7 @@ The figures below show the transition process from row $i = 1$ to row $i = 2$ in
=== "<6>"

-In the code implementation, we only need to delete the first dimension $i$ of the array `dp` and change the inner loop to reverse traversal:
+In the code implementation, we simply need to delete the first dimension $i$ of the array `dp` and change the inner loop to reverse traversal:
```src
[file]{knapsack}-[class]{}-[func]{knapsack_dp_comp}
diff --git a/en/docs/chapter_dynamic_programming/summary.md b/en/docs/chapter_dynamic_programming/summary.md
index d7e48eb41..5503dde43 100644
--- a/en/docs/chapter_dynamic_programming/summary.md
+++ b/en/docs/chapter_dynamic_programming/summary.md
@@ -1,23 +1,23 @@
# Summary
-- Dynamic programming decomposes problems and improves computational efficiency by avoiding redundant computations through storing solutions of subproblems.
-- Without considering time, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree has many overlapping subproblems, resulting in very low efficiency. By introducing a memorization list, it's possible to store solutions of all computed subproblems, ensuring that overlapping subproblems are only computed once.
-- Memorization search is a top-down recursive solution, whereas dynamic programming corresponds to a bottom-up iterative approach, akin to "filling out a table." Since the current state only depends on certain local states, we can eliminate one dimension of the dp table to reduce space complexity.
-- Decomposition of subproblems is a universal algorithmic approach, differing in characteristics among divide and conquer, dynamic programming, and backtracking.
-- Dynamic programming problems have three main characteristics: overlapping subproblems, optimal substructure, and no aftereffects.
-- If the optimal solution of the original problem can be constructed from the optimal solutions of its subproblems, it has an optimal substructure.
-- No aftereffects mean that the future development of a state depends only on the current state and not on all past states experienced. Many combinatorial optimization problems do not have this property and cannot be quickly solved using dynamic programming.
+- Dynamic programming decomposes problems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving computational efficiency.
+- Without considering time constraints, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree contains a large number of overlapping subproblems, resulting in extremely low efficiency. By introducing a memo list, we can store the solutions to all computed subproblems, ensuring that overlapping subproblems are only computed once.
+- Memoization is a top-down recursive solution, while the corresponding dynamic programming is a bottom-up iterative solution, similar to "filling in a table". Since the current state only depends on certain local states, we can eliminate one dimension of the $dp$ table to reduce space complexity.
+- Subproblem decomposition is a general algorithmic approach, with different properties in divide and conquer, dynamic programming, and backtracking.
+- Dynamic programming problems have three major characteristics: overlapping subproblems, optimal substructure, and no aftereffects.
+- If the optimal solution to the original problem can be constructed from the optimal solutions to the subproblems, then it has optimal substructure.
+- No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not have no aftereffects and cannot be quickly solved using dynamic programming.
**Knapsack problem**
-- The knapsack problem is one of the most typical dynamic programming problems, with variants including the 0-1 knapsack, unbounded knapsack, and multiple knapsacks.
-- The state definition of the 0-1 knapsack is the maximum value in a knapsack of capacity $c$ with the first $i$ items. Based on decisions not to include or to include an item in the knapsack, optimal substructures can be identified and state transition equations constructed. In space optimization, since each state depends on the state directly above and to the upper left, the list should be traversed in reverse order to avoid overwriting the upper left state.
-- In the unbounded knapsack problem, there is no limit on the number of each kind of item that can be chosen, thus the state transition for including items differs from the 0-1 knapsack. Since the state depends on the state directly above and to the left, space optimization should involve forward traversal.
-- The coin change problem is a variant of the unbounded knapsack problem, shifting from seeking the “maximum” value to seeking the “minimum” number of coins, thus the state transition equation should change $\max()$ to $\min()$. From pursuing “not exceeding” the capacity of the knapsack to seeking exactly the target amount, thus use $amt + 1$ to represent the invalid solution of “unable to make up the target amount.”
-- Coin Change Problem II shifts from seeking the “minimum number of coins” to seeking the “number of coin combinations,” changing the state transition equation accordingly from $\min()$ to summation operator.
+- The knapsack problem is one of the most typical dynamic programming problems, with variants such as the 0-1 knapsack, unbounded knapsack, and multiple knapsack.
+- The state definition for the 0-1 knapsack is the maximum value among the first $i$ items in a knapsack of capacity $c$. Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state.
+- The unbounded knapsack problem has no limit on the selection quantity of each type of item, so the state transition for choosing to put in an item differs from the 0-1 knapsack problem. Since the state depends on the state directly above and directly to the left, space optimization should use forward traversal.
+- The coin change problem is a variant of the unbounded knapsack problem. It changes from seeking the "maximum" value to seeking the "minimum" number of coins, so $\max()$ in the state transition equation should be changed to $\min()$. It changes from seeking "not exceeding" the knapsack capacity to seeking "exactly" making up the target amount, so $amt + 1$ is used to represent the invalid solution of "unable to make up the target amount".
+- Coin change problem II changes from seeking the "minimum number of coins" to seeking the "number of coin combinations", so the state transition equation correspondingly changes from $\min()$ to a summation operator.
**Edit distance problem**
-- Edit distance (Levenshtein distance) measures the similarity between two strings, defined as the minimum number of editing steps needed to change one string into another, with editing operations including adding, deleting, or replacing.
-- The state definition for the edit distance problem is the minimum number of editing steps needed to change the first $i$ characters of $s$ into the first $j$ characters of $t$. When $s[i] \ne t[j]$, there are three decisions: add, delete, replace, each with their corresponding residual subproblems. From this, optimal substructures can be identified, and state transition equations built. When $s[i] = t[j]$, no editing of the current character is necessary.
-- In edit distance, the state depends on the state directly above, to the left, and to the upper left. Therefore, after space optimization, neither forward nor reverse traversal can correctly perform state transitions. To address this, we use a variable to temporarily store the upper left state, making it equivalent to the situation in the unbounded knapsack problem, allowing for forward traversal after space optimization.
+- Edit distance (Levenshtein distance) is used to measure the similarity between two strings, defined as the minimum number of edit steps from one string to another, with edit operations including insert, delete, and replace.
+- The state definition for the edit distance problem is the minimum number of edit steps required to change the first $i$ characters of $s$ into the first $j$ characters of $t$. When $s[i] \ne t[j]$, there are three decisions: insert, delete, replace, each with corresponding remaining subproblems. From this, the optimal substructure can be identified and the state transition equation constructed. When $s[i] = t[j]$, no edit is required for the current character.
+- In edit distance, the state depends on the state directly above, directly to the left, and to the upper-left, so after space optimization, neither forward nor reverse traversal can correctly perform state transitions. For this reason, we use a variable to temporarily store the upper-left state, thus transforming to a situation equivalent to the unbounded knapsack problem, allowing for forward traversal after space optimization.
diff --git a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
index 309ee21b4..56a54d0e6 100644
--- a/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
+++ b/en/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md
@@ -6,23 +6,23 @@ In this section, we first solve another common knapsack problem: the unbounded k
!!! question
- Given $n$ items, where the weight of the $i^{th}$ item is $wgt[i-1]$ and its value is $val[i-1]$, and a backpack with a capacity of $cap$. **Each item can be selected multiple times**. What is the maximum value of the items that can be put into the backpack without exceeding its capacity? See the example below.
+ Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. **Each item can be selected multiple times**. What is the maximum value that can be placed in the knapsack within the capacity limit? An example is shown in the figure below.
-
+
### Dynamic programming approach
-The unbounded knapsack problem is very similar to the 0-1 knapsack problem, **the only difference being that there is no limit on the number of times an item can be chosen**.
+The unbounded knapsack problem is very similar to the 0-1 knapsack problem, **differing only in that there is no limit on the number of times an item can be selected**.
-- In the 0-1 knapsack problem, there is only one of each item, so after placing item $i$ into the backpack, you can only choose from the previous $i-1$ items.
-- In the unbounded knapsack problem, the quantity of each item is unlimited, so after placing item $i$ in the backpack, **you can still choose from the previous $i$ items**.
+- In the 0-1 knapsack problem, there is only one of each type of item, so after placing item $i$ in the knapsack, we can only choose from the first $i-1$ items.
+- In the unbounded knapsack problem, the quantity of each type of item is unlimited, so after placing item $i$ in the knapsack, **we can still choose from the first $i$ items**.
-Under the rules of the unbounded knapsack problem, the state $[i, c]$ can change in two ways.
+Under the rules of the unbounded knapsack problem, the changes in state $[i, c]$ are divided into two cases.
-- **Not putting item $i$ in**: As with the 0-1 knapsack problem, transition to $[i-1, c]$.
-- **Putting item $i$ in**: Unlike the 0-1 knapsack problem, transition to $[i, c-wgt[i-1]]$.
+- **Not putting item $i$**: Same as the 0-1 knapsack problem, transfer to $[i-1, c]$.
+- **Putting item $i$**: Different from the 0-1 knapsack problem, transfer to $[i, c-wgt[i-1]]$.
-The state transition equation thus becomes:
+Thus, the state transition equation becomes:
$$
dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
@@ -30,7 +30,7 @@ $$
### Code implementation
-Comparing the code for the two problems, the state transition changes from $i-1$ to $i$, the rest is completely identical:
+Comparing the code for the two problems, there is one change in state transition from $i-1$ to $i$, with everything else identical:
```src
[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp}
@@ -38,12 +38,12 @@ Comparing the code for the two problems, the state transition changes from $i-1$
### Space optimization
-Since the current state comes from the state to the left and above, **the space-optimized solution should perform a forward traversal for each row in the $dp$ table**.
+Since the current state is transferred from states on the left and above, **after space optimization, each row in the $dp$ table should be traversed in forward order**.
-This traversal order is the opposite of that for the 0-1 knapsack. Please refer to the figure below to understand the difference.
+This traversal order is exactly opposite to the 0-1 knapsack. Please refer to the figure below to understand the difference between the two.
=== "<1>"
- 
+ 
=== "<2>"

@@ -60,7 +60,7 @@ This traversal order is the opposite of that for the 0-1 knapsack. Please refer
=== "<6>"

-The code implementation is quite simple, just remove the first dimension of the array `dp`:
+The code implementation is relatively simple, just delete the first dimension of the array `dp`:
```src
[file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp}
@@ -68,59 +68,59 @@ The code implementation is quite simple, just remove the first dimension of the
## Coin change problem
-The knapsack problem is a representative of a large class of dynamic programming problems and has many variants, such as the coin change problem.
+The knapsack problem represents a large class of dynamic programming problems and has many variants, such as the coin change problem.
!!! question
- Given $n$ types of coins, the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. **Each type of coin can be selected multiple times**. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. See the example below.
+ Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$. **Each type of coin can be selected multiple times**. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. An example is shown in the figure below.
-
+
### Dynamic programming approach
-**The coin change can be seen as a special case of the unbounded knapsack problem**, sharing the following similarities and differences.
+**The coin change problem can be viewed as a special case of the unbounded knapsack problem**, with the following connections and differences.
-- The two problems can be converted into each other: "item" corresponds to "coin", "item weight" corresponds to "coin denomination", and "backpack capacity" corresponds to "target amount".
-- The optimization goals are opposite: the unbounded knapsack problem aims to maximize the value of items, while the coin change problem aims to minimize the number of coins.
-- The unbounded knapsack problem seeks solutions "not exceeding" the backpack capacity, while the coin change seeks solutions that "exactly" make up the target amount.
+- The two problems can be converted to each other: "item" corresponds to "coin", "item weight" corresponds to "coin denomination", and "knapsack capacity" corresponds to "target amount".
+- The optimization goals are opposite: the unbounded knapsack problem aims to maximize item value, while the coin change problem aims to minimize the number of coins.
+- The unbounded knapsack problem seeks solutions "not exceeding" the knapsack capacity, while the coin change problem seeks solutions that "exactly" make up the target amount.
-**First step: Think through each round's decision-making, define the state, and thus derive the $dp$ table**
+**Step 1: Think about the decisions in each round, define the state, and thus obtain the $dp$ table**
-The state $[i, a]$ corresponds to the sub-problem: **the minimum number of coins that can make up the amount $a$ using the first $i$ types of coins**, denoted as $dp[i, a]$.
+State $[i, a]$ corresponds to the subproblem: **the minimum number of coins among the first $i$ types of coins that can make up amount $a$**, denoted as $dp[i, a]$.
-The two-dimensional $dp$ table is of size $(n+1) \times (amt+1)$.
+The two-dimensional $dp$ table has size $(n+1) \times (amt+1)$.
-**Second step: Identify the optimal substructure and derive the state transition equation**
+**Step 2: Identify the optimal substructure, and then derive the state transition equation**
-This problem differs from the unbounded knapsack problem in two aspects of the state transition equation.
+This problem differs from the unbounded knapsack problem in the following two aspects regarding the state transition equation.
-- This problem seeks the minimum, so the operator $\max()$ needs to be changed to $\min()$.
-- The optimization is focused on the number of coins, so simply add $+1$ when a coin is chosen.
+- This problem seeks the minimum value, so the operator $\max()$ needs to be changed to $\min()$.
+- The optimization target is the number of coins rather than item value, so when a coin is selected, simply execute $+1$.
$$
dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
$$
-**Third step: Define boundary conditions and state transition order**
+**Step 3: Determine boundary conditions and state transition order**
-When the target amount is $0$, the minimum number of coins needed to make it up is $0$, so all $dp[i, 0]$ in the first column are $0$.
+When the target amount is $0$, the minimum number of coins needed to make it up is $0$, so all $dp[i, 0]$ in the first column equal $0$.
-When there are no coins, **it is impossible to make up any amount >0**, which is an invalid solution. To allow the $\min()$ function in the state transition equation to recognize and filter out invalid solutions, consider using $+\infty$ to represent them, i.e., set all $dp[0, a]$ in the first row to $+\infty$.
+When there are no coins, **it is impossible to make up any amount $> 0$**, which is an invalid solution. To enable the $\min()$ function in the state transition equation to identify and filter out invalid solutions, we consider using $+ \infty$ to represent them, i.e., set all $dp[0, a]$ in the first row to $+ \infty$.
### Code implementation
-Most programming languages do not provide a $+\infty$ variable, only the maximum value of an integer `int` can be used as a substitute. This can lead to overflow: the $+1$ operation in the state transition equation may overflow.
+Most programming languages do not provide a $+ \infty$ variable, and can only use the maximum value of integer type `int` as a substitute. However, this can lead to large number overflow: the $+ 1$ operation in the state transition equation may cause overflow.
-For this reason, we use the number $amt + 1$ to represent an invalid solution, because the maximum number of coins needed to make up $amt$ is at most $amt$. Before returning the result, check if $dp[n, amt]$ equals $amt + 1$, and if so, return $-1$, indicating that the target amount cannot be made up. The code is as follows:
+For this reason, we use the number $amt + 1$ to represent invalid solutions, because the maximum number of coins needed to make up $amt$ is at most $amt$. Before returning, check whether $dp[n, amt]$ equals $amt + 1$; if so, return $-1$, indicating that the target amount cannot be made up. The code is as follows:
```src
[file]{coin_change}-[class]{}-[func]{coin_change_dp}
```
-The figure below show the dynamic programming process for the coin change problem, which is very similar to the unbounded knapsack problem.
+The figure below shows the dynamic programming process for coin change, which is very similar to the unbounded knapsack problem.
=== "<1>"
- 
+ 
=== "<2>"

@@ -166,7 +166,7 @@ The figure below show the dynamic programming process for the coin change proble
### Space optimization
-The space optimization for the coin change problem is handled in the same way as for the unbounded knapsack problem:
+The space optimization for the coin change problem is handled in the same way as the unbounded knapsack problem:
```src
[file]{coin_change}-[class]{}-[func]{coin_change_dp_comp}
@@ -176,21 +176,21 @@ The space optimization for the coin change problem is handled in the same way as
!!! question
- Given $n$ types of coins, where the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. Each type of coin can be selected multiple times, **ask how many combinations of coins can make up the target amount**. See the example below.
+ Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$. Each type of coin can be selected multiple times. **What is the number of coin combinations that can make up the target amount?** An example is shown in the figure below.
-
+
### Dynamic programming approach
-Compared to the previous problem, the goal of this problem is to determine the number of combinations, so the sub-problem becomes: **the number of combinations that can make up amount $a$ using the first $i$ types of coins**. The $dp$ table remains a two-dimensional matrix of size $(n+1) \times (amt + 1)$.
+Compared to the previous problem, this problem's goal is to find the number of combinations, so the subproblem becomes: **the number of combinations among the first $i$ types of coins that can make up amount $a$**. The $dp$ table remains a two-dimensional matrix of size $(n+1) \times (amt + 1)$.
-The number of combinations for the current state is the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is:
+The number of combinations for the current state equals the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is:
$$
dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]]
$$
-When the target amount is $0$, no coins are needed to make up the target amount, so all $dp[i, 0]$ in the first column should be initialized to $1$. When there are no coins, it is impossible to make up any amount >0, so all $dp[0, a]$ in the first row should be set to $0$.
+When the target amount is $0$, no coins need to be selected to make up the target amount, so all $dp[i, 0]$ in the first column should be initialized to $1$. When there are no coins, it is impossible to make up any amount $>0$, so all $dp[0, a]$ in the first row equal $0$.
### Code implementation
@@ -200,7 +200,7 @@ When the target amount is $0$, no coins are needed to make up the target amount,
### Space optimization
-The space optimization approach is the same, just remove the coin dimension:
+The space optimization is handled in the same way, just delete the coin dimension:
```src
[file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp}
diff --git a/en/docs/chapter_graph/graph.md b/en/docs/chapter_graph/graph.md
index 0e081237a..73422ff73 100644
--- a/en/docs/chapter_graph/graph.md
+++ b/en/docs/chapter_graph/graph.md
@@ -1,6 +1,6 @@
# Graph
-A graph is a type of nonlinear data structure, consisting of vertices and edges. A graph $G$ can be abstractly represented as a collection of a set of vertices $V$ and a set of edges $E$. The following example shows a graph containing 5 vertices and 7 edges.
+A graph is a nonlinear data structure consisting of vertices and edges. We can abstractly represent a graph $G$ as a set of vertices $V$ and a set of edges $E$. The following example shows a graph containing 5 vertices and 7 edges.
$$
\begin{aligned}
@@ -10,74 +10,74 @@ G & = \{ V, E \} \newline
\end{aligned}
$$
-If vertices are viewed as nodes and edges as references (pointers) connecting the nodes, graphs can be seen as a data structure that extends from linked lists. As shown in the figure below, **compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) are more complex due to their higher degree of freedom**.
+If we view vertices as nodes and edges as references (pointers) connecting the nodes, we can see graphs as a data structure extended from linked lists. As shown in the figure below, **compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex**.
-
+
-## Common types and terminologies of graphs
+## Common types and terminology of graphs
-Graphs can be divided into undirected graphs and directed graphs depending on whether edges have direction, as shown in the figure below.
+Graphs can be divided into undirected graphs and directed graphs based on whether edges have direction, as shown in the figure below.
-- In undirected graphs, edges represent a "bidirectional" connection between two vertices, for example, the "friends" in Facebook.
-- In directed graphs, edges have directionality, that is, the edges $A \rightarrow B$ and $A \leftarrow B$ are independent of each other. For example, the "follow" and "followed" relationship on Instagram or TikTok.
+- In undirected graphs, edges represent a "bidirectional" connection between two vertices, such as the "friend relationship" on WeChat or QQ.
+- In directed graphs, edges have directionality, meaning edges $A \rightarrow B$ and $A \leftarrow B$ are independent of each other, such as the "follow" and "be followed" relationships on Weibo or TikTok.

-Depending on whether all vertices are connected, graphs can be divided into connected graphs and disconnected graphs, as shown in the figure below.
+Graphs can be divided into connected graphs and disconnected graphs based on whether all vertices are connected, as shown in the figure below.
-- For connected graphs, it is possible to reach any other vertex starting from an arbitrary vertex.
-- For disconnected graphs, there is at least one vertex that cannot be reached from an arbitrary starting vertex.
+- For connected graphs, starting from any vertex, all other vertices can be reached.
+- For disconnected graphs, starting from a certain vertex, at least one vertex cannot be reached.

-We can also add a weight variable to edges, resulting in weighted graphs as shown in the figure below. For example, in Instagram, the system sorts your follower and following list by the level of interaction between you and other users (likes, views, comments, etc.). Such an interaction network can be represented by a weighted graph.
+We can also add a "weight" variable to edges, resulting in weighted graphs as shown in the figure below. For example, in mobile games like "Honor of Kings", the system calculates the "intimacy" between players based on their shared game time, and such intimacy networks can be represented using weighted graphs.

Graph data structures include the following commonly used terms.
-- Adjacency: When there is an edge connecting two vertices, these two vertices are said to be "adjacent". In the figure above, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
-- Path: The sequence of edges passed from vertex A to vertex B is called a path from A to B. In the figure above, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
-- Degree: The number of edges a vertex has. For directed graphs, in-degree refers to how many edges point to the vertex, and out-degree refers to how many edges point out from the vertex.
+- Adjacency: When two vertices are connected by an edge, these two vertices are said to be "adjacent". In the figure above, the adjacent vertices of vertex 1 are vertices 2, 3, and 5.
+- Path: The sequence of edges from vertex A to vertex B is called a "path" from A to B. In the figure above, the edge sequence 1-5-2-4 is a path from vertex 1 to vertex 4.
+- Degree: The number of edges a vertex has. For directed graphs, in-degree indicates how many edges point to the vertex, and out-degree indicates how many edges point out from the vertex.
## Representation of graphs
-Common representations of graphs include "adjacency matrix" and "adjacency list". The following examples use undirected graphs.
+Common representations of graphs include "adjacency matrices" and "adjacency lists". The following uses undirected graphs as examples.
### Adjacency matrix
-Let the number of vertices in the graph be $n$, the adjacency matrix uses an $n \times n$ matrix to represent the graph, where each row (column) represents a vertex, and the matrix elements represent edges, with $1$ or $0$ indicating whether there is an edge between two vertices.
+Given a graph with $n$ vertices, an adjacency matrix uses an $n \times n$ matrix to represent the graph, where each row (column) represents a vertex, and matrix elements represent edges, using $1$ or $0$ to indicate whether an edge exists between two vertices.
-As shown in the figure below, let the adjacency matrix be $M$, and the list of vertices be $V$, then the matrix element $M[i, j] = 1$ indicates there is an edge between vertex $V[i]$ and vertex $V[j]$, conversely $M[i, j] = 0$ indicates there is no edge between the two vertices.
+As shown in the figure below, let the adjacency matrix be $M$ and the vertex list be $V$. Then matrix element $M[i, j] = 1$ indicates that an edge exists between vertex $V[i]$ and vertex $V[j]$, whereas $M[i, j] = 0$ indicates no edge between the two vertices.
-
+
-Adjacency matrices have the following characteristics.
+Adjacency matrices have the following properties.
-- A vertex cannot be connected to itself, so the elements on the main diagonal of the adjacency matrix are meaningless.
-- For undirected graphs, edges in both directions are equivalent, thus the adjacency matrix is symmetric with regard to the main diagonal.
-- By replacing the elements of the adjacency matrix from $1$ and $0$ to weights, we can represent weighted graphs.
+- In simple graphs, vertices cannot connect to themselves, so the elements on the main diagonal of the adjacency matrix are meaningless.
+- For undirected graphs, edges in both directions are equivalent, so the adjacency matrix is symmetric about the main diagonal.
+- Replacing the elements of the adjacency matrix from $1$ and $0$ to weights allows representation of weighted graphs.
-When representing graphs with adjacency matrices, it is possible to directly access matrix elements to obtain edges, resulting in efficient operations of addition, deletion, lookup, and modification, all with a time complexity of $O(1)$. However, the space complexity of the matrix is $O(n^2)$, which consumes more memory.
+When using adjacency matrices to represent graphs, we can directly access matrix elements to obtain edges, resulting in highly efficient addition, deletion, lookup, and modification operations, all with a time complexity of $O(1)$. However, the space complexity of the matrix is $O(n^2)$, which consumes significant memory.
### Adjacency list
-The adjacency list uses $n$ linked lists to represent the graph, with each linked list node representing a vertex. The $i$-th linked list corresponds to vertex $i$ and contains all adjacent vertices (vertices connected to that vertex). The figure below shows an example of a graph stored using an adjacency list.
+An adjacency list uses $n$ linked lists to represent a graph, with linked list nodes representing vertices. The $i$-th linked list corresponds to vertex $i$ and stores all adjacent vertices of that vertex (vertices connected to that vertex). The figure below shows an example of a graph stored using an adjacency list.
-
+
-The adjacency list only stores actual edges, and the total number of edges is often much less than $n^2$, making it more space-efficient. However, finding edges in the adjacency list requires traversing the linked list, so its time efficiency is not as good as that of the adjacency matrix.
+Adjacency lists only store edges that actually exist, and the total number of edges is typically much less than $n^2$, making them more space-efficient. However, finding edges in an adjacency list requires traversing the linked list, so its time efficiency is inferior to that of adjacency matrices.
-Observing the figure above, **the structure of the adjacency list is very similar to the "chaining" in hash tables, hence we can use similar methods to optimize efficiency**. For example, when the linked list is long, it can be transformed into an AVL tree or red-black tree, thus optimizing the time efficiency from $O(n)$ to $O(\log n)$; the linked list can also be transformed into a hash table, thus reducing the time complexity to $O(1)$.
+Observing the figure above, **the structure of adjacency lists is very similar to "chaining" in hash tables, so we can adopt similar methods to optimize efficiency**. For example, when linked lists are long, they can be converted to AVL trees or red-black trees, thereby optimizing time efficiency from $O(n)$ to $O(\log n)$; linked lists can also be converted to hash tables, thereby reducing time complexity to $O(1)$.
## Common applications of graphs
-As shown in the table below, many real-world systems can be modeled with graphs, and corresponding problems can be reduced to graph computing problems.
+As shown in the table below, many real-world systems can be modeled using graphs, and corresponding problems can be reduced to graph computation problems.
Table Common graphs in real life
-| | Vertices | Edges | Graph Computing Problem |
-| --------------- | ---------------- | --------------------------------------------- | -------------------------------- |
-| Social Networks | Users | Follow / Followed | Potential Following Recommendations |
-| Subway Lines | Stations | Connectivity Between Stations | Shortest Route Recommendations |
-| Solar System | Celestial Bodies | Gravitational Forces Between Celestial Bodies | Planetary Orbit Calculations |
+| | Vertices | Edges | Graph Computation Problem |
+| -------------- | --------------- | -------------------------------------- | ----------------------------- |
+| Social network | Users | Friend relationships | Potential friend recommendation |
+| Subway lines | Stations | Connectivity between stations | Shortest route recommendation |
+| Solar system | Celestial bodies | Gravitational forces between celestial bodies | Planetary orbit calculation |
diff --git a/en/docs/chapter_graph/graph_operations.md b/en/docs/chapter_graph/graph_operations.md
index 40c74d71b..c73079550 100644
--- a/en/docs/chapter_graph/graph_operations.md
+++ b/en/docs/chapter_graph/graph_operations.md
@@ -1,15 +1,15 @@
# Basic operations on graphs
-The basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementations are different.
+Basic operations on graphs can be divided into operations on "edges" and operations on "vertices". Under the two representation methods of "adjacency matrix" and "adjacency list", the implementation methods differ.
## Implementation based on adjacency matrix
Given an undirected graph with $n$ vertices, the various operations are implemented as shown in the figure below.
-- **Adding or removing an edge**: Directly modify the specified edge in the adjacency matrix, using $O(1)$ time. Since it is an undirected graph, it is necessary to update the edges in both directions simultaneously.
+- **Adding or removing an edge**: Directly modify the specified edge in the adjacency matrix, using $O(1)$ time. Since it is an undirected graph, both directions of the edge need to be updated simultaneously.
- **Adding a vertex**: Add a row and a column at the end of the adjacency matrix and fill them all with $0$s, using $O(n)$ time.
-- **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case is when the first row and column are removed, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time.
-- **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an $n \times n$ size adjacency matrix `adjMat`, using $O(n^2)$ time.
+- **Removing a vertex**: Delete a row and a column in the adjacency matrix. The worst case occurs when removing the first row and column, requiring $(n-1)^2$ elements to be "moved up and to the left", thus using $O(n^2)$ time.
+- **Initialization**: Pass in $n$ vertices, initialize a vertex list `vertices` of length $n$, using $O(n)$ time; initialize an adjacency matrix `adjMat` of size $n \times n$, using $O(n^2)$ time.
=== "Initialize adjacency matrix"

@@ -26,7 +26,7 @@ Given an undirected graph with $n$ vertices, the various operations are implemen
=== "Remove a vertex"

-Below is the implementation code for graphs represented using an adjacency matrix:
+The following is the implementation code for graphs represented using an adjacency matrix:
```src
[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{}
@@ -36,10 +36,10 @@ Below is the implementation code for graphs represented using an adjacency matri
Given an undirected graph with a total of $n$ vertices and $m$ edges, the various operations can be implemented as shown in the figure below.
-- **Adding an edge**: Simply add the edge at the end of the corresponding vertex's linked list, using $O(1)$ time. Because it is an undirected graph, it is necessary to add edges in both directions simultaneously.
-- **Removing an edge**: Find and remove the specified edge in the corresponding vertex's linked list, using $O(m)$ time. In an undirected graph, it is necessary to remove edges in both directions simultaneously.
-- **Adding a vertex**: Add a linked list in the adjacency list and make the new vertex the head node of the list, using $O(1)$ time.
-- **Removing a vertex**: It is necessary to traverse the entire adjacency list, removing all edges that include the specified vertex, using $O(n + m)$ time.
+- **Adding an edge**: Add the edge at the end of the corresponding vertex's linked list, using $O(1)$ time. Since it is an undirected graph, edges in both directions need to be added simultaneously.
+- **Removing an edge**: Find and remove the specified edge in the corresponding vertex's linked list, using $O(m)$ time. In an undirected graph, edges in both directions need to be removed simultaneously.
+- **Adding a vertex**: Add a linked list in the adjacency list and set the new vertex as the head node of the list, using $O(1)$ time.
+- **Removing a vertex**: Traverse the entire adjacency list and remove all edges containing the specified vertex, using $O(n + m)$ time.
- **Initialization**: Create $n$ vertices and $2m$ edges in the adjacency list, using $O(n + m)$ time.
=== "Initialize adjacency list"
@@ -57,12 +57,12 @@ Given an undirected graph with a total of $n$ vertices and $m$ edges, the variou
=== "Remove a vertex"

-Below is the adjacency list code implementation. Compared to the figure above, the actual code has the following differences.
+The following is the adjacency list code implementation. Compared to the figure above, the actual code has the following differences.
- For convenience in adding and removing vertices, and to simplify the code, we use lists (dynamic arrays) instead of linked lists.
-- Use a hash table to store the adjacency list, `key` being the vertex instance, `value` being the list (linked list) of adjacent vertices of that vertex.
+- A hash table is used to store the adjacency list, where `key` is the vertex instance and `value` is the list (linked list) of adjacent vertices for that vertex.
-Additionally, we use the `Vertex` class to represent vertices in the adjacency list. The reason for this is: if, like with the adjacency matrix, list indexes were used to distinguish different vertices, then suppose you want to delete the vertex at index $i$, you would need to traverse the entire adjacency list and decrement all indexes greater than $i$ by $1$, which is very inefficient. However, if each vertex is a unique `Vertex` instance, then deleting a vertex does not require any changes to other vertices.
+Additionally, we use the `Vertex` class to represent vertices in the adjacency list. The reason for this is: if we used list indices to distinguish different vertices as with adjacency matrices, then to delete the vertex at index $i$, we would need to traverse the entire adjacency list and decrement all indices greater than $i$ by $1$, which is very inefficient. However, if each vertex is a unique `Vertex` instance, deleting a vertex does not require modifying other vertices.
```src
[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{}
@@ -70,17 +70,17 @@ Additionally, we use the `Vertex` class to represent vertices in the adjacency l
## Efficiency comparison
-Assuming there are $n$ vertices and $m$ edges in the graph, the table below compares the time efficiency and space efficiency of the adjacency matrix and adjacency list.
+Assuming the graph has $n$ vertices and $m$ edges, the table below compares the time efficiency and space efficiency of adjacency matrices and adjacency lists. Note that the adjacency list (linked list) corresponds to the implementation in this text, while the adjacency list (hash table) refers specifically to the implementation where all linked lists are replaced with hash tables.
Table Comparison of adjacency matrix and adjacency list
-| | Adjacency matrix | Adjacency list (Linked list) | Adjacency list (Hash table) |
-| ------------------- | ---------------- | ---------------------------- | --------------------------- |
-| Determine adjacency | $O(1)$ | $O(m)$ | $O(1)$ |
-| Add an edge | $O(1)$ | $O(1)$ | $O(1)$ |
-| Remove an edge | $O(1)$ | $O(m)$ | $O(1)$ |
-| Add a vertex | $O(n)$ | $O(1)$ | $O(1)$ |
-| Remove a vertex | $O(n^2)$ | $O(n + m)$ | $O(n)$ |
-| Memory space usage | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ |
+| | Adjacency matrix | Adjacency list (linked list) | Adjacency list (hash table) |
+| ---------------------- | ---------------- | ---------------------------- | --------------------------- |
+| Determine adjacency | $O(1)$ | $O(n)$ | $O(1)$ |
+| Add an edge | $O(1)$ | $O(1)$ | $O(1)$ |
+| Remove an edge | $O(1)$ | $O(n)$ | $O(1)$ |
+| Add a vertex | $O(n)$ | $O(1)$ | $O(1)$ |
+| Remove a vertex | $O(n^2)$ | $O(n + m)$ | $O(n)$ |
+| Memory space usage | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ |
-Observing the table above, it seems that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, the adjacency matrix exemplifies the principle of "space for time", while the adjacency list exemplifies "time for space".
+Observing the table above, it appears that the adjacency list (hash table) has the best time efficiency and space efficiency. However, in practice, operating on edges in the adjacency matrix is more efficient, requiring only a single array access or assignment operation. Overall, adjacency matrices embody the principle of "trading space for time", while adjacency lists embody "trading time for space".
diff --git a/en/docs/chapter_graph/graph_traversal.md b/en/docs/chapter_graph/graph_traversal.md
index 9c7f9640e..a1e413f86 100644
--- a/en/docs/chapter_graph/graph_traversal.md
+++ b/en/docs/chapter_graph/graph_traversal.md
@@ -1,30 +1,34 @@
# Graph traversal
-Trees represent a "one-to-many" relationship, while graphs have a higher degree of freedom and can represent any "many-to-many" relationship. Therefore, we can consider tree as a special case of graph. Clearly, **tree traversal operations are also a special case of graph traversal operations**.
+Trees represent "one-to-many" relationships, while graphs have a higher degree of freedom and can represent any "many-to-many" relationships. Therefore, we can view trees as a special case of graphs. Clearly, **tree traversal operations are also a special case of graph traversal operations**.
-Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal can be divided into two types: Breadth-First Search (BFS) and Depth-First Search (DFS).
+Both graphs and trees require the application of search algorithms to implement traversal operations. Graph traversal methods can also be divided into two types: breadth-first traversal and depth-first traversal.
## Breadth-first search
-**Breadth-first search is a near-to-far traversal method, starting from a certain node, always prioritizing the visit to the nearest vertices and expanding outwards layer by layer**. As shown in the figure below, starting from the top left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.
+**Breadth-first search is a near-to-far traversal method that, starting from a certain node, always prioritizes visiting the nearest vertices and expands outward layer by layer**. As shown in the figure below, starting from the top-left vertex, first traverse all adjacent vertices of that vertex, then traverse all adjacent vertices of the next vertex, and so on, until all vertices have been visited.
-
+
### Algorithm implementation
-BFS is usually implemented with the help of a queue, as shown in the code below. The queue is "first in, first out", which aligns with the BFS idea of traversing "from near to far".
+BFS is typically implemented with the help of a queue, as shown in the code below. The queue has a "first in, first out" property, which aligns with the BFS idea of "near to far".
-1. Add the starting vertex `startVet` to the queue and start the loop.
+1. Add the starting vertex `startVet` to the queue and begin the loop.
2. In each iteration of the loop, pop the vertex at the front of the queue and record it as visited, then add all adjacent vertices of that vertex to the back of the queue.
3. Repeat step `2.` until all vertices have been visited.
To prevent revisiting vertices, we use a hash set `visited` to record which nodes have been visited.
+!!! tip
+
+ A hash set can be viewed as a hash table that stores only `key` without storing `value`. It can perform addition, deletion, lookup, and modification operations on `key` in $O(1)$ time complexity. Based on the uniqueness of `key`, hash sets are typically used for data deduplication and similar scenarios.
+
```src
[file]{graph_bfs}-[class]{}-[func]{graph_bfs}
```
-The code is relatively abstract, you can compare it with the figure below to get a better understanding.
+The code is relatively abstract; it is recommended to refer to the figure below to deepen understanding.
=== "<1>"

@@ -59,36 +63,36 @@ The code is relatively abstract, you can compare it with the figure below to get
=== "<11>"

-!!! question "Is the sequence of breadth-first traversal unique?"
+!!! question "Is the breadth-first traversal sequence unique?"
- Not unique. Breadth-first traversal only requires traversing in a "near to far" order, **and the traversal order of the vertices with the same distance can be arbitrary**. For example, in the figure above, the visit order of vertices $1$ and $3$ can be swapped, as can the order of vertices $2$, $4$, and $6$.
+ Not unique. Breadth-first search only requires traversing in a "near to far" order, **and the traversal order of vertices at the same distance can be arbitrarily shuffled**. Taking the figure above as an example, the visit order of vertices $1$ and $3$ can be swapped, as can the visit order of vertices $2$, $4$, and $6$.
### Complexity analysis
**Time complexity**: All vertices will be enqueued and dequeued once, using $O(|V|)$ time; in the process of traversing adjacent vertices, since it is an undirected graph, all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
-**Space complexity**: The maximum number of vertices in list `res`, hash set `visited`, and queue `que` is $|V|$, using $O(|V|)$ space.
+**Space complexity**: The list `res`, hash set `visited`, and queue `que` can contain at most $|V|$ vertices, using $O(|V|)$ space.
## Depth-first search
-**Depth-first search is a traversal method that prioritizes going as far as possible and then backtracks when no further path is available**. As shown in the figure below, starting from the top left vertex, visit some adjacent vertex of the current vertex until no further path is available, then return and continue until all vertices are traversed.
+**Depth-first search is a traversal method that prioritizes going as far as possible, then backtracks when no path remains**. As shown in the figure below, starting from the top-left vertex, visit an adjacent vertex of the current vertex, continuing until reaching a dead end, then return and continue going as far as possible before returning again, and so on, until all vertices have been traversed.
-
+
### Algorithm implementation
-This "go as far as possible and then return" algorithm paradigm is usually implemented based on recursion. Similar to breadth-first search, in depth-first search, we also need the help of a hash set `visited` to record the visited vertices to avoid revisiting.
+This "go as far as possible then return" algorithm paradigm is typically implemented using recursion. Similar to breadth-first search, in depth-first search we also need a hash set `visited` to record visited vertices and avoid revisiting.
```src
[file]{graph_dfs}-[class]{}-[func]{graph_dfs}
```
-The algorithm process of depth-first search is shown in the figure below.
+The algorithm flow of depth-first search is shown in the figure below.
-- **Dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex.
-- **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where this method was initiated.
+- **Straight dashed lines represent downward recursion**, indicating that a new recursive method has been initiated to visit a new vertex.
+- **Curved dashed lines represent upward backtracking**, indicating that this recursive method has returned to the position where it was initiated.
-To deepen the understanding, it is suggested to combine the figure below with the code to simulate (or draw) the entire DFS process in your mind, including when each recursive method is initiated and when it returns.
+To deepen understanding, it is recommended to combine the figure below with the code to mentally simulate (or draw out) the entire DFS process, including when each recursive method is initiated and when it returns.
=== "<1>"

@@ -123,14 +127,14 @@ To deepen the understanding, it is suggested to combine the figure below with th
=== "<11>"

-!!! question "Is the sequence of depth-first traversal unique?"
+!!! question "Is the depth-first traversal sequence unique?"
- Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal.
+ Similar to breadth-first search, the order of depth-first traversal sequences is also not unique. Given a certain vertex, exploring in any direction first is valid, meaning the order of adjacent vertices can be arbitrarily shuffled, all being depth-first search.
- Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal.
+ Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", and "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They represent three different traversal priorities, yet all three belong to depth-first search.
### Complexity analysis
-**Time complexity**: All vertices will be visited once, using $O(|V|)$ time; all edges will be visited twice, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
+**Time complexity**: All vertices will be visited $1$ time, using $O(|V|)$ time; all edges will be visited $2$ times, using $O(2|E|)$ time; overall using $O(|V| + |E|)$ time.
-**Space complexity**: The maximum number of vertices in list `res`, hash set `visited` is $|V|$, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space.
+**Space complexity**: The list `res` and hash set `visited` can contain at most $|V|$ vertices, and the maximum recursion depth is $|V|$, therefore using $O(|V|)$ space.
diff --git a/en/docs/chapter_graph/index.md b/en/docs/chapter_graph/index.md
index 46d9c9687..dd16be4dc 100644
--- a/en/docs/chapter_graph/index.md
+++ b/en/docs/chapter_graph/index.md
@@ -4,6 +4,6 @@
!!! abstract
- In the journey of life, each of us is a node, connected by countless invisible edges.
-
- Each encounter and parting leaves a unique imprint on this vast graph of life.
+ In the journey of life, we are like nodes, connected by countless invisible edges.
+
+ Each encounter and parting leaves a unique mark on this vast network graph.
diff --git a/en/docs/chapter_graph/summary.md b/en/docs/chapter_graph/summary.md
index 8480efe26..af1ab4a5a 100644
--- a/en/docs/chapter_graph/summary.md
+++ b/en/docs/chapter_graph/summary.md
@@ -2,30 +2,30 @@
### Key review
-- A graph is made up of vertices and edges. It can be described as a set of vertices and a set of edges.
-- Compared to linear relationships (like linked lists) and hierarchical relationships (like trees), network relationships (graphs) offer greater flexibility, making them more complex.
-- In a directed graph, edges have directions. In a connected graph, any vertex can be reached from any other vertex. In a weighted graph, each edge has an associated weight variable.
-- An adjacency matrix is a way to represent a graph using matrix (2D array). The rows and columns represent the vertices. The matrix element value indicates whether there is an edge between two vertices, using $1$ for an edge or $0$ for no edge. Adjacency matrices are highly efficient for operations like adding, deleting, or checking edges, but they require more space.
-- An adjacency list is another common way to represent a graph using a collection of linked lists. Each vertex in the graph has a list that contains all its adjacent vertices. The $i^{th}$ list represents vertex $i$. Adjacency lists use less space compared to adjacency matrices. However, since it requires traversing the list to find edges, the time efficiency is lower.
-- When the linked lists in an adjacency list are long enough, they can be converted into red-black trees or hash tables to improve lookup efficiency.
-- From the perspective of algorithmic design, an adjacency matrix reflects the concept of "trading space for time", whereas an adjacency list reflects "trading time for space".
-- Graphs can be used to model various real-world systems, such as social networks, subway routes.
-- A tree is a special case of a graph, and tree traversal is also a special case of graph traversal.
-- Breadth-first traversal of a graph is a search method that expands layer by layer from near to far, typically using a queue.
-- Depth-first traversal of a graph is a search method that prioritizes reaching the end before backtracking when no further path is available. It is often implemented using recursion.
+- Graphs consist of vertices and edges and can be represented as a set of vertices and a set of edges.
+- Compared to linear relationships (linked lists) and divide-and-conquer relationships (trees), network relationships (graphs) have a higher degree of freedom and are therefore more complex.
+- Directed graphs have edges with directionality, connected graphs have all vertices reachable from any vertex, and weighted graphs have edges that each contain a weight variable.
+- Adjacency matrices use matrices to represent graphs, where each row (column) represents a vertex, and matrix elements represent edges, using $1$ or $0$ to indicate whether two vertices have an edge or not. Adjacency matrices are highly efficient for addition, deletion, lookup, and modification operations, but consume significant space.
+- Adjacency lists use multiple linked lists to represent graphs, where the $i$-th linked list corresponds to vertex $i$ and stores all adjacent vertices of that vertex. Adjacency lists are more space-efficient than adjacency matrices, but have lower time efficiency because they require traversing linked lists to find edges.
+- When linked lists in adjacency lists become too long, they can be converted to red-black trees or hash tables, thereby improving lookup efficiency.
+- From an algorithmic perspective, adjacency matrices embody "trading space for time", while adjacency lists embody "trading time for space".
+- Graphs can be used to model various real-world systems, such as social networks and subway lines.
+- Trees are a special case of graphs, and tree traversal is a special case of graph traversal.
+- Breadth-first search of graphs is a near-to-far, layer-by-layer expansion search method, typically implemented using a queue.
+- Depth-first search of graphs is a search method that prioritizes going as far as possible and backtracks when no path remains, commonly implemented using recursion.
### Q & A
**Q**: Is a path defined as a sequence of vertices or a sequence of edges?
-In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
+The definitions in different language versions of Wikipedia are inconsistent: the English version states "a path is a sequence of edges", while the Chinese version states "a path is a sequence of vertices". The following is the original English text: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
-In this document, a path is considered a sequence of edges, rather than a sequence of vertices. This is because there might be multiple edges connecting two vertices, in which case each edge corresponds to a path.
+In this text, a path is viewed as a sequence of edges, not a sequence of vertices. This is because there may be multiple edges connecting two vertices, in which case each edge corresponds to a path.
-**Q**: In a disconnected graph, are there points that cannot be traversed?
+**Q**: In a disconnected graph, will there be unreachable vertices?
-In a disconnected graph, there is at least one vertex that cannot be reached from a specific point. To traverse a disconnected graph, you need to set multiple starting points to traverse all the connected components of the graph.
+In a disconnected graph, starting from a certain vertex, at least one vertex cannot be reached. Traversing a disconnected graph requires setting multiple starting points to traverse all connected components of the graph.
-**Q**: In an adjacency list, does the order of "all vertices connected to that vertex" matter?
+**Q**: In an adjacency list, is there a requirement for the order of "all vertices connected to that vertex"?
-It can be in any order. However, in real-world applications, it might be necessary to sort them according to certain rules, such as the order in which vertices are added, or the order of vertex values. This can help find vertices quickly with certain extreme values.
+It can be in any order. However, in practical applications, it may be necessary to sort according to specified rules, such as the order in which vertices were added, or the order of vertex values, which helps quickly find vertices "with certain extreme values".
diff --git a/en/docs/chapter_greedy/fractional_knapsack_problem.md b/en/docs/chapter_greedy/fractional_knapsack_problem.md
index feb9239f7..9914076b3 100644
--- a/en/docs/chapter_greedy/fractional_knapsack_problem.md
+++ b/en/docs/chapter_greedy/fractional_knapsack_problem.md
@@ -2,49 +2,51 @@
!!! question
- Given $n$ items, the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with a capacity of $cap$. Each item can be chosen only once, **but a part of the item can be selected, with its value calculated based on the proportion of the weight chosen**, what is the maximum value of the items in the knapsack under the limited capacity? An example is shown in the figure below.
+ Given $n$ items, where the weight of the $i$-th item is $wgt[i-1]$ and its value is $val[i-1]$, and a knapsack with capacity $cap$. Each item can be selected only once, **but a portion of an item can be selected, with the value calculated based on the proportion of weight selected**, what is the maximum value of items in the knapsack under the limited capacity? An example is shown in the figure below.
-
+
-The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, involving the current item $i$ and capacity $c$, aiming to maximize the value within the limited capacity of the knapsack.
+The fractional knapsack problem is very similar overall to the 0-1 knapsack problem, with states including the current item $i$ and capacity $c$, and the goal being to maximize value under the limited knapsack capacity.
-The difference is that, in this problem, only a part of an item can be chosen. As shown in the figure below, **we can arbitrarily split the items and calculate the corresponding value based on the weight proportion**.
+The difference is that this problem allows selecting only a portion of an item. As shown in the figure below, **we can arbitrarily split items and calculate the corresponding value based on the weight proportion**.
-1. For item $i$, its value per unit weight is $val[i-1] / wgt[i-1]$, referred to as the unit value.
-2. Suppose we put a part of item $i$ with weight $w$ into the knapsack, then the value added to the knapsack is $w \times val[i-1] / wgt[i-1]$.
+1. For item $i$, its value per unit weight is $val[i-1] / wgt[i-1]$, referred to as unit value.
+2. Suppose we put a portion of item $i$ with weight $w$ into the knapsack, then the value added to the knapsack is $w \times val[i-1] / wgt[i-1]$.
-
+
### Greedy strategy determination
-Maximizing the total value of the items in the knapsack **essentially means maximizing the value per unit weight**. From this, the greedy strategy shown in the figure below can be deduced.
+Maximizing the total value of items in the knapsack **is essentially maximizing the value per unit weight of items**. From this, we can derive the greedy strategy shown in the figure below.
-1. Sort the items by their unit value from high to low.
-2. Iterate over all items, **greedily choosing the item with the highest unit value in each round**.
-3. If the remaining capacity of the knapsack is insufficient, use part of the current item to fill the knapsack.
+1. Sort items by unit value from high to low.
+2. Iterate through all items, **greedily selecting the item with the highest unit value in each round**.
+3. If the remaining knapsack capacity is insufficient, use a portion of the current item to fill the knapsack.
-
+
### Code implementation
-We have created an `Item` class in order to sort the items by their unit value. We loop and make greedy choices until the knapsack is full, then exit and return the solution:
+We created an `Item` class to facilitate sorting items by unit value. We loop to make greedy selections, breaking when the knapsack is full and returning the solution:
```src
[file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack}
```
-Apart from sorting, in the worst case, the entire list of items needs to be traversed, **hence the time complexity is $O(n)$**, where $n$ is the number of items.
+The time complexity of built-in sorting algorithms is usually $O(\log n)$, and the space complexity is usually $O(\log n)$ or $O(n)$, depending on the specific implementation of the programming language.
+
+Apart from sorting, in the worst case the entire item list needs to be traversed, **therefore the time complexity is $O(n)$**, where $n$ is the number of items.
Since an `Item` object list is initialized, **the space complexity is $O(n)$**.
### Correctness proof
-Using proof by contradiction. Suppose item $x$ has the highest unit value, and some algorithm yields a maximum value `res`, but the solution does not include item $x$.
+Using proof by contradiction. Suppose item $x$ has the highest unit value, and some algorithm yields a maximum value of `res`, but this solution does not include item $x$.
-Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item $x$. Since the unit value of item $x$ is the highest, the total value after replacement will definitely be greater than `res`. **This contradicts the assumption that `res` is the optimal solution, proving that the optimal solution must include item $x$**.
+Now remove a unit weight of any item from the knapsack and replace it with a unit weight of item $x$. Since item $x$ has the highest unit value, the total value after replacement will definitely be greater than `res`. **This contradicts the assumption that `res` is the optimal solution, proving that the optimal solution must include item $x$**.
-For other items in this solution, we can also construct the above contradiction. Overall, **items with greater unit value are always better choices**, proving that the greedy strategy is effective.
+For other items in this solution, we can also construct the above contradiction. In summary, **items with greater unit value are always better choices**, which proves that the greedy strategy is effective.
-As shown in the figure below, if the item weight and unit value are viewed as the horizontal and vertical axes of a two-dimensional chart respectively, the fractional knapsack problem can be transformed into "seeking the largest area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.
+As shown in the figure below, if we view item weight and item unit value as the horizontal and vertical axes of a two-dimensional chart respectively, then the fractional knapsack problem can be transformed into "finding the maximum area enclosed within a limited horizontal axis range". This analogy can help us understand the effectiveness of the greedy strategy from a geometric perspective.

diff --git a/en/docs/chapter_greedy/greedy_algorithm.md b/en/docs/chapter_greedy/greedy_algorithm.md
index 9e61c0978..9568a6799 100644
--- a/en/docs/chapter_greedy/greedy_algorithm.md
+++ b/en/docs/chapter_greedy/greedy_algorithm.md
@@ -1,19 +1,19 @@
-# Greedy algorithms
+# Greedy algorithm
-Greedy algorithm is a common algorithm for solving optimization problems, which fundamentally involves making the seemingly best choice at each decision-making stage of the problem, i.e., greedily making locally optimal decisions in hopes of finding a globally optimal solution. Greedy algorithms are concise and efficient, and are widely used in many practical problems.
+Greedy algorithm is a common algorithm for solving optimization problems. Its basic idea is to make the seemingly best choice at each decision stage of the problem, that is, to greedily make locally optimal decisions in hopes of obtaining a globally optimal solution. Greedy algorithms are simple and efficient, and are widely applied in many practical problems.
-Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as relying on the property of optimal substructure, but they operate differently.
+Greedy algorithms and dynamic programming are both commonly used to solve optimization problems. They share some similarities, such as both relying on the optimal substructure property, but they work differently.
-- Dynamic programming considers all previous decisions at the current decision stage and uses solutions to past subproblems to construct solutions for the current subproblem.
-- Greedy algorithms do not consider past decisions; instead, they proceed with greedy choices, continually narrowing the scope of the problem until it is solved.
+- Dynamic programming considers all previous decisions when making the current decision, and uses solutions to past subproblems to construct the solution to the current subproblem.
+- Greedy algorithms do not consider past decisions, but instead make greedy choices moving forward, continually reducing the problem size until the problem is solved.
-Let's first understand the working principle of the greedy algorithm through the example of "coin change," which has been introduced in the "Complete Knapsack Problem" chapter. I believe you are already familiar with it.
+We will first understand how greedy algorithms work through the example problem "coin change". This problem has already been introduced in the "Complete Knapsack Problem" chapter, so I believe you are not unfamiliar with it.
!!! question
- Given $n$ types of coins, where the denomination of the $i$th type of coin is $coins[i - 1]$, and the target amount is $amt$, with each type of coin available indefinitely, what is the minimum number of coins needed to make up the target amount? If it is not possible to make up the target amount, return $-1$.
+ Given $n$ types of coins, where the denomination of the $i$-th type of coin is $coins[i - 1]$, and the target amount is $amt$, with each type of coin available for repeated selection, what is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$.
-The greedy strategy adopted in this problem is shown in the figure below. Given the target amount, **we greedily choose the coin that is closest to and not greater than it**, repeatedly following this step until the target amount is met.
+The greedy strategy adopted for this problem is shown in the figure below. Given a target amount, **we greedily select the coin that is not greater than and closest to it**, and continuously repeat this step until the target amount is reached.

@@ -27,41 +27,41 @@ You might exclaim: So clean! The greedy algorithm solves the coin change problem
## Advantages and limitations of greedy algorithms
-**Greedy algorithms are not only straightforward and simple to implement, but they are also usually very efficient**. In the code above, if the smallest coin denomination is $\min(coins)$, the greedy choice loops at most $amt / \min(coins)$ times, giving a time complexity of $O(amt / \min(coins))$. This is an order of magnitude smaller than the time complexity of the dynamic programming solution, which is $O(n \times amt)$.
+**Greedy algorithms are not only straightforward and simple to implement, but are also usually very efficient**. In the code above, if the smallest coin denomination is $\min(coins)$, the greedy choice loops at most $amt / \min(coins)$ times, giving a time complexity of $O(amt / \min(coins))$. This is an order of magnitude smaller than the time complexity of the dynamic programming solution $O(n \times amt)$.
-However, **for some combinations of coin denominations, greedy algorithms cannot find the optimal solution**. The figure below provides two examples.
+However, **for certain coin denomination combinations, greedy algorithms cannot find the optimal solution**. The figure below provides two examples.
-- **Positive example $coins = [1, 5, 10, 20, 50, 100]$**: In this coin combination, given any $amt$, the greedy algorithm can find the optimal solution.
-- **Negative example $coins = [1, 20, 50]$**: Suppose $amt = 60$, the greedy algorithm can only find the combination $50 + 1 \times 10$, totaling 11 coins, but dynamic programming can find the optimal solution of $20 + 20 + 20$, needing only 3 coins.
-- **Negative example $coins = [1, 49, 50]$**: Suppose $amt = 98$, the greedy algorithm can only find the combination $50 + 1 \times 48$, totaling 49 coins, but dynamic programming can find the optimal solution of $49 + 49$, needing only 2 coins.
+- **Positive example $coins = [1, 5, 10, 20, 50, 100]$**: With this coin combination, given any $amt$, the greedy algorithm can find the optimal solution.
+- **Negative example $coins = [1, 20, 50]$**: Suppose $amt = 60$, the greedy algorithm can only find the combination $50 + 1 \times 10$, totaling $11$ coins, but dynamic programming can find the optimal solution $20 + 20 + 20$, requiring only $3$ coins.
+- **Negative example $coins = [1, 49, 50]$**: Suppose $amt = 98$, the greedy algorithm can only find the combination $50 + 1 \times 48$, totaling $49$ coins, but dynamic programming can find the optimal solution $49 + 49$, requiring only $2$ coins.
-
+
-This means that for the coin change problem, greedy algorithms cannot guarantee finding the globally optimal solution, and they might find a very poor solution. They are better suited for dynamic programming.
+In other words, for the coin change problem, greedy algorithms cannot guarantee finding the global optimal solution, and may even find very poor solutions. It is better suited for solving with dynamic programming.
-Generally, the suitability of greedy algorithms falls into two categories.
+Generally, the applicability of greedy algorithms falls into the following two situations.
-1. **Guaranteed to find the optimal solution**: In these cases, greedy algorithms are often the best choice, as they tend to be more efficient than backtracking or dynamic programming.
-2. **Can find a near-optimal solution**: Greedy algorithms are also applicable here. For many complex problems, finding the global optimal solution is very challenging, and being able to find a high-efficiency suboptimal solution is also very commendable.
+1. **Can guarantee finding the optimal solution**: In this situation, greedy algorithms are often the best choice, because they tend to be more efficient than backtracking and dynamic programming.
+2. **Can find an approximate optimal solution**: Greedy algorithms are also applicable in this situation. For many complex problems, finding the global optimal solution is very difficult, and being able to find a suboptimal solution with high efficiency is also very good.
## Characteristics of greedy algorithms
-So, what kind of problems are suitable for solving with greedy algorithms? Or rather, under what conditions can greedy algorithms guarantee to find the optimal solution?
+So the question arises: what kind of problems are suitable for solving with greedy algorithms? Or in other words, under what conditions can greedy algorithms guarantee finding the optimal solution?
-Compared to dynamic programming, greedy algorithms have stricter usage conditions, focusing mainly on two properties of the problem.
+Compared to dynamic programming, the conditions for using greedy algorithms are stricter, mainly focusing on two properties of the problem.
-- **Greedy choice property**: Only when the locally optimal choice can always lead to a globally optimal solution can greedy algorithms guarantee to obtain the optimal solution.
-- **Optimal substructure**: The optimal solution to the original problem contains the optimal solutions to its subproblems.
+- **Greedy choice property**: Only when locally optimal choices can always lead to a globally optimal solution can greedy algorithms guarantee obtaining the optimal solution.
+- **Optimal substructure**: The optimal solution to the original problem contains the optimal solutions to subproblems.
-Optimal substructure has already been introduced in the "Dynamic Programming" chapter, so it is not discussed further here. It's important to note that some problems do not have an obvious optimal substructure, but can still be solved using greedy algorithms.
+Optimal substructure has already been introduced in the "Dynamic Programming" chapter, so we won't elaborate on it here. It's worth noting that the optimal substructure of some problems is not obvious, but they can still be solved using greedy algorithms.
-We mainly explore the method for determining the greedy choice property. Although its description seems simple, **in practice, proving the greedy choice property for many problems is not easy**.
+We mainly explore methods for determining the greedy choice property. Although its description seems relatively simple, **in practice, for many problems, proving the greedy choice property is not easy**.
-For example, in the coin change problem, although we can easily cite counterexamples to disprove the greedy choice property, proving it is much more challenging. If asked, **what conditions must a coin combination meet to be solvable using a greedy algorithm**? We often have to rely on intuition or examples to provide an ambiguous answer, as it is difficult to provide a rigorous mathematical proof.
+For example, in the coin change problem, although we can easily provide counterexamples to disprove the greedy choice property, proving it is quite difficult. If asked: **what conditions must a coin combination satisfy to be solvable using a greedy algorithm**? We often can only rely on intuition or examples to give an ambiguous answer, and find it difficult to provide a rigorous mathematical proof.
!!! quote
- A paper presents an algorithm with a time complexity of $O(n^3)$ for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount.
+ There is a paper that presents an algorithm with $O(n^3)$ time complexity for determining whether a coin combination can use a greedy algorithm to find the optimal solution for any amount.
Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.
@@ -69,26 +69,26 @@ For example, in the coin change problem, although we can easily cite counterexam
The problem-solving process for greedy problems can generally be divided into the following three steps.
-1. **Problem analysis**: Sort out and understand the characteristics of the problem, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming.
-2. **Determine the greedy strategy**: Determine how to make a greedy choice at each step. This strategy can reduce the scale of the problem at each step and eventually solve the entire problem.
-3. **Proof of correctness**: It is usually necessary to prove that the problem has both a greedy choice property and optimal substructure. This step may require mathematical proofs, such as induction or reductio ad absurdum.
+1. **Problem analysis**: Sort out and understand the problem characteristics, including state definition, optimization objectives, and constraints, etc. This step is also involved in backtracking and dynamic programming.
+2. **Determine the greedy strategy**: Determine how to make greedy choices at each step. This strategy should be able to reduce the problem size at each step, ultimately solving the entire problem.
+3. **Correctness proof**: It is usually necessary to prove that the problem has both greedy choice property and optimal substructure. This step may require mathematical proofs, such as mathematical induction or proof by contradiction.
Determining the greedy strategy is the core step in solving the problem, but it may not be easy to implement, mainly for the following reasons.
-- **Greedy strategies vary greatly between different problems**. For many problems, the greedy strategy is fairly straightforward, and we can come up with it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which is a real test of individual problem-solving experience and algorithmic capability.
-- **Some greedy strategies are quite misleading**. When we confidently design a greedy strategy, write the code, and submit it for testing, it is quite possible that some test cases will not pass. This is because the designed greedy strategy is only "partially correct," as described above with the coin change example.
+- **Greedy strategies differ greatly between different problems**. For many problems, the greedy strategy is relatively straightforward, and we can derive it through some general thinking and attempts. However, for some complex problems, the greedy strategy may be very elusive, which really tests one's problem-solving experience and algorithmic ability.
+- **Some greedy strategies are highly misleading**. When we confidently design a greedy strategy, write the solution code and submit it for testing, we may find that some test cases cannot pass. This is because the designed greedy strategy is only "partially correct", as exemplified by the coin change problem discussed above.
-To ensure accuracy, we should provide rigorous mathematical proofs for the greedy strategy, **usually involving reductio ad absurdum or mathematical induction**.
+To ensure correctness, we should rigorously mathematically prove the greedy strategy, **usually using proof by contradiction or mathematical induction**.
-However, proving correctness may not be an easy task. If we are at a loss, we usually choose to debug the code based on test cases, modifying and verifying the greedy strategy step by step.
+However, correctness proofs may also not be easy. If we have no clue, we usually choose to debug the code based on test cases, step by step modifying and verifying the greedy strategy.
## Typical problems solved by greedy algorithms
-Greedy algorithms are often applied to optimization problems that satisfy the properties of greedy choice and optimal substructure. Below are some typical greedy algorithm problems.
+Greedy algorithms are often applied to optimization problems that satisfy greedy choice property and optimal substructure. Below are some typical greedy algorithm problems.
-- **Coin change problem**: In some coin combinations, the greedy algorithm always provides the optimal solution.
-- **Interval scheduling problem**: Suppose you have several tasks, each of which takes place over a period of time. Your goal is to complete as many tasks as possible. If you always choose the task that ends the earliest, then the greedy algorithm can achieve the optimal solution.
-- **Fractional knapsack problem**: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), the greedy algorithm can achieve the optimal solution in some cases.
-- **Stock trading problem**: Given a set of historical stock prices, you can make multiple trades, but you cannot buy again until after you have sold if you already own stocks. The goal is to achieve the maximum profit.
-- **Huffman coding**: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree, it always merges the two nodes with the lowest frequency, resulting in a Huffman tree with the minimum weighted path length (coding length).
+- **Coin change problem**: With certain coin combinations, greedy algorithms can always obtain the optimal solution.
+- **Interval scheduling problem**: Suppose you have some tasks, each taking place during a period of time, and your goal is to complete as many tasks as possible. If you always choose the task that ends earliest, then the greedy algorithm can obtain the optimal solution.
+- **Fractional knapsack problem**: Given a set of items and a carrying capacity, your goal is to select a set of items such that the total weight does not exceed the carrying capacity and the total value is maximized. If you always choose the item with the highest value-to-weight ratio (value / weight), then the greedy algorithm can obtain the optimal solution in some cases.
+- **Stock trading problem**: Given a set of historical stock prices, you can make multiple trades, but if you already hold stocks, you cannot buy again before selling, and the goal is to obtain the maximum profit.
+- **Huffman coding**: Huffman coding is a greedy algorithm used for lossless data compression. By constructing a Huffman tree and always merging the two nodes with the lowest frequency, the resulting Huffman tree has the minimum weighted path length (encoding length).
- **Dijkstra's algorithm**: It is a greedy algorithm for solving the shortest path problem from a given source vertex to all other vertices.
diff --git a/en/docs/chapter_greedy/index.md b/en/docs/chapter_greedy/index.md
index ba4314f7a..f773db26a 100644
--- a/en/docs/chapter_greedy/index.md
+++ b/en/docs/chapter_greedy/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Sunflowers turn towards the sun, always seeking the greatest possible growth for themselves.
+ Sunflowers turn toward the sun, constantly pursuing the maximum potential for their own growth.
- Greedy strategy guides to the best answer step by step through rounds of simple choices.
+ Through rounds of simple choices, greedy strategies gradually lead to the best answer.
diff --git a/en/docs/chapter_greedy/max_capacity_problem.md b/en/docs/chapter_greedy/max_capacity_problem.md
index ba2ca009f..fc24c51dd 100644
--- a/en/docs/chapter_greedy/max_capacity_problem.md
+++ b/en/docs/chapter_greedy/max_capacity_problem.md
@@ -1,52 +1,52 @@
-# Maximum capacity problem
+# Max capacity problem
!!! question
Input an array $ht$, where each element represents the height of a vertical partition. Any two partitions in the array, along with the space between them, can form a container.
-
- The capacity of the container is the product of the height and the width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions.
-
- Please select two partitions in the array that maximize the container's capacity and return this maximum capacity. An example is shown in the figure below.
-
+ The capacity of the container equals the product of height and width (area), where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions.
-The container is formed by any two partitions, **therefore the state of this problem is represented by the indices of the two partitions, denoted as $[i, j]$**.
+ Please select two partitions in the array such that the capacity of the formed container is maximized, and return the maximum capacity. An example is shown in the figure below.
-According to the problem statement, the capacity equals the product of height and width, where the height is determined by the shorter partition, and the width is the difference in array indices between the two partitions. The formula for capacity $cap[i, j]$ is:
+
+
+The container is formed by any two partitions, **therefore the state of this problem is the indices of two partitions, denoted as $[i, j]$**.
+
+According to the problem description, capacity equals height multiplied by width, where height is determined by the shorter partition, and width is the difference in array indices between the two partitions. Let the capacity be $cap[i, j]$, then the calculation formula is:
$$
cap[i, j] = \min(ht[i], ht[j]) \times (j - i)
$$
-Assuming the length of the array is $n$, the number of combinations of two partitions (total number of states) is $C_n^2 = \frac{n(n - 1)}{2}$. The most straightforward approach is to **enumerate all possible states**, resulting in a time complexity of $O(n^2)$.
+Let the array length be $n$, then the number of combinations of two partitions (total number of states) is $C_n^2 = \frac{n(n - 1)}{2}$. Most directly, **we can exhaustively enumerate all states** to find the maximum capacity, with time complexity $O(n^2)$.
-### Determination of a greedy strategy
+### Greedy strategy determination
-There is a more efficient solution to this problem. As shown in the figure below, we select a state $[i, j]$ where the indices $i < j$ and the height $ht[i] < ht[j]$, meaning $i$ is the shorter partition, and $j$ is the taller one.
+This problem has a more efficient solution. As shown in the figure below, select a state $[i, j]$ where index $i < j$ and height $ht[i] < ht[j]$, meaning $i$ is the short partition and $j$ is the long partition.

-As shown in the figure below, **if we move the taller partition $j$ closer to the shorter partition $i$, the capacity will definitely decrease**.
+As shown in the figure below, **if we now move the long partition $j$ closer to the short partition $i$, the capacity will definitely decrease**.
-This is because when moving the taller partition $j$, the width $j-i$ definitely decreases; and since the height is determined by the shorter partition, the height can only remain the same (if $i$ remains the shorter partition) or decrease (if the moved $j$ becomes the shorter partition).
+This is because after moving the long partition $j$, the width $j-i$ definitely decreases; and since height is determined by the short partition, the height can only remain unchanged ($i$ is still the short partition) or decrease (the moved $j$ becomes the short partition).
-
+
-Conversely, **we can only possibly increase the capacity by moving the shorter partition $i$ inward**. Although the width will definitely decrease, **the height may increase** (if the moved shorter partition $i$ becomes taller). For example, in the figure below, the area increases after moving the shorter partition.
+Conversely, **we can only possibly increase capacity by contracting the short partition $i$ inward**. Because although width will definitely decrease, **height may increase** (the moved short partition $i$ may become taller). For example, in the figure below, the area increases after moving the short partition.
-
+
-This leads us to the greedy strategy for this problem: initialize two pointers at the ends of the container, and in each round, move the pointer corresponding to the shorter partition inward until the two pointers meet.
+From this we can derive the greedy strategy for this problem: initialize two pointers at both ends of the container, and in each round contract the pointer corresponding to the short partition inward, until the two pointers meet.
-The figure below illustrate the execution of the greedy strategy.
+The figure below shows the execution process of the greedy strategy.
-1. Initially, the pointers $i$ and $j$ are positioned at the ends of the array.
-2. Calculate the current state's capacity $cap[i, j]$ and update the maximum capacity.
-3. Compare the heights of partitions $i$ and $j$, and move the shorter partition inward by one step.
-4. Repeat steps `2.` and `3.` until $i$ and $j$ meet.
+1. In the initial state, pointers $i$ and $j$ are at both ends of the array.
+2. Calculate the capacity of the current state $cap[i, j]$, and update the maximum capacity.
+3. Compare the heights of partition $i$ and partition $j$, and move the short partition inward by one position.
+4. Loop through steps `2.` and `3.` until $i$ and $j$ meet.
=== "<1>"
- 
+ 
=== "<2>"

@@ -72,28 +72,28 @@ The figure below illustrate the execution of the greedy strategy.
=== "<9>"

-### Implementation
+### Code implementation
-The code loops at most $n$ times, **thus the time complexity is $O(n)$**.
+The code loops at most $n$ rounds, **therefore the time complexity is $O(n)$**.
-The variables $i$, $j$, and $res$ use a constant amount of extra space, **thus the space complexity is $O(1)$**.
+Variables $i$, $j$, and $res$ use a constant amount of extra space, **therefore the space complexity is $O(1)$**.
```src
[file]{max_capacity}-[class]{}-[func]{max_capacity}
```
-### Proof of correctness
+### Correctness proof
-The reason why the greedy method is faster than enumeration is that each round of greedy selection "skips" some states.
+The reason greedy is faster than exhaustive enumeration is that each round of greedy selection "skips" some states.
-For example, under the state $cap[i, j]$ where $i$ is the shorter partition and $j$ is the taller partition, greedily moving the shorter partition $i$ inward by one step leads to the "skipped" states shown in the figure below. **This means that these states' capacities cannot be verified later**.
+For example, in state $cap[i, j]$ where $i$ is the short partition and $j$ is the long partition, if we greedily move the short partition $i$ inward by one position, the states shown in the figure below will be "skipped". **This means that the capacities of these states cannot be verified later**.
$$
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]
$$
-
+
-It is observed that **these skipped states are actually all states where the taller partition $j$ is moved inward**. We have already proven that moving the taller partition inward will definitely decrease the capacity. Therefore, the skipped states cannot possibly be the optimal solution, **and skipping them does not lead to missing the optimal solution**.
+Observing carefully, **these skipped states are actually all the states obtained by moving the long partition $j$ inward**. We have already proven that moving the long partition inward will definitely decrease capacity. That is, the skipped states cannot possibly be the optimal solution, **skipping them will not cause us to miss the optimal solution**.
-The analysis shows that the operation of moving the shorter partition is "safe", and the greedy strategy is effective.
+The above analysis shows that the operation of moving the short partition is "safe", and the greedy strategy is effective.
diff --git a/en/docs/chapter_greedy/max_product_cutting_problem.md b/en/docs/chapter_greedy/max_product_cutting_problem.md
index 165af4f9f..3c2239517 100644
--- a/en/docs/chapter_greedy/max_product_cutting_problem.md
+++ b/en/docs/chapter_greedy/max_product_cutting_problem.md
@@ -1,28 +1,28 @@
-# Maximum product cutting problem
+# Max product cutting problem
!!! question
- Given a positive integer $n$, split it into at least two positive integers that sum up to $n$, and find the maximum product of these integers, as illustrated in the figure below.
+ Given a positive integer $n$, split it into the sum of at least two positive integers, and find the maximum product of all integers after splitting, as shown in the figure below.
-
+
-Assume we split $n$ into $m$ integer factors, where the $i$-th factor is denoted as $n_i$, that is,
+Suppose we split $n$ into $m$ integer factors, where the $i$-th factor is denoted as $n_i$, that is
$$
n = \sum_{i=1}^{m}n_i
$$
-The goal of this problem is to find the maximum product of all integer factors, namely,
+The goal of this problem is to find the maximum product of all integer factors, namely
$$
\max(\prod_{i=1}^{m}n_i)
$$
-We need to consider: How large should the number of splits $m$ be, and what should each $n_i$ be?
+We need to think about: how large should the splitting count $m$ be, and what should each $n_i$ be?
### Greedy strategy determination
-Experience suggests that the product of two integers is often greater than their sum. Suppose we split a factor of $2$ from $n$, then their product is $2(n-2)$. Compare this product with $n$:
+Based on experience, the product of two integers is often greater than their sum. Suppose we split out a factor of $2$ from $n$, then their product is $2(n-2)$. We compare this product with $n$:
$$
\begin{aligned}
@@ -32,54 +32,54 @@ n & \geq 4
\end{aligned}
$$
-As shown in the figure below, when $n \geq 4$, splitting out a $2$ increases the product, **which indicates that integers greater than or equal to $4$ should be split**.
+As shown in the figure below, when $n \geq 4$, splitting out a $2$ will increase the product, **which indicates that integers greater than or equal to $4$ should all be split**.
-**Greedy strategy one**: If the splitting scheme includes factors $\geq 4$, they should be further split. The final split should only include factors $1$, $2$, and $3$.
+**Greedy strategy one**: If the splitting scheme includes factors $\geq 4$, then they should continue to be split. The final splitting scheme should only contain factors $1$, $2$, and $3$.
-
+
-Next, consider which factor is optimal. Among the factors $1$, $2$, and $3$, clearly $1$ is the worst, as $1 \times (n-1) < n$ always holds, meaning splitting out $1$ actually decreases the product.
+Next, consider which factor is optimal. Among the three factors $1$, $2$, and $3$, clearly $1$ is the worst, because $1 \times (n-1) < n$ always holds, meaning splitting out $1$ will actually decrease the product.
-As shown in the figure below, when $n = 6$, $3 \times 3 > 2 \times 2 \times 2$. **This means splitting out $3$ is better than splitting out $2$**.
+As shown in the figure below, when $n = 6$, we have $3 \times 3 > 2 \times 2 \times 2$. **This means that splitting out $3$ is better than splitting out $2$**.
-**Greedy strategy two**: In the splitting scheme, there should be at most two $2$s. Because three $2$s can always be replaced by two $3$s to obtain a higher product.
+**Greedy strategy two**: In the splitting scheme, there should be at most two $2$s. Because three $2$s can always be replaced by two $3$s to obtain a larger product.
-
+
-From the above, the following greedy strategies can be derived.
+In summary, the following greedy strategies can be derived.
-1. Input integer $n$, continually split out factor $3$ until the remainder is $0$, $1$, or $2$.
-2. When the remainder is $0$, it means $n$ is a multiple of $3$, so no further action is taken.
-3. When the remainder is $2$, do not continue to split, keep it.
+1. Input integer $n$, continuously split out factor $3$ until the remainder is $0$, $1$, or $2$.
+2. When the remainder is $0$, it means $n$ is a multiple of $3$, so no further action is needed.
+3. When the remainder is $2$, do not continue splitting, keep it.
4. When the remainder is $1$, since $2 \times 2 > 1 \times 3$, the last $3$ should be replaced with $2$.
### Code implementation
-As shown in the figure below, we do not need to use loops to split the integer but can use the floor division operation to get the number of $3$s, $a$, and the modulo operation to get the remainder, $b$, thus:
+As shown in the figure below, we don't need to use loops to split the integer, but can use integer division to get the count of $3$s as $a$, and modulo operation to get the remainder as $b$, at which point we have:
$$
-n = 3a + b
+n = 3 a + b
$$
-Please note, for the boundary case where $n \leq 3$, a $1$ must be split out, with a product of $1 \times (n - 1)$.
+Please note that for the edge case of $n \leq 3$, a $1$ must be split out, with product $1 \times (n - 1)$.
```src
[file]{max_product_cutting}-[class]{}-[func]{max_product_cutting}
```
-
+
-**Time complexity depends on the implementation of the power operation in the programming language**. For Python, the commonly used power calculation functions are three types:
+**The time complexity depends on the implementation of the exponentiation operation in the programming language**. Taking Python as an example, there are three commonly used power calculation functions.
-- Both the operator `**` and the function `pow()` have a time complexity of $O(\log a)$.
-- The `math.pow()` function internally calls the C language library's `pow()` function, performing floating-point exponentiation, with a time complexity of $O(1)$.
+- Both the operator `**` and the function `pow()` have time complexity $O(\log a)$.
+- The function `math.pow()` internally calls the C library's `pow()` function, which performs floating-point exponentiation, with time complexity $O(1)$.
-Variables $a$ and $b$ use constant size of extra space, **hence the space complexity is $O(1)$**.
+Variables $a$ and $b$ use a constant amount of extra space, **therefore the space complexity is $O(1)$**.
### Correctness proof
-Using the proof by contradiction, only analyze cases where $n \geq 3$.
+Using proof by contradiction, only analyzing the case where $n \geq 4$.
-1. **All factors $\leq 3$**: Assume the optimal splitting scheme includes a factor $x \geq 4$, then it can definitely be further split into $2(x-2)$, obtaining a larger product. This contradicts the assumption.
-2. **The splitting scheme does not contain $1$**: Assume the optimal splitting scheme includes a factor of $1$, then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption.
-3. **The splitting scheme contains at most two $2$s**: Assume the optimal splitting scheme includes three $2$s, then they can definitely be replaced by two $3$s, achieving a higher product. This contradicts the assumption.
+1. **All factors $\leq 3$**: Suppose the optimal splitting scheme includes a factor $x \geq 4$, then it can definitely continue to be split into $2(x-2)$ to obtain a larger (or equal) product. This contradicts the assumption.
+2. **The splitting scheme does not contain $1$**: Suppose the optimal splitting scheme includes a factor of $1$, then it can definitely be merged into another factor to obtain a larger product. This contradicts the assumption.
+3. **The splitting scheme contains at most two $2$s**: Suppose the optimal splitting scheme includes three $2$s, then they can definitely be replaced by two $3$s for a larger product. This contradicts the assumption.
diff --git a/en/docs/chapter_greedy/summary.md b/en/docs/chapter_greedy/summary.md
index bf71f93cd..1b72a00de 100644
--- a/en/docs/chapter_greedy/summary.md
+++ b/en/docs/chapter_greedy/summary.md
@@ -1,12 +1,12 @@
# Summary
-- Greedy algorithms are often used to solve optimization problems, where the principle is to make locally optimal decisions at each decision stage in order to achieve a globally optimal solution.
-- Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller sub-problem with each round, until the problem is resolved.
-- Greedy algorithms are not only simple to implement but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms generally have a lower time complexity.
-- In the problem of coin change, greedy algorithms can guarantee the optimal solution for certain combinations of coins; for others, however, the greedy algorithm might find a very poor solution.
-- Problems suitable for greedy algorithm solutions possess two main properties: greedy-choice property and optimal substructure. The greedy-choice property represents the effectiveness of the greedy strategy.
-- For some complex problems, proving the greedy-choice property is not straightforward. Contrarily, proving the invalidity is often easier, such as with the coin change problem.
-- Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and proving correctness. Among these, determining the greedy strategy is the key step, while proving correctness often poses the challenge.
-- The fractional knapsack problem builds on the 0-1 knapsack problem by allowing the selection of a part of the items, hence it can be solved using a greedy algorithm. The correctness of the greedy strategy can be proved by contradiction.
-- The maximum capacity problem can be solved using the exhaustive method, with a time complexity of $O(n^2)$. By designing a greedy strategy, each round moves inwardly shortening the board, optimizing the time complexity to $O(n)$.
-- In the problem of maximum product after cutting, we deduce two greedy strategies: integers $\geq 4$ should continue to be cut, with the optimal cutting factor being $3$. The code includes power operations, and the time complexity depends on the method of implementing power operations, generally being $O(1)$ or $O(\log n)$.
+- Greedy algorithms are typically used to solve optimization problems. The principle is to make locally optimal decisions at each decision stage in hopes of obtaining a globally optimal solution.
+- Greedy algorithms iteratively make one greedy choice after another, transforming the problem into a smaller subproblem in each round, until the problem is solved.
+- Greedy algorithms are not only simple to implement, but also have high problem-solving efficiency. Compared to dynamic programming, greedy algorithms typically have lower time complexity.
+- In the coin change problem, for certain coin combinations, greedy algorithms can guarantee finding the optimal solution; for other coin combinations, however, greedy algorithms may find very poor solutions.
+- Problems suitable for solving with greedy algorithms have two major properties: greedy choice property and optimal substructure. The greedy choice property represents the effectiveness of the greedy strategy.
+- For some complex problems, proving the greedy choice property is not simple. Relatively speaking, disproving it is easier, such as in the coin change problem.
+- Solving greedy problems mainly consists of three steps: problem analysis, determining the greedy strategy, and correctness proof. Among these, determining the greedy strategy is the core step, and correctness proof is often the difficult point.
+- The fractional knapsack problem, based on the 0-1 knapsack problem, allows selecting a portion of items, and therefore can be solved using greedy algorithms. The correctness of the greedy strategy can be proven using proof by contradiction.
+- The max capacity problem can be solved using exhaustive enumeration with time complexity $O(n^2)$. By designing a greedy strategy to move the short partition inward in each round, the time complexity can be optimized to $O(n)$.
+- In the max product cutting problem, we successively derive two greedy strategies: integers $\geq 4$ should all continue to be split, and the optimal splitting factor is $3$. The code includes exponentiation operations, and the time complexity depends on the implementation method of exponentiation, typically being $O(1)$ or $O(\log n)$.
diff --git a/en/docs/chapter_hashing/hash_algorithm.md b/en/docs/chapter_hashing/hash_algorithm.md
index d84ce1f5e..415989529 100644
--- a/en/docs/chapter_hashing/hash_algorithm.md
+++ b/en/docs/chapter_hashing/hash_algorithm.md
@@ -1,12 +1,12 @@
-# Hash algorithms
+# Hash algorithm
-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**.
+The previous two sections introduced the working principle of hash tables and the methods to handle hash collisions. However, both open addressing and separate chaining **can only ensure that the hash table functions normally when hash 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 below, 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)$.
+If hash collisions occur too frequently, the performance of the hash table will deteriorate drastically. As shown in the figure below, for a separate 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)$.

-**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:
+**The distribution of key-value pairs is determined by the hash function**. Recalling the calculation steps of the hash function, first compute the hash value, then take the modulo by the array length:
```shell
index = hash(key) % capacity
@@ -35,7 +35,7 @@ For cryptographic applications, to prevent reverse engineering such as deducing
- **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.
+Note that **"uniform distribution" and "collision resistance" are two independent 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.
## Design of hash algorithms
@@ -346,7 +346,57 @@ We know that the keys in a hash table can be of various data types such as integ
=== "Kotlin"
```kotlin title="built_in_hash.kt"
+ val num = 3
+ val hashNum = num.hashCode()
+ // Hash value of integer 3 is 3
+ val bol = true
+ val hashBol = bol.hashCode()
+ // Hash value of boolean true is 1231
+
+ val dec = 3.14159
+ val hashDec = dec.hashCode()
+ // Hash value of decimal 3.14159 is -1340954729
+
+ val str = "Hello 算法"
+ val hashStr = str.hashCode()
+ // Hash value of string "Hello 算法" is -727081396
+
+ val arr = arrayOf(12836, "小哈")
+ val hashTup = arr.hashCode()
+ // Hash value of array [12836, 小哈] is 189568618
+
+ val obj = ListNode(0)
+ val hashObj = obj.hashCode()
+ // Hash value of ListNode object utils.ListNode@1d81eb93 is 495053715
+ ```
+
+=== "Ruby"
+
+ ```ruby title="built_in_hash.rb"
+ num = 3
+ hash_num = num.hash
+ # Hash value of integer 3 is -4385856518450339636
+
+ bol = true
+ hash_bol = bol.hash
+ # Hash value of boolean true is -1617938112149317027
+
+ dec = 3.14159
+ hash_dec = dec.hash
+ # Hash value of decimal 3.14159 is -1479186995943067893
+
+ str = "Hello 算法"
+ hash_str = str.hash
+ # Hash value of string "Hello 算法" is -4075943250025831763
+
+ tup = [12836, '小哈']
+ hash_tup = tup.hash
+ # Hash value of tuple (12836, '小哈') is 1999544809202288822
+
+ obj = ListNode.new(0)
+ hash_obj = obj.hash
+ # Hash value of ListNode object # is 4302940560806366381
```
=== "Zig"
@@ -355,7 +405,7 @@ We know that the keys in a hash table can be of various data types such as integ
```
-??? pythontutor "Code Visualization"
+??? pythontutor "Visualized Execution"
https://pythontutor.com/render.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&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
diff --git a/en/docs/chapter_hashing/hash_collision.md b/en/docs/chapter_hashing/hash_collision.md
index 7d10b5bad..abc8a0106 100644
--- a/en/docs/chapter_hashing/hash_collision.md
+++ b/en/docs/chapter_hashing/hash_collision.md
@@ -1,25 +1,25 @@
# Hash collision
-The previous section mentioned that, **in most cases, the input space of a hash function is much larger than the output space**, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the size of the array capacity, then multiple integers will inevitably be mapped to the same bucket index.
+The previous section mentioned that, **in most cases, the input space of a hash function is much larger than the output space**, so theoretically, hash collisions are inevitable. For example, if the input space is all integers and the output space is the array capacity size, then multiple integers will inevitably be mapped to the same bucket index.
-Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we perform hash table resizing until the collision disappears. This approach is pretty simple, straightforward, and working well. However, it appears to be pretty inefficient as the table expansion involves a lot of data migration as well as recalculation of hash code, which are expansive. To improve efficiency, we can adopt the following strategies:
+Hash collisions can lead to incorrect query results, severely impacting the usability of the hash table. To address this issue, whenever a hash collision occurs, we can perform hash table expansion until the collision disappears. This approach is simple, straightforward, and effective, but it is very inefficient because hash table expansion involves a large amount of data migration and hash value recalculation. To improve efficiency, we can adopt the following strategies:
-1. Improve the hash table data structure in a way that **locating target element is still functioning well in the event of a hash collision**.
-2. Expansion is the last resort before it becomes necessary, when severe collisions are observed.
+1. Improve the hash table data structure so that **the hash table can function normally when hash collisions occur**.
+2. Only expand when necessary, that is, only when hash collisions are severe.
-There are mainly two methods for improving the structure of hash tables: "Separate Chaining" and "Open Addressing".
+The main methods for improving the structure of hash tables include "separate chaining" and "open addressing".
## Separate chaining
-In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as list nodes, storing all colliding key-value pairs in the same linked list. The figure below shows an example of a hash table with separate chaining.
+In the original hash table, each bucket can store only one key-value pair. Separate chaining converts a single element into a linked list, treating key-value pairs as linked list nodes and storing all colliding key-value pairs in the same linked list. The figure below shows an example of a separate chaining hash table.

The operations of a hash table implemented with separate chaining have changed as follows:
-- **Querying Elements**: Input `key`, obtain the bucket index through the hash function, then access the head node of the linked list. Traverse the linked list and compare key to find the target key-value pair.
-- **Adding Elements**: Access the head node of the linked list via the hash function, then append the node (key-value pair) to the list.
-- **Deleting Elements**: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it.
+- **Querying elements**: Input `key`, obtain the bucket index through the hash function, then access the head node of the linked list, then traverse the linked list and compare `key` to find the target key-value pair.
+- **Adding elements**: First access the linked list head node through the hash function, then append the node (key-value pair) to the linked list.
+- **Deleting elements**: Access the head of the linked list based on the result of the hash function, then traverse the linked list to find the target node and delete it.
Separate chaining has the following limitations:
@@ -28,8 +28,8 @@ Separate chaining has the following limitations:
The code below provides a simple implementation of a separate chaining hash table, with two things to note:
-- Lists (dynamic arrays) are used instead of linked lists for simplicity. In this setup, the hash table (array) contains multiple buckets, each of which is a list.
-- This implementation includes a hash table resizing method. When the load factor exceeds $\frac{2}{3}$, we expand the hash table to twice its original size.
+- Lists (dynamic arrays) are used instead of linked lists to simplify the code. In this setup, the hash table (array) contains multiple buckets, each of which is a list.
+- This implementation includes a hash table expansion method. When the load factor exceeds $\frac{2}{3}$, we expand the hash table to $2$ times its original size.
```src
[file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{}
@@ -39,34 +39,34 @@ It's worth noting that when the linked list is very long, the query efficiency $
## Open addressing
-Open addressing does not introduce additional data structures but instead handles hash collisions through "multiple probing". The probing methods mainly include linear probing, quadratic probing, and double hashing.
+Open addressing does not introduce additional data structures but instead handles hash collisions through "multiple probes". The probing methods mainly include linear probing, quadratic probing, and double hashing.
Let's use linear probing as an example to introduce the mechanism of open addressing hash tables.
### Linear probing
-Linear probing uses a fixed-step linear search for probing, differing from ordinary hash tables.
+Linear probing uses a fixed-step linear search for probing, and its operation method differs from ordinary hash tables.
-- **Inserting Elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element.
-- **Searching for Elements**: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`.
+- **Inserting elements**: Calculate the bucket index using the hash function. If the bucket already contains an element, linearly traverse forward from the conflict position (usually with a step size of $1$) until an empty bucket is found, then insert the element.
+- **Searching for elements**: If a hash collision is encountered, use the same step size to linearly traverse forward until the corresponding element is found and return `value`; if an empty bucket is encountered, it means the target element is not in the hash table, so return `None`.
The figure below shows the distribution of key-value pairs in an open addressing (linear probing) hash table. According to this hash function, keys with the same last two digits will be mapped to the same bucket. Through linear probing, they are stored sequentially in that bucket and the buckets below it.

-However, **linear probing is prone to create "clustering"**. Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting the growth of clustering at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations.
+However, **linear probing is prone to create "clustering"**. Specifically, the longer the continuously occupied positions in the array, the greater the probability of hash collisions occurring in these continuous positions, further promoting clustering growth at that position, forming a vicious cycle, and ultimately leading to degraded efficiency of insertion, deletion, query, and update operations.
-It's important to note that **we cannot directly delete elements in an open addressing hash table**. Deleting an element creates an empty bucket `None` in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the figure below.
+It's important to note that **we cannot directly delete elements in an open addressing hash table**. Deleting an element creates an empty bucket `None` in the array. When searching for elements, if linear probing encounters this empty bucket, it will return, making the elements below this empty bucket inaccessible. The program may incorrectly assume these elements do not exist, as shown in the figure below.

To solve this problem, we can adopt the lazy deletion mechanism: instead of directly removing elements from the hash table, **use a constant `TOMBSTONE` to mark the bucket**. In this mechanism, both `None` and `TOMBSTONE` represent empty buckets and can hold key-value pairs. However, when linear probing encounters `TOMBSTONE`, it should continue traversing since there may still be key-value pairs below it.
-However, **lazy deletion may accelerate the performance degradation of the hash table**. Every deletion operation produces a delete mark, and as `TOMBSTONE` increases, the search time will also increase because linear probing may need to skip multiple `TOMBSTONE` to find the target element.
+However, **lazy deletion may accelerate the performance degradation of the hash table**. Every deletion operation produces a deletion mark, and as `TOMBSTONE` increases, the search time will also increase because linear probing may need to skip multiple `TOMBSTONE` to find the target element.
-To address this, consider recording the index of the first encountered `TOMBSTONE` during linear probing and swapping the positions of the searched target element with that `TOMBSTONE`. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency.
+To address this, consider recording the index of the first encountered `TOMBSTONE` during linear probing and swapping the searched target element with that `TOMBSTONE`. The benefit of doing this is that each time an element is queried or added, the element will be moved to a bucket closer to its ideal position (the starting point of probing), thereby optimizing query efficiency.
-The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a "circular array,". When going beyond the end of the array, we return to the beginning and continue traversing.
+The code below implements an open addressing (linear probing) hash table with lazy deletion. To make better use of the hash table space, we treat the hash table as a "circular array". When going beyond the end of the array, we return to the beginning and continue traversing.
```src
[file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{}
@@ -74,11 +74,11 @@ The code below implements an open addressing (linear probing) hash table with la
### Quadratic probing
-Quadratic probing is similar to linear probing and is one of the common strategies of open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the "square of the number of probes", i.e., $1, 4, 9, \dots$ steps.
+Quadratic probing is similar to linear probing and is one of the common strategies for open addressing. When a collision occurs, quadratic probing does not simply skip a fixed number of steps but skips a number of steps equal to the "square of the number of probes", i.e., $1, 4, 9, \dots$ steps.
Quadratic probing has the following advantages:
-- Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping the distance of the square of the number of probes.
+- Quadratic probing attempts to alleviate the clustering effect of linear probing by skipping distances equal to the square of the probe count.
- Quadratic probing skips larger distances to find empty positions, which helps to distribute data more evenly.
However, quadratic probing is not perfect:
@@ -90,14 +90,14 @@ However, quadratic probing is not perfect:
As the name suggests, the double hashing method uses multiple hash functions $f_1(x)$, $f_2(x)$, $f_3(x)$, $\dots$ for probing.
-- **Inserting Elements**: If hash function $f_1(x)$ encounters a conflict, it tries $f_2(x)$, and so on, until an empty position is found and the element is inserted.
-- **Searching for Elements**: Search in the same order of hash functions until the target element is found and returned; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`.
+- **Inserting elements**: If hash function $f_1(x)$ encounters a conflict, try $f_2(x)$, and so on, until an empty position is found and the element is inserted.
+- **Searching for elements**: Search in the same order of hash functions until the target element is found and return it; if an empty position is encountered or all hash functions have been tried, it indicates the element is not in the hash table, then return `None`.
Compared to linear probing, the double hashing method is less prone to clustering, but multiple hash functions introduce additional computational overhead.
!!! tip
- Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of "can not directly delete elements."
+ Please note that open addressing (linear probing, quadratic probing, and double hashing) hash tables all have the problem of "cannot directly delete elements".
## Choice of programming languages
@@ -105,4 +105,4 @@ Different programming languages adopt different hash table implementation strate
- Python uses open addressing. The `dict` dictionary uses pseudo-random numbers for probing.
- Java uses separate chaining. Since JDK 1.8, when the array length in `HashMap` reaches 64 and the length of a linked list reaches 8, the linked list is converted to a red-black tree to improve search performance.
-- Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity resizing operation is performed to ensure performance.
+- Go uses separate chaining. Go stipulates that each bucket can store up to 8 key-value pairs, and if the capacity is exceeded, an overflow bucket is linked; when there are too many overflow buckets, a special equal-capacity expansion operation is performed to ensure performance.
diff --git a/en/docs/chapter_hashing/hash_map.md b/en/docs/chapter_hashing/hash_map.md
index 76576648c..ab15e1e8c 100755
--- a/en/docs/chapter_hashing/hash_map.md
+++ b/en/docs/chapter_hashing/hash_map.md
@@ -1,30 +1,30 @@
# Hash table
-A hash table, also known as a hash map, is a data structure that establishes a mapping between keys and values, enabling efficient element retrieval. Specifically, when we input a `key` into the hash table, we can retrieve the corresponding `value` in $O(1)$ time complexity.
+A hash table, also known as a hash map, establishes a mapping between keys `key` and values `value`, enabling efficient element retrieval. Specifically, when we input a key `key` into a hash table, we can retrieve the corresponding value `value` in $O(1)$ time.
-As shown in the figure below, given $n$ students, each student has two data fields: "Name" and "Student ID". If we want to implement a query function that takes a student ID as input and returns the corresponding name, we can use the hash table shown in the figure below.
+As shown in the figure below, given $n$ students, each with two pieces of data: "name" and "student ID". If we want to implement a query function that "inputs a student ID and returns the corresponding name", we can use the hash table shown below.

-In addition to hash tables, arrays and linked lists can also be used to implement query functionality, but the time complexity is different. Their efficiency is compared in the table below:
+In addition to hash tables, arrays and linked lists can also implement query functionality. Their efficiency comparison is shown in the following table.
-- **Inserting an element**: Simply append the element to the tail of the array (or linked list). The time complexity of this operation is $O(1)$.
-- **Searching for an element**: As the array (or linked list) is unsorted, searching for an element requires traversing through all of the elements. The time complexity of this operation is $O(n)$.
-- **Deleting an element**: To remove an element, we first need to locate it. Then, we delete it from the array (or linked list). The time complexity of this operation is $O(n)$.
+- **Adding elements**: Simply add elements to the end of the array (linked list), using $O(1)$ time.
+- **Querying elements**: Since the array (linked list) is unordered, all elements need to be traversed, using $O(n)$ time.
+- **Deleting elements**: The element must first be located, then deleted from the array (linked list), using $O(n)$ time.
- Table Comparison of time efficiency for common operations
+ Table Comparison of element query efficiency
-| | Array | Linked List | Hash Table |
-| -------------- | ------ | ----------- | ---------- |
-| Search Elements | $O(n)$ | $O(n)$ | $O(1)$ |
-| Insert Elements | $O(1)$ | $O(1)$ | $O(1)$ |
-| Delete Elements | $O(n)$ | $O(n)$ | $O(1)$ |
+| | Array | Linked List | Hash Table |
+| --------------- | ------ | ----------- | ---------- |
+| Find element | $O(n)$ | $O(n)$ | $O(1)$ |
+| Add element | $O(1)$ | $O(1)$ | $O(1)$ |
+| Delete element | $O(n)$ | $O(n)$ | $O(1)$ |
-As observed, **the time complexity for operations (insertion, deletion, searching, and modification) in a hash table is $O(1)$**, which is highly efficient.
+As observed, **the time complexity for insertion, deletion, search, and modification operations in a hash table is $O(1)$**, which is very efficient.
-## Common operations of hash table
+## Common hash table operations
-Common operations of a hash table include: initialization, querying, adding key-value pairs, and deleting key-value pairs. Here is an example code:
+Common operations on hash tables include: initialization, query operations, adding key-value pairs, and deleting key-value pairs. Example code is as follows:
=== "Python"
@@ -33,15 +33,15 @@ Common operations of a hash table include: initialization, querying, adding key-
hmap: dict = {}
# Add operation
- # Add key-value pair (key, value) to the hash table
- hmap[12836] = "Xiao Ha"
- hmap[15937] = "Xiao Luo"
- hmap[16750] = "Xiao Suan"
- hmap[13276] = "Xiao Fa"
- hmap[10583] = "Xiao Ya"
+ # Add key-value pair (key, value) to hash table
+ hmap[12836] = "小哈"
+ hmap[15937] = "小啰"
+ hmap[16750] = "小算"
+ hmap[13276] = "小法"
+ hmap[10583] = "小鸭"
# Query operation
- # Input key into hash table, get value
+ # Input key into hash table to get value
name: str = hmap[15937]
# Delete operation
@@ -57,14 +57,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- map[12836] = "Xiao Ha";
- map[15937] = "Xiao Luo";
- map[16750] = "Xiao Suan";
- map[13276] = "Xiao Fa";
- map[10583] = "Xiao Ya";
+ map[12836] = "小哈";
+ map[15937] = "小啰";
+ map[16750] = "小算";
+ map[13276] = "小法";
+ map[10583] = "小鸭";
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
string name = map[15937];
/* Delete operation */
@@ -80,14 +80,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- map.put(12836, "Xiao Ha");
- map.put(15937, "Xiao Luo");
- map.put(16750, "Xiao Suan");
- map.put(13276, "Xiao Fa");
- map.put(10583, "Xiao Ya");
+ map.put(12836, "小哈");
+ map.put(15937, "小啰");
+ map.put(16750, "小算");
+ map.put(13276, "小法");
+ map.put(10583, "小鸭");
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
String name = map.get(15937);
/* Delete operation */
@@ -102,15 +102,15 @@ Common operations of a hash table include: initialization, querying, adding key-
Dictionary map = new() {
/* Add operation */
// Add key-value pair (key, value) to hash table
- { 12836, "Xiao Ha" },
- { 15937, "Xiao Luo" },
- { 16750, "Xiao Suan" },
- { 13276, "Xiao Fa" },
- { 10583, "Xiao Ya" }
+ { 12836, "小哈" },
+ { 15937, "小啰" },
+ { 16750, "小算" },
+ { 13276, "小法" },
+ { 10583, "小鸭" }
};
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
string name = map[15937];
/* Delete operation */
@@ -126,14 +126,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- hmap[12836] = "Xiao Ha"
- hmap[15937] = "Xiao Luo"
- hmap[16750] = "Xiao Suan"
- hmap[13276] = "Xiao Fa"
- hmap[10583] = "Xiao Ya"
+ hmap[12836] = "小哈"
+ hmap[15937] = "小啰"
+ hmap[16750] = "小算"
+ hmap[13276] = "小法"
+ hmap[10583] = "小鸭"
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
name := hmap[15937]
/* Delete operation */
@@ -149,14 +149,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- map[12836] = "Xiao Ha"
- map[15937] = "Xiao Luo"
- map[16750] = "Xiao Suan"
- map[13276] = "Xiao Fa"
- map[10583] = "Xiao Ya"
+ map[12836] = "小哈"
+ map[15937] = "小啰"
+ map[16750] = "小算"
+ map[13276] = "小法"
+ map[10583] = "小鸭"
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
let name = map[15937]!
/* Delete operation */
@@ -170,15 +170,15 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Initialize hash table */
const map = new Map();
/* Add operation */
- // Add key-value pair (key, value) to the hash table
- map.set(12836, 'Xiao Ha');
- map.set(15937, 'Xiao Luo');
- map.set(16750, 'Xiao Suan');
- map.set(13276, 'Xiao Fa');
- map.set(10583, 'Xiao Ya');
+ // Add key-value pair (key, value) to hash table
+ map.set(12836, '小哈');
+ map.set(15937, '小啰');
+ map.set(16750, '小算');
+ map.set(13276, '小法');
+ map.set(10583, '小鸭');
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
let name = map.get(15937);
/* Delete operation */
@@ -193,23 +193,23 @@ Common operations of a hash table include: initialization, querying, adding key-
const map = new Map();
/* Add operation */
// Add key-value pair (key, value) to hash table
- map.set(12836, 'Xiao Ha');
- map.set(15937, 'Xiao Luo');
- map.set(16750, 'Xiao Suan');
- map.set(13276, 'Xiao Fa');
- map.set(10583, 'Xiao Ya');
- console.info('\nAfter adding, the hash table is\nKey -> Value');
+ map.set(12836, '小哈');
+ map.set(15937, '小啰');
+ map.set(16750, '小算');
+ map.set(13276, '小法');
+ map.set(10583, '小鸭');
+ console.info('\nAfter adding, hash table is\nKey -> Value');
console.info(map);
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
let name = map.get(15937);
- console.info('\nInput student number 15937, query name ' + name);
+ console.info('\nInput student ID 15937, queried name ' + name);
/* Delete operation */
// Delete key-value pair (key, value) from hash table
map.delete(10583);
- console.info('\nAfter deleting 10583, the hash table is\nKey -> Value');
+ console.info('\nAfter deleting 10583, hash table is\nKey -> Value');
console.info(map);
```
@@ -221,14 +221,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- map[12836] = "Xiao Ha";
- map[15937] = "Xiao Luo";
- map[16750] = "Xiao Suan";
- map[13276] = "Xiao Fa";
- map[10583] = "Xiao Ya";
+ map[12836] = "小哈";
+ map[15937] = "小啰";
+ map[16750] = "小算";
+ map[13276] = "小法";
+ map[10583] = "小鸭";
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
String name = map[15937];
/* Delete operation */
@@ -246,14 +246,14 @@ Common operations of a hash table include: initialization, querying, adding key-
/* Add operation */
// Add key-value pair (key, value) to hash table
- map.insert(12836, "Xiao Ha".to_string());
- map.insert(15937, "Xiao Luo".to_string());
- map.insert(16750, "Xiao Suan".to_string());
- map.insert(13279, "Xiao Fa".to_string());
- map.insert(10583, "Xiao Ya".to_string());
+ map.insert(12836, "小哈".to_string());
+ map.insert(15937, "小啰".to_string());
+ map.insert(16750, "小算".to_string());
+ map.insert(13279, "小法".to_string());
+ map.insert(10583, "小鸭".to_string());
/* Query operation */
- // Input key into hash table, get value
+ // Input key into hash table to get value
let _name: Option<&String> = map.get(&15937);
/* Delete operation */
@@ -270,7 +270,47 @@ Common operations of a hash table include: initialization, querying, adding key-
=== "Kotlin"
```kotlin title="hash_map.kt"
+ /* Initialize hash table */
+ val map = HashMap()
+ /* Add operation */
+ // Add key-value pair (key, value) to hash table
+ map[12836] = "小哈"
+ map[15937] = "小啰"
+ map[16750] = "小算"
+ map[13276] = "小法"
+ map[10583] = "小鸭"
+
+ /* Query operation */
+ // Input key into hash table to get value
+ val name = map[15937]
+
+ /* Delete operation */
+ // Delete key-value pair (key, value) from hash table
+ map.remove(10583)
+ ```
+
+=== "Ruby"
+
+ ```ruby title="hash_map.rb"
+ # Initialize hash table
+ hmap = {}
+
+ # Add operation
+ # Add key-value pair (key, value) to hash table
+ hmap[12836] = "小哈"
+ hmap[15937] = "小啰"
+ hmap[16750] = "小算"
+ hmap[13276] = "小法"
+ hmap[10583] = "小鸭"
+
+ # Query operation
+ # Input key into hash table to get value
+ name = hmap[15937]
+
+ # Delete operation
+ # Delete key-value pair (key, value) from hash table
+ hmap.delete(10583)
```
=== "Zig"
@@ -279,11 +319,11 @@ Common operations of a hash table include: initialization, querying, adding key-
```
-??? pythontutor "Code Visualization"
+??? pythontutor "Visualized Execution"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Here is an example code:
+There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Example code is as follows:
=== "Python"
@@ -428,17 +468,17 @@ There are three common ways to traverse a hash table: traversing key-value pairs
/* Traverse hash table */
// Traverse key-value pairs Key->Value
map.forEach((key, value) {
- print('$key -> $value');
+ print('$key -> $value');
});
- // Traverse keys only Key
+ // Traverse keys only
map.keys.forEach((key) {
- print(key);
+ print(key);
});
- // Traverse values only Value
+ // Traverse values only
map.values.forEach((value) {
- print(value);
+ print(value);
});
```
@@ -451,12 +491,12 @@ There are three common ways to traverse a hash table: traversing key-value pairs
println!("{key} -> {value}");
}
- // Traverse keys only Key
+ // Traverse keys only
for key in map.keys() {
- println!("{key}");
+ println!("{key}");
}
- // Traverse values only Value
+ // Traverse values only
for value in map.values() {
println!("{value}");
}
@@ -471,41 +511,67 @@ There are three common ways to traverse a hash table: traversing key-value pairs
=== "Kotlin"
```kotlin title="hash_map.kt"
+ /* Traverse hash table */
+ // Traverse key-value pairs key->value
+ for ((key, value) in map) {
+ println("$key -> $value")
+ }
+ // Traverse keys only
+ for (key in map.keys) {
+ println(key)
+ }
+ // Traverse values only
+ for (_val in map.values) {
+ println(_val)
+ }
+ ```
+=== "Ruby"
+
+ ```ruby title="hash_map.rb"
+ # Traverse hash table
+ # Traverse key-value pairs key->value
+ hmap.entries.each { |key, value| puts "#{key} -> #{value}" }
+
+ # Traverse keys only
+ hmap.keys.each { |key| puts key }
+
+ # Traverse values only
+ hmap.values.each { |val| puts val }
```
=== "Zig"
```zig title="hash_map.zig"
- // Zig example is not provided
+
```
-??? pythontutor "Code Visualization"
+??? pythontutor "Visualized Execution"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-## Simple implementation of a hash table
+## Simple hash table implementation
-First, let's consider the simplest case: **implementing a hash table using only one array**. In the hash table, each empty slot in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation involves finding the bucket corresponding to the `key` and retrieving the `value` from it.
+Let's first consider the simplest case: **implementing a hash table using only an array**. In a hash table, each empty position in the array is called a bucket, and each bucket can store a key-value pair. Therefore, the query operation is to find the bucket corresponding to `key` and retrieve the `value` from the bucket.
-So, how do we locate the corresponding bucket based on the `key`? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space consists of all the keys, and the output space consists of all the buckets (array indices). In other words, given a `key`, **we can use the hash function to determine the storage location of the corresponding key-value pair in the array**.
+So how do we locate the corresponding bucket based on `key`? This is achieved through a hash function. The role of the hash function is to map a larger input space to a smaller output space. In a hash table, the input space is all `key`s, and the output space is all buckets (array indices). In other words, given a `key`, **we can use the hash function to obtain the storage location of the key-value pair corresponding to that `key` in the array**.
-With a given `key`, the calculation of the hash function consists of two steps:
+When inputting a `key`, the hash function's calculation process consists of the following two steps:
-1. Calculate the hash value by using a certain hash algorithm `hash()`.
-2. Take the modulus of the hash value with the bucket count (array length) `capacity` to obtain the array `index` corresponding to the key.
+1. Calculate the hash value through a hash algorithm `hash()`.
+2. Take the modulo of the hash value by the number of buckets (array length) `capacity` to obtain the bucket (array index) `index` corresponding to that `key`.
```shell
index = hash(key) % capacity
```
-Afterward, we can use the `index` to access the corresponding bucket in the hash table and thereby retrieve the `value`.
+Subsequently, we can use `index` to access the corresponding bucket in the hash table and retrieve the `value`.
-Let's assume that the array length is `capacity = 100`, and the hash algorithm is defined as `hash(key) = key`. Therefore, the hash function can be expressed as `key % 100`. The following figure illustrates the working principle of the hash function using `key` as student ID and `value` as name.
+Assuming the array length is `capacity = 100` and the hash algorithm is `hash(key) = key`, the hash function becomes `key % 100`. The figure below shows the working principle of the hash function using `key` as student ID and `value` as name.

-The following code implements a simple hash table. Here, we encapsulate `key` and `value` into a class `Pair` to represent the key-value pair.
+The following code implements a simple hash table. Here, we encapsulate `key` and `value` into a class `Pair` to represent a key-value pair.
```src
[file]{array_hash_map}-[class]{array_hash_map}-[func]{}
@@ -513,25 +579,25 @@ The following code implements a simple hash table. Here, we encapsulate `key` an
## Hash collision and resizing
-Essentially, the role of the hash function is to map the entire input space of all keys to the output space of all array indices. However, the input space is often much larger than the output space. Therefore, **theoretically, there will always be cases where "multiple inputs correspond to the same output"**.
+Fundamentally, the role of a hash function is to map the input space consisting of all `key`s to the output space consisting of all array indices, and the input space is often much larger than the output space. Therefore, **theoretically there must be cases where "multiple inputs correspond to the same output"**.
-In the example above, with the given hash function, when the last two digits of the input `key` are the same, the hash function produces the same output. For instance, when querying two students with student IDs 12836 and 20336, we find:
+For the hash function in the above example, when the input `key`s have the same last two digits, the hash function produces the same output. For example, when querying two students with IDs 12836 and 20336, we get:
```shell
12836 % 100 = 36
20336 % 100 = 36
```
-As shown in the figure below, both student IDs point to the same name, which is obviously incorrect. This situation where multiple inputs correspond to the same output is called hash collision.
+As shown in the figure below, two student IDs point to the same name, which is obviously incorrect. We call this situation where multiple inputs correspond to the same output a hash collision.
-
+
-It is easy to understand that as the capacity $n$ of the hash table increases, the probability of multiple keys being assigned to the same bucket decreases, resulting in fewer collisions. Therefore, **we can reduce hash collisions by resizing the hash table**.
+It's easy to see that the larger the hash table capacity $n$, the lower the probability that multiple `key`s will be assigned to the same bucket, and the fewer collisions. Therefore, **we can reduce hash collisions by expanding the hash table**.
-As shown in the figure below, before resizing, the key-value pairs `(136, A)` and `(236, D)` collide. However, after resizing, the collision is resolved.
+As shown in the figure below, before expansion, the key-value pairs `(136, A)` and `(236, D)` collided, but after expansion, the collision disappears.

-Similar to array expansion, resizing a hash table requires migrating all key-value pairs from the original hash table to the new one, which is time-consuming. Furthermore, since the `capacity` of the hash table changes, we need to recalculate the storage positions of all key-value pairs using the hash function, further increasing the computational overhead of the resizing process. Therefore, programming languages often allocate a sufficiently large capacity for the hash table to prevent frequent resizing.
+Similar to array expansion, hash table expansion requires migrating all key-value pairs from the original hash table to the new hash table, which is very time-consuming. Moreover, since the hash table capacity `capacity` changes, we need to recalculate the storage locations of all key-value pairs through the hash function, further increasing the computational overhead of the expansion process. For this reason, programming languages typically reserve a sufficiently large hash table capacity to prevent frequent expansion.
-The load factor is an important concept in hash tables. It is defined as the ratio of the number of elements in the hash table to the number of buckets. It is used to measure the severity of hash collisions and **often serves as a trigger for hash table resizing**. For example, in Java, when the load factor exceeds $0.75$, the system will resize the hash table to twice its original size.
+The load factor is an important concept for hash tables. It is defined as the number of elements in the hash table divided by the number of buckets, and is used to measure the severity of hash collisions. **It is also commonly used as a trigger condition for hash table expansion**. For example, in Java, when the load factor exceeds $0.75$, the system will expand the hash table to $2$ times its original size.
diff --git a/en/docs/chapter_hashing/index.md b/en/docs/chapter_hashing/index.md
index 1090ec053..135c681df 100644
--- a/en/docs/chapter_hashing/index.md
+++ b/en/docs/chapter_hashing/index.md
@@ -1,9 +1,9 @@
-# Hash table
+# Hashing
-
+
!!! 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.
+ In the world of computing, a hash table is like a clever librarian.
+
+ They know how to calculate call numbers, enabling them to quickly locate the target book.
diff --git a/en/docs/chapter_hashing/summary.md b/en/docs/chapter_hashing/summary.md
index 21aaf97f5..bc98f04bd 100644
--- a/en/docs/chapter_hashing/summary.md
+++ b/en/docs/chapter_hashing/summary.md
@@ -6,13 +6,13 @@
- 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 and retrieval of 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.
-- The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table resizing.
-- 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 it cannot delete elements and is prone to clustering. Multiple hashing uses several hash functions for probing which reduces clustering compared to linear probing but increases computational overhead.
-- Different programming languages adopt various hash table implementations. For example, Java's `HashMap` uses chaining, while Python's `dict` employs open addressing.
+- The larger the capacity of the hash table, the lower the probability of hash collisions. Therefore, hash table expansion can mitigate hash collisions. Similar to array expansion, hash table expansion is costly.
+- The load factor, defined as the number of elements divided by the number of buckets, reflects the severity of hash collisions and is often used as a condition to trigger hash table expansion.
+- Separate chaining addresses hash collisions by converting each element into a linked list, storing all colliding elements in the same linked list. However, excessively long linked lists can reduce query efficiency, which can be improved by converting the linked lists into red-black trees.
+- Open addressing handles hash collisions through multiple probing. Linear probing uses a fixed step size but cannot delete elements and is prone to clustering. Double hashing uses multiple hash functions for probing, which reduces clustering compared to linear probing but increases computational overhead.
+- Different programming languages adopt various hash table implementations. For example, Java's `HashMap` uses separate 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.
+- Hash algorithms typically use large prime numbers as moduli to maximize the 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.
@@ -32,16 +32,16 @@ Firstly, hash tables have higher time efficiency but lower space efficiency. A s
Secondly, hash tables are only more time-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.
+Lastly, the time complexity of hash tables can degrade. For example, in separate 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?
+**Q**: Does double 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.
+Double 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.
+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 downward 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?
+**Q**: Why can expanding 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.
+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 expanding, 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 expansion, thereby mitigating hash collisions.
diff --git a/en/docs/chapter_heap/build_heap.md b/en/docs/chapter_heap/build_heap.md
index b66f15e83..fc9a8409c 100644
--- a/en/docs/chapter_heap/build_heap.md
+++ b/en/docs/chapter_heap/build_heap.md
@@ -1,27 +1,27 @@
# Heap construction operation
-In some cases, we want to build a heap using all elements of a list, and this process is known as "heap construction operation."
+In some cases, we want to build a heap using all elements of a list, and this process is called "heap construction operation."
-## Implementing with heap insertion operation
+## Implementing with element insertion
-First, we create an empty heap and then iterate through the list, performing the "heap insertion operation" on each element in turn. This means adding the element to the end of the heap and then "heapifying" it from bottom to top.
+We first create an empty heap, then iterate through the list, performing the "element insertion operation" on each element in sequence. This means adding the element to the bottom of the heap and then performing "bottom-to-top" heapify on that element.
-Each time an element is added to the heap, the length of the heap increases by one. Since nodes are added to the binary tree from top to bottom, the heap is constructed "from top to bottom."
+Each time an element is inserted into the heap, the heap's length increases by one. Since nodes are added to the binary tree sequentially from top to bottom, the heap is constructed "from top to bottom."
-Let the number of elements be $n$, and each element's insertion operation takes $O(\log{n})$ time, thus the time complexity of this heap construction method is $O(n \log n)$.
+Given $n$ elements, each element's insertion operation takes $O(\log{n})$ time, so the time complexity of this heap construction method is $O(n \log n)$.
-## Implementing by heapifying through traversal
+## Implementing through heapify traversal
-In fact, we can implement a more efficient method of heap construction in two steps.
+In fact, we can implement a more efficient heap construction method in two steps.
-1. Add all elements of the list as they are into the heap, at this point the properties of the heap are not yet satisfied.
-2. Traverse the heap in reverse order (reverse of level-order traversal), and perform "top to bottom heapify" on each non-leaf node.
+1. Add all elements of the list as-is to the heap, at which point the heap property is not yet satisfied.
+2. Traverse the heap in reverse order (reverse of level-order traversal), performing "top-to-bottom heapify" on each non-leaf node in sequence.
-**After heapifying a node, the subtree with that node as the root becomes a valid sub-heap**. Since the traversal is in reverse order, the heap is built "from bottom to top."
+**After heapifying a node, the subtree rooted at that node becomes a valid sub-heap**. Since we traverse in reverse order, the heap is constructed "from bottom to top."
-The reason for choosing reverse traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective.
+The reason for choosing reverse order traversal is that it ensures the subtree below the current node is already a valid sub-heap, making the heapification of the current node effective.
-It's worth mentioning that **since leaf nodes have no children, they naturally form valid sub-heaps and do not need to be heapified**. As shown in the following code, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification:
+It's worth noting that **since leaf nodes have no children, they are naturally valid sub-heaps and do not require heapification**. As shown in the code below, the last non-leaf node is the parent of the last node; we start from it and traverse in reverse order to perform heapification:
```src
[file]{my_heap}-[class]{max_heap}-[func]{__init__}
@@ -29,39 +29,39 @@ It's worth mentioning that **since leaf nodes have no children, they naturally f
## Complexity analysis
-Next, let's attempt to calculate the time complexity of this second method of heap construction.
+Next, let's attempt to derive the time complexity of this second heap construction method.
-- Assuming the number of nodes in the complete binary tree is $n$, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is integer division. Therefore, the number of nodes that need to be heapified is $(n - 1) / 2$.
-- In the process of "top to bottom heapification," each node is heapified to the leaf nodes at most, so the maximum number of iterations is the height of the binary tree $\log n$.
+- Assuming the complete binary tree has $n$ nodes, then the number of leaf nodes is $(n + 1) / 2$, where $/$ is floor division. Therefore, the number of nodes that need heapification is $(n - 1) / 2$.
+- In the top-to-bottom heapify process, each node is heapified at most to the leaf nodes, so the maximum number of iterations is the binary tree height $\log n$.
-Multiplying the two, we get the time complexity of the heap construction process as $O(n \log n)$. **But this estimate is not accurate, because it does not take into account the nature of the binary tree having far more nodes at the lower levels than at the top.**
+Multiplying these two together, we get a time complexity of $O(n \log n)$ for the heap construction process. **However, this estimate is not accurate because it doesn't account for the property that binary trees have far more nodes at lower levels than at upper levels**.
-Let's perform a more accurate calculation. To simplify the calculation, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result.
+Let's perform a more accurate calculation. To reduce calculation difficulty, assume a "perfect binary tree" with $n$ nodes and height $h$; this assumption does not affect the correctness of the result.
-
+
-As shown in the figure above, the maximum number of iterations for a node "to be heapified from top to bottom" is equal to the distance from that node to the leaf nodes, which is precisely "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level, **to get the total number of heapification iterations for all nodes**.
+As shown in the figure above, the maximum number of iterations for a node's "top-to-bottom heapify" equals the distance from that node to the leaf nodes, which is precisely the "node height." Therefore, we can sum the "number of nodes $\times$ node height" at each level to **obtain the total number of heapify iterations for all nodes**.
$$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1
$$
-To simplify the above equation, we need to use knowledge of sequences from high school, first multiply $T(h)$ by $2$, to get:
+To simplify the above expression, we need to use sequence knowledge from high school. First, multiply $T(h)$ by $2$ to get:
$$
\begin{aligned}
T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline
-2T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^h\times1 \newline
+2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline
\end{aligned}
$$
-By subtracting $T(h)$ from $2T(h)$ using the method of displacement, we get:
+Using the method of differences, subtract the first equation $T(h)$ from the second equation $2 T(h)$ to get:
$$
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h
$$
-Observing the equation, $T(h)$ is an geometric series, which can be directly calculated using the sum formula, resulting in a time complexity of:
+Observing the above expression, we find that $T(h)$ is a geometric series, which can be calculated directly using the sum formula, yielding a time complexity of:
$$
\begin{aligned}
@@ -71,4 +71,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline
\end{aligned}
$$
-Further, a perfect binary tree with height $h$ has $n = 2^{h+1} - 1$ nodes, thus the complexity is $O(2^h) = O(n)$. This calculation shows that **the time complexity of inputting a list and constructing a heap is $O(n)$, which is very efficient**.
+Furthermore, a perfect binary tree with height $h$ has $n = 2^{h+1} - 1$ nodes, so the complexity is $O(2^h) = O(n)$. This derivation shows that **the time complexity of building a heap from an input list is $O(n)$, which is highly efficient**.
diff --git a/en/docs/chapter_heap/heap.md b/en/docs/chapter_heap/heap.md
index 5424fa764..0e0f130a5 100644
--- a/en/docs/chapter_heap/heap.md
+++ b/en/docs/chapter_heap/heap.md
@@ -7,33 +7,33 @@ A heap is a complete binary tree that satisfies specific conditions and c

-As a special case of a complete binary tree, a heap has the following characteristics:
+As a special case of a complete binary tree, heaps have the following characteristics.
- The bottom layer nodes are filled from left to right, and nodes in other layers are fully filled.
-- The root node of the binary tree is called the "top" of the heap, and the bottom-rightmost node is called the "bottom" of the heap.
-- For max heaps (min heaps), the value of the top element (root) is the largest (smallest) among all elements.
+- We call the root node of the binary tree the "heap top" and the bottom-rightmost node the "heap bottom."
+- For max heaps (min heaps), the value of the heap top element (root node) is the largest (smallest).
## Common heap operations
It should be noted that many programming languages provide a priority queue, which is an abstract data structure defined as a queue with priority sorting.
-In practice, **heaps are often used to implement priority queues. A max heap corresponds to a priority queue where elements are dequeued in descending order**. From a usage perspective, we can consider "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two, uniformly referring to them as "heap."
+In fact, **heaps are typically used to implement priority queues, with max heaps corresponding to priority queues where elements are dequeued in descending order**. From a usage perspective, we can regard "priority queue" and "heap" as equivalent data structures. Therefore, this book does not make a special distinction between the two and uniformly refers to them as "heap."
-Common operations on heaps are shown in the table below, and the method names may vary based on the programming language.
+Common heap operations are shown in the table below, and method names need to be determined based on the programming language.
Table Efficiency of Heap Operations
-| Method name | Description | Time complexity |
-| ----------- | ------------------------------------------------------------ | --------------- |
-| `push()` | Add an element to the heap | $O(\log n)$ |
-| `pop()` | Remove the top element from the heap | $O(\log n)$ |
-| `peek()` | Access the top element (for max/min heap, the max/min value) | $O(1)$ |
-| `size()` | Get the number of elements in the heap | $O(1)$ |
-| `isEmpty()` | Check if the heap is empty | $O(1)$ |
+| Method name | Description | Time complexity |
+| ----------- | ----------------------------------------------------------------- | --------------- |
+| `push()` | Insert an element into the heap | $O(\log n)$ |
+| `pop()` | Remove the heap top element | $O(\log n)$ |
+| `peek()` | Access the heap top element (max/min value for max/min heap) | $O(1)$ |
+| `size()` | Get the number of elements in the heap | $O(1)$ |
+| `isEmpty()` | Check if the heap is empty | $O(1)$ |
-In practice, we can directly use the heap class (or priority queue class) provided by programming languages.
+In practical applications, we can directly use the heap class (or priority queue class) provided by programming languages.
-Similar to sorting algorithms where we have "ascending order" and "descending order", we can switch between "min heap" and "max heap" by setting a `flag` or modifying the `Comparator`. The code is as follows:
+Similar to "ascending order" and "descending order" in sorting algorithms, we can implement conversion between "min heap" and "max heap" by setting a `flag` or modifying the `Comparator`. The code is as follows:
=== "Python"
@@ -44,8 +44,8 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
max_heap, flag = [], -1
# Python's heapq module implements a min heap by default
- # By negating the elements before pushing them to the heap, we invert the order and thus implement a max heap
- # In this example, flag = 1 corresponds to a min heap, while flag = -1 corresponds to a max heap
+ # Consider negating elements before pushing them to the heap, which inverts the size relationship and thus implements a max heap
+ # In this example, flag = 1 corresponds to a min heap, flag = -1 corresponds to a max heap
# Push elements into the heap
heapq.heappush(max_heap, flag * 1)
@@ -54,24 +54,24 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
heapq.heappush(max_heap, flag * 5)
heapq.heappush(max_heap, flag * 4)
- # Retrieve the top element of the heap
+ # Get the heap top element
peek: int = flag * max_heap[0] # 5
- # Pop the top element of the heap
- # The popped elements will form a sequence in descending order
+ # Remove the heap top element
+ # The removed elements will form a descending sequence
val = flag * heapq.heappop(max_heap) # 5
val = flag * heapq.heappop(max_heap) # 4
val = flag * heapq.heappop(max_heap) # 3
val = flag * heapq.heappop(max_heap) # 2
val = flag * heapq.heappop(max_heap) # 1
- # Get the size of the heap
+ # Get the heap size
size: int = len(max_heap)
# Check if the heap is empty
is_empty: bool = not max_heap
- # Create a heap from a list
+ # Build a heap from an input list
min_heap: list[int] = [1, 3, 2, 5, 4]
heapq.heapify(min_heap)
```
@@ -92,24 +92,24 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
maxHeap.push(5);
maxHeap.push(4);
- /* Retrieve the top element of the heap */
+ /* Get the heap top element */
int peek = maxHeap.top(); // 5
- /* Pop the top element of the heap */
- // The popped elements will form a sequence in descending order
+ /* Remove the heap top element */
+ // The removed elements will form a descending sequence
maxHeap.pop(); // 5
maxHeap.pop(); // 4
maxHeap.pop(); // 3
maxHeap.pop(); // 2
maxHeap.pop(); // 1
- /* Get the size of the heap */
+ /* Get the heap size */
int size = maxHeap.size();
/* Check if the heap is empty */
bool isEmpty = maxHeap.empty();
- /* Create a heap from a list */
+ /* Build a heap from an input list */
vector input{1, 3, 2, 5, 4};
priority_queue, greater> minHeap(input.begin(), input.end());
```
@@ -120,34 +120,34 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
/* Initialize a heap */
// Initialize a min heap
Queue minHeap = new PriorityQueue<>();
- // Initialize a max heap (Simply modify the Comparator using a lambda expression)
+ // Initialize a max heap (use lambda expression to modify Comparator)
Queue maxHeap = new PriorityQueue<>((a, b) -> b - a);
-
+
/* Push elements into the heap */
maxHeap.offer(1);
maxHeap.offer(3);
maxHeap.offer(2);
maxHeap.offer(5);
maxHeap.offer(4);
-
- /* Retrieve the top element of the heap */
+
+ /* Get the heap top element */
int peek = maxHeap.peek(); // 5
-
- /* Pop the top element of the heap */
- // The popped elements will form a sequence in descending order
+
+ /* Remove the heap top element */
+ // The removed elements will form a descending sequence
peek = maxHeap.poll(); // 5
peek = maxHeap.poll(); // 4
peek = maxHeap.poll(); // 3
peek = maxHeap.poll(); // 2
peek = maxHeap.poll(); // 1
-
- /* Get the size of the heap */
+
+ /* Get the heap size */
int size = maxHeap.size();
-
+
/* Check if the heap is empty */
boolean isEmpty = maxHeap.isEmpty();
-
- /* Create a heap from a list */
+
+ /* Build a heap from an input list */
minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
```
@@ -157,8 +157,8 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
/* Initialize a heap */
// Initialize a min heap
PriorityQueue minHeap = new();
- // Initialize a max heap (Simply modify the Comparator using a lambda expression)
- PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x));
+ // Initialize a max heap (use lambda expression to modify Comparer)
+ PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x)));
/* Push elements into the heap */
maxHeap.Enqueue(1, 1);
@@ -167,24 +167,24 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
maxHeap.Enqueue(5, 5);
maxHeap.Enqueue(4, 4);
- /* Retrieve the top element of the heap */
+ /* Get the heap top element */
int peek = maxHeap.Peek();//5
- /* Pop the top element of the heap */
- // The popped elements will form a sequence in descending order
+ /* Remove the heap top element */
+ // The removed elements will form a descending sequence
peek = maxHeap.Dequeue(); // 5
peek = maxHeap.Dequeue(); // 4
peek = maxHeap.Dequeue(); // 3
peek = maxHeap.Dequeue(); // 2
peek = maxHeap.Dequeue(); // 1
- /* Get the size of the heap */
+ /* Get the heap size */
int size = maxHeap.Count;
/* Check if the heap is empty */
bool isEmpty = maxHeap.Count == 0;
- /* Create a heap from a list */
+ /* Build a heap from an input list */
minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);
```
@@ -192,41 +192,41 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
```go title="heap.go"
// In Go, we can construct a max heap of integers by implementing heap.Interface
- // Note that implementing heap.Interface requires also implementing sort.Interface
+ // Implementing heap.Interface also requires implementing sort.Interface
type intHeap []any
- // Push method of heap.Interface, which pushes an element into the heap
+ // Push implements the heap.Interface method for pushing an element into the heap
func (h *intHeap) Push(x any) {
- // Both Push and Pop use a pointer receiver
- // because they not only adjust the elements of the slice but also change its length
+ // Push and Pop use pointer receiver as parameters
+ // because they not only adjust the slice contents but also modify the slice length
*h = append(*h, x.(int))
}
- // Pop method of heap.Interface, which removes the top element of the heap
+ // Pop implements the heap.Interface method for popping the heap top element
func (h *intHeap) Pop() any {
- // The element to pop from the heap is stored at the end
+ // The element to be removed is stored at the end
last := (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
return last
}
- // Len method of sort.Interface
+ // Len is a sort.Interface method
func (h *intHeap) Len() int {
return len(*h)
}
- // Less method of sort.Interface
+ // Less is a sort.Interface method
func (h *intHeap) Less(i, j int) bool {
- // If you want to implement a min heap, you would change this to a less-than comparison
+ // To implement a min heap, change this to a less-than sign
return (*h)[i].(int) > (*h)[j].(int)
}
- // Swap method of sort.Interface
+ // Swap is a sort.Interface method
func (h *intHeap) Swap(i, j int) {
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}
- // Top Retrieve the top element of the heap
+ // Top gets the heap top element
func (h *intHeap) Top() any {
return (*h)[0]
}
@@ -238,28 +238,28 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
maxHeap := &intHeap{}
heap.Init(maxHeap)
/* Push elements into the heap */
- // Call the methods of heap.Interface to add elements
+ // Call heap.Interface methods to add elements
heap.Push(maxHeap, 1)
heap.Push(maxHeap, 3)
heap.Push(maxHeap, 2)
heap.Push(maxHeap, 4)
heap.Push(maxHeap, 5)
- /* Retrieve the top element of the heap */
+ /* Get the heap top element */
top := maxHeap.Top()
- fmt.Printf("The top element of the heap is %d\n", top)
+ fmt.Printf("Heap top element is %d\n", top)
- /* Pop the top element of the heap */
- // Call the methods of heap.Interface to remove elements
+ /* Remove the heap top element */
+ // Call heap.Interface methods to remove elements
heap.Pop(maxHeap) // 5
heap.Pop(maxHeap) // 4
heap.Pop(maxHeap) // 3
heap.Pop(maxHeap) // 2
heap.Pop(maxHeap) // 1
- /* Get the size of the heap */
+ /* Get the heap size */
size := len(*maxHeap)
- fmt.Printf("The number of elements in the heap is %d\n", size)
+ fmt.Printf("Number of heap elements is %d\n", size)
/* Check if the heap is empty */
isEmpty := len(*maxHeap) == 0
@@ -271,7 +271,7 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
```swift title="heap.swift"
/* Initialize a heap */
- // Swift’s Heap type supports both max heaps and min heaps, and need the swift-collections library
+ // Swift's Heap type supports both max heaps and min heaps, and requires importing swift-collections
var heap = Heap()
/* Push elements into the heap */
@@ -281,23 +281,23 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
heap.insert(5)
heap.insert(4)
- /* Retrieve the top element of the heap */
+ /* Get the heap top element */
var peek = heap.max()!
- /* Pop the top element of the heap */
+ /* Remove the heap top element */
peek = heap.removeMax() // 5
peek = heap.removeMax() // 4
peek = heap.removeMax() // 3
peek = heap.removeMax() // 2
peek = heap.removeMax() // 1
- /* Get the size of the heap */
+ /* Get the heap size */
let size = heap.count
/* Check if the heap is empty */
let isEmpty = heap.isEmpty
- /* Create a heap from a list */
+ /* Build a heap from an input list */
let heap2 = Heap([1, 3, 2, 5, 4])
```
@@ -337,25 +337,25 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
max_heap.push(2);
max_heap.push(5);
max_heap.push(4);
-
- /* Retrieve the top element of the heap */
+
+ /* Get the heap top element */
let peek = max_heap.peek().unwrap(); // 5
- /* Pop the top element of the heap */
- // The popped elements will form a sequence in descending order
+ /* Remove the heap top element */
+ // The removed elements will form a descending sequence
let peek = max_heap.pop().unwrap(); // 5
let peek = max_heap.pop().unwrap(); // 4
let peek = max_heap.pop().unwrap(); // 3
let peek = max_heap.pop().unwrap(); // 2
let peek = max_heap.pop().unwrap(); // 1
- /* Get the size of the heap */
+ /* Get the heap size */
let size = max_heap.len();
/* Check if the heap is empty */
let is_empty = max_heap.is_empty();
- /* Create a heap from a list */
+ /* Build a heap from an input list */
let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);
```
@@ -371,41 +371,41 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
/* Initialize a heap */
// Initialize a min heap
var minHeap = PriorityQueue()
- // Initialize a max heap (Simply modify the Comparator using a lambda expression)
+ // Initialize a max heap (use lambda expression to modify Comparator)
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
-
+
/* Push elements into the heap */
maxHeap.offer(1)
maxHeap.offer(3)
maxHeap.offer(2)
maxHeap.offer(5)
maxHeap.offer(4)
-
- /* Retrieve the top element of the heap */
+
+ /* Get the heap top element */
var peek = maxHeap.peek() // 5
-
- /* Pop the top element of the heap */
- // The popped elements will form a sequence in descending order
+
+ /* Remove the heap top element */
+ // The removed elements will form a descending sequence
peek = maxHeap.poll() // 5
peek = maxHeap.poll() // 4
peek = maxHeap.poll() // 3
peek = maxHeap.poll() // 2
peek = maxHeap.poll() // 1
-
- /* Get the size of the heap */
+
+ /* Get the heap size */
val size = maxHeap.size
-
+
/* Check if the heap is empty */
val isEmpty = maxHeap.isEmpty()
-
- /* Create a heap from a list */
+
+ /* Build a heap from an input list */
minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))
```
=== "Ruby"
```ruby title="heap.rb"
-
+ # Ruby does not provide a built-in Heap class
```
=== "Zig"
@@ -414,33 +414,33 @@ Similar to sorting algorithms where we have "ascending order" and "descending or
```
-??? pythontutor "Code visualization"
+??? pythontutor "Code Visualization"
https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## Implementation of the heap
-The following implementation is of a max heap. To convert it into a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement it on their own.
+The following implementation is of a max heap. To convert it to a min heap, simply invert all size logic comparisons (for example, replace $\geq$ with $\leq$). Interested readers are encouraged to implement this on their own.
### Heap storage and representation
-As mentioned in the "Binary Trees" section, complete binary trees are highly suitable for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**.
+As mentioned in the "Binary Tree" chapter, complete binary trees are well-suited for array representation. Since heaps are a type of complete binary tree, **we will use arrays to store heaps**.
-When using an array to represent a binary tree, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through an index mapping formula**.
+When representing a binary tree with an array, elements represent node values, and indexes represent node positions in the binary tree. **Node pointers are implemented through index mapping formulas**.
-As shown in the figure below, given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When the index is out of bounds, it signifies a null node or the node does not exist.
+As shown in the figure below, given an index $i$, the index of its left child is $2i + 1$, the index of its right child is $2i + 2$, and the index of its parent is $(i - 1) / 2$ (floor division). When an index is out of bounds, it indicates a null node or that the node does not exist.

-We can encapsulate the index mapping formula into functions for convenient later use:
+We can encapsulate the index mapping formula into functions for convenient subsequent use:
```src
[file]{my_heap}-[class]{max_heap}-[func]{parent}
```
-### Accessing the top element of the heap
+### Accessing the heap top element
-The top element of the heap is the root node of the binary tree, which is also the first element of the list:
+The heap top element is the root node of the binary tree, which is also the first element of the list:
```src
[file]{my_heap}-[class]{max_heap}-[func]{peek}
@@ -448,12 +448,12 @@ The top element of the heap is the root node of the binary tree, which is also t
### Inserting an element into the heap
-Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's integrity might be compromised, **thus it's necessary to repair the path from the inserted node to the root node**. This operation is called heapify.
+Given an element `val`, we first add it to the bottom of the heap. After addition, since `val` may be larger than other elements in the heap, the heap's property may be violated. **Therefore, it's necessary to repair the path from the inserted node to the root node**. This operation is called heapify.
-Considering starting from the node inserted, **perform heapify from bottom to top**. As shown in the figure below, we compare the value of the inserted node with its parent node, and if the inserted node is larger, we swap them. Then continue this operation, repairing each node in the heap from bottom to top until reaching the root or a node that does not need swapping.
+Starting from the inserted node, **perform heapify from bottom to top**. As shown in the figure below, we compare the inserted node with its parent node, and if the inserted node is larger, swap them. Then continue this operation, repairing nodes in the heap from bottom to top until we pass the root node or encounter a node that does not need swapping.
=== "<1>"
- 
+ 
=== "<2>"

@@ -479,24 +479,24 @@ Considering starting from the node inserted, **perform heapify from bottom to to
=== "<9>"

-Given a total of $n$ nodes, the height of the tree is $O(\log n)$. Hence, the loop iterations for the heapify operation are at most $O(\log n)$, **making the time complexity of the element insertion operation $O(\log n)$**. The code is as shown:
+Given a total of $n$ nodes, the tree height is $O(\log n)$. Thus, the number of loop iterations in the heapify operation is at most $O(\log n)$, **making the time complexity of the element insertion operation $O(\log n)$**. The code is as follows:
```src
[file]{my_heap}-[class]{max_heap}-[func]{sift_up}
```
-### Removing the top element from the heap
+### Removing the heap top element
-The top element of the heap is the root node of the binary tree, that is, the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree will change, making it difficult to use heapify for subsequent repairs. To minimize changes in element indexes, we use the following steps.
+The heap top element is the root node of the binary tree, which is the first element of the list. If we directly remove the first element from the list, all node indexes in the binary tree would change, making subsequent repair with heapify difficult. To minimize changes in element indexes, we use the following steps.
-1. Swap the top element with the bottom element of the heap (swap the root node with the rightmost leaf node).
-2. After swapping, remove the bottom of the heap from the list (note that since it has been swapped, the original top element is actually being removed).
+1. Swap the heap top element with the heap bottom element (swap the root node with the rightmost leaf node).
+2. After swapping, remove the heap bottom from the list (note that since we've swapped, we're actually removing the original heap top element).
3. Starting from the root node, **perform heapify from top to bottom**.
-As shown in the figure below, **the direction of "heapify from top to bottom" is opposite to "heapify from bottom to top"**. We compare the value of the root node with its two children and swap it with the largest child. Then, repeat this operation until reaching the leaf node or encountering a node that does not need swapping.
+As shown in the figure below, **the direction of "top-to-bottom heapify" is opposite to "bottom-to-top heapify"**. We compare the root node's value with its two children and swap it with the largest child. Then loop this operation until we pass a leaf node or encounter a node that doesn't need swapping.
=== "<1>"
- 
+ 
=== "<2>"

@@ -525,7 +525,7 @@ As shown in the figure below, **the direction of "heapify from top to bottom" is
=== "<10>"

-Similar to the element insertion operation, the time complexity of the top element removal operation is also $O(\log n)$. The code is as follows:
+Similar to the element insertion operation, the time complexity of the heap top element removal operation is also $O(\log n)$. The code is as follows:
```src
[file]{my_heap}-[class]{max_heap}-[func]{sift_down}
@@ -533,6 +533,6 @@ Similar to the element insertion operation, the time complexity of the top eleme
## Common applications of heaps
-- **Priority Queue**: Heaps are often the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and building a queue having a time complexity of $O(n)$, all of which are very efficient.
-- **Heap Sort**: Given a set of data, we can create a heap from them and then continually perform element removal operations to obtain ordered data. However, there is a more elegant way to implement heap sort, as explained in the "Heap Sort" chapter.
-- **Finding the Largest $k$ Elements**: This is a classic algorithm problem and also a common use case, such as selecting the top 10 hot news for Weibo hot search, picking the top 10 selling products, etc.
+- **Priority queue**: Heaps are typically the preferred data structure for implementing priority queues, with both enqueue and dequeue operations having a time complexity of $O(\log n)$, and the heap construction operation having $O(n)$, all of which are highly efficient.
+- **Heap sort**: Given a set of data, we can build a heap with them and then continuously perform element removal operations to obtain sorted data. However, we usually use a more elegant approach to implement heap sort, as detailed in the "Heap Sort" chapter.
+- **Getting the largest $k$ elements**: This is a classic algorithm problem and also a typical application, such as selecting the top 10 trending news for Weibo hot search, selecting the top 10 best-selling products, etc.
diff --git a/en/docs/chapter_heap/index.md b/en/docs/chapter_heap/index.md
index 30f2e3e10..405cfc4a1 100644
--- a/en/docs/chapter_heap/index.md
+++ b/en/docs/chapter_heap/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Heaps resemble mountains and their jagged peaks, layered and undulating, each with its unique form.
+ Heaps are like mountain peaks, layered and undulating, each with its unique form.
- Each mountain peak rises and falls in scattered heights, yet the tallest always captures attention first.
+ The peaks rise and fall at varying heights, yet the tallest peak always catches the eye first.
diff --git a/en/docs/chapter_heap/summary.md b/en/docs/chapter_heap/summary.md
index 83b126f5f..6c65ea733 100644
--- a/en/docs/chapter_heap/summary.md
+++ b/en/docs/chapter_heap/summary.md
@@ -2,16 +2,16 @@
### Key review
-- A heap is a complete binary tree that can be categorized as either a max heap or a min heap based on its building property, where the top element of a max heap is the largest and the top element of a min heap is the smallest.
-- A priority queue is defined as a queue with dequeue priority, usually implemented using a heap.
-- Common operations of a heap and their corresponding time complexities include: element insertion into the heap $O(\log n)$, removing the top element from the heap $O(\log n)$, and accessing the top element of the heap $O(1)$.
-- A complete binary tree is well-suited to be represented by an array, thus heaps are commonly stored using arrays.
-- Heapify operations are used to maintain the properties of the heap and are used in both heap insertion and removal operations.
-- The time complexity of building a heap given an input of $n$ elements can be optimized to $O(n)$, which is highly efficient.
+- A heap is a complete binary tree that can be categorized as a max heap or min heap based on its property. The heap top element of a max heap (min heap) is the largest (smallest).
+- A priority queue is defined as a queue with priority sorting, typically implemented using heaps.
+- Common heap operations and their corresponding time complexities include: element insertion $O(\log n)$, heap top element removal $O(\log n)$, and accessing the heap top element $O(1)$.
+- Complete binary trees are well-suited for array representation, so we typically use arrays to store heaps.
+- Heapify operations are used to maintain the heap property and are employed in both element insertion and removal operations.
+- The time complexity of building a heap with $n$ input elements can be optimized to $O(n)$, which is highly efficient.
- Top-k is a classic algorithm problem that can be efficiently solved using the heap data structure, with a time complexity of $O(n \log k)$.
### Q & A
-**Q**: Is the "heap" in data structures the same concept as the "heap" in memory management?
+**Q**: Are the "heap" in data structures and the "heap" in memory management the same concept?
-The two are not the same concept, even though they are both referred to as "heap". The heap in computer system memory is part of dynamic memory allocation, where the program can use it to store data during execution. The program can request a certain amount of heap memory to store complex structures like objects and arrays. When the allocated data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, the management and usage of heap memory demands more caution, as improper use may lead to memory leaks and dangling pointers.
+The two are not the same concept; they just happen to share the name "heap." The heap in computer system memory is part of dynamic memory allocation, where programs can use it to store data during runtime. Programs can request a certain amount of heap memory to store complex structures such as objects and arrays. When this data is no longer needed, the program needs to release this memory to prevent memory leaks. Compared to stack memory, heap memory management and usage require more caution, as improper use can lead to issues such as memory leaks and dangling pointers.
diff --git a/en/docs/chapter_heap/top_k.md b/en/docs/chapter_heap/top_k.md
index f267c0320..746241fd5 100644
--- a/en/docs/chapter_heap/top_k.md
+++ b/en/docs/chapter_heap/top_k.md
@@ -4,39 +4,39 @@
Given an unordered array `nums` of length $n$, return the largest $k$ elements in the array.
-For this problem, we will first introduce two straightforward solutions, then explain a more efficient heap-based method.
+For this problem, we'll first introduce two solutions with relatively straightforward approaches, then introduce a more efficient heap-based solution.
## Method 1: Iterative selection
-We can perform $k$ rounds of iterations as shown in the figure below, extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$.
+We can perform $k$ rounds of traversal as shown in the figure below, extracting the $1^{st}$, $2^{nd}$, $\dots$, $k^{th}$ largest elements in each round, with a time complexity of $O(nk)$.
-This method is only suitable when $k \ll n$, as the time complexity approaches $O(n^2)$ when $k$ is close to $n$, which is very time-consuming.
+This method is only suitable when $k \ll n$, because when $k$ is close to $n$, the time complexity approaches $O(n^2)$, which is very time-consuming.
-
+
!!! tip
- When $k = n$, we can obtain a complete ordered sequence, which is equivalent to the "selection sort" algorithm.
+ When $k = n$, we can obtain a complete sorted sequence, which is equivalent to the "selection sort" algorithm.
## Method 2: Sorting
-As shown in the figure below, we can first sort the array `nums` and then return the last $k$ elements, with a time complexity of $O(n \log n)$.
+As shown in the figure below, we can first sort the array `nums`, then return the rightmost $k$ elements, with a time complexity of $O(n \log n)$.
-Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without the need to sort the other elements.
+Clearly, this method "overachieves" the task, as we only need to find the largest $k$ elements, without needing to sort the other elements.

## Method 3: Heap
-We can solve the Top-k problem more efficiently based on heaps, as shown in the following process.
+We can solve the Top-k problem more efficiently using heaps, with the process shown in the figure below.
-1. Initialize a min heap, where the top element is the smallest.
-2. First, insert the first $k$ elements of the array into the heap.
-3. Starting from the $k + 1^{th}$ element, if the current element is greater than the top element of the heap, remove the top element of the heap and insert the current element into the heap.
-4. After completing the traversal, the heap contains the largest $k$ elements.
+1. Initialize a min heap, where the heap top element is the smallest.
+2. First, insert the first $k$ elements of the array into the heap in sequence.
+3. Starting from the $(k + 1)^{th}$ element, if the current element is greater than the heap top element, remove the heap top element and insert the current element into the heap.
+4. After traversal is complete, the heap contains the largest $k$ elements.
=== "<1>"
- 
+ 
=== "<2>"

@@ -68,6 +68,6 @@ Example code is as follows:
[file]{top_k}-[class]{}-[func]{top_k_heap}
```
-A total of $n$ rounds of heap insertions and deletions are performed, with the maximum heap size being $k$, hence the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity tends towards $O(n)$; when $k$ is large, the time complexity will not exceed $O(n \log n)$.
+A total of $n$ rounds of heap insertions and removals are performed, with the heap's maximum length being $k$, so the time complexity is $O(n \log k)$. This method is very efficient; when $k$ is small, the time complexity approaches $O(n)$; when $k$ is large, the time complexity does not exceed $O(n \log n)$.
-Additionally, this method is suitable for scenarios with dynamic data streams. By continuously adding data, we can maintain the elements within the heap, thereby achieving dynamic updates of the largest $k$ elements.
+Additionally, this method is suitable for dynamic data stream scenarios. By continuously adding data, we can maintain the elements in the heap, thus achieving dynamic updates of the largest $k$ elements.
diff --git a/en/docs/chapter_hello_algo/index.md b/en/docs/chapter_hello_algo/index.md
index 6953f39a5..7d398ed55 100644
--- a/en/docs/chapter_hello_algo/index.md
+++ b/en/docs/chapter_hello_algo/index.md
@@ -3,28 +3,28 @@ comments: true
icon: material/rocket-launch-outline
---
-# Before starting
+# Before Starting
-A few years ago, I shared the "Sword for Offer" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most common question I encountered was "how to get started with algorithms." Gradually, I developed a keen interest in this question.
+A few years ago, I shared the "Sword for Offer" problem solutions on LeetCode, receiving encouragement and support from many readers. During interactions with readers, the most frequently asked question I encountered was "how to get started with algorithms." Gradually, I developed a keen interest in this question.
-Directly solving problems seems to be the most popular method — it's simple, direct, and effective. However, problem-solving is like playing Minesweeper: those with strong self-study skills can navigate the pitfalls one by one, while those lacking a solid foundation may find themselves repeatedly stumbling and retreating in frustration. Reading through textbooks is also a common practice, but for job seekers, writing graduation thesis, submitting resumes, preparing for written tests and interviews have already consumed most of their energy, and reading thick books often becomes a daunting challenge.
+Diving straight into problem-solving seems to be the most popular approach—it's simple, direct, and effective. However, problem-solving is like playing Minesweeper: those with strong self-learning abilities can successfully defuse the mines one by one, while those with insufficient foundations may end up bruised and battered, retreating step by step in frustration. Reading through textbooks is also a common practice, but for job seekers, graduation theses, resume submissions, and preparations for written tests and interviews have already consumed most of their energy, making working through thick books an arduous challenge.
-If you're facing similar troubles, then this book is lucky to have found you. This book is my answer to the question. While it may not be the best solution, it is at least a positive attempt. Although this book is not enough to get you an offer directly, it will guide you to explore the "knowledge map" of data structures and algorithms, help you understand the shapes, sizes, and locations of different "mines", and enable you to master various "mine clearance methods". With these skills, I believe you can solve problems and read literature more comfortably, gradually building a knowledge system.
+If you're facing similar struggles, then it's fortunate that this book has "found" you. This book is my answer to this question—even if it may not be the optimal solution, it is at least a positive attempt. While this book alone won't directly land you a job offer, it will guide you to explore the "knowledge map" of data structures and algorithms, help you understand the shapes, sizes, and distributions of different "mines," and enable you to master various "mine-clearing methods." With these skills, I believe you can tackle problems and read technical literature more confidently, gradually building a complete knowledge system.
-I deeply agree with Professor Feynman's statement: "Knowledge isn't free. You have to pay attention." In this sense, this book is not entirely "free." In order to live up to your precious "attention" for this book, I will do my best and devote my greatest "attention" to write this book.
+I deeply agree with Professor Feynman's words: "Knowledge isn't free. You have to pay attention." In this sense, this book is not entirely "free." In order to live up to the precious "attention" you invest in this book, I will do my utmost and devote my greatest "attention" to completing this work.
-Aware of my limitations, I recognize that despite the content of this book being refined over time, errors surely remain. I sincerely welcome critiques and corrections from both teachers and students.
+I'm acutely aware of my limited knowledge and shallow expertise. Although the content of this book has been refined over a period of time, there are certainly still many errors, and I sincerely welcome critiques and corrections from teachers and fellow students.
-{ class="cover-image" }
+{ class="cover-image" }
-
Hello, Algo!
+ Hello, Algorithms!
-The advent of computers has brought significant changes to the world. With their high-speed computing power and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics of video games, the intelligent decisions in autonomous driving, the brilliant Go games of AlphaGo, or the natural interactions of ChatGPT, these applications are all exquisite demonstrations of algorithms at work on computers.
+The advent of computers has brought tremendous changes to the world. With their high-speed computing capabilities and excellent programmability, they have become the ideal medium for executing algorithms and processing data. Whether it's the realistic graphics in video games, the intelligent decision-making in autonomous driving, AlphaGo's brilliant Go matches, or ChatGPT's natural interactions, these applications are all exquisite interpretations of algorithms on computers.
-In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms became more refined and complex. From the exquisite craftsmanship of artisans, to industrial products that liberate productive forces, to the scientific laws governing the universe, almost every ordinary or astonishing thing has behind it the ingenious thought of algorithms.
+In fact, before the advent of computers, algorithms and data structures already existed in every corner of the world. Early algorithms were relatively simple, such as ancient counting methods and tool-making procedures. As civilization progressed, algorithms gradually became more refined and complex. From the ingenious craftsmanship of master artisans, to industrial products that liberate productive forces, to the scientific laws governing the operation of the universe, behind almost every ordinary or astonishing thing lies ingenious algorithmic thinking.
-Similarly, data structures are everywhere: from social networks to subway lines, many systems can be modeled as "graphs"; from a country to a family, the main forms of social organization exhibit characteristics of "trees"; winter clothes are like a "stack", where the first item worn is the last to be taken off; a badminton shuttle tube resembles a "queue", with one end for insertion and the other for retrieval; a dictionary is like a "hash table", enabling quick search for target entries.
+Similarly, data structures are everywhere: from large-scale social networks to small subway systems, many systems can be modeled as "graphs"; from a nation to a family, the primary organizational forms of society exhibit characteristics of "trees"; winter clothing is like a "stack," where the first item put on is the last to be taken off; a badminton tube is like a "queue," with items inserted at one end and retrieved from the other; a dictionary is like a "hash table," enabling quick lookup of target entries.
-This book aims to help readers understand the core concepts of algorithms and data structures through clear, easy-to-understand animated illustrations and runnable code examples, and to be able to implement them through programming. On this basis, this book strives to reveal the vivid manifestations of algorithms in the complex world, showcasing the beauty of algorithms. I hope this book can help you!
+This book aims to help readers understand the core concepts of algorithms and data structures through clear and accessible animated illustrations and runnable code examples, and to implement them through programming. Building on this foundation, the book endeavors to reveal the vivid manifestations of algorithms in the complex world and showcase the beauty of algorithms. I hope this book can be of help to you!
diff --git a/en/docs/chapter_introduction/index.md b/en/docs/chapter_introduction/index.md
index 33c6c0daf..f6a58672a 100644
--- a/en/docs/chapter_introduction/index.md
+++ b/en/docs/chapter_introduction/index.md
@@ -1,9 +1,9 @@
-# Encounter with algorithms
+# Introduction to algorithms
-
+
!!! 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.
+ A young girl dances gracefully, intertwined with data, her skirt flowing with the melody of algorithms.
+
+ She invites you to dance with her. Follow her steps closely and enter the world of algorithms, full of logic and beauty.
diff --git a/en/docs/chapter_introduction/summary.md b/en/docs/chapter_introduction/summary.md
index 0b40864cb..65160bfea 100644
--- a/en/docs/chapter_introduction/summary.md
+++ b/en/docs/chapter_introduction/summary.md
@@ -1,22 +1,22 @@
# 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 step-by-step instructions for solving a specific problem within a finite time, while a data structure defines how 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 compare data structures and algorithms to assembling 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.
+- Algorithms are ubiquitous in daily life and are not distant, esoteric knowledge. In fact, we have already learned many algorithms unconsciously and use them to solve problems big and small in life.
+- The principle of looking up a dictionary is consistent with the binary search algorithm. Binary search embodies the important algorithmic idea of divide and conquer.
+- The process of organizing playing cards is very similar to the insertion sort algorithm. Insertion sort is suitable for sorting small datasets.
+- The steps of making change are essentially a greedy algorithm, where the best choice is made at each step based on the current situation.
+- An algorithm is a set of instructions or operational steps that solves a specific problem within a finite amount of time, while a data structure is the way computers organize and store data.
+- Data structures and algorithms are closely connected. Data structures are the foundation of algorithms, and algorithms breathe life into data structures.
+- We can compare data structures and algorithms to assembling building blocks. The blocks represent data, the shape and connection method of the blocks represent the data structure, and the steps to assemble the blocks correspond to the algorithm.
### Q & A
-**Q**:As a programmer, I’ve rarely needed to implement algorithms manually in my daily work. Most commonly used algorithms are already built into programming languages and libraries, ready to use. Does this suggest that the problems we encounter in our work haven’t yet reached the level of complexity that demands custom algorithm design?
+**Q**: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already encapsulated by programming languages and can be used directly. Does this mean that the problems in our work have not yet reached the level where algorithms are needed?
-If specific work skills are like the "moves" in martial arts, then fundamental subjects are more like "internal strength".
+If we compare specific work skills to "techniques" in martial arts, then fundamental subjects should be more like "internal skills".
-I believe the significance of learning algorithms (and other fundamental subjects) isn’t necessarily to implement them from scratch at work, but to enable more professional decision-making and problem-solving based on a solid understanding of the concepts. This, in turn, raises the overall quality of our work. For example, every programming language provides a built-in sorting function:
+I believe the significance of learning algorithms (and other fundamental subjects) is not to implement them from scratch at work, but rather to be able to make professional reactions and judgments when solving problems based on the knowledge learned, thereby improving the overall quality of work. Here is a simple example. Every programming language has a built-in sorting function:
-- If we have not learned data structures and algorithms, then given any data, we might just give it to this sorting function. It runs smoothly, has good performance, and seems to have no problems.
-- However, if we’ve studied algorithms, we understand that the time complexity of a built-in sorting function is typically $O(n \log n)$. Moreover, if the data consists of integers with a fixed number of digits (such as student IDs), we can apply a more efficient approach like radix sort, reducing the time complexity to O(nk) , where k is the number of digits. When handling large volumes of data, the time saved can turn into significant value — lowering costs, improving user experience, and enhancing system performance.
+- If we have not studied data structures and algorithms, we might simply feed any given data to this sorting function. It runs smoothly with good performance, and there doesn't seem to be any problem.
+- But if we have studied algorithms, we would know that the time complexity of the built-in sorting function is $O(n \log n)$. However, if the given data consists of integers with a fixed number of digits (such as student IDs), we can use the more efficient "radix sort", reducing the time complexity to $O(nk)$, where $k$ is the number of digits. When the data volume is very large, the saved running time can create significant value (reduced costs, improved experience, etc.).
-In engineering, many problems are difficult to solve optimally; most are addressed with ‘near-optimal’ solutions. The difficulty of a problem depends not only on its inherent complexity but also on the knowledge and experience of the person tackling it. The deeper one’s expertise and experience, the more thorough the analysis, and the more elegantly the problem can be solved.
+In the field of engineering, a large number of problems are difficult to reach optimal solutions, and many problems are only solved "approximately". The difficulty of a problem depends on one hand on the nature of the problem itself, and on the other hand on the knowledge reserve of the person observing the problem. The more complete a person's knowledge and the more experience they have, the deeper their analysis of the problem will be, and the more elegantly the problem can be solved.
diff --git a/en/docs/chapter_introduction/what_is_dsa.md b/en/docs/chapter_introduction/what_is_dsa.md
index 264e93e13..7af5f3ad6 100644
--- a/en/docs/chapter_introduction/what_is_dsa.md
+++ b/en/docs/chapter_introduction/what_is_dsa.md
@@ -1,53 +1,53 @@
# What is an algorithm
-## Definition of an algorithm
+## Algorithm definition
-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:
+An algorithm is a set of instructions or operational steps that solves 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.
+- The problem is well-defined, with clear input and output definitions.
+- It is feasible and can be completed within a finite number of steps, time, and memory space.
+- Each step has a definite meaning, and under the same input and operating conditions, the output is always the same.
-## Definition of a data structure
+## Data structure definition
-A data structure is a way of organizing and storing data in a computer, with the following design goals:
+A data structure is a way of organizing and storing data, covering the data content, relationships between data, and methods for data operations. It has the following design objectives.
-- 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.
+- Occupy as little space as possible to save computer memory.
+- Data operations should be as fast as possible, covering data access, addition, deletion, update, etc.
+- Provide a concise data representation and logical information so that algorithms can run efficiently.
-**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:
+**Data structure design is a process full of trade-offs**. If we want to achieve improvements in one aspect, we often need to make compromises in another aspect. Here are two examples.
-- Compared to arrays, linked lists offer more convenience in data addition and deletion but sacrifice data access speed.
-- Compared with linked lists, graphs provide richer logical information but require more memory space.
+- Compared to arrays, linked lists are more convenient for data addition and deletion operations but sacrifice data access speed.
+- Compared to linked lists, graphs provide richer logical information but require larger memory space.
-## Relationship between data structures and algorithms
+## The relationship between data structures and algorithms
-As shown in the figure below, data structures and algorithms are highly related and closely integrated, specifically in the following three aspects:
+As shown in the figure below, data structures and algorithms are highly related and tightly coupled, specifically manifested 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 inject vitality into data structures. 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.
+- Data structures are the foundation of algorithms. Data structures provide algorithms with structured storage of data and methods for operating on data.
+- Algorithms breathe life into data structures. Data structures themselves only store data information; combined with algorithms, they can solve specific problems.
+- Algorithms can usually be implemented based on different data structures, but execution efficiency may vary greatly. Choosing the appropriate data structure is key.
-
+
-Data structures and algorithms can be likened to a set of building blocks, as illustrated in the figure below. 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.
+Data structures and algorithms are like assembling building blocks as shown in the figure below. A set of building blocks, in addition to containing many parts, also comes with detailed assembly instructions. By following the instructions step by step, we can assemble an exquisite building block model.

The detailed correspondence between the two is shown in the table below.
- Table Comparing data structures and algorithms to building blocks
+ Table Comparing data structures and algorithms to assembling building blocks
-| 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 |
+| Data structures and algorithms | Assembling building blocks |
+| ------------------------------ | ------------------------------------------------------------------ |
+| Input data | Unassembled building blocks |
+| Data structure | Organization form of building blocks, including shape, size, connection method, etc. |
+| Algorithm | A series of operational steps to assemble the blocks into the target form |
+| Output data | Building block model |
-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.
+It is worth noting that data structures and algorithms are independent of programming languages. For this reason, this book is able to provide implementations based on multiple programming languages.
-!!! tip "Conventional Abbreviation"
+!!! 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 questions actually test knowledge of both data structures and algorithms.
+ In actual discussions, we usually abbreviate "data structures and algorithms" as "algorithms". For example, the well-known LeetCode algorithm problems actually examine knowledge of both data structures and algorithms.
diff --git a/en/docs/chapter_preface/about_the_book.md b/en/docs/chapter_preface/about_the_book.md
index 010bfe34b..5e98f36fe 100644
--- a/en/docs/chapter_preface/about_the_book.md
+++ b/en/docs/chapter_preface/about_the_book.md
@@ -1,52 +1,52 @@
# About this book
-This open-source project aims to create a free, and beginner-friendly crash course on data structures and algorithms.
+This project aims to create an open-source, free, beginner-friendly introductory tutorial on data structures and algorithms.
-- Animated illustrations, easy-to-understand content, and a smooth learning curve help beginners explore the "knowledge map" of data structures and algorithms.
-- Run code with just one click, helping readers improve their programming skills and understand the working principle of algorithms and the underlying implementation of data structures.
-- Promoting learning by teaching, feel free to ask questions and share insights. Let's grow together through discussion.
+- The entire book uses animated illustrations, with clear and easy-to-understand content and a smooth learning curve, guiding beginners to explore the knowledge map of data structures and algorithms.
+- The source code can be run with one click, helping readers improve their programming skills through practice and understand how algorithms work and the underlying implementation of data structures.
+- We encourage readers to learn from each other, and everyone is welcome to ask questions and share insights in the comments section, making progress together through discussion and exchange.
## 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 are an algorithm beginner who has never been exposed to algorithms, or if you already have some problem-solving experience and have a vague understanding of data structures and algorithms, oscillating between knowing and not knowing, then this book is tailor-made 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 have already accumulated a certain amount of problem-solving experience and are familiar with most question types, this book can help you review and organize your algorithm knowledge system, and the repository's source code can be used as a "problem-solving toolkit" or "algorithm dictionary."
-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/).
+If you are an algorithm "expert," we look forward to receiving your valuable suggestions, or [participating in creation together](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.
+ You need to have at least a programming foundation in any language, and be able to read and write simple code.
## Content structure
-The main content of the book is shown in the figure below.
+The main content of this book is shown in the figure below.
-- **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.
+- **Complexity analysis**: Evaluation dimensions and methods for data structures and algorithms. Methods for calculating time complexity and space complexity, common types, examples, etc.
+- **Data structures**: Classification methods for basic data types and data structures. The definition, advantages and disadvantages, common operations, common types, typical applications, implementation methods, etc. of data structures such as arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs.
+- **Algorithms**: The definition, advantages and disadvantages, efficiency, application scenarios, problem-solving steps, and example problems of algorithms such as searching, sorting, divide and conquer, backtracking, dynamic programming, and greedy algorithms.
-
+
## 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, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, msk397, gvenusleo, khoaxuantu, RiverTwilight, rongyi, gyt95, zhuoqinyue, K3v123, Zuoxun, mingXta, hello-ikun, FangYuan33, GN-Yu, yuelinxin, longsizhuo, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, QiLOL, pengchzn, Guanngxu, L-Super, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, longranger2, cy-by-side, xiongsp, JeffersonHuang, Transmigration-zhou, magentaqin, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, Shyam-Chen, nanlei, hongyun-robot, Phoenix0415, MolDuM, Nigh, he-weilai, junminhong, mgisr, iron-irax, yd-j, XiaChuerwu, XC-Zero, seven1240, SamJin98, wodray, reeswell, NI-SW, Horbin-Magician, Enlightenus, xjr7670, YangXuanyi, DullSword, boloboloda, iStig, qq909244296, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, liuxjerry, lucaswangdev, lyl625760, hts0000, gledfish, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, luluxia, xb534, bitsmi, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, steventimes, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, ZhongGuanbin, shanghai-Jerry, JackYang-hellobobo, Javesun99, lipusheng, BlindTerran, ShiMaRing, FreddieLi, FloranceYeh, iFleey, fanchenggang, gltianwen, goerll, Dr-XYZ, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, fanenr, eagleanurag, LifeGoesOnionOnionOnion, 52coder, foursevenlove, KorsChen, hezhizhen, linzeyan, ZJKung, GaochaoZhu, hopkings2008, yang-le, Evilrabbit520, Turing-1024-Lee, thomasq0, Suremotoo, Allen-Scai, Risuntsy, Richard-Zhang1019, qingpeng9802, primexiao, nidhoggfgg, 1ch0, MwumLi, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai and 0130w.
+This book has been continuously improved through the joint efforts of many contributors in the open-source community. Thanks to every writer who invested time and effort, they are (in the order automatically generated by GitHub): krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, msk397, gvenusleo, khoaxuantu, RiverTwilight, rongyi, gyt95, zhuoqinyue, K3v123, Zuoxun, mingXta, hello-ikun, FangYuan33, GN-Yu, yuelinxin, longsizhuo, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, QiLOL, pengchzn, Guanngxu, L-Super, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, longranger2, cy-by-side, xiongsp, JeffersonHuang, Transmigration-zhou, magentaqin, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, Shyam-Chen, nanlei, hongyun-robot, Phoenix0415, MolDuM, Nigh, he-weilai, junminhong, mgisr, iron-irax, yd-j, XiaChuerwu, XC-Zero, seven1240, SamJin98, wodray, reeswell, NI-SW, Horbin-Magician, Enlightenus, xjr7670, YangXuanyi, DullSword, boloboloda, iStig, qq909244296, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, liuxjerry, lucaswangdev, lyl625760, hts0000, gledfish, fbigm, echo1937, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, luluxia, xb534, bitsmi, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, steventimes, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, ZhongGuanbin, shanghai-Jerry, JackYang-hellobobo, Javesun99, lipusheng, BlindTerran, ShiMaRing, FreddieLi, FloranceYeh, iFleey, fanchenggang, gltianwen, goerll, Dr-XYZ, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, fanenr, eagleanurag, LifeGoesOnionOnionOnion, 52coder, foursevenlove, KorsChen, hezhizhen, linzeyan, ZJKung, GaochaoZhu, hopkings2008, yang-le, Evilrabbit520, Turing-1024-Lee, thomasq0, Suremotoo, Allen-Scai, Risuntsy, Richard-Zhang1019, qingpeng9802, primexiao, nidhoggfgg, 1ch0, MwumLi, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Keynman, KeiichiKasai and 0130w.
-The code review work for this book was completed by coderonion, Gonglja, gvenusleo, hpstory, justin‐tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (listed in alphabetical order). Thanks to them for their time and effort, ensuring the standardization and uniformity of the code in various languages.
+The code review work for this book was completed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi (in alphabetical order). Thanks to them for the time and effort they put in, it is they who ensure the standardization and unity of code in various languages.
-The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, while the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0, and magentaqin. It is thanks to their continuous contributions that this book can reach and serve a broader audience.
+The Traditional Chinese version of this book was reviewed by Shyam-Chen and Dr-XYZ, and the English version was reviewed by yuelinxin, K3v123, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, thomasq0 and magentaqin. It is because of their continuous contributions that this book can serve a wider readership, and we thank them.
-Throughout the creation of this book, numerous individuals provided invaluable assistance, including but not limited to:
+During the creation of this book, I received help from many people.
-- 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 my mentor at the company, Dr. Li Xi, who encouraged me to "take action quickly" during a conversation, strengthening my determination to write this book;
+- Thanks to my girlfriend Bubble as the first reader of this book, who provided many valuable suggestions from the perspective of an algorithm beginner, making this book more suitable for novices to read;
- 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).
+- Thanks to Xiaoquan for providing professional help in intellectual property rights, which played an important role in the improvement of this open-source book;
+- Thanks to Sutong for designing the beautiful cover and logo for this book, and for patiently making revisions multiple times driven by my obsessive-compulsive disorder;
+- Thanks to @squidfunk for the typesetting suggestions, as well as for developing the 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!
+During the writing process, I read many textbooks and articles on data structures and algorithms. These works provided excellent examples for this book and ensured the accuracy and quality of the book's content. I would like to thank all the teachers and predecessors for their outstanding 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-en). I highly recommend this excellent book to all readers.
+This book advocates a learning method that combines hands and brain, and in this regard I was deeply inspired by [*Dive into Deep Learning*](https://github.com/d2l-ai/d2l-zh). I highly recommend this excellent work to all readers.
-**Heartfelt thanks to my parents, whose ongoing support and encouragement have allowed me to do this interesting work**.
+**Heartfelt thanks to my parents, it is your support and encouragement that has given me the opportunity to do this interesting thing**.
diff --git a/en/docs/chapter_preface/index.md b/en/docs/chapter_preface/index.md
index 332d997c7..971809b55 100644
--- a/en/docs/chapter_preface/index.md
+++ b/en/docs/chapter_preface/index.md
@@ -4,6 +4,6 @@
!!! 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.
+ Algorithms are like a beautiful symphony, each line of code flows like a melody.
+
+ May this book gently resonate in your mind, leaving a unique and profound melody.
diff --git a/en/docs/chapter_preface/suggestions.md b/en/docs/chapter_preface/suggestions.md
index 393e50447..d15472c47 100644
--- a/en/docs/chapter_preface/suggestions.md
+++ b/en/docs/chapter_preface/suggestions.md
@@ -1,239 +1,252 @@
-# How to read
+# How to use this book
!!! tip
For the best reading experience, it is recommended that you read through this section.
-## Writing conventions
+## Writing style 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.
-- Technical terms will be in boldface (in the print and PDF versions) or underlined (in the web version), for instance, array. It's advisable to familiarize yourself with these for better comprehension of technical texts.
-- **Bolded text** indicates key content or summary statements, which deserve special attention.
-- Words and phrases with specific meanings are indicated with “quotation marks” to avoid ambiguity.
-- When it comes to terms that are inconsistent between programming languages, this book follows Python, for example using `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.
+- Titles marked with `*` are optional sections with relatively difficult content. If you have limited time, you can skip them first.
+- Technical terms will be in bold (in paper and PDF versions) or underlined (in web versions), such as array. It is recommended to memorize them for reading literature.
+- Key content and summary statements will be **bolded**, and such text deserves special attention.
+- Words and phrases with specific meanings will be marked with "quotation marks" to avoid ambiguity.
+- When it comes to nouns that are inconsistent between programming languages, this book uses Python as the standard, for example, using `None` to represent "null".
+- This book partially abandons the comment conventions of programming languages in favor of more compact content layout. Comments are mainly divided into 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
-
+ """Title comment, used to label functions, classes, test cases, etc."""
+
+ # Content comment, used to explain code in detail
+
"""
- Multiline
- comments
+ Multi-line
+ comment
"""
```
=== "C++"
```cpp title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Java"
```java title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "C#"
```csharp title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Go"
```go title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Swift"
```swift title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "JS"
```javascript title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "TS"
```typescript title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Dart"
```dart title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Rust"
```rust title=""
- /* Header comments for labeling functions, classes, test samples, etc */
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
- // Comments for explaining details.
-
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "C"
```c title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
=== "Kotlin"
```kotlin title=""
- /* Header comments for labeling functions, classes, test samples, etc */
-
- // Comments for explaining details.
-
+ /* Title comment, used to label functions, classes, test cases, etc. */
+
+ // Content comment, used to explain code in detail
+
/**
- * Multiline
- * comments
+ * Multi-line
+ * comment
*/
```
+=== "Ruby"
+
+ ```ruby title=""
+ ### Title comment, used to label functions, classes, test cases, etc. ###
+
+ # Content comment, used to explain code in detail
+
+ # Multi-line
+ # comment
+ ```
+
=== "Zig"
```zig title=""
- // Header comments for labeling functions, classes, test samples, etc
-
- // Comments for explaining details.
-
- // Multiline
- // comments
+ // Title comment, used to label functions, classes, test cases, etc.
+
+ // Content comment, used to explain code in detail
+
+ // Multi-line
+ // comment
```
-## Efficient learning via animated illustrations
+## Learning efficiently with 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.
+Compared to text, videos and images have higher information density and structural organization, making them easier to understand. In this book, **key and difficult knowledge will mainly be presented in the form of animated illustrations**, with text serving as explanation and supplement.
-When encountering content with animations or illustrations as shown in the figure below, **prioritize understanding the figure, with text as supplementary**, integrating both for a comprehensive understanding.
+If you find that a section of content provides animated illustrations as shown in the figure below while reading this book, **please focus on the illustrations first, with text as a supplement**, and combine the two to understand the content.
-
+
-## Deepen understanding through coding practice
+## Deepening understanding through code practice
-The source code of this book is hosted on the [GitHub Repository](https://github.com/krahets/hello-algo). As shown in the figure below, **the source code comes with test examples and can be executed with just a single click**.
+The accompanying code for this book is hosted in the [GitHub repository](https://github.com/krahets/hello-algo). As shown in the figure below, **the source code comes with test cases and can be run with one 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.
+If time permits, **it is recommended that you type out the code yourself**. If you have limited study time, please at least read through and run all the code.
-Compared to just reading code, writing code often yields more learning. **Learning by doing is the real way to learn.**
+Compared to reading code, the process of writing code often brings more rewards. **Learning by doing is the real learning**.
-
+
-Setting up to run the code involves three main steps.
+The preliminary work for running code is mainly divided into three 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 1: Install the local programming environment**. Please follow the [tutorial](https://www.hello-algo.com/chapter_appendix/installation/) shown in the appendix for installation. If already installed, you can skip this step.
-**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:
+**Step 2: Clone or download the code repository**. Visit the [GitHub repository](https://github.com/krahets/hello-algo). If you have already installed [Git](https://git-scm.com/downloads), you can clone this repository with the following command:
```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 below to directly download the code as a compressed ZIP file. Then, you can simply extract it locally.
+Of course, you can also click the "Download ZIP" button at the location shown in the figure below to directly download the code compressed package, and then extract it locally.
-
+
-**Step 3: Run the source code**. As shown in the figure below, 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.
+**Step 3: Run the source code**. As shown in the figure below, for code blocks with file names at the top, we can find the corresponding source code files in the `codes` folder of the repository. The source code files can be run with one click, which will help you save unnecessary debugging time and allow you to focus on learning content.
-
+
-## Learning together in discussion
+In addition to running code locally, **the web version also supports visual running of Python code** (implemented based on [pythontutor](https://pythontutor.com/)). As shown in the figure below, you can click "Visual Run" below the code block to expand the view and observe the execution process of the algorithm code; you can also click "Full Screen View" for a better viewing experience.
-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 below, 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.
+## Growing together through questions and discussions
-
+When reading this book, please do not easily skip knowledge points that you have not learned well. **Feel free to ask your questions in the comments section**, and my friends and I will do our best to answer you, and generally reply within two days.
-## Algorithm learning path
+As shown in the figure below, the web version has a comments section at the bottom of each chapter. I hope you will pay more attention to the content of the comments section. On the one hand, you can learn about the problems that everyone encounters, thus checking for omissions and stimulating deeper thinking. On the other hand, I hope you can generously answer other friends' questions, share your insights, and help others progress.
-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.
+## Algorithm learning roadmap
-As shown in the figure below, this book mainly covers “Stage 1,” aiming to help you more efficiently embark on Stages 2 and 3.
+From an overall perspective, we can divide the process of learning data structures and algorithms into three stages.
-
+1. **Stage 1: Algorithm introduction**. We need to familiarize ourselves with the characteristics and usage of various data structures, and learn the principles, processes, uses, and efficiency of different algorithms.
+2. **Stage 2: Practice algorithm problems**. It is recommended to start with popular problems, and accumulate at least 100 problems first, to familiarize yourself with mainstream algorithm problems. When first practicing problems, "knowledge forgetting" may be a challenge, but rest assured, this is very normal. We can review problems according to the "Ebbinghaus forgetting curve", and usually after 3-5 rounds of repetition, we can firmly remember them. For recommended problem lists and practice plans, please see this [GitHub repository](https://github.com/krahets/LeetCode-Book).
+3. **Stage 3: Building a knowledge system**. In terms of learning, we can read algorithm column articles, problem-solving frameworks, and algorithm textbooks to continuously enrich our knowledge system. In terms of practicing problems, we can try advanced problem-solving strategies, such as categorization by topic, one problem multiple solutions, one solution multiple problems, etc. Related problem-solving insights can be found in various communities.
+
+As shown in the figure below, the content of this book mainly covers "Stage 1", aiming to help you more efficiently carry out Stage 2 and Stage 3 learning.
+
+
diff --git a/en/docs/chapter_preface/summary.md b/en/docs/chapter_preface/summary.md
index a1f02862a..1dc45a96d 100644
--- a/en/docs/chapter_preface/summary.md
+++ b/en/docs/chapter_preface/summary.md
@@ -1,8 +1,8 @@
# 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.
+- The main audience of this book is algorithm beginners. If you already have a certain foundation, this book can help you systematically review algorithm knowledge, and the source code in the book can also be used as a "problem-solving toolkit."
+- The content of the book mainly includes three parts: complexity analysis, data structures, and algorithms, covering most topics in this field.
+- For algorithm novices, reading an introductory book during the initial learning stage is crucial, as it can help you avoid many detours.
+- The animated illustrations in the book are usually used to introduce key and difficult knowledge. When reading this book, you should pay more attention to these contents.
+- Practice is the best way to learn programming. It is strongly recommended to run the source code and type the code yourself.
+- The web version of this book has a comments section for each chapter, where you are welcome to share your questions and insights at any time.
diff --git a/en/docs/chapter_reference/index.md b/en/docs/chapter_reference/index.md
index 39cf6a52c..2c015705f 100644
--- a/en/docs/chapter_reference/index.md
+++ b/en/docs/chapter_reference/index.md
@@ -16,7 +16,7 @@ icon: material/bookshelf
[6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition).
-[7] Cheng Jie. Speaking of Data Structures.
+[7] Cheng Jie. Conversational Data Structures.
[8] Wang Zheng. The Beauty of Data Structures and Algorithms.
diff --git a/en/docs/chapter_searching/binary_search.md b/en/docs/chapter_searching/binary_search.md
index 5384dd66f..e9d6eb61e 100644
--- a/en/docs/chapter_searching/binary_search.md
+++ b/en/docs/chapter_searching/binary_search.md
@@ -1,24 +1,24 @@
# Binary search
-Binary search is an efficient search algorithm that uses a divide-and-conquer strategy. It takes advantage of the sorted order of elements in an array by reducing the search interval by half in each iteration, continuing until either the target element is found or the search interval becomes empty.
+Binary search is an efficient searching algorithm based on the divide-and-conquer strategy. It leverages the orderliness of data to reduce the search range by half in each round until the target element is found or the search interval becomes empty.
!!! question
- Given an array `nums` of length $n$, where elements are arranged in ascending order without duplicates. Please find and return the index of element `target` in this array. If the array does not contain the element, return $-1$. An example is shown in the figure below.
+ Given an array `nums` of length $n$ with elements arranged in ascending order and no duplicates, search for and return the index of element `target` in the array. If the array does not contain the element, return $-1$. An example is shown in the figure below.

-As shown in the figure below, we firstly initialize pointers with $i = 0$ and $j = n - 1$, pointing to the first and last element of the array respectively. They also represent the whole search interval $[0, n - 1]$. Please note that square brackets indicate a closed interval, which includes the boundary values themselves.
+As shown in the figure below, we first initialize pointers $i = 0$ and $j = n - 1$, pointing to the first and last elements of the array respectively, representing the search interval $[0, n - 1]$. Note that square brackets denote a closed interval, which includes the boundary values themselves.
-And then the following two steps may be performed in a loop.
+Next, perform the following two steps in a loop:
1. Calculate the midpoint index $m = \lfloor {(i + j) / 2} \rfloor$, where $\lfloor \: \rfloor$ denotes the floor operation.
-2. Based on the comparison between the value of `nums[m]` and `target`, one of the following three cases will be chosen to execute.
- 1. If `nums[m] < target`, it indicates that `target` is in the interval $[m + 1, j]$, thus set $i = m + 1$.
- 2. If `nums[m] > target`, it indicates that `target` is in the interval $[i, m - 1]$, thus set $j = m - 1$.
- 3. If `nums[m] = target`, it indicates that `target` is found, thus return index $m$.
+2. Compare `nums[m]` and `target`, which results in three cases:
+ 1. When `nums[m] < target`, it indicates that `target` is in the interval $[m + 1, j]$, so execute $i = m + 1$.
+ 2. When `nums[m] > target`, it indicates that `target` is in the interval $[i, m - 1]$, so execute $j = m - 1$.
+ 3. When `nums[m] = target`, it indicates that `target` has been found, so return index $m$.
-If the array does not contain the target element, the search interval will eventually reduce to empty, ending up returning $-1$.
+If the array does not contain the target element, the search interval will eventually shrink to empty. In this case, return $-1$.
=== "<1>"

@@ -41,21 +41,21 @@ If the array does not contain the target element, the search interval will event
=== "<7>"

-It's worth noting that as $i$ and $j$ are both of type `int`, **$i + j$ might exceed the range of `int` type**. To avoid large number overflow, we usually use the formula $m = \lfloor {i + (j - i) / 2} \rfloor$ to calculate the midpoint.
+It's worth noting that since both $i$ and $j$ are of `int` type, **$i + j$ may exceed the range of the `int` type**. To avoid large number overflow, we typically use the formula $m = \lfloor {i + (j - i) / 2} \rfloor$ to calculate the midpoint.
-The code is as follows:
+The code is shown below:
```src
[file]{binary_search}-[class]{}-[func]{binary_search}
```
-**Time complexity is $O(\log n)$** : In the binary loop, the interval decreases by half each round, hence the number of iterations is $\log_2 n$.
+**Time complexity is $O(\log n)$**: In the binary loop, the interval is reduced by half each round, so the number of loops is $\log_2 n$.
-**Space complexity is $O(1)$** : Pointers $i$ and $j$ occupies constant size of space.
+**Space complexity is $O(1)$**: Pointers $i$ and $j$ use constant-size space.
## Interval representation methods
-Besides the above closed interval, another common interval representation is the "left-closed right-open" interval, defined as $[0, n)$, where the left boundary includes itself, and the right boundary does not. In this representation, the interval $[i, j)$ is empty when $i = j$.
+In addition to the closed interval mentioned above, another common interval representation is the "left-closed right-open" interval, defined as $[0, n)$, meaning the left boundary includes itself while the right boundary does not. Under this representation, the interval $[i, j)$ is empty when $i = j$.
We can implement a binary search algorithm with the same functionality based on this representation:
@@ -63,21 +63,21 @@ We can implement a binary search algorithm with the same functionality based on
[file]{binary_search}-[class]{}-[func]{binary_search_lcro}
```
-As shown in the figure below, under the two types of interval representations, the initialization, loop condition, and narrowing interval operation of the binary search algorithm differ.
+As shown in the figure below, under the two interval representations, the initialization, loop condition, and interval narrowing operations of the binary search algorithm are all different.
-Since both boundaries in the "closed interval" representation are inclusive, the operations to narrow the interval through pointers $i$ and $j$ are also symmetrical. This makes it less prone to errors, **therefore, it is generally recommended to use the "closed interval" approach**.
+Since both the left and right boundaries in the "closed interval" representation are defined as closed, the operations to narrow the interval through pointers $i$ and $j$ are also symmetric. This makes it less error-prone, **so the "closed interval" approach is generally recommended**.
-
+
## Advantages and limitations
Binary search performs well in both time and space aspects.
-- Binary search is time-efficient. With large dataset, the logarithmic time complexity offers a major advantage. For instance, given a dataset with size $n = 2^{20}$, linear search requires $2^{20} = 1048576$ iterations, while binary search only demands $\log_2 2^{20} = 20$ loops.
-- Binary search does not need extra space. Compared to search algorithms that rely on additional space (like hash search), binary search is more space-efficient.
+- Binary search has high time efficiency. With large data volumes, the logarithmic time complexity has significant advantages. For example, when the data size $n = 2^{20}$, linear search requires $2^{20} = 1048576$ loop rounds, while binary search only needs $\log_2 2^{20} = 20$ rounds.
+- Binary search requires no extra space. Compared to searching algorithms that require additional space (such as hash-based search), binary search is more space-efficient.
-However, binary search may not be suitable for all scenarios due to the following concerns.
+However, binary search is not suitable for all situations, mainly for the following reasons:
-- Binary search can only be applied to sorted data. Unsorted data must be sorted before applying binary search, which may not be worthwhile as sorting algorithm typically has a time complexity of $O(n \log n)$. Such cost is even higher than linear search, not to mention binary search itself. For scenarios with frequent insertion, the cost of remaining the array in order is pretty high as the time complexity of inserting new elements into specific positions is $O(n)$.
-- Binary search may use array only. Binary search requires non-continuous (jumping) element access, which is inefficient in linked list. As a result, linked list or data structures based on linked list may not be suitable for this algorithm.
-- Linear search performs better on small dataset. In linear search, only 1 decision operation is required for each iteration; whereas in binary search, it involves 1 addition, 1 division, 1 to 3 decision operations, 1 addition (subtraction), totaling 4 to 6 operations. Therefore, if data size $n$ is small, linear search is faster than binary search.
+- Binary search is only applicable to sorted data. If the input data is unsorted, sorting specifically to use binary search would be counterproductive, as sorting algorithms typically have a time complexity of $O(n \log n)$, which is higher than both linear search and binary search. For scenarios with frequent element insertions, maintaining array orderliness requires inserting elements at specific positions with a time complexity of $O(n)$, which is also very expensive.
+- Binary search is only applicable to arrays. Binary search requires jump-style (non-contiguous) element access, and jump-style access has low efficiency in linked lists, making it unsuitable for linked lists or data structures based on linked list implementations.
+- For small data volumes, linear search performs better. In linear search, each round requires only 1 comparison operation; while in binary search, it requires 1 addition, 1 division, 1-3 comparison operations, and 1 addition (subtraction), totaling 4-6 unit operations. Therefore, when the data volume $n$ is small, linear search is actually faster than binary search.
diff --git a/en/docs/chapter_searching/binary_search_edge.md b/en/docs/chapter_searching/binary_search_edge.md
index 844c2fb20..b2fe2a23c 100644
--- a/en/docs/chapter_searching/binary_search_edge.md
+++ b/en/docs/chapter_searching/binary_search_edge.md
@@ -1,56 +1,56 @@
-# Binary search boundaries
+# Binary search edge cases
-## Find the left boundary
+## Finding the left boundary
!!! question
- Given a sorted array `nums` of length $n$, which may contain duplicate elements, return the index of the leftmost element `target`. If the element is not present in the array, return $-1$.
+ Given a sorted array `nums` of length $n$ that may contain duplicate elements, return the index of the leftmost element `target` in the array. If the array does not contain the element, return $-1$.
-Recalling the method of binary search for an insertion point, after the search is completed, the index $i$ will point to the leftmost occurrence of `target`. Therefore, **searching for the insertion point is essentially the same as finding the index of the leftmost `target`**.
+Recall the method for finding the insertion point with binary search. After the search completes, $i$ points to the leftmost `target`, **so finding the insertion point is essentially finding the index of the leftmost `target`**.
-We can use the function for finding an insertion point to find the left boundary of `target`. Note that the array might not contain `target`, which could lead to the following two results:
+Consider implementing the left boundary search using the insertion point finding function. Note that the array may not contain `target`, which could result in the following two cases:
-- The index $i$ of the insertion point is out of bounds.
+- The insertion point index $i$ is out of bounds.
- The element `nums[i]` is not equal to `target`.
-In these cases, simply return $-1$. The code is as follows:
+When either of these situations occurs, simply return $-1$. The code is shown below:
```src
[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge}
```
-## Find the right boundary
+## Finding the right boundary
-How do we find the rightmost occurrence of `target`? The most straightforward way is to modify the traditional binary search logic by changing how we adjust the search boundaries in the case of `nums[m] == target`. The code is omitted here. If you are interested, try to implement the code on your own.
+So how do we find the rightmost `target`? The most direct approach is to modify the code and replace the pointer shrinking operation in the `nums[m] == target` case. The code is omitted here; interested readers can implement it themselves.
-Below we are going to introduce two more ingenious methods.
+Below we introduce two more clever methods.
-### Reuse the left boundary search
+### Reusing left boundary search
-To find the rightmost occurrence of `target`, we can reuse the function used for locating the leftmost `target`. Specifically, we transform the search for the rightmost target into a search for the leftmost target + 1.
+In fact, we can use the function for finding the leftmost element to find the rightmost element. The specific method is: **Convert finding the rightmost `target` into finding the leftmost `target + 1`**.
-As shown in the figure below, after the search is complete, pointer $i$ will point to the leftmost `target + 1` (if exists), while pointer $j$ will point to the rightmost occurrence of `target`. Therefore, returning $j$ will give us the right boundary.
+As shown in the figure below, after the search completes, pointer $i$ points to the leftmost `target + 1` (if it exists), while $j$ points to the rightmost `target`, **so we can simply return $j$**.
-
+
-Note that the insertion point returned is $i$, therefore, it should be subtracted by $1$ to obtain $j$:
+Note that the returned insertion point is $i$, so we need to subtract $1$ from it to obtain $j$:
```src
[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge}
```
-### Transform into an element search
+### Converting to element search
-When the array does not contain `target`, $i$ and $j$ will eventually point to the first element greater and smaller than `target` respectively.
+We know that when the array does not contain `target`, $i$ and $j$ will eventually point to the first elements greater than and less than `target`, respectively.
-Thus, as shown in the figure below, we can construct an element that does not exist in the array, to search for the left and right boundaries.
+Therefore, as shown in the figure below, we can construct an element that does not exist in the array to find the left and right boundaries.
-- To find the leftmost `target`: it can be transformed into searching for `target - 0.5`, and return the pointer $i$.
-- To find the rightmost `target`: it can be transformed into searching for `target + 0.5`, and return the pointer $j$.
+- Finding the leftmost `target`: Can be converted to finding `target - 0.5` and returning pointer $i$.
+- Finding the rightmost `target`: Can be converted to finding `target + 0.5` and returning pointer $j$.
-
+
-The code is omitted here, but here are two important points to note about this approach.
+The code is omitted here, but the following two points are worth noting:
-- The given array `nums` does not contain decimal, so handling equal cases is not a concern.
-- However, introducing decimals in this approach requires modifying the `target` variable to a floating-point type (no change needed in Python).
+- Since the given array does not contain decimals, we don't need to worry about how to handle equal cases.
+- Because this method introduces decimals, the variable `target` in the function needs to be changed to a floating-point type (Python does not require this change).
diff --git a/en/docs/chapter_searching/binary_search_insertion.md b/en/docs/chapter_searching/binary_search_insertion.md
index dd2c8a310..710c3c328 100644
--- a/en/docs/chapter_searching/binary_search_insertion.md
+++ b/en/docs/chapter_searching/binary_search_insertion.md
@@ -1,26 +1,26 @@
-# Binary search insertion
+# Binary search insertion point
-Binary search is not only used to search for target elements but also to solve many variant problems, such as searching for the insertion position of target elements.
+Binary search can not only be used to search for target elements but also to solve many variant problems, such as searching for the insertion position of a target element.
-## Case with no duplicate elements
+## Case without duplicate elements
!!! question
- Given a sorted array `nums` of length $n$ with unique elements and an element `target`, insert `target` into `nums` while maintaining its sorted order. If `target` already exists in the array, insert it to the left of the existing element. Return the index of `target` in the array after insertion. See the example shown in the figure below.
+ Given a sorted array `nums` of length $n$ and an element `target`, where the array contains no duplicate elements. Insert `target` into the array `nums` while maintaining its sorted order. If the array already contains the element `target`, insert it to its left. Return the index of `target` in the array after insertion. An example is shown in the figure below.
-
+
-If you want to reuse the binary search code from the previous section, you need to answer the following two questions.
+If we want to reuse the binary search code from the previous section, we need to answer the following two questions.
-**Question one**: If the array already contains `target`, would the insertion point be the index of existing element?
+**Question 1**: When the array contains `target`, is the insertion point index the same as that element's index?
-The requirement to insert `target` to the left of equal elements means that the newly inserted `target` will replace the original `target` position. In other words, **when the array contains `target`, the insertion point is indeed the index of that `target`**.
+The problem requires inserting `target` to the left of equal elements, which means the newly inserted `target` replaces the position of the original `target`. In other words, **when the array contains `target`, the insertion point index is the index of that `target`**.
-**Question two**: When the array does not contain `target`, at which index would it be inserted?
+**Question 2**: When the array does not contain `target`, what is the insertion point index?
-Let's further consider the binary search process: when `nums[m] < target`, pointer $i$ moves, meaning that pointer $i$ is approaching an element greater than or equal to `target`. Similarly, pointer $j$ is always approaching an element less than or equal to `target`.
+Further consider the binary search process: When `nums[m] < target`, $i$ moves, which means pointer $i$ is approaching elements greater than or equal to `target`. Similarly, pointer $j$ is always approaching elements less than or equal to `target`.
-Therefore, at the end of the binary, it is certain that: $i$ points to the first element greater than `target`, and $j$ points to the first element less than `target`. **It is easy to see that when the array does not contain `target`, the insertion point is $i$**. The code is as follows:
+Therefore, when the binary search ends, we must have: $i$ points to the first element greater than `target`, and $j$ points to the first element less than `target`. **It's easy to see that when the array does not contain `target`, the insertion index is $i$**. The code is shown below:
```src
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple}
@@ -30,25 +30,25 @@ Therefore, at the end of the binary, it is certain that: $i$ points to the first
!!! question
- Based on the previous question, assume the array may contain duplicate elements, all else remains the same.
+ Based on the previous problem, assume the array may contain duplicate elements, with everything else remaining the same.
-When there are multiple occurrences of `target` in the array, a regular binary search can only return the index of one occurrence of `target`, **and it cannot determine how many occurrences of `target` are to the left and right of that position**.
+Suppose there are multiple `target` elements in the array. Ordinary binary search can only return the index of one `target`, **and cannot determine how many `target` elements are to the left and right of that element**.
-The problem requires inserting the target element at the leftmost position, **so we need to find the index of the leftmost `target` in the array**. Initially consider implementing this through the steps shown in the figure below.
+The problem requires inserting the target element at the leftmost position, **so we need to find the index of the leftmost `target` in the array**. Initially, consider implementing this through the steps shown in the figure below:
-1. Perform a binary search to find any index of `target`, say $k$.
-2. Starting from index $k$, conduct a linear search to the left until the leftmost occurrence of `target` is found, then return this index.
+1. Perform binary search to obtain the index of any `target`, denoted as $k$.
+2. Starting from index $k$, perform linear traversal to the left, and return when the leftmost `target` is found.
-
+
-Although this method is feasible, it includes linear search, so its time complexity is $O(n)$. This method is inefficient when the array contains many duplicate `target`s.
+Although this method works, it includes linear search, resulting in a time complexity of $O(n)$. When the array contains many duplicate `target` elements, this method is very inefficient.
-Now consider extending the binary search code. As shown in the figure below, the overall process remains the same. In each round, we first calculate the middle index $m$, then compare the value of `target` with `nums[m]`, leading to the following cases.
+Now consider extending the binary search code. As shown in the figure below, the overall process remains unchanged: calculate the midpoint index $m$ in each round, then compare `target` with `nums[m]`, divided into the following cases:
-- When `nums[m] < target` or `nums[m] > target`, it means `target` has not been found yet, thus use the normal binary search to narrow the search range, **bringing pointers $i$ and $j$ closer to `target`**.
-- When `nums[m] == target`, it indicates that the elements less than `target` are in the range $[i, m - 1]$, therefore use $j = m - 1$ to narrow the range, **thus bringing pointer $j$ closer to the elements less than `target`**.
+- When `nums[m] < target` or `nums[m] > target`, it means `target` has not been found yet, so use the ordinary binary search interval narrowing operation to **make pointers $i$ and $j$ approach `target`**.
+- When `nums[m] == target`, it means elements less than `target` are in the interval $[i, m - 1]$, so use $j = m - 1$ to narrow the interval, thereby **making pointer $j$ approach elements less than `target`**.
-After the loop, $i$ points to the leftmost `target`, and $j$ points to the first element less than `target`, **therefore index $i$ is the insertion point**.
+After the loop completes, $i$ points to the leftmost `target`, and $j$ points to the first element less than `target`, **so index $i$ is the insertion point**.
=== "<1>"

@@ -74,9 +74,9 @@ After the loop, $i$ points to the leftmost `target`, and $j$ points to the first
=== "<8>"

-Observe the following code. The operations in the branches `nums[m] > target` and `nums[m] == target` are the same, so these two branches can be merged.
+Observe the following code: the operations for branches `nums[m] > target` and `nums[m] == target` are the same, so the two can be merged.
-Even so, we can still keep the conditions expanded, as it makes the logic clearer and improves readability.
+Even so, we can still keep the conditional branches expanded, as the logic is clearer and more readable.
```src
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}
@@ -84,8 +84,8 @@ Even so, we can still keep the conditions expanded, as it makes the logic cleare
!!! tip
- The code in this section uses "closed interval". If you are interested in "left-closed, right-open", try to implement the code on your own.
+ The code in this section all uses the "closed interval" approach. Interested readers can implement the "left-closed right-open" approach themselves.
-In summary, binary search essentially involves setting search targets for pointers $i$ and $j$. These targets could be a specific element (like `target`) or a range of elements (such as those smaller than `target`).
+Overall, binary search is simply about setting search targets for pointers $i$ and $j$ separately. The target could be a specific element (such as `target`) or a range of elements (such as elements less than `target`).
-In the continuous loop of binary search, pointers $i$ and $j$ gradually approach the predefined target. Ultimately, they either find the answer or stop after crossing the boundary.
+Through continuous binary iterations, both pointers $i$ and $j$ gradually approach their preset targets. Ultimately, they either successfully find the answer or stop after crossing the boundaries.
diff --git a/en/docs/chapter_searching/index.md b/en/docs/chapter_searching/index.md
index cf8ac7f2f..89abf6061 100644
--- a/en/docs/chapter_searching/index.md
+++ b/en/docs/chapter_searching/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Searching is an adventure into the unknown; where we may need to traverse every corner of a mysterious space, or perhaps we’ll quickly locate our target.
-
- On this journey of discovery, each exploration may end up with an unexpected answer.
+ Searching is an adventure into the unknown, where we may need to traverse every corner of the mysterious space, or we may be able to quickly lock onto the target.
+
+ In this journey of discovery, each exploration may yield an unexpected answer.
diff --git a/en/docs/chapter_searching/replace_linear_by_hashing.md b/en/docs/chapter_searching/replace_linear_by_hashing.md
index c34ea97b9..7b0855ebb 100644
--- a/en/docs/chapter_searching/replace_linear_by_hashing.md
+++ b/en/docs/chapter_searching/replace_linear_by_hashing.md
@@ -1,16 +1,16 @@
-# Hash optimization strategies
+# Hash optimization strategy
-In algorithm problems, **we often reduce the time complexity of an algorithm by replacing a linear search with a hash-based search**. Let's use an algorithm problem to deepen the understanding.
+In algorithm problems, **we often reduce the time complexity of algorithms by replacing linear search with hash-based search**. Let's use an algorithm problem to deepen our understanding.
!!! question
- Given an integer array `nums` and a target element `target`, please search for two elements in the array whose "sum" equals `target`, and return their array indices. Any solution is acceptable.
+ Given an integer array `nums` and a target element `target`, search for two elements in the array whose "sum" equals `target`, and return their array indices. Any solution will do.
## Linear search: trading time for space
-Consider traversing through all possible combinations directly. As shown in the figure below, we initiate a nested loop, and in each iteration, we determine whether the sum of the two integers equals `target`. If so, we return their indices.
+Consider directly traversing all possible combinations. As shown in the figure below, we open a two-layer loop and judge in each round whether the sum of two integers equals `target`. If so, return their indices.
-
+
The code is shown below:
@@ -18,17 +18,17 @@ The code is shown below:
[file]{two_sum}-[class]{}-[func]{two_sum_brute_force}
```
-This method has a time complexity of $O(n^2)$ and a space complexity of $O(1)$, which can be very time-consuming with large data volumes.
+This method has a time complexity of $O(n^2)$ and a space complexity of $O(1)$, which is very time-consuming with large data volumes.
-## Hash search: trading space for time
+## Hash-based search: trading space for time
-Consider using a hash table, where the key-value pairs are the array elements and their indices, respectively. Loop through the array, performing the steps shown in the figure below during each iteration.
+Consider using a hash table where key-value pairs are array elements and element indices respectively. Loop through the array, performing the steps shown in the figure below in each round:
1. Check if the number `target - nums[i]` is in the hash table. If so, directly return the indices of these two elements.
2. Add the key-value pair `nums[i]` and index `i` to the hash table.
=== "<1>"
- 
+ 
=== "<2>"

@@ -42,6 +42,6 @@ The implementation code is shown below, requiring only a single loop:
[file]{two_sum}-[class]{}-[func]{two_sum_hash_table}
```
-This method reduces the time complexity from $O(n^2)$ to $O(n)$ by using hash search, significantly enhancing runtime efficiency.
+This method reduces the time complexity from $O(n^2)$ to $O(n)$ through hash-based search, greatly improving runtime efficiency.
-As it requires maintaining an additional hash table, the space complexity is $O(n)$. **Nevertheless, this method has a more balanced time-space efficiency overall, making it the optimal solution for this problem**.
+Since an additional hash table needs to be maintained, the space complexity is $O(n)$. **Nevertheless, this method achieves a more balanced overall time-space efficiency, making it the optimal solution for this problem**.
diff --git a/en/docs/chapter_searching/searching_algorithm_revisited.md b/en/docs/chapter_searching/searching_algorithm_revisited.md
index edee2ff28..4a24b8ab9 100644
--- a/en/docs/chapter_searching/searching_algorithm_revisited.md
+++ b/en/docs/chapter_searching/searching_algorithm_revisited.md
@@ -1,84 +1,84 @@
-# Search algorithms revisited
+# Searching algorithms revisited
-Searching algorithms (search algorithms) are used to retrieve one or more elements that meet specific criteria within data structures such as arrays, linked lists, trees, or graphs.
+Searching algorithms are used to search for one or a group of elements that meet specific conditions in data structures (such as arrays, linked lists, trees, or graphs).
-Searching algorithms can be divided into the following two categories based on their approach.
+Searching algorithms can be divided into the following two categories based on their implementation approach:
-- **Locating the target element by traversing the data structure**, such as traversals of arrays, linked lists, trees, and graphs, etc.
-- **Using the organizational structure of the data or existing data to achieve efficient element searches**, such as binary search, hash search, binary search tree search, etc.
+- **Locating target elements by traversing the data structure**, such as traversing arrays, linked lists, trees, and graphs.
+- **Achieving efficient element search by utilizing data organization structure or prior information contained in the data**, such as binary search, hash-based search, and binary search tree search.
-These topics were introduced in previous chapters, so they are not unfamiliar to us. In this section, we will revisit searching algorithms from a more systematic perspective.
+It's not hard to see that these topics have all been covered in previous chapters, so searching algorithms are not unfamiliar to us. In this section, we will approach from a more systematic perspective and re-examine searching algorithms.
## Brute-force search
-A Brute-force search locates the target element by traversing every element of the data structure.
+Brute-force search locates target elements by traversing each element of the data structure.
-- "Linear search" is suitable for linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses each element one by one until the target element is found or the other end is reached without finding the target element.
-- "Breadth-first search" and "Depth-first search" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer (left to right), accessing nodes from near to far. Depth-first search starts from the initial node, follows a path until the end (top to bottom), then backtracks and tries other paths until the entire data structure is traversed.
+- "Linear search" is applicable to linear data structures such as arrays and linked lists. It starts from one end of the data structure and accesses elements one by one until the target element is found or the other end is reached without finding the target element.
+- "Breadth-first search" and "depth-first search" are two traversal strategies for graphs and trees. Breadth-first search starts from the initial node and searches layer by layer, visiting nodes from near to far. Depth-first search starts from the initial node, follows a path to the end, then backtracks and tries other paths until the entire data structure is traversed.
-The advantage of brute-force search is its simplicity and versatility, **no need for data preprocessing or the help of additional data structures**.
+The advantage of brute-force search is that it is simple and has good generality, **requiring no data preprocessing or additional data structures**.
-However, **the time complexity of this type of algorithm is $O(n)$**, where $n$ is the number of elements, so the performance is poor with large data sets.
+However, **the time complexity of such algorithms is $O(n)$**, where $n$ is the number of elements, so performance is poor when dealing with large amounts of data.
## Adaptive search
-An Adaptive search uses the unique properties of data (such as order) to optimize the search process, thereby locating the target element more efficiently.
+Adaptive search utilizes the unique properties of data (such as orderliness) to optimize the search process, thereby locating target elements more efficiently.
-- "Binary search" uses the orderliness of data to achieve efficient searching, only suitable for arrays.
-- "Hash search" uses a hash table to establish a key-value mapping between search data and target data, thus implementing the query operation.
-- "Tree search" in a specific tree structure (such as a binary search tree), quickly eliminates nodes based on node value comparisons, thus locating the target element.
+- "Binary search" uses the orderliness of data to achieve efficient searching, applicable only to arrays.
+- "Hash-based search" uses hash tables to establish key-value pair mappings between search data and target data, thereby achieving query operations.
+- "Tree search" in specific tree structures (such as binary search trees), quickly eliminates nodes based on comparing node values to locate target elements.
-The advantage of these algorithms is high efficiency, **with time complexities reaching $O(\log n)$ or even $O(1)$**.
+The advantage of such algorithms is high efficiency, **with time complexity reaching $O(\log n)$ or even $O(1)$**.
-However, **using these algorithms often requires data preprocessing**. For example, binary search requires sorting the array in advance, and hash search and tree search both require the help of additional data structures. Maintaining these structures also requires more overhead in terms of time and space.
+However, **using these algorithms often requires data preprocessing**. For example, binary search requires pre-sorting the array, while hash-based search and tree search both require additional data structures, and maintaining these data structures also requires extra time and space overhead.
!!! tip
- Adaptive search algorithms are often referred to as search algorithms, **mainly used for quickly retrieving target elements in specific data structures**.
+ Adaptive search algorithms are often called lookup algorithms, **mainly used to quickly retrieve target elements in specific data structures**.
-## Choosing a search method
+## Search method selection
-Given a set of data of size $n$, we can use a linear search, binary search, tree search, hash search, or other methods to retrieve the target element. The working principles of these methods are shown in the figure below.
+Given a dataset of size $n$, we can use linear search, binary search, tree search, hash-based search, and other methods to search for the target element. The working principles of each method are shown in the figure below.
-
+
-The characteristics and operational efficiency of the aforementioned methods are shown in the following table.
+The operational efficiency and characteristics of the above methods are as follows:
Table Comparison of search algorithm efficiency
-| | Linear search | Binary search | Tree search | Hash search |
+| | Linear search | Binary search | Tree search | Hash-based search |
| ------------------ | ------------- | --------------------- | --------------------------- | -------------------------- |
| Search element | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ |
| Insert element | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ |
| Delete element | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ |
| Extra space | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ |
-| Data preprocessing | / | Sorting $O(n \log n)$ | Building tree $O(n \log n)$ | Building hash table $O(n)$ |
-| Data orderliness | Unordered | Ordered | Ordered | Unordered |
+| Data preprocessing | / | Sorting $O(n \log n)$ | Tree building $O(n \log n)$ | Hash table building $O(n)$ |
+| Data ordered | Unordered | Ordered | Ordered | Unordered |
-The choice of search algorithm also depends on the volume of data, search performance requirements, frequency of data queries and updates, etc.
+The choice of search algorithm also depends on data volume, search performance requirements, data query and update frequency, etc.
**Linear search**
-- Good versatility, no need for any data preprocessing operations. If we only need to query the data once, then the time for data preprocessing in the other three methods would be longer than the time for a linear search.
-- Suitable for small volumes of data, where time complexity has a smaller impact on efficiency.
-- Suitable for scenarios with very frequent data updates, because this method does not require any additional maintenance of the data.
+- Good generality, requiring no data preprocessing operations. If we only need to query the data once, the data preprocessing time for the other three methods would be longer than linear search.
+- Suitable for small data volumes, where time complexity has less impact on efficiency.
+- Suitable for scenarios with high data update frequency, as this method does not require any additional data maintenance.
**Binary search**
-- Suitable for larger data volumes, with stable performance and a worst-case time complexity of $O(\log n)$.
-- However, the data volume cannot be too large, because storing arrays requires contiguous memory space.
-- Not suitable for scenarios with frequent additions and deletions, because maintaining an ordered array incurs a lot of overhead.
+- Suitable for large data volumes with stable efficiency performance, worst-case time complexity of $O(\log n)$.
+- Data volume cannot be too large, as storing arrays requires contiguous memory space.
+- Not suitable for scenarios with frequent data insertion and deletion, as maintaining a sorted array has high overhead.
-**Hash search**
+**Hash-based search**
-- Suitable for scenarios where fast query performance is essential, with an average time complexity of $O(1)$.
-- Not suitable for scenarios needing ordered data or range searches, because hash tables cannot maintain data orderliness.
-- High dependency on hash functions and hash collision handling strategies, with significant performance degradation risks.
-- Not suitable for overly large data volumes, because hash tables need extra space to minimize collisions and provide good query performance.
+- Suitable for scenarios with high query performance requirements, with an average time complexity of $O(1)$.
+- Not suitable for scenarios requiring ordered data or range searches, as hash tables cannot maintain data orderliness.
+- High dependence on hash functions and hash collision handling strategies, with significant risk of performance degradation.
+- Not suitable for excessively large data volumes, as hash tables require extra space to minimize collisions and thus provide good query performance.
**Tree search**
-- Suitable for massive data, because tree nodes are stored scattered in memory.
-- Suitable for maintaining ordered data or range searches.
-- With the continuous addition and deletion of nodes, the binary search tree may become skewed, degrading the time complexity to $O(n)$.
-- If using AVL trees or red-black trees, operations can run stably at $O(\log n)$ efficiency, but the operation to maintain tree balance adds extra overhead.
+- Suitable for massive data, as tree nodes are stored dispersedly in memory.
+- Suitable for scenarios requiring maintained ordered data or range searches.
+- During continuous node insertion and deletion, binary search trees may become skewed, degrading time complexity to $O(n)$.
+- If using AVL trees or red-black trees, all operations can run stably at $O(\log n)$ efficiency, but operations to maintain tree balance add extra overhead.
diff --git a/en/docs/chapter_searching/summary.md b/en/docs/chapter_searching/summary.md
index d6169a17a..aa834a598 100644
--- a/en/docs/chapter_searching/summary.md
+++ b/en/docs/chapter_searching/summary.md
@@ -1,8 +1,8 @@
# Summary
-- Binary search depends on the order of data and performs the search by iteratively halving the search interval. It requires the input data to be sorted and is only applicable to arrays or array-based data structures.
-- Brute force search may be required to locate an entry in an unordered dataset. Different search algorithms can be applied based on the data structure: Linear search is suitable for arrays and linked lists, while breadth-first search (BFS) and depth-first search (DFS) are suitable for graphs and trees. These algorithms are highly versatile, requiring no preprocessing of data, but they have a higher time complexity of $O(n)$.
-- Hash search, tree search, and binary search are efficient search methods that can quickly locate target elements within specific data structures. These algorithms are highly efficient, with time complexities reaching $O(\log n)$ or even $O(1)$, but they usually require extra space to accommodate additional data structures.
-- In practice, we need to analyze factors such as data volume, search performance requirements, data query and update frequencies, etc., to choose an appropriate search method.
-- Linear search is ideal for small or frequently updated (volatile) data. Binary search works well for large and sorted data. Hash search is suitable for data that requires high query efficiency and does not need range queries. Tree search is best suited for large dynamic data that require maintaining order and need to support range queries.
-- Replacing linear search with hash search is a common strategy to optimize runtime performance, reducing the time complexity from $O(n)$ to $O(1)$.
+- Binary search relies on data orderliness and progressively reduces the search interval by half through loops. It requires input data to be sorted and is only applicable to arrays or data structures based on array implementations.
+- Brute-force search locates data by traversing the data structure. Linear search is applicable to arrays and linked lists, while breadth-first search and depth-first search are applicable to graphs and trees. Such algorithms have good generality and require no data preprocessing, but have a relatively high time complexity of $O(n)$.
+- Hash-based search, tree search, and binary search are efficient search methods that can quickly locate target elements in specific data structures. Such algorithms are highly efficient with time complexity reaching $O(\log n)$ or even $O(1)$, but typically require additional data structures.
+- In practice, we need to analyze factors such as data scale, search performance requirements, and data query and update frequency to choose the appropriate search method.
+- Linear search is suitable for small-scale or frequently updated data; binary search is suitable for large-scale, sorted data; hash-based search is suitable for data with high query efficiency requirements and no need for range queries; tree search is suitable for large-scale dynamic data that needs to maintain order and support range queries.
+- Replacing linear search with hash-based search is a commonly used strategy to optimize runtime, reducing time complexity from $O(n)$ to $O(1)$.
diff --git a/en/docs/chapter_sorting/bubble_sort.md b/en/docs/chapter_sorting/bubble_sort.md
index 577ec2eff..9873539c1 100644
--- a/en/docs/chapter_sorting/bubble_sort.md
+++ b/en/docs/chapter_sorting/bubble_sort.md
@@ -1,11 +1,11 @@
# Bubble sort
-Bubble sort works by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name "bubble sort."
+Bubble sort (bubble sort) achieves sorting by continuously comparing and swapping adjacent elements. This process is like bubbles rising from the bottom to the top, hence the name bubble sort.
-As shown in the figure below, the bubbling process can be simulated using element swaps: start from the leftmost end of the array and move right, comparing each pair of adjacent elements. If the left element is greater than the right element, swap them. After the traversal, the largest element will have bubbled up to the rightmost end of the array.
+As shown in the figure below, the bubbling process can be simulated using element swap operations: starting from the leftmost end of the array and traversing to the right, compare the size of adjacent elements, and if "left element > right element", swap them. After completing the traversal, the largest element will be moved to the rightmost end of the array.
=== "<1>"
- 
+ 
=== "<2>"

@@ -25,16 +25,16 @@ As shown in the figure below, the bubbling process can be simulated using elemen
=== "<7>"

-## Algorithm process
+## Algorithm flow
-Assume the array has length $n$. The steps of bubble sort are shown in the figure below:
+Assume the array has length $n$. The steps of bubble sort are shown in the figure below.
-1. First, perform one "bubble" pass on $n$ elements, **swapping the largest element to its correct position**.
-2. Next, perform a "bubble" pass on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**.
-3. Continue in this manner; after $n - 1$ such passes, **the largest $n - 1$ elements will have been moved to their correct positions**.
-4. The only remaining element **must** be the smallest, so **no** further sorting is required. At this point, the array is sorted.
+1. First, perform "bubbling" on $n$ elements, **swapping the largest element of the array to its correct position**.
+2. Next, perform "bubbling" on the remaining $n - 1$ elements, **swapping the second largest element to its correct position**.
+3. And so on. After $n - 1$ rounds of "bubbling", **the largest $n - 1$ elements have all been swapped to their correct positions**.
+4. The only remaining element must be the smallest element, requiring no sorting, so the array sorting is complete.
-
+
Example code is as follows:
@@ -44,9 +44,9 @@ Example code is as follows:
## Efficiency optimization
-If no swaps occur during a round of "bubbling," the array is already sorted, so we can return immediately. To detect this, we can add a `flag` variable; whenever no swaps are made in a pass, we set the flag and return early.
+We notice that if no swap operations are performed during a certain round of "bubbling", it means the array has already completed sorting and can directly return the result. Therefore, we can add a flag `flag` to monitor this situation and return immediately once it occurs.
-Even with this optimization, the worst time complexity and average time complexity of bubble sort remains $O(n^2)$. However, if the input array is already sorted, the best-case time complexity can be as low as $O(n)$.
+After optimization, the worst-case time complexity and average time complexity of bubble sort remain $O(n^2)$; but when the input array is completely ordered, the best-case time complexity can reach $O(n)$.
```src
[file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag}
@@ -54,6 +54,6 @@ Even with this optimization, the worst time complexity and average time complexi
## Algorithm characteristics
-- **Time complexity of $O(n^2)$, adaptive sorting.** Each round of "bubbling" traverses array segments of length $n - 1$, $n - 2$, $\dots$, $2$, $1$, which sums to $(n - 1) n / 2$. With a `flag` optimization, the best-case time complexity can reach $O(n)$ when the array is already sorted.
-- **Space complexity of $O(1)$, in-place sorting.** Only a constant amount of extra space is used by pointers $i$ and $j$.
-- **Stable sorting.** Because equal elements are not swapped during "bubbling," their original order is preserved, making this a stable sort.
+- **Time complexity of $O(n^2)$, adaptive sorting**: The array lengths traversed in each round of "bubbling" are $n - 1$, $n - 2$, $\dots$, $2$, $1$, totaling $(n - 1) n / 2$. After introducing the `flag` optimization, the best-case time complexity can reach $O(n)$.
+- **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space.
+- **Stable sorting**: Since equal elements are not swapped during "bubbling".
diff --git a/en/docs/chapter_sorting/bucket_sort.md b/en/docs/chapter_sorting/bucket_sort.md
index 0ff796b37..e0d617520 100644
--- a/en/docs/chapter_sorting/bucket_sort.md
+++ b/en/docs/chapter_sorting/bucket_sort.md
@@ -1,20 +1,20 @@
# Bucket sort
-The previously mentioned sorting algorithms are all "comparison-based sorting algorithms," which sort elements by comparing their values. Such sorting algorithms cannot have better time complexity of $O(n \log n)$. Next, we will discuss several "non-comparison sorting algorithms" that could achieve linear time complexity.
+The several sorting algorithms mentioned earlier all belong to "comparison-based sorting algorithms", which achieve sorting by comparing the size of elements. The time complexity of such sorting algorithms cannot exceed $O(n \log n)$. Next, we will explore several "non-comparison sorting algorithms", whose time complexity can reach linear order.
-Bucket sort is a typical application of the divide-and-conquer strategy. It works by setting up a series of ordered buckets, each containing a range of data, and distributing the input data evenly across these buckets. And then, the data in each bucket is sorted individually. Finally, the sorted data from all the buckets is merged in sequence to produce the final result.
+Bucket sort (bucket sort) is a typical application of the divide-and-conquer strategy. It works by setting up buckets with size order, each bucket corresponding to a data range, evenly distributing data to each bucket; then, sorting within each bucket separately; finally, merging all data in the order of the buckets.
-## Algorithm process
+## Algorithm flow
-Consider an array of length $n$, with float numbers in the range $[0, 1)$. The bucket sort process is illustrated in the figure below.
+Consider an array of length $n$, whose elements are floating-point numbers in the range $[0, 1)$. The flow of bucket sort is shown in the figure below.
-1. Initialize $k$ buckets and distribute $n$ elements into these $k$ buckets.
-2. Sort each bucket individually (using the built-in sorting function of the programming language).
-3. Merge the results in the order from the smallest to the largest bucket.
+1. Initialize $k$ buckets and distribute the $n$ elements into the $k$ buckets.
+2. Sort each bucket separately (here we use the built-in sorting function of the programming language).
+3. Merge the results in order from smallest to largest bucket.
-
+
-The code is shown as follows:
+The code is as follows:
```src
[file]{bucket_sort}-[class]{}-[func]{bucket_sort}
@@ -22,24 +22,24 @@ The code is shown as follows:
## Algorithm characteristics
-Bucket sort is suitable for handling very large data sets. For example, if the input data includes 1 million elements, and system memory limitations prevent loading all the data at the same time, you can divide the data into 1,000 buckets and sort each bucket separately before merging the results.
+Bucket sort is suitable for processing very large data volumes. For example, if the input data contains 1 million elements and system memory cannot load all the data at once, the data can be divided into 1000 buckets, each bucket sorted separately, and then the results merged.
-- **Time complexity is $O(n + k)$**: Assuming the elements are evenly distributed across the buckets, the number of elements in each bucket is $n/k$. Assuming sorting a single bucket takes $O(n/k \log(n/k))$ time, sorting all buckets takes $O(n \log(n/k))$ time. **When the number of buckets $k$ is relatively large, the time complexity approaches $O(n)$**. Merging the results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into a single bucket, and sorting that bucket takes $O(n^2)$ time.
-- **Space complexity is $O(n + k)$, non-in-place sorting**: It requires additional space for $k$ buckets and a total of $n$ elements.
-- Whether bucket sort is stable depends on whether the sorting algorithm used within each bucket is stable.
+- **Time complexity of $O(n + k)$**: Assuming the elements are evenly distributed among the buckets, then the number of elements in each bucket is $\frac{n}{k}$. Assuming sorting a single bucket uses $O(\frac{n}{k} \log\frac{n}{k})$ time, then sorting all buckets uses $O(n \log\frac{n}{k})$ time. **When the number of buckets $k$ is relatively large, the time complexity approaches $O(n)$**. Merging results requires traversing all buckets and elements, taking $O(n + k)$ time. In the worst case, all data is distributed into one bucket, and sorting that bucket uses $O(n^2)$ time.
+- **Space complexity of $O(n + k)$, non-in-place sorting**: Additional space is required for $k$ buckets and a total of $n$ elements.
+- Whether bucket sort is stable depends on whether the algorithm for sorting elements within buckets is stable.
## How to achieve even distribution
-The theoretical time complexity of bucket sort can reach $O(n)$. **The key is to evenly distribute the elements across all buckets** as real-world data is often not uniformly distributed. For example, we may want to evenly distribute all products on eBay by price range into 10 buckets. However, the distribution of product prices may not be even, with many under $100 and few over $500. If the price range is evenly divided into 10, the difference in the number of products in each bucket will be significant.
+Theoretically, bucket sort can achieve $O(n)$ time complexity. **The key is to evenly distribute elements to each bucket**, because real data is often not evenly distributed. For example, if we want to evenly distribute all products on Taobao into 10 buckets by price range, there may be very many products below 100 yuan and very few above 1000 yuan. If the price intervals are evenly divided into 10, the difference in the number of products in each bucket will be very large.
-To achieve even distribution, we can initially set an approximate boundary to roughly divide the data into 3 buckets. **After the distribution is complete, the buckets with more items can be further divided into 3 buckets, until the number of elements in all buckets is roughly equal**.
+To achieve even distribution, we can first set an approximate dividing line to roughly divide the data into 3 buckets. **After distribution is complete, continue dividing buckets with more products into 3 buckets until the number of elements in all buckets is roughly equal**.
-As shown in the figure below, this method essentially constructs a recursive tree, aiming to ensure the element counts in leaf nodes are as even as possible. Of course, you don't have to divide the data into 3 buckets each round - the partitioning strategy can be adaptively tailored to the data's unique characteristics.
+As shown in the figure below, this method essentially creates a recursion tree, with the goal of making the values of leaf nodes as even as possible. Of course, it is not necessary to divide the data into 3 buckets every round; the specific division method can be flexibly chosen according to data characteristics.
-
+
-If we know the probability distribution of product prices in advance, **we can set the price boundaries for each bucket based on the data probability distribution**. It is worth noting that it is not necessarily required to specifically calculate the data distribution; instead, it can be approximated based on data characteristics using a probability model.
+If we know the probability distribution of product prices in advance, **we can set the price dividing line for each bucket based on the data probability distribution**. It is worth noting that the data distribution does not necessarily need to be specifically calculated, but can also be approximated using a certain probability model based on data characteristics.
-As shown in the figure below, assuming that product prices follow a normal distribution, we can define reasonable price intervals to balance the distribution of items across the buckets.
+As shown in the figure below, we assume that product prices follow a normal distribution, which allows us to reasonably set price intervals to evenly distribute products to each bucket.

diff --git a/en/docs/chapter_sorting/counting_sort.md b/en/docs/chapter_sorting/counting_sort.md
index 281b0d315..81a157a6a 100644
--- a/en/docs/chapter_sorting/counting_sort.md
+++ b/en/docs/chapter_sorting/counting_sort.md
@@ -1,18 +1,18 @@
# Counting sort
-Counting sort achieves sorting by counting the number of elements, usually applied to integer arrays.
+Counting sort (counting sort) achieves sorting by counting the number of elements, typically applied to integer arrays.
## Simple implementation
-Let's start with a simple example. Given an array `nums` of length $n$, where all elements are "non-negative integers", the overall process of counting sort is shown in the figure below.
+Let's start with a simple example. Given an array `nums` of length $n$, where the elements are all "non-negative integers", the overall flow of counting sort is shown in the figure below.
-1. Traverse the array to find the maximum number, denoted as $m$, then create an auxiliary array `counter` of length $m + 1$.
-2. **Use `counter` to count the occurrence of each number in `nums`**, where `counter[num]` corresponds to the occurrence of the number `num`. The counting method is simple, just traverse `nums` (suppose the current number is `num`), and increase `counter[num]` by $1$ each round.
-3. **Since the indices of `counter` are naturally ordered, all numbers are essentially sorted already**. Next, we traverse `counter`, and fill in `nums` in ascending order of occurrence.
+1. Traverse the array to find the largest number, denoted as $m$, and then create an auxiliary array `counter` of length $m + 1$.
+2. **Use `counter` to count the number of occurrences of each number in `nums`**, where `counter[num]` corresponds to the number of occurrences of the number `num`. The counting method is simple: just traverse `nums` (let the current number be `num`), and increase `counter[num]` by $1$ in each round.
+3. **Since each index of `counter` is naturally ordered, this is equivalent to all numbers being sorted**. Next, we traverse `counter` and fill in `nums` in ascending order based on the number of occurrences of each number.
-
+
-The code is shown below:
+The code is as follows:
```src
[file]{counting_sort}-[class]{}-[func]{counting_sort_naive}
@@ -20,27 +20,27 @@ The code is shown below:
!!! note "Connection between counting sort and bucket sort"
- From the perspective of bucket sort, we can consider each index of the counting array `counter` in counting sort as a bucket, and the process of counting as distributing elements into the corresponding buckets. Essentially, counting sort is a special case of bucket sort for integer data.
+ From the perspective of bucket sort, we can regard each index of the counting array `counter` in counting sort as a bucket, and the process of counting quantities as distributing each element to the corresponding bucket. Essentially, counting sort is a special case of bucket sort for integer data.
## Complete implementation
-Observant readers might notice, **if the input data is an object, the above step `3.` is invalid**. Suppose the input data is a product object, we want to sort the products by the price (a class member variable), but the above algorithm can only give the sorted price as the result.
+Observant readers may have noticed that **if the input data is objects, step `3.` above becomes invalid**. Suppose the input data is product objects, and we want to sort the products by price (a member variable of the class), but the above algorithm can only give the sorting result of prices.
-So how can we get the sorting result for the original data? First, we calculate the "prefix sum" of `counter`. As the name suggests, the prefix sum at index `i`, `prefix[i]`, equals the sum of the first `i` elements of the array:
+So how can we obtain the sorting result of the original data? We first calculate the "prefix sum" of `counter`. As the name suggests, the prefix sum at index `i`, `prefix[i]`, equals the sum of the first `i` elements of the array:
$$
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
$$
-**The prefix sum has a clear meaning, `prefix[num] - 1` represents the index of the last occurrence of element `num` in the result array `res`**. This information is crucial, as it tells us where each element should appear in the result array. Next, we traverse each element `num` of the original array `nums` in reverse order, performing the following two steps in each iteration.
+**The prefix sum has a clear meaning: `prefix[num] - 1` represents the index of the last occurrence of element `num` in the result array `res`**. This information is very critical because it tells us where each element should appear in the result array. Next, we traverse each element `num` of the original array `nums` in reverse order, performing the following two steps in each iteration.
-1. Fill `num` into the array `res` at the index `prefix[num] - 1`.
-2. Decrease the prefix sum `prefix[num]` by $1$ to obtain the next index to place `num`.
+1. Fill `num` into the array `res` at index `prefix[num] - 1`.
+2. Decrease the prefix sum `prefix[num]` by $1$ to get the index for the next placement of `num`.
-After the traversal, the array `res` contains the sorted result, and finally, `res` replaces the original array `nums`. The complete counting sort process is shown in the figure below.
+After the traversal is complete, the array `res` contains the sorted result, and finally `res` is used to overwrite the original array `nums`. The complete counting sort flow is shown in the figure below.
=== "<1>"
- 
+ 
=== "<2>"

@@ -63,7 +63,7 @@ After the traversal, the array `res` contains the sorted result, and finally, `r
=== "<8>"

-The implementation code of counting sort is shown below:
+The implementation code of counting sort is as follows:
```src
[file]{counting_sort}-[class]{}-[func]{counting_sort}
@@ -71,14 +71,14 @@ The implementation code of counting sort is shown below:
## Algorithm characteristics
-- **Time complexity is $O(n + m)$, non-adaptive sort**: It involves traversing `nums` and `counter`, both using linear time. Generally, $n \gg m$, and the time complexity tends towards $O(n)$.
-- **Space complexity is $O(n + m)$, non-in-place sort**: It uses array `res` of lengths $n$ and array `counter` of length $m$ respectively.
-- **Stable sort**: Since elements are filled into `res` in a "right-to-left" order, reversing the traversal of `nums` can prevent changing the relative position between equal elements, thereby achieving a stable sort. Actually, traversing `nums` in order can also produce the correct sorting result, but the outcome is unstable.
+- **Time complexity of $O(n + m)$, non-adaptive sorting**: Involves traversing `nums` and traversing `counter`, both using linear time. Generally, $n \gg m$, and time complexity tends toward $O(n)$.
+- **Space complexity of $O(n + m)$, non-in-place sorting**: Uses arrays `res` and `counter` of lengths $n$ and $m$ respectively.
+- **Stable sorting**: Since elements are filled into `res` in a "right-to-left" order, traversing `nums` in reverse can avoid changing the relative positions of equal elements, thereby achieving stable sorting. In fact, traversing `nums` in forward order can also yield correct sorting results, but the result would be unstable.
## Limitations
-By now, you might find counting sort very clever, as it can achieve efficient sorting merely by counting quantities. However, the prerequisites for using counting sort are relatively strict.
+By this point, you might think counting sort is very clever, as it can achieve efficient sorting just by counting quantities. However, the prerequisites for using counting sort are relatively strict.
-**Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that these data can be converted to non-negative integers without changing the original order of the elements. For example, for an array containing negative integers, you can first add a constant to all numbers, converting them all to positive numbers, and then convert them back after sorting is complete.
+**Counting sort is only suitable for non-negative integers**. If you want to apply it to other types of data, you need to ensure that the data can be converted to non-negative integers without changing the relative size relationships between elements. For example, for an integer array containing negative numbers, you can first add a constant to all numbers to convert them all to positive numbers, and then convert them back after sorting is complete.
-**Counting sort is suitable for large datasets with a small range of values**. For example, in the above example, $m$ should not be too large, otherwise, it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms.
+**Counting sort is suitable for situations where the data volume is large but the data range is small**. For example, in the above example, $m$ cannot be too large, otherwise it will occupy too much space. And when $n \ll m$, counting sort uses $O(m)$ time, which may be slower than $O(n \log n)$ sorting algorithms.
diff --git a/en/docs/chapter_sorting/heap_sort.md b/en/docs/chapter_sorting/heap_sort.md
index 0a735056a..18415f78e 100644
--- a/en/docs/chapter_sorting/heap_sort.md
+++ b/en/docs/chapter_sorting/heap_sort.md
@@ -4,28 +4,28 @@
Before reading this section, please ensure you have completed the "Heap" chapter.
-Heap sort is an efficient sorting algorithm based on the heap data structure. We can implement heap sort using the "heap creation" and "element extraction" operations we have already learned.
+Heap sort (heap sort) is an efficient sorting algorithm based on the heap data structure. We can use the "build heap operation" and "element out-heap operation" that we have already learned to implement heap sort.
-1. Input the array and construct a min-heap, where the smallest element is at the top of the heap.
-2. Continuously perform the extraction operation, record the extracted elements sequentially to obtain a sorted list from smallest to largest.
+1. Input the array and build a min-heap, at which point the smallest element is at the heap top.
+2. Continuously perform the out-heap operation, record the out-heap elements in sequence, and an ascending sorted sequence can be obtained.
-Although the above method is feasible, it requires an additional array to store the popped elements, which is somewhat space-consuming. In practice, we usually use a more elegant implementation.
+Although the above method is feasible, it requires an additional array to save the popped elements, which is quite wasteful of space. In practice, we usually use a more elegant implementation method.
## Algorithm flow
-Suppose the array length is $n$, the heap sort process is as follows.
+Assume the array length is $n$. The flow of heap sort is shown in the figure below.
-1. Input the array and establish a max-heap. After this step, the largest element is positioned at the top of the heap.
-2. Swap the top element of the heap (the first element) with the heap's bottom element (the last element). Following this swap, reduce the heap's length by $1$ and increase the sorted elements count by $1$.
-3. Starting from the heap top, perform the sift-down operation from top to bottom. After the sift-down, the heap's property is restored.
-4. Repeat steps `2.` and `3.` Loop for $n - 1$ rounds to complete the sorting of the array.
+1. Input the array and build a max-heap. After completion, the largest element is at the heap top.
+2. Swap the heap top element (first element) with the heap bottom element (last element). After the swap is complete, reduce the heap length by $1$ and increase the count of sorted elements by $1$.
+3. Starting from the heap top element, perform top-to-bottom heapify operation (sift down). After heapify is complete, the heap property is restored.
+4. Loop through steps `2.` and `3.` After looping $n - 1$ rounds, the array sorting can be completed.
!!! tip
- In fact, the element extraction operation also includes steps `2.` and `3.`, with an additional step to pop (remove) the extracted element from the heap.
+ In fact, the element out-heap operation also includes steps `2.` and `3.`, with just an additional step to pop the element.
=== "<1>"
- 
+ 
=== "<2>"

@@ -60,7 +60,7 @@ Suppose the array length is $n$, the heap sort process is as follows.
=== "<12>"

-In the code implementation, we used the sift-down function `sift_down()` from the "Heap" chapter. It is important to note that since the heap's length decreases as the maximum element is extracted, we need to add a length parameter $n$ to the `sift_down()` function to specify the current effective length of the heap. The code is shown below:
+In the code implementation, we use the same top-to-bottom heapify function `sift_down()` from the "Heap" chapter. It is worth noting that since the heap length will decrease as the largest element is extracted, we need to add a length parameter $n$ to the `sift_down()` function to specify the current effective length of the heap. The code is as follows:
```src
[file]{heap_sort}-[class]{}-[func]{heap_sort}
@@ -68,6 +68,6 @@ In the code implementation, we used the sift-down function `sift_down()` from th
## Algorithm characteristics
-- **Time complexity is $O(n \log n)$, non-adaptive sort**: The heap creation uses $O(n)$ time. Extracting the largest element from the heap takes $O(\log n)$ time, looping for $n - 1$ rounds.
-- **Space complexity is $O(1)$, in-place sort**: A few pointer variables use $O(1)$ space. The element swapping and heapifying operations are performed on the original array.
-- **Non-stable sort**: The relative positions of equal elements may change during the swapping of the heap's top and bottom elements.
+- **Time complexity of $O(n \log n)$, non-adaptive sorting**: The build heap operation uses $O(n)$ time. Extracting the largest element from the heap has a time complexity of $O(\log n)$, looping a total of $n - 1$ rounds.
+- **Space complexity of $O(1)$, in-place sorting**: A few pointer variables use $O(1)$ space. Element swapping and heapify operations are both performed on the original array.
+- **Non-stable sorting**: When swapping the heap top element and heap bottom element, the relative positions of equal elements may change.
diff --git a/en/docs/chapter_sorting/index.md b/en/docs/chapter_sorting/index.md
index 6694befbd..80fca2401 100644
--- a/en/docs/chapter_sorting/index.md
+++ b/en/docs/chapter_sorting/index.md
@@ -4,6 +4,6 @@
!!! abstract
- Sorting is like a magical key that turns chaos into order, enabling us to understand and handle data more efficiently.
+ Sorting is like a magic key that transforms chaos into order, enabling us to understand and process data more efficiently.
- Whether it's simple ascending order or complex categorical arrangements, sorting reveals the harmonious beauty of data.
+ Whether it's simple ascending order or complex categorized arrangements, sorting demonstrates the harmonious beauty of data.
diff --git a/en/docs/chapter_sorting/insertion_sort.md b/en/docs/chapter_sorting/insertion_sort.md
index aca1f8af1..19c9bbb55 100644
--- a/en/docs/chapter_sorting/insertion_sort.md
+++ b/en/docs/chapter_sorting/insertion_sort.md
@@ -1,23 +1,23 @@
# Insertion sort
-Insertion sort is a simple sorting algorithm that works very much like the process of manually sorting a deck of cards.
+Insertion sort (insertion sort) is a simple sorting algorithm that works very similarly to the process of manually organizing a deck of cards.
-Specifically, we select a base element from the unsorted interval, compare it with the elements in the sorted interval to its left, and insert the element into the correct position.
+Specifically, we select a base element from the unsorted interval, compare the element with elements in the sorted interval to its left one by one, and insert the element into the correct position.
-The figure below illustrates how an element is inserted into the array. Assuming the base element is `base`, we need to shift all elements from the target index up to `base` one position to the right, then assign `base` to the target index.
+The figure below shows the operation flow of inserting an element into the array. Let the base element be `base`. We need to move all elements from the target index to `base` one position to the right, and then assign `base` to the target index.

-## Algorithm process
+## Algorithm flow
-The overall process of insertion sort is shown in the figure below.
+The overall flow of insertion sort is shown in the figure below.
-1. Consider the first element of the array as sorted.
-2. Select the second element as `base`, insert it into its correct position, **leaving the first two elements sorted**.
-3. Select the third element as `base`, insert it into its correct position, **leaving the first three elements sorted**.
-4. Continuing in this manner, in the final iteration, the last element is taken as `base`, and after inserting it into the correct position, **all elements are sorted**.
+1. Initially, the first element of the array has completed sorting.
+2. Select the second element of the array as `base`, and after inserting it into the correct position, **the first 2 elements of the array are sorted**.
+3. Select the third element as `base`, and after inserting it into the correct position, **the first 3 elements of the array are sorted**.
+4. And so on. In the last round, select the last element as `base`, and after inserting it into the correct position, **all elements are sorted**.
-
+
Example code is as follows:
@@ -27,20 +27,20 @@ Example code is as follows:
## Algorithm characteristics
-- **Time complexity is $O(n^2)$, adaptive sorting**: In the worst case, each insertion operation requires $n - 1$, $n-2$, ..., $2$, $1$ loops, summing up to $(n - 1) n / 2$, thus the time complexity is $O(n^2)$. In the case of ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best time complexity of $O(n)$.
-- **Space complexity is $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space.
-- **Stable sorting**: During the insertion operation, we insert elements to the right of equal elements, not changing their order.
+- **Time complexity of $O(n^2)$, adaptive sorting**: In the worst case, each insertion operation requires loops of $n - 1$, $n-2$, $\dots$, $2$, $1$, summing to $(n - 1) n / 2$, so the time complexity is $O(n^2)$. When encountering ordered data, the insertion operation will terminate early. When the input array is completely ordered, insertion sort achieves the best-case time complexity of $O(n)$.
+- **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space.
+- **Stable sorting**: During the insertion operation process, we insert elements to the right of equal elements, without changing their order.
## Advantages of insertion sort
-The time complexity of insertion sort is $O(n^2)$, while the time complexity of quicksort, which we will study next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **it is usually faster in small input sizes**.
+The time complexity of insertion sort is $O(n^2)$, while the time complexity of quick sort, which we will learn about next, is $O(n \log n)$. Although insertion sort has a higher time complexity, **insertion sort is usually faster for smaller data volumes**.
-This conclusion is similar to that for linear and binary search. Algorithms like quicksort that have a time complexity of $O(n \log n)$ and are based on the divide-and-conquer strategy often involve more unit operations. For small input sizes, the numerical values of $n^2$ and $n \log n$ are close, and complexity does not dominate, with the number of unit operations per round playing a decisive role.
+This conclusion is similar to the applicable situations of linear search and binary search. Algorithms like quick sort with $O(n \log n)$ complexity are sorting algorithms based on divide-and-conquer strategy and often contain more unit computation operations. When the data volume is small, $n^2$ and $n \log n$ are numerically close, and complexity does not dominate; the number of unit operations per round plays a decisive role.
-In fact, many programming languages (such as Java) use insertion sort within their built-in sorting functions. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategies, such as quicksort; for short arrays, use insertion sort directly.
+In fact, the built-in sorting functions in many programming languages (such as Java) adopt insertion sort. The general approach is: for long arrays, use sorting algorithms based on divide-and-conquer strategy, such as quick sort; for short arrays, directly use insertion sort.
-Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in practice, **insertion sort is commonly used than bubble sort and selection sort**, mainly for the following reasons.
+Although bubble sort, selection sort, and insertion sort all have a time complexity of $O(n^2)$, in actual situations, **insertion sort is used significantly more frequently than bubble sort and selection sort**, mainly for the following reasons.
-- Bubble sort is based on element swapping, which requires the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, **the computational overhead of bubble sort is generally higher than that of insertion sort**.
-- The time complexity of selection sort is always $O(n^2)$. **Given a set of partially ordered data, insertion sort is usually more efficient than selection sort**.
+- Bubble sort is based on element swapping, requiring the use of a temporary variable, involving 3 unit operations; insertion sort is based on element assignment, requiring only 1 unit operation. Therefore, **the computational overhead of bubble sort is usually higher than that of insertion sort**.
+- Selection sort has a time complexity of $O(n^2)$ in any case. **If given a set of partially ordered data, insertion sort is usually more efficient than selection sort**.
- Selection sort is unstable and cannot be applied to multi-level sorting.
diff --git a/en/docs/chapter_sorting/merge_sort.md b/en/docs/chapter_sorting/merge_sort.md
index 7073ec387..0edd25c11 100644
--- a/en/docs/chapter_sorting/merge_sort.md
+++ b/en/docs/chapter_sorting/merge_sort.md
@@ -1,23 +1,23 @@
# Merge sort
-Merge sort is a sorting algorithm based on the divide-and-conquer strategy, involving the "divide" and "merge" phases shown in the figure below.
+Merge sort (merge sort) is a sorting algorithm based on the divide-and-conquer strategy, which includes the "divide" and "merge" phases shown in the figure below.
-1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into shorter arrays.
-2. **Merge phase**: Stop dividing when the length of the sub-array is 1, and then begin merging. The two shorter sorted arrays are continuously merged into a longer sorted array until the process is complete.
+1. **Divide phase**: Recursively split the array from the midpoint, transforming the sorting problem of a long array into the sorting problems of shorter arrays.
+2. **Merge phase**: When the sub-array length is 1, terminate the division and start merging, continuously merging two shorter sorted arrays into one longer sorted array until the process is complete.
-
+
-## Algorithm workflow
+## Algorithm flow
As shown in the figure below, the "divide phase" recursively splits the array from the midpoint into two sub-arrays from top to bottom.
-1. Calculate the midpoint `mid`, recursively divide the left sub-array (interval `[left, mid]`) and the right sub-array (interval `[mid + 1, right]`).
-2. Continue with step `1.` recursively until sub-array length becomes 1, then stops.
+1. Calculate the array midpoint `mid`, recursively divide the left sub-array (interval `[left, mid]`) and right sub-array (interval `[mid + 1, right]`).
+2. Recursively execute step `1.` until the sub-array interval length is 1, then terminate.
-The "merge phase" combines the left and right sub-arrays into a sorted array from bottom to top. It is important to note that, merging starts with sub-arrays of length 1, and each sub-array is sorted during the merge phase.
+The "merge phase" merges the left sub-array and right sub-array into a sorted array from bottom to top. Note that merging starts from sub-arrays of length 1, and each sub-array in the merge phase is sorted.
=== "<1>"
- 
+ 
=== "<2>"

@@ -46,12 +46,12 @@ The "merge phase" combines the left and right sub-arrays into a sorted array fro
=== "<10>"

-It can be observed that the order of recursion in merge sort is consistent with the post-order traversal of a binary tree.
+It can be observed that the recursive order of merge sort is consistent with the post-order traversal of a binary tree.
-- **Post-order traversal**: First recursively traverse the left subtree, then the right subtree, and finally process the root node.
-- **Merge sort**: First recursively process the left sub-array, then the right sub-array, and finally perform the merge.
+- **Post-order traversal**: First recursively traverse the left subtree, then recursively traverse the right subtree, and finally process the root node.
+- **Merge sort**: First recursively process the left sub-array, then recursively process the right sub-array, and finally perform the merge.
-The implementation of merge sort is shown in the following code. Note that the interval to be merged in `nums` is `[left, right]`, while the corresponding interval in `tmp` is `[0, right - left]`.
+The implementation of merge sort is shown in the code below. Note that the interval to be merged in `nums` is `[left, right]`, while the corresponding interval in `tmp` is `[0, right - left]`.
```src
[file]{merge_sort}-[class]{}-[func]{merge_sort}
@@ -59,15 +59,15 @@ The implementation of merge sort is shown in the following code. Note that the i
## Algorithm characteristics
-- **Time complexity of $O(n \log n)$, non-adaptive sort**: The division creates a recursion tree of height $\log n$, with each layer merging a total of $n$ operations, resulting in an overall time complexity of $O(n \log n)$.
-- **Space complexity of $O(n)$, non-in-place sort**: The recursion depth is $\log n$, using $O(\log n)$ stack frame space. The merging operation requires auxiliary arrays, using an additional space of $O(n)$.
-- **Stable sort**: During the merging process, the order of equal elements remains unchanged.
+- **Time complexity of $O(n \log n)$, non-adaptive sorting**: The division produces a recursion tree of height $\log n$, and the total number of merge operations at each level is $n$, so the overall time complexity is $O(n \log n)$.
+- **Space complexity of $O(n)$, non-in-place sorting**: The recursion depth is $\log n$, using $O(\log n)$ size of stack frame space. The merge operation requires the aid of an auxiliary array, using $O(n)$ size of additional space.
+- **Stable sorting**: In the merge process, the order of equal elements remains unchanged.
-## Linked List sorting
+## Linked list sorting
-For linked lists, merge sort has significant advantages over other sorting algorithms. **It can optimize the space complexity of the linked list sorting task to $O(1)$**.
+For linked lists, merge sort has significant advantages over other sorting algorithms, **and can optimize the space complexity of linked list sorting tasks to $O(1)$**.
-- **Divide phase**: "Iteration" can be used instead of "recursion" to perform the linked list division work, thus saving the stack frame space used by recursion.
-- **Merge phase**: In linked lists, node insertion and deletion operations can be achieved by changing references (pointers), so no extra lists need to be created during the merge phase (combining two short ordered lists into one long ordered list).
+- **Divide phase**: "Iteration" can be used instead of "recursion" to implement linked list division work, thus saving the stack frame space used by recursion.
+- **Merge phase**: In linked lists, node insertion and deletion operations can be achieved by just changing references (pointers), so there is no need to create additional linked lists during the merge phase (merging two short ordered linked lists into one long ordered linked list).
-The implementation details are relatively complex, and interested readers can consult related materials for learning.
+The specific implementation details are quite complex, and interested readers can consult related materials for learning.
diff --git a/en/docs/chapter_sorting/quick_sort.md b/en/docs/chapter_sorting/quick_sort.md
index 9ca1df7b9..cfc0121fa 100644
--- a/en/docs/chapter_sorting/quick_sort.md
+++ b/en/docs/chapter_sorting/quick_sort.md
@@ -1,15 +1,15 @@
# Quick sort
-Quick sort is a sorting algorithm based on the divide-and-conquer strategy, known for its efficiency and wide application.
+Quick sort (quick sort) is a sorting algorithm based on the divide-and-conquer strategy, which operates efficiently and is widely applied.
-The core operation of quick sort is "pivot partitioning," which aims to select an element from the array as the "pivot" and move all elements less than the pivot to its left side, while moving all elements greater than the pivot to its right side. Specifically, the process of pivot partitioning is illustrated in the figure below.
+The core operation of quick sort is "sentinel partitioning", which aims to: select a certain element in the array as the "pivot", move all elements smaller than the pivot to its left, and move elements larger than the pivot to its right. Specifically, the flow of sentinel partitioning is shown in the figure below.
-1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` to point to the two ends of the array respectively.
-2. Set up a loop where each round uses `i` (`j`) to search for the first element larger (smaller) than the pivot, then swap these two elements.
-3. Repeat step `2.` until `i` and `j` meet, finally swap the pivot to the boundary between the two sub-arrays.
+1. Select the leftmost element of the array as the pivot, and initialize two pointers `i` and `j` pointing to the two ends of the array.
+2. Set up a loop in which `i` (`j`) is used in each round to find the first element larger (smaller) than the pivot, and then swap these two elements.
+3. Loop through step `2.` until `i` and `j` meet, and finally swap the pivot to the boundary line of the two sub-arrays.
=== "<1>"
- 
+ 
=== "<2>"

@@ -35,66 +35,65 @@ The core operation of quick sort is "pivot partitioning," which aims to select a
=== "<9>"

-After the pivot partitioning, the original array is divided into three parts: left sub-array, pivot, and right sub-array, satisfying "any element in the left sub-array $\leq$ pivot $\leq$ any element in the right sub-array." Therefore, we then only need to sort these two sub-arrays.
+After sentinel partitioning is complete, the original array is divided into three parts: left sub-array, pivot, right sub-array, satisfying "any element in left sub-array $\leq$ pivot $\leq$ any element in right sub-array". Therefore, we next only need to sort these two sub-arrays.
-!!! note "Divide-and-conquer strategy for quick sort"
+!!! note "Divide-and-conquer strategy of quick sort"
- The essence of pivot partitioning is to simplify the sorting problem of a longer array into two shorter arrays.
+ The essence of sentinel partitioning is to simplify the sorting problem of a longer array into the sorting problems of two shorter arrays.
```src
[file]{quick_sort}-[class]{quick_sort}-[func]{partition}
```
-## Algorithm process
+## Algorithm flow
-The overall process of quick sort is shown in the figure below.
+The overall flow of quick sort is shown in the figure below.
-1. First, perform a "pivot partitioning" on the original array to obtain the unsorted left and right sub-arrays.
-2. Then, recursively perform "pivot partitioning" on the left and right sub-arrays separately.
-3. Continue recursively until the length of sub-array is 1, thus completing the sorting of the entire array.
+1. First, perform one "sentinel partitioning" on the original array to obtain the unsorted left sub-array and right sub-array.
+2. Then, recursively perform "sentinel partitioning" on the left sub-array and right sub-array respectively.
+3. Continue recursively until the sub-array length is 1, at which point sorting of the entire array is complete.
-
+
```src
[file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort}
```
-## Algorithm features
+## Algorithm characteristics
-- **Time complexity of $O(n \log n)$, non-adaptive sorting**: In average cases, the recursive levels of pivot partitioning are $\log n$, and the total number of loops per level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of pivot partitioning divides an array of length $n$ into two sub-arrays of lengths $0$ and $n - 1$, when the number of recursive levels reaches $n$, the number of loops in each level is $n$, and the total time used is $O(n^2)$.
-- **Space complexity of $O(n)$, in-place sorting**: In the case where the input array is completely reversed, the worst recursive depth reaches $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of additional arrays.
-- **Non-stable sorting**: In the final step of pivot partitioning, the pivot may be swapped to the right of equal elements.
+- **Time complexity of $O(n \log n)$, non-adaptive sorting**: In the average case, the number of recursive levels of sentinel partitioning is $\log n$, and the total number of loops at each level is $n$, using $O(n \log n)$ time overall. In the worst case, each round of sentinel partitioning divides an array of length $n$ into two sub-arrays of length $0$ and $n - 1$, at which point the number of recursive levels reaches $n$, the number of loops at each level is $n$, and the total time used is $O(n^2)$.
+- **Space complexity of $O(n)$, in-place sorting**: In the case where the input array is completely reversed, the worst recursive depth reaches $n$, using $O(n)$ stack frame space. The sorting operation is performed on the original array without the aid of an additional array.
+- **Non-stable sorting**: In the last step of sentinel partitioning, the pivot may be swapped to the right of equal elements.
## Why is quick sort fast
-As the name suggests, quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as that of "merge sort" and "heap sort," it is generally more efficient for the following reasons.
+From the name, we can see that quick sort should have certain advantages in terms of efficiency. Although the average time complexity of quick sort is the same as "merge sort" and "heap sort", quick sort is usually more efficient, mainly for the following reasons.
-- **Low probability of worst-case scenarios**: Although the worst time complexity of quick sort is $O(n^2)$, less stable than merge sort, in most cases, quick sort can operate under a time complexity of $O(n \log n)$.
-- **High cache utilization**: During the pivot partitioning operation, the system can load the entire sub-array into the cache, thus accessing elements more efficiently. In contrast, algorithms like "heap sort" need to access elements in a jumping manner, lacking this feature.
-- **Small constant coefficient of complexity**: Among the three algorithms mentioned above, quick sort has the least total number of operations such as comparisons, assignments, and swaps. This is similar to why "insertion sort" is faster than "bubble sort."
+- **The probability of the worst case occurring is very low**: Although the worst-case time complexity of quick sort is $O(n^2)$, which is not as stable as merge sort, in the vast majority of cases, quick sort can run with a time complexity of $O(n \log n)$.
+- **High cache utilization**: When performing sentinel partitioning operations, the system can load the entire sub-array into the cache, so element access efficiency is relatively high. Algorithms like "heap sort" require jump-style access to elements, thus lacking this characteristic.
+- **Small constant coefficient of complexity**: Among the three algorithms mentioned above, quick sort has the smallest total number of operations such as comparisons, assignments, and swaps. This is similar to the reason why "insertion sort" is faster than "bubble sort".
## Pivot optimization
-**Quick sort's time efficiency may degrade under certain inputs**. For example, if the input array is completely reversed, since we select the leftmost element as the pivot, after the pivot partitioning, the pivot is swapped to the array's right end, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. Continuing this way, each round of pivot partitioning will have a sub-array length of $0$, and the divide-and-conquer strategy fails, degrading quick sort to a form similar to "bubble sort."
+**Quick sort may have reduced time efficiency for certain inputs**. Take an extreme example: suppose the input array is completely reversed. Since we select the leftmost element as the pivot, after sentinel partitioning is complete, the pivot is swapped to the rightmost end of the array, causing the left sub-array length to be $n - 1$ and the right sub-array length to be $0$. If we recurse down like this, each round of sentinel partitioning will have a sub-array length of $0$, the divide-and-conquer strategy fails, and quick sort degrades to a form approximate to "bubble sort".
-To avoid this situation, **we can optimize the pivot selection strategy in the pivot partitioning**. For instance, we can randomly select an element as the pivot. However, if luck is not on our side, and we consistently select suboptimal pivots, the efficiency is still not satisfactory.
+To avoid this situation as much as possible, **we can optimize the pivot selection strategy in sentinel partitioning**. For example, we can randomly select an element as the pivot. However, if luck is not good and we select a non-ideal pivot every time, efficiency is still not satisfactory.
-It's important to note that programming languages usually generate "pseudo-random numbers". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade.
+It should be noted that programming languages usually generate "pseudo-random numbers". If we construct a specific test case for a pseudo-random number sequence, the efficiency of quick sort may still degrade.
-For further improvement, we can select three candidate elements (usually the first, last, and midpoint elements of the array), **and use the median of these three candidate elements as the pivot**. This way, the probability that the pivot is "neither too small nor too large" will be greatly increased. Of course, we can also select more candidate elements to further enhance robustness of the algorithm. With this method, the probability of the time complexity degrading to $O(n^2)$ is greatly reduced.
+For further improvement, we can select three candidate elements in the array (usually the first, last, and middle elements of the array), **and use the median of these three candidate elements as the pivot**. In this way, the probability that the pivot is "neither too small nor too large" will be greatly increased. Of course, we can also select more candidate elements to further improve the robustness of the algorithm. After adopting this method, the probability of time complexity degrading to $O(n^2)$ is greatly reduced.
-Sample code is as follows:
+Example code is as follows:
```src
[file]{quick_sort}-[class]{quick_sort_median}-[func]{partition}
```
-## Tail recursion optimization
+## Recursive depth optimization
-**Under certain inputs, quick sort may occupy more space**. For example, consider a completely ordered input array. Let the length of the sub-array in the recursion be $m$. In each round of pivot partitioning, a left sub-array of length $0$ and a right sub-array of length $m - 1$ are produced. This means that the problem size is reduced by only one element per recursive call, resulting in a very small reduction at each level of recursion.
-As a result, the height of the recursion tree can reach $n − 1$ , which requires $O(n)$ of stack frame space.
+**For certain inputs, quick sort may occupy more space**. Taking a completely ordered input array as an example, let the length of the sub-array in recursion be $m$. Each round of sentinel partitioning will produce a left sub-array of length $0$ and a right sub-array of length $m - 1$, which means that the problem scale reduced per recursive call is very small (only one element is reduced), and the height of the recursion tree will reach $n - 1$, at which point $O(n)$ size of stack frame space is required.
-To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of pivot sorting, **and only recursively sort the shorter sub-array**. Since the length of the shorter sub-array will not exceed $n / 2$, this method ensures that the recursion depth does not exceed $\log n$, thus optimizing the worst space complexity to $O(\log n)$. The code is as follows:
+To prevent the accumulation of stack frame space, we can compare the lengths of the two sub-arrays after each round of sentinel sorting is complete, **and only recurse on the shorter sub-array**. Since the length of the shorter sub-array will not exceed $n / 2$, this method can ensure that the recursion depth does not exceed $\log n$, thus optimizing the worst-case space complexity to $O(\log n)$. The code is as follows:
```src
[file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort}
diff --git a/en/docs/chapter_sorting/radix_sort.md b/en/docs/chapter_sorting/radix_sort.md
index 08612d796..39b9dff77 100644
--- a/en/docs/chapter_sorting/radix_sort.md
+++ b/en/docs/chapter_sorting/radix_sort.md
@@ -1,41 +1,41 @@
# Radix sort
-The previous section introduced counting sort, which is suitable for scenarios where the data size $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, where each ID is an $8$-digit number. This means the data range $m = 10^8$ is very large. Using counting sort in this case would require significant memory space. Radix sort can avoid this situation.
+The previous section introduced counting sort, which is suitable for situations where the data volume $n$ is large but the data range $m$ is small. Suppose we need to sort $n = 10^6$ student IDs, and the student ID is an 8-digit number, which means the data range $m = 10^8$ is very large. Using counting sort would require allocating a large amount of memory space, whereas radix sort can avoid this situation.
-Radix sort shares the same core concept as counting sort, which also sorts by counting the frequency of elements. Meanwhile, radix sort builds upon this by utilizing the progressive relationship between the digits of numbers. It processes and sorts the digits one at a time, achieving the final sorted order.
+Radix sort (radix sort) has a core idea consistent with counting sort, which also achieves sorting by counting quantities. Building on this, radix sort utilizes the progressive relationship between the digits of numbers, sorting each digit in turn to obtain the final sorting result.
-## Algorithm process
+## Algorithm flow
-Taking the student ID data as an example, assume the least significant digit is the $1^{st}$ and the most significant is the $8^{th}$, the radix sort process is illustrated in the figure below.
+Taking student ID data as an example, assume the lowest digit is the $1$st digit and the highest digit is the $8$th digit. The flow of radix sort is shown in the figure below.
-1. Initialize digit $k = 1$.
-2. Perform "counting sort" on the $k^{th}$ digit of the student IDs. After completion, the data will be sorted from smallest to largest based on the $k^{th}$ digit.
-3. Increment $k$ by $1$, then return to step `2.` and continue iterating until all digits have been sorted, at which point the process ends.
+1. Initialize the digit $k = 1$.
+2. Perform "counting sort" on the $k$th digit of the student IDs. After completion, the data will be sorted from smallest to largest according to the $k$th digit.
+3. Increase $k$ by $1$, then return to step `2.` and continue iterating until all digits are sorted, at which point the process ends.
-
+
-Below we dissect the code implementation. For a number $x$ in base $d$, to obtain its $k^{th}$ digit $x_k$, the following calculation formula can be used:
+Below we analyze the code implementation. For a $d$-base number $x$, to get its $k$th digit $x_k$, the following calculation formula can be used:
$$
x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d
$$
-Where $\lfloor a \rfloor$ denotes rounding down the floating point number $a$, and $\bmod \: d$ denotes taking the modulus of $d$. For student ID data, $d = 10$ and $k \in [1, 8]$.
+Where $\lfloor a \rfloor$ denotes rounding down the floating-point number $a$, and $\bmod \: d$ denotes taking the modulo (remainder) with respect to $d$. For student ID data, $d = 10$ and $k \in [1, 8]$.
-Additionally, we need to slightly modify the counting sort code to allow sorting based on the $k^{th}$ digit:
+Additionally, we need to slightly modify the counting sort code to make it sort based on the $k$th digit of the number:
```src
[file]{radix_sort}-[class]{}-[func]{radix_sort}
```
-!!! question "Why start sorting from the least significant digit?"
+!!! question "Why start sorting from the lowest digit?"
- In consecutive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the result of the first round is $a < b$ and the second round is $a > b$, the second round's result will replace the first round's result. Since higher-order digits take precedence over lower-order digits, it makes sense to sort the lower digits before the higher digits.
+ In successive sorting rounds, the result of a later round will override the result of an earlier round. For example, if the first round result is $a < b$, while the second round result is $a > b$, then the second round's result will replace the first round's result. Since higher-order digits have higher priority than lower-order digits, we should sort the lower digits first and then sort the higher digits.
## Algorithm characteristics
-Compared to counting sort, radix sort is suitable for larger numerical ranges, **but it assumes that the data can be represented in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are unsuitable for radix sort, as their digit count $k$ may be large, potentially leading to a time complexity $O(nk) \gg O(n^2)$.
+Compared to counting sort, radix sort is suitable for larger numerical ranges, **but the prerequisite is that the data must be representable in a fixed number of digits, and the number of digits should not be too large**. For example, floating-point numbers are not suitable for radix sort because their number of digits $k$ may be too large, potentially leading to time complexity $O(nk) \gg O(n^2)$.
-- **Time complexity is $O(nk)$, non-adaptive sorting**: Assuming the data size is $n$, the data is in base $d$, and the maximum number of digits is $k$, then sorting a single digit takes $O(n + d)$ time, and sorting all $k$ digits takes $O((n + d)k)$ time. Generally, both $d$ and $k$ are relatively small, leading to a time complexity approaching $O(n)$.
-- **Space complexity is $O(n + d)$, non-in-place sorting**: Like counting sort, radix sort relies on arrays `res` and `counter` of lengths $n$ and $d$ respectively.
-- **Stable sorting**: When counting sort is stable, radix sort is also stable; if counting sort is unstable, radix sort cannot ensure a correct sorting order.
+- **Time complexity of $O(nk)$, non-adaptive sorting**: Let the data volume be $n$, the data be in base $d$, and the maximum number of digits be $k$. Then performing counting sort on a certain digit uses $O(n + d)$ time, and sorting all $k$ digits uses $O((n + d)k)$ time. Typically, both $d$ and $k$ are relatively small, and the time complexity approaches $O(n)$.
+- **Space complexity of $O(n + d)$, non-in-place sorting**: Same as counting sort, radix sort requires auxiliary arrays `res` and `counter` of lengths $n$ and $d$.
+- **Stable sorting**: When counting sort is stable, radix sort is also stable; when counting sort is unstable, radix sort cannot guarantee obtaining correct sorting results.
diff --git a/en/docs/chapter_sorting/selection_sort.md b/en/docs/chapter_sorting/selection_sort.md
index 3b62a435b..03af7d454 100644
--- a/en/docs/chapter_sorting/selection_sort.md
+++ b/en/docs/chapter_sorting/selection_sort.md
@@ -1,17 +1,17 @@
# Selection sort
-Selection sort works on a very simple principle: it uses a loop where each iteration selects the smallest element from the unsorted interval and moves it to the end of the sorted section.
+Selection sort (selection sort) works very simply: it opens a loop, and in each round, selects the smallest element from the unsorted interval and places it at the end of the sorted interval.
-Suppose the length of the array is $n$, the steps of selection sort is shown in the figure below.
+Assume the array has length $n$. The algorithm flow of selection sort is shown in the figure below.
1. Initially, all elements are unsorted, i.e., the unsorted (index) interval is $[0, n-1]$.
-2. Select the smallest element in the interval $[0, n-1]$ and swap it with the element at index $0$. After this, the first element of the array is sorted.
-3. Select the smallest element in the interval $[1, n-1]$ and swap it with the element at index $1$. After this, the first two elements of the array are sorted.
-4. Continue in this manner. After $n - 1$ rounds of selection and swapping, the first $n - 1$ elements are sorted.
-5. The only remaining element is subsequently the largest element and does not need sorting, thus the array is sorted.
+2. Select the smallest element in the interval $[0, n-1]$ and swap it with the element at index $0$. After completion, the first element of the array is sorted.
+3. Select the smallest element in the interval $[1, n-1]$ and swap it with the element at index $1$. After completion, the first 2 elements of the array are sorted.
+4. And so on. After $n - 1$ rounds of selection and swapping, the first $n - 1$ elements of the array are sorted.
+5. The only remaining element must be the largest element, requiring no sorting, so the array sorting is complete.
=== "<1>"
- 
+ 
=== "<2>"

@@ -51,8 +51,8 @@ In the code, we use $k$ to record the smallest element within the unsorted inter
## Algorithm characteristics
-- **Time complexity of $O(n^2)$, non-adaptive sort**: There are $n - 1$ iterations in the outer loop, with the length of the unsorted section starting at $n$ in the first iteration and decreasing to $2$ in the last iteration, i.e., each outer loop iterations contain $n$, $n - 1$, $\dots$, $3$, $2$ inner loop iterations respectively, summing up to $\frac{(n - 1)(n + 2)}{2}$.
-- **Space complexity of $O(1)$, in-place sort**: Uses constant extra space with pointers $i$ and $j$.
-- **Non-stable sort**: As shown in the figure below, an element `nums[i]` may be swapped to the right of an equal element, causing their relative order to change.
+- **Time complexity of $O(n^2)$, non-adaptive sorting**: The outer loop has $n - 1$ rounds in total. The length of the unsorted interval in the first round is $n$, and the length of the unsorted interval in the last round is $2$. That is, each round of the outer loop contains $n$, $n - 1$, $\dots$, $3$, $2$ inner loop iterations, summing to $\frac{(n - 1)(n + 2)}{2}$.
+- **Space complexity of $O(1)$, in-place sorting**: Pointers $i$ and $j$ use a constant amount of extra space.
+- **Non-stable sorting**: As shown in the figure below, element `nums[i]` may be swapped to the right of an element equal to it, causing a change in their relative order.
-
+
diff --git a/en/docs/chapter_sorting/sorting_algorithm.md b/en/docs/chapter_sorting/sorting_algorithm.md
index 5980af21c..d93ff3082 100644
--- a/en/docs/chapter_sorting/sorting_algorithm.md
+++ b/en/docs/chapter_sorting/sorting_algorithm.md
@@ -1,20 +1,20 @@
-# Sorting algorithms
+# Sorting algorithm
-Sorting algorithms are used to arrange a set of data in a specific order. Sorting algorithms have a wide range of applications because ordered data can usually be searched, analyzed, and processed more efficiently.
+Sorting algorithm (sorting algorithm) is used to arrange a group of data in a specific order. Sorting algorithms have extensive applications because ordered data can usually be searched, analyzed, and processed more efficiently.
-As shown in the figure below, the data types in sorting algorithms can be integers, floating point numbers, characters, or strings, etc. Sorting criterion can be set according to needs, such as numerical size, character ASCII order, or custom criterion.
+As shown in the figure below, data types in sorting algorithms can be integers, floating-point numbers, characters, or strings, etc. The sorting criterion can be set according to requirements, such as numerical size, character ASCII code order, or custom rules.
-
+
## Evaluation dimensions
-**Execution efficiency**: We expect the time complexity of sorting algorithms to be as low as possible, as well as a lower number of overall operations (lowering the constant term of time complexity). For large data volumes, execution efficiency is particularly important.
+**Execution efficiency**: We expect the time complexity of sorting algorithms to be as low as possible, with a smaller total number of operations (reducing the constant factor in time complexity). For large data volumes, execution efficiency is particularly important.
-**In-place property**: As the name implies, in-place sorting is achieved by directly manipulating the original array, without the need for additional helper arrays, thus saving memory. Generally, in-place sorting involves fewer data moving operations and is faster.
+**In-place property**: As the name implies, in-place sorting achieves sorting by operating directly on the original array without requiring additional auxiliary arrays, thus saving memory. Typically, in-place sorting involves fewer data movement operations and runs faster.
-**Stability**: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting.
+**Stability**: Stable sorting ensures that the relative order of equal elements in the array does not change after sorting is completed.
-Stable sorting is a necessary condition for multi-key sorting scenarios. Suppose we have a table storing student information, with the first and second columns being name and age, respectively. In this case, unstable sorting might lead to a loss of order in the input data:
+Stable sorting is a necessary condition for multi-level sorting scenarios. Suppose we have a table storing student information, where column 1 and column 2 are name and age, respectively. In this case, unstable sorting may cause the ordered nature of the input data to be lost:
```shell
# Input data is sorted by name
@@ -25,9 +25,9 @@ Stable sorting is a necessary condition for multi-key sorting scenarios. Suppose
('D', 19)
('E', 23)
-# Assuming an unstable sorting algorithm is used to sort the list by age,
-# the result changes the relative position of ('D', 19) and ('A', 19),
-# and the property of the input data being sorted by name is lost
+# Assuming we use an unstable sorting algorithm to sort the list by age,
+# in the result, the relative positions of ('D', 19) and ('A', 19) are changed,
+# and the property that the input data is sorted by name is lost
('B', 18)
('D', 19)
('A', 19)
@@ -35,12 +35,12 @@ Stable sorting is a necessary condition for multi-key sorting scenarios. Suppose
('E', 23)
```
-**Adaptability**: Adaptive sorting leverages existing order information within the input data to reduce computational effort, achieving more optimal time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than their average-case time complexity.
+**Adaptability**: Adaptive sorting can utilize the existing order information in the input data to reduce the amount of computation, achieving better time efficiency. The best-case time complexity of adaptive sorting algorithms is typically better than the average time complexity.
-**Comparison or non-comparison-based**: Comparison-based sorting relies on comparison operators ($<$, $=$, $>$) to determine the relative order of elements and thus sort the entire array, with the theoretical optimal time complexity being $O(n \log n)$. Meanwhile, non-comparison sorting does not use comparison operators and can achieve a time complexity of $O(n)$, but its versatility is relatively poor.
+**Comparison-based or not**: Comparison-based sorting relies on comparison operators ($<$, $=$, $>$) to determine the relative order of elements, thereby sorting the entire array, with a theoretical optimal time complexity of $O(n \log n)$. Non-comparison sorting does not use comparison operators and can achieve a time complexity of $O(n)$, but its versatility is relatively limited.
## Ideal sorting algorithm
-**Fast execution, in-place, stable, adaptive, and versatile**. Clearly, no sorting algorithm that combines all these features has been found to date. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem.
+**Fast execution, in-place, stable, adaptive, good versatility**. Clearly, no sorting algorithm has been discovered to date that combines all of these characteristics. Therefore, when selecting a sorting algorithm, it is necessary to decide based on the specific characteristics of the data and the requirements of the problem.
-Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each based on the above evaluation dimensions.
+Next, we will learn about various sorting algorithms together and analyze the advantages and disadvantages of each sorting algorithm based on the above evaluation dimensions.
diff --git a/en/docs/chapter_sorting/summary.md b/en/docs/chapter_sorting/summary.md
index e367d48ef..5ea33fb81 100644
--- a/en/docs/chapter_sorting/summary.md
+++ b/en/docs/chapter_sorting/summary.md
@@ -2,46 +2,46 @@
### Key review
-- Bubble sort works by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to $O(n)$.
-- Insertion sort sorts each round by inserting elements from the unsorted interval into the correct position in the sorted interval. Although the time complexity of insertion sort is $O(n^2)$, it is very popular in sorting small amounts of data due to relatively fewer operations per unit.
-- Quick sort is based on sentinel partitioning operations. In sentinel partitioning, it's possible to always pick the worst pivot, leading to a time complexity degradation to $O(n^2)$. Introducing median or random pivots can reduce the probability of such degradation. Tail recursion effectively reduce the recursion depth, optimizing the space complexity to $O(\log n)$.
-- Merge sort includes dividing and merging two phases, typically embodying the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, resulting in a space complexity of $O(n)$; however, the space complexity for sorting a list can be optimized to $O(1)$.
-- Bucket sort consists of three steps: distributing data into buckets, sorting within each bucket, and merging results in bucket order. It also embodies the divide-and-conquer strategy, suitable for very large datasets. The key to bucket sort is the even distribution of data.
-- Counting sort is a variant of bucket sort, which sorts by counting the occurrences of each data point. Counting sort is suitable for large datasets with a limited range of data and requires data conversion to positive integers.
-- Radix sort processes data by sorting it digit by digit, requiring data to be represented as fixed-length numbers.
-- Overall, we seek sorting algorithm that has high efficiency, stability, in-place operation, and adaptability. However, like other data structures and algorithms, no sorting algorithm can meet all these conditions simultaneously. In practical applications, we need to choose the appropriate sorting algorithm based on the characteristics of the data.
-- The figure below compares mainstream sorting algorithms in terms of efficiency, stability, in-place nature, and adaptability.
+- Bubble sort achieves sorting by swapping adjacent elements. By adding a flag to enable early return, we can optimize the best-case time complexity of bubble sort to $O(n)$.
+- Insertion sort completes sorting by inserting elements from the unsorted interval into the correct position in the sorted interval each round. Although the time complexity of insertion sort is $O(n^2)$, it is very popular in small data volume sorting tasks because it involves relatively few unit operations.
+- Quick sort is implemented based on sentinel partitioning operations. In sentinel partitioning, it is possible to select the worst pivot every time, causing the time complexity to degrade to $O(n^2)$. Introducing median pivot or random pivot can reduce the probability of such degradation. By preferentially recursing on the shorter sub-interval, the recursion depth can be effectively reduced, optimizing the space complexity to $O(\log n)$.
+- Merge sort includes two phases: divide and merge, which typically embody the divide-and-conquer strategy. In merge sort, sorting an array requires creating auxiliary arrays, with a space complexity of $O(n)$; however, the space complexity of sorting a linked list can be optimized to $O(1)$.
+- Bucket sort consists of three steps: distributing data into buckets, sorting within buckets, and merging results. It also embodies the divide-and-conquer strategy and is suitable for very large data volumes. The key to bucket sort is distributing data evenly.
+- Counting sort is a special case of bucket sort, which achieves sorting by counting the number of occurrences of data. Counting sort is suitable for situations where the data volume is large but the data range is limited, and requires that data can be converted to positive integers.
+- Radix sort achieves data sorting by sorting digit by digit, requiring that data can be represented as fixed-digit numbers.
+- Overall, we hope to find a sorting algorithm that is efficient, stable, in-place, and adaptive, with good versatility. However, just like other data structures and algorithms, no sorting algorithm has been found so far that simultaneously possesses all these characteristics. In practical applications, we need to select the appropriate sorting algorithm based on the specific characteristics of the data.
+- The figure below compares mainstream sorting algorithms in terms of efficiency, stability, in-place property, and adaptability.
-
+
### Q & A
-**Q**: When is the stability of sorting algorithms necessary?
+**Q**: In what situations is the stability of sorting algorithms necessary?
-In reality, we might sort based on one attribute of an object. For example, students have names and heights as attributes, and we aim to implement multi-level sorting: first by name to get `(A, 180) (B, 185) (C, 170) (D, 170)`; then by height. Because the sorting algorithm is unstable, we might end up with `(D, 170) (C, 170) (A, 180) (B, 185)`.
+In reality, we may sort based on a certain attribute of objects. For example, students have two attributes: name and height. We want to implement multi-level sorting: first sort by name to get `(A, 180) (B, 185) (C, 170) (D, 170)`; then sort by height. Because the sorting algorithm is unstable, we may get `(D, 170) (C, 170) (A, 180) (B, 185)`.
-It can be seen that the positions of students D and C have been swapped, disrupting the orderliness of the names, which is undesirable.
+It can be seen that the positions of students D and C have been swapped, and the orderliness of names has been disrupted, which is something we don't want to see.
**Q**: Can the order of "searching from right to left" and "searching from left to right" in sentinel partitioning be swapped?
-No, when using the leftmost element as the pivot, we must first "search from right to left" then "search from left to right". This conclusion is somewhat counterintuitive, so let's analyze the reason.
+No. When we use the leftmost element as the pivot, we must first "search from right to left" and then "search from left to right". This conclusion is somewhat counterintuitive; let's analyze the reason.
-The last step of the sentinel partition `partition()` is to swap `nums[left]` and `nums[i]`. After the swap, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we "search from left to right" first, and if no element larger than the pivot is found, **we will exit the loop when `i == j`, possibly with `nums[j] == nums[i] > nums[left]`**. In other words, the final swap operation will exchange an element larger than the pivot to the left end of the array, causing the sentinel partition to fail.
+The last step of sentinel partitioning `partition()` is to swap `nums[left]` and `nums[i]`. After the swap is complete, the elements to the left of the pivot are all `<=` the pivot, **which requires that `nums[left] >= nums[i]` must hold before the last swap**. Suppose we first "search from left to right", then if we cannot find an element larger than the pivot, **we will exit the loop when `i == j`, at which point it may be that `nums[j] == nums[i] > nums[left]`**. In other words, the last swap operation will swap an element larger than the pivot to the leftmost end of the array, causing sentinel partitioning to fail.
-For example, given the array `[0, 0, 0, 0, 1]`, if we first "search from left to right", the array after the sentinel partition is `[1, 0, 0, 0, 0]`, which is incorrect.
+For example, given the array `[0, 0, 0, 0, 1]`, if we first "search from left to right", the array after sentinel partitioning is `[1, 0, 0, 0, 0]`, which is incorrect.
-Upon further consideration, if we choose `nums[right]` as the pivot, then exactly the opposite, we must first "search from left to right".
+Thinking deeper, if we select `nums[right]` as the pivot, then it's exactly the opposite - we must first "search from left to right".
-**Q**: Regarding tail recursion optimization, why does choosing the shorter array ensure that the recursion depth does not exceed $\log n$?
+**Q**: Regarding the optimization of recursion depth in quick sort, why can selecting the shorter array ensure that the recursion depth does not exceed $\log n$?
-The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partition divides the original array into two subarrays. With tail recursion optimization, the length of the subarray to be recursively followed is at most half of the original array length. Assuming the worst case always halves the length, the final recursion depth will be $\log n$.
+The recursion depth is the number of currently unreturned recursive methods. Each round of sentinel partitioning divides the original array into two sub-arrays. After recursion depth optimization, the length of the sub-array to be recursively processed is at most half of the original array length. Assuming the worst case is always half the length, the final recursion depth will be $\log n$.
-Reviewing the original quicksort, we might continuously recursively process larger arrays, in the worst case from $n$, $n - 1$, ..., $2$, $1$, with a recursion depth of $n$. Tail recursion optimization can avoid this scenario.
+Reviewing the original quick sort, we may continuously recurse on the longer array. In the worst case, it would be $n$, $n - 1$, $\dots$, $2$, $1$, with a recursion depth of $n$. Recursion depth optimization can avoid this situation.
-**Q**: When all elements in the array are equal, is the time complexity of quicksort $O(n^2)$? How should this degenerate case be handled?
+**Q**: When all elements in the array are equal, is the time complexity of quick sort $O(n^2)$? How should this degenerate case be handled?
-Yes. For this situation, consider using sentinel partitioning to divide the array into three parts: less than, equal to, and greater than the pivot. Only recursively proceed with the less than and greater than parts. In this method, an array where all input elements are equal can be sorted in just one round of sentinel partitioning.
+Yes. For this situation, consider partitioning the array into three parts through sentinel partitioning: less than, equal to, and greater than the pivot. Only recursively process the less than and greater than parts. Under this method, an array where all input elements are equal can complete sorting in just one round of sentinel partitioning.
**Q**: Why is the worst-case time complexity of bucket sort $O(n^2)$?
-In the worst case, all elements are placed in the same bucket. If we use an $O(n^2)$ algorithm to sort these elements, the time complexity will be $O(n^2)$.
+In the worst case, all elements are distributed into the same bucket. If we use an $O(n^2)$ algorithm to sort these elements, the time complexity will be $O(n^2)$.
diff --git a/en/docs/chapter_stack_and_queue/deque.md b/en/docs/chapter_stack_and_queue/deque.md
index b0d0e0ca7..3a87e195d 100644
--- a/en/docs/chapter_stack_and_queue/deque.md
+++ b/en/docs/chapter_stack_and_queue/deque.md
@@ -1,320 +1,320 @@
-# Double-ended queue
+# Deque
-In a queue, we can only delete elements from the head or add elements to the tail. As shown in the figure below, a double-ended queue (deque) offers more flexibility, allowing the addition or removal of elements at both the head and the tail.
+In a queue, we can only remove elements from the front or add elements at the rear. As shown in the figure below, a double-ended queue (deque) provides greater flexibility, allowing the addition or removal of elements at both the front and rear.
-
+
-## Common operations in double-ended queue
+## Common Deque Operations
-The common operations in a double-ended queue are listed below, and the names of specific methods depend on the programming language used.
+The common operations on a deque are shown in the table below. The specific method names depend on the programming language used.
- Table Efficiency of double-ended queue operations
+ Table Efficiency of Deque Operations
-| Method Name | Description | Time Complexity |
-| ------------- | -------------------------- | --------------- |
-| `pushFirst()` | Add an element to the head | $O(1)$ |
-| `pushLast()` | Add an element to the tail | $O(1)$ |
-| `popFirst()` | Remove the first element | $O(1)$ |
-| `popLast()` | Remove the last element | $O(1)$ |
-| `peekFirst()` | Access the first element | $O(1)$ |
-| `peekLast()` | Access the last element | $O(1)$ |
+| Method | Description | Time Complexity |
+| -------------- | ------------------------- | --------------- |
+| `push_first()` | Add element to front | $O(1)$ |
+| `push_last()` | Add element to rear | $O(1)$ |
+| `pop_first()` | Remove front element | $O(1)$ |
+| `pop_last()` | Remove rear element | $O(1)$ |
+| `peek_first()` | Access front element | $O(1)$ |
+| `peek_last()` | Access rear element | $O(1)$ |
-Similarly, we can directly use the double-ended queue classes implemented in programming languages:
+Similarly, we can directly use the deque classes already implemented in programming languages:
=== "Python"
```python title="deque.py"
from collections import deque
- # Initialize the deque
+ # Initialize deque
deq: deque[int] = deque()
# Enqueue elements
- deq.append(2) # Add to the tail
+ deq.append(2) # Add to rear
deq.append(5)
deq.append(4)
- deq.appendleft(3) # Add to the head
+ deq.appendleft(3) # Add to front
deq.appendleft(1)
# Access elements
- front: int = deq[0] # The first element
- rear: int = deq[-1] # The last element
+ front: int = deq[0] # Front element
+ rear: int = deq[-1] # Rear element
# Dequeue elements
- pop_front: int = deq.popleft() # The first element dequeued
- pop_rear: int = deq.pop() # The last element dequeued
+ pop_front: int = deq.popleft() # Front element dequeue
+ pop_rear: int = deq.pop() # Rear element dequeue
- # Get the length of the deque
+ # Get deque length
size: int = len(deq)
- # Check if the deque is empty
+ # Check if deque is empty
is_empty: bool = len(deq) == 0
```
=== "C++"
```cpp title="deque.cpp"
- /* Initialize the deque */
+ /* Initialize deque */
deque deque;
/* Enqueue elements */
- deque.push_back(2); // Add to the tail
+ deque.push_back(2); // Add to rear
deque.push_back(5);
deque.push_back(4);
- deque.push_front(3); // Add to the head
+ deque.push_front(3); // Add to front
deque.push_front(1);
/* Access elements */
- int front = deque.front(); // The first element
- int back = deque.back(); // The last element
+ int front = deque.front(); // Front element
+ int back = deque.back(); // Rear element
/* Dequeue elements */
- deque.pop_front(); // The first element dequeued
- deque.pop_back(); // The last element dequeued
+ deque.pop_front(); // Front element dequeue
+ deque.pop_back(); // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
int size = deque.size();
- /* Check if the deque is empty */
+ /* Check if deque is empty */
bool empty = deque.empty();
```
=== "Java"
```java title="deque.java"
- /* Initialize the deque */
+ /* Initialize deque */
Deque deque = new LinkedList<>();
/* Enqueue elements */
- deque.offerLast(2); // Add to the tail
+ deque.offerLast(2); // Add to rear
deque.offerLast(5);
deque.offerLast(4);
- deque.offerFirst(3); // Add to the head
+ deque.offerFirst(3); // Add to front
deque.offerFirst(1);
/* Access elements */
- int peekFirst = deque.peekFirst(); // The first element
- int peekLast = deque.peekLast(); // The last element
+ int peekFirst = deque.peekFirst(); // Front element
+ int peekLast = deque.peekLast(); // Rear element
/* Dequeue elements */
- int popFirst = deque.pollFirst(); // The first element dequeued
- int popLast = deque.pollLast(); // The last element dequeued
+ int popFirst = deque.pollFirst(); // Front element dequeue
+ int popLast = deque.pollLast(); // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
int size = deque.size();
- /* Check if the deque is empty */
+ /* Check if deque is empty */
boolean isEmpty = deque.isEmpty();
```
=== "C#"
```csharp title="deque.cs"
- /* Initialize the deque */
- // In C#, LinkedList is used as a deque
+ /* Initialize deque */
+ // In C#, use LinkedList as a deque
LinkedList deque = new();
/* Enqueue elements */
- deque.AddLast(2); // Add to the tail
+ deque.AddLast(2); // Add to rear
deque.AddLast(5);
deque.AddLast(4);
- deque.AddFirst(3); // Add to the head
+ deque.AddFirst(3); // Add to front
deque.AddFirst(1);
/* Access elements */
- int peekFirst = deque.First.Value; // The first element
- int peekLast = deque.Last.Value; // The last element
+ int peekFirst = deque.First.Value; // Front element
+ int peekLast = deque.Last.Value; // Rear element
/* Dequeue elements */
- deque.RemoveFirst(); // The first element dequeued
- deque.RemoveLast(); // The last element dequeued
+ deque.RemoveFirst(); // Front element dequeue
+ deque.RemoveLast(); // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
int size = deque.Count;
- /* Check if the deque is empty */
+ /* Check if deque is empty */
bool isEmpty = deque.Count == 0;
```
=== "Go"
```go title="deque_test.go"
- /* Initialize the deque */
+ /* Initialize deque */
// In Go, use list as a deque
deque := list.New()
/* Enqueue elements */
- deque.PushBack(2) // Add to the tail
+ deque.PushBack(2) // Add to rear
deque.PushBack(5)
deque.PushBack(4)
- deque.PushFront(3) // Add to the head
+ deque.PushFront(3) // Add to front
deque.PushFront(1)
/* Access elements */
- front := deque.Front() // The first element
- rear := deque.Back() // The last element
+ front := deque.Front() // Front element
+ rear := deque.Back() // Rear element
/* Dequeue elements */
- deque.Remove(front) // The first element dequeued
- deque.Remove(rear) // The last element dequeued
+ deque.Remove(front) // Front element dequeue
+ deque.Remove(rear) // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
size := deque.Len()
- /* Check if the deque is empty */
+ /* Check if deque is empty */
isEmpty := deque.Len() == 0
```
=== "Swift"
```swift title="deque.swift"
- /* Initialize the deque */
- // Swift does not have a built-in deque class, so Array can be used as a deque
+ /* Initialize deque */
+ // Swift does not have a built-in deque class, can use Array as a deque
var deque: [Int] = []
/* Enqueue elements */
- deque.append(2) // Add to the tail
+ deque.append(2) // Add to rear
deque.append(5)
deque.append(4)
- deque.insert(3, at: 0) // Add to the head
+ deque.insert(3, at: 0) // Add to front
deque.insert(1, at: 0)
/* Access elements */
- let peekFirst = deque.first! // The first element
- let peekLast = deque.last! // The last element
+ let peekFirst = deque.first! // Front element
+ let peekLast = deque.last! // Rear element
/* Dequeue elements */
- // Using Array, popFirst has a complexity of O(n)
- let popFirst = deque.removeFirst() // The first element dequeued
- let popLast = deque.removeLast() // The last element dequeued
+ // When using Array simulation, popFirst has O(n) complexity
+ let popFirst = deque.removeFirst() // Front element dequeue
+ let popLast = deque.removeLast() // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
let size = deque.count
- /* Check if the deque is empty */
+ /* Check if deque is empty */
let isEmpty = deque.isEmpty
```
=== "JS"
```javascript title="deque.js"
- /* Initialize the deque */
- // JavaScript does not have a built-in deque, so Array is used as a deque
+ /* Initialize deque */
+ // JavaScript does not have a built-in deque, can only use Array as a deque
const deque = [];
/* Enqueue elements */
deque.push(2);
deque.push(5);
deque.push(4);
- // Note that unshift() has a time complexity of O(n) as it's an array
+ // Please note that since it's an array, unshift() has O(n) time complexity
deque.unshift(3);
deque.unshift(1);
/* Access elements */
- const peekFirst = deque[0]; // The first element
- const peekLast = deque[deque.length - 1]; // The last element
+ const peekFirst = deque[0];
+ const peekLast = deque[deque.length - 1];
/* Dequeue elements */
- // Note that shift() has a time complexity of O(n) as it's an array
- const popFront = deque.shift(); // The first element dequeued
- const popBack = deque.pop(); // The last element dequeued
+ // Please note that since it's an array, shift() has O(n) time complexity
+ const popFront = deque.shift();
+ const popBack = deque.pop();
- /* Get the length of the deque */
+ /* Get deque length */
const size = deque.length;
- /* Check if the deque is empty */
+ /* Check if deque is empty */
const isEmpty = size === 0;
```
=== "TS"
```typescript title="deque.ts"
- /* Initialize the deque */
- // TypeScript does not have a built-in deque, so Array is used as a deque
+ /* Initialize deque */
+ // TypeScript does not have a built-in deque, can only use Array as a deque
const deque: number[] = [];
/* Enqueue elements */
deque.push(2);
deque.push(5);
deque.push(4);
- // Note that unshift() has a time complexity of O(n) as it's an array
+ // Please note that since it's an array, unshift() has O(n) time complexity
deque.unshift(3);
deque.unshift(1);
/* Access elements */
- const peekFirst: number = deque[0]; // The first element
- const peekLast: number = deque[deque.length - 1]; // The last element
+ const peekFirst: number = deque[0];
+ const peekLast: number = deque[deque.length - 1];
/* Dequeue elements */
- // Note that shift() has a time complexity of O(n) as it's an array
- const popFront: number = deque.shift() as number; // The first element dequeued
- const popBack: number = deque.pop() as number; // The last element dequeued
+ // Please note that since it's an array, shift() has O(n) time complexity
+ const popFront: number = deque.shift() as number;
+ const popBack: number = deque.pop() as number;
- /* Get the length of the deque */
+ /* Get deque length */
const size: number = deque.length;
- /* Check if the deque is empty */
+ /* Check if deque is empty */
const isEmpty: boolean = size === 0;
```
=== "Dart"
```dart title="deque.dart"
- /* Initialize the deque */
+ /* Initialize deque */
// In Dart, Queue is defined as a deque
Queue deque = Queue();
/* Enqueue elements */
- deque.addLast(2); // Add to the tail
+ deque.addLast(2); // Add to rear
deque.addLast(5);
deque.addLast(4);
- deque.addFirst(3); // Add to the head
+ deque.addFirst(3); // Add to front
deque.addFirst(1);
/* Access elements */
- int peekFirst = deque.first; // The first element
- int peekLast = deque.last; // The last element
+ int peekFirst = deque.first; // Front element
+ int peekLast = deque.last; // Rear element
/* Dequeue elements */
- int popFirst = deque.removeFirst(); // The first element dequeued
- int popLast = deque.removeLast(); // The last element dequeued
+ int popFirst = deque.removeFirst(); // Front element dequeue
+ int popLast = deque.removeLast(); // Rear element dequeue
- /* Get the length of the deque */
+ /* Get deque length */
int size = deque.length;
- /* Check if the deque is empty */
+ /* Check if deque is empty */
bool isEmpty = deque.isEmpty;
```
=== "Rust"
```rust title="deque.rs"
- /* Initialize the deque */
+ /* Initialize deque */
let mut deque: VecDeque = VecDeque::new();
/* Enqueue elements */
- deque.push_back(2); // Add to the tail
+ deque.push_back(2); // Add to rear
deque.push_back(5);
deque.push_back(4);
- deque.push_front(3); // Add to the head
+ deque.push_front(3); // Add to front
deque.push_front(1);
/* Access elements */
- if let Some(front) = deque.front() { // The first element
+ if let Some(front) = deque.front() { // Front element
}
- if let Some(rear) = deque.back() { // The last element
+ if let Some(rear) = deque.back() { // Rear element
}
/* Dequeue elements */
- if let Some(pop_front) = deque.pop_front() { // The first element dequeued
+ if let Some(pop_front) = deque.pop_front() { // Front element dequeue
}
- if let Some(pop_rear) = deque.pop_back() { // The last element dequeued
+ if let Some(pop_rear) = deque.pop_back() { // Rear element dequeue
}
- /* Get the length of the deque */
+ /* Get deque length */
let size = deque.len();
- /* Check if the deque is empty */
+ /* Check if deque is empty */
let is_empty = deque.is_empty();
```
@@ -327,7 +327,60 @@ Similarly, we can directly use the double-ended queue classes implemented in pro
=== "Kotlin"
```kotlin title="deque.kt"
+ /* Initialize deque */
+ val deque = LinkedList()
+ /* Enqueue elements */
+ deque.offerLast(2) // Add to rear
+ deque.offerLast(5)
+ deque.offerLast(4)
+ deque.offerFirst(3) // Add to front
+ deque.offerFirst(1)
+
+ /* Access elements */
+ val peekFirst = deque.peekFirst() // Front element
+ val peekLast = deque.peekLast() // Rear element
+
+ /* Dequeue elements */
+ val popFirst = deque.pollFirst() // Front element dequeue
+ val popLast = deque.pollLast() // Rear element dequeue
+
+ /* Get deque length */
+ val size = deque.size
+
+ /* Check if deque is empty */
+ val isEmpty = deque.isEmpty()
+ ```
+
+=== "Ruby"
+
+ ```ruby title="deque.rb"
+ # Initialize deque
+ # Ruby does not have a built-in deque, can only use Array as a deque
+ deque = []
+
+ # Enqueue elements
+ deque << 2
+ deque << 5
+ deque << 4
+ # Please note that since it's an array, Array#unshift has O(n) time complexity
+ deque.unshift(3)
+ deque.unshift(1)
+
+ # Access elements
+ peek_first = deque.first
+ peek_last = deque.last
+
+ # Dequeue elements
+ # Please note that since it's an array, Array#shift has O(n) time complexity
+ pop_front = deque.shift
+ pop_back = deque.pop
+
+ # Get deque length
+ size = deque.length
+
+ # Check if deque is empty
+ is_empty = size.zero?
```
=== "Zig"
@@ -336,70 +389,70 @@ Similarly, we can directly use the double-ended queue classes implemented in pro
```
-??? pythontutor "Visualizing Code"
+??? pythontutor "Visualize Execution"
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-## Implementing a double-ended queue *
+## Deque Implementation *
-The implementation of a double-ended queue is similar to that of a regular queue, it can be based on either a linked list or an array as the underlying data structure.
+The implementation of a deque is similar to that of a queue. You can choose either a linked list or an array as the underlying data structure.
-### Implementation based on doubly linked list
+### Doubly Linked List Implementation
-Recall from the previous section that we used a regular singly linked list to implement a queue, as it conveniently allows for deleting from the head (corresponding to the dequeue operation) and adding new elements after the tail (corresponding to the enqueue operation).
+Reviewing the previous section, we used a regular singly linked list to implement a queue because it conveniently allows deleting the head node (corresponding to dequeue) and adding new nodes after the tail node (corresponding to enqueue).
-For a double-ended queue, both the head and the tail can perform enqueue and dequeue operations. In other words, a double-ended queue needs to implement operations in the opposite direction as well. For this, we use a "doubly linked list" as the underlying data structure of the double-ended queue.
+For a deque, both the front and rear can perform enqueue and dequeue operations. In other words, a deque needs to implement operations in the opposite direction as well. For this reason, we use a "doubly linked list" as the underlying data structure for the deque.
-As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the double-ended queue, respectively, and implement the functionality to add and remove nodes at both ends.
+As shown in the figure below, we treat the head and tail nodes of the doubly linked list as the front and rear of the deque, implementing functionality to add and remove nodes at both ends.
=== "LinkedListDeque"
- 
+ 
-=== "pushLast()"
+=== "push_last()"

-=== "pushFirst()"
+=== "push_first()"

-=== "popLast()"
+=== "pop_last()"

-=== "popFirst()"
+=== "pop_first()"

-The implementation code is as follows:
+The implementation code is shown below:
```src
[file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{}
```
-### Implementation based on array
+### Array Implementation
-As shown in the figure below, similar to implementing a queue with an array, we can also use a circular array to implement a double-ended queue.
+As shown in the figure below, similar to implementing a queue based on an array, we can also use a circular array to implement a deque.
=== "ArrayDeque"
- 
+ 
-=== "pushLast()"
+=== "push_last()"

-=== "pushFirst()"
+=== "push_first()"

-=== "popLast()"
+=== "pop_last()"

-=== "popFirst()"
+=== "pop_first()"

-The implementation only needs to add methods for "front enqueue" and "rear dequeue":
+Based on the queue implementation, we only need to add methods for "enqueue at front" and "dequeue from rear":
```src
[file]{array_deque}-[class]{array_deque}-[func]{}
```
-## Applications of double-ended queue
+## Deque Applications
-The double-ended queue combines the logic of both stacks and queues, **thus, it can implement all their respective use cases while offering greater flexibility**.
+A deque combines the logic of both stacks and queues. **Therefore, it can implement all application scenarios of both, while providing greater flexibility**.
-We know that software's "undo" feature is typically implemented using a stack: the system `pushes` each change operation onto the stack and then `pops` to implement undoing. However, considering the limitations of system resources, software often restricts the number of undo steps (for example, only allowing the last 50 steps). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (the front of the queue). **But a regular stack cannot perform this function, where a double-ended queue becomes necessary**. Note that the core logic of "undo" still follows the Last-In-First-Out principle of a stack, but a double-ended queue can more flexibly implement some additional logic.
+We know that the "undo" function in software is typically implemented using a stack: the system pushes each change operation onto the stack and then implements undo through pop. However, considering system resource limitations, software usually limits the number of undo steps (for example, only allowing 50 steps to be saved). When the stack length exceeds 50, the software needs to perform a deletion operation at the bottom of the stack (front of the queue). **But a stack cannot implement this functionality, so a deque is needed to replace the stack**. Note that the core logic of "undo" still follows the LIFO principle of a stack; it's just that the deque can more flexibly implement some additional logic.
diff --git a/en/docs/chapter_stack_and_queue/index.md b/en/docs/chapter_stack_and_queue/index.md
index 7d2d4ff55..461d82a27 100644
--- a/en/docs/chapter_stack_and_queue/index.md
+++ b/en/docs/chapter_stack_and_queue/index.md
@@ -1,9 +1,9 @@
-# Stack and queue
+# Stack and Queue
-
+
!!! 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.
+ Stacks are like stacking cats, while queues are like cats lining up.
+
+ They represent LIFO (Last In First Out) and FIFO (First In First Out) logic, respectively.
diff --git a/en/docs/chapter_stack_and_queue/queue.md b/en/docs/chapter_stack_and_queue/queue.md
index 186f932d7..8f43c8d9b 100755
--- a/en/docs/chapter_stack_and_queue/queue.md
+++ b/en/docs/chapter_stack_and_queue/queue.md
@@ -1,22 +1,22 @@
# Queue
-A queue is a linear data structure that follows the First-In-First-Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers join the queue at the rear, and the person at the front leaves the queue first.
+A queue is a linear data structure that follows the First In First Out (FIFO) rule. As the name suggests, a queue simulates the phenomenon of lining up, where newcomers continuously join the end of the queue, while people at the front of the queue leave one by one.
-As shown in the figure below, we call the front of the queue the "head" and the back the "tail." The operation of adding elements to the rear of the queue is termed "enqueue," and the operation of removing elements from the front is termed "dequeue."
+As shown in the figure below, we call the front of the queue the "front" and the end the "rear." The operation of adding an element to the rear is called "enqueue," and the operation of removing the front element is called "dequeue."
-
+
-## Common operations on queue
+## Common Queue Operations
-The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. Here, we use the same naming convention as that used for stacks.
+The common operations on a queue are shown in the table below. Note that method names may vary across different programming languages. We adopt the same naming convention as for stacks here.
- Table Efficiency of queue operations
+ Table Efficiency of Queue Operations
-| Method Name | Description | Time Complexity |
-| ----------- | -------------------------------------- | --------------- |
-| `push()` | Enqueue an element, add it to the tail | $O(1)$ |
-| `pop()` | Dequeue the head element | $O(1)$ |
-| `peek()` | Access the head element | $O(1)$ |
+| Method | Description | Time Complexity |
+| -------- | ------------------------------------------ | --------------- |
+| `push()` | Enqueue element, add element to rear | $O(1)$ |
+| `pop()` | Dequeue front element | $O(1)$ |
+| `peek()` | Access front element | $O(1)$ |
We can directly use the ready-made queue classes in programming languages:
@@ -25,9 +25,9 @@ We can directly use the ready-made queue classes in programming languages:
```python title="queue.py"
from collections import deque
- # Initialize the queue
+ # Initialize queue
# In Python, we generally use the deque class as a queue
- # Although queue.Queue() is a pure queue class, it's not very user-friendly, so it's not recommended
+ # Although queue.Queue() is a pure queue class, it is not very user-friendly, so it is not recommended
que: deque[int] = deque()
# Enqueue elements
@@ -37,23 +37,23 @@ We can directly use the ready-made queue classes in programming languages:
que.append(5)
que.append(4)
- # Access the first element
+ # Access front element
front: int = que[0]
- # Dequeue an element
+ # Dequeue element
pop: int = que.popleft()
- # Get the length of the queue
+ # Get queue length
size: int = len(que)
- # Check if the queue is empty
+ # Check if queue is empty
is_empty: bool = len(que) == 0
```
=== "C++"
```cpp title="queue.cpp"
- /* Initialize the queue */
+ /* Initialize queue */
queue queue;
/* Enqueue elements */
@@ -63,23 +63,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
- /* Access the first element*/
+ /* Access front element */
int front = queue.front();
- /* Dequeue an element */
+ /* Dequeue element */
queue.pop();
- /* Get the length of the queue */
+ /* Get queue length */
int size = queue.size();
- /* Check if the queue is empty */
+ /* Check if queue is empty */
bool empty = queue.empty();
```
=== "Java"
```java title="queue.java"
- /* Initialize the queue */
+ /* Initialize queue */
Queue queue = new LinkedList<>();
/* Enqueue elements */
@@ -89,23 +89,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.offer(5);
queue.offer(4);
- /* Access the first element */
+ /* Access front element */
int peek = queue.peek();
- /* Dequeue an element */
+ /* Dequeue element */
int pop = queue.poll();
- /* Get the length of the queue */
+ /* Get queue length */
int size = queue.size();
- /* Check if the queue is empty */
+ /* Check if queue is empty */
boolean isEmpty = queue.isEmpty();
```
=== "C#"
```csharp title="queue.cs"
- /* Initialize the queue */
+ /* Initialize queue */
Queue queue = new();
/* Enqueue elements */
@@ -115,23 +115,23 @@ We can directly use the ready-made queue classes in programming languages:
queue.Enqueue(5);
queue.Enqueue(4);
- /* Access the first element */
+ /* Access front element */
int peek = queue.Peek();
- /* Dequeue an element */
+ /* Dequeue element */
int pop = queue.Dequeue();
- /* Get the length of the queue */
+ /* Get queue length */
int size = queue.Count;
- /* Check if the queue is empty */
+ /* Check if queue is empty */
bool isEmpty = queue.Count == 0;
```
=== "Go"
```go title="queue_test.go"
- /* Initialize the queue */
+ /* Initialize queue */
// In Go, use list as a queue
queue := list.New()
@@ -142,25 +142,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.PushBack(5)
queue.PushBack(4)
- /* Access the first element */
+ /* Access front element */
peek := queue.Front()
- /* Dequeue an element */
+ /* Dequeue element */
pop := queue.Front()
queue.Remove(pop)
- /* Get the length of the queue */
+ /* Get queue length */
size := queue.Len()
- /* Check if the queue is empty */
+ /* Check if queue is empty */
isEmpty := queue.Len() == 0
```
=== "Swift"
```swift title="queue.swift"
- /* Initialize the queue */
- // Swift does not have a built-in queue class, so Array can be used as a queue
+ /* Initialize queue */
+ // Swift does not have a built-in queue class, can use Array as a queue
var queue: [Int] = []
/* Enqueue elements */
@@ -170,25 +170,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.append(5)
queue.append(4)
- /* Access the first element */
+ /* Access front element */
let peek = queue.first!
- /* Dequeue an element */
- // Since it's an array, removeFirst has a complexity of O(n)
+ /* Dequeue element */
+ // Since it's an array, removeFirst has O(n) complexity
let pool = queue.removeFirst()
- /* Get the length of the queue */
+ /* Get queue length */
let size = queue.count
- /* Check if the queue is empty */
+ /* Check if queue is empty */
let isEmpty = queue.isEmpty
```
=== "JS"
```javascript title="queue.js"
- /* Initialize the queue */
- // JavaScript does not have a built-in queue, so Array can be used as a queue
+ /* Initialize queue */
+ // JavaScript does not have a built-in queue, can use Array as a queue
const queue = [];
/* Enqueue elements */
@@ -198,25 +198,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
- /* Access the first element */
+ /* Access front element */
const peek = queue[0];
- /* Dequeue an element */
- // Since the underlying structure is an array, shift() method has a time complexity of O(n)
+ /* Dequeue element */
+ // The underlying structure is an array, so shift() has O(n) time complexity
const pop = queue.shift();
- /* Get the length of the queue */
+ /* Get queue length */
const size = queue.length;
- /* Check if the queue is empty */
+ /* Check if queue is empty */
const empty = queue.length === 0;
```
=== "TS"
```typescript title="queue.ts"
- /* Initialize the queue */
- // TypeScript does not have a built-in queue, so Array can be used as a queue
+ /* Initialize queue */
+ // TypeScript does not have a built-in queue, can use Array as a queue
const queue: number[] = [];
/* Enqueue elements */
@@ -226,25 +226,25 @@ We can directly use the ready-made queue classes in programming languages:
queue.push(5);
queue.push(4);
- /* Access the first element */
+ /* Access front element */
const peek = queue[0];
- /* Dequeue an element */
- // Since the underlying structure is an array, shift() method has a time complexity of O(n)
+ /* Dequeue element */
+ // The underlying structure is an array, so shift() has O(n) time complexity
const pop = queue.shift();
- /* Get the length of the queue */
+ /* Get queue length */
const size = queue.length;
- /* Check if the queue is empty */
+ /* Check if queue is empty */
const empty = queue.length === 0;
```
=== "Dart"
```dart title="queue.dart"
- /* Initialize the queue */
- // In Dart, the Queue class is a double-ended queue but can be used as a queue
+ /* Initialize queue */
+ // In Dart, the Queue class is a deque and can also be used as a queue
Queue queue = Queue();
/* Enqueue elements */
@@ -254,24 +254,24 @@ We can directly use the ready-made queue classes in programming languages:
queue.add(5);
queue.add(4);
- /* Access the first element */
+ /* Access front element */
int peek = queue.first;
- /* Dequeue an element */
+ /* Dequeue element */
int pop = queue.removeFirst();
- /* Get the length of the queue */
+ /* Get queue length */
int size = queue.length;
- /* Check if the queue is empty */
+ /* Check if queue is empty */
bool isEmpty = queue.isEmpty;
```
=== "Rust"
```rust title="queue.rs"
- /* Initialize the double-ended queue */
- // In Rust, use a double-ended queue as a regular queue
+ /* Initialize deque */
+ // In Rust, use deque as a regular queue
let mut deque: VecDeque = VecDeque::new();
/* Enqueue elements */
@@ -281,18 +281,18 @@ We can directly use the ready-made queue classes in programming languages:
deque.push_back(5);
deque.push_back(4);
- /* Access the first element */
+ /* Access front element */
if let Some(front) = deque.front() {
}
- /* Dequeue an element */
+ /* Dequeue element */
if let Some(pop) = deque.pop_front() {
}
- /* Get the length of the queue */
+ /* Get queue length */
let size = deque.len();
- /* Check if the queue is empty */
+ /* Check if queue is empty */
let is_empty = deque.is_empty();
```
@@ -305,7 +305,55 @@ We can directly use the ready-made queue classes in programming languages:
=== "Kotlin"
```kotlin title="queue.kt"
+ /* Initialize queue */
+ val queue = LinkedList()
+ /* Enqueue elements */
+ queue.offer(1)
+ queue.offer(3)
+ queue.offer(2)
+ queue.offer(5)
+ queue.offer(4)
+
+ /* Access front element */
+ val peek = queue.peek()
+
+ /* Dequeue element */
+ val pop = queue.poll()
+
+ /* Get queue length */
+ val size = queue.size
+
+ /* Check if queue is empty */
+ val isEmpty = queue.isEmpty()
+ ```
+
+=== "Ruby"
+
+ ```ruby title="queue.rb"
+ # Initialize queue
+ # Ruby's built-in queue (Thread::Queue) does not have peek and traversal methods, can use Array as a queue
+ queue = []
+
+ # Enqueue elements
+ queue.push(1)
+ queue.push(3)
+ queue.push(2)
+ queue.push(5)
+ queue.push(4)
+
+ # Access front element
+ peek = queue.first
+
+ # Dequeue element
+ # Please note that since it's an array, Array#shift has O(n) time complexity
+ pop = queue.shift
+
+ # Get queue length
+ size = queue.length
+
+ # Check if queue is empty
+ is_empty = queue.empty?
```
=== "Zig"
@@ -314,20 +362,20 @@ We can directly use the ready-made queue classes in programming languages:
```
-??? pythontutor "Code Visualization"
+??? pythontutor "Visualize Execution"
https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-## Implementing a queue
+## Queue Implementation
-To implement a queue, we need a data structure that allows adding elements at one end and removing them at the other. Both linked lists and arrays meet this requirement.
+To implement a queue, we need a data structure that allows adding elements at one end and removing elements at the other end. Both linked lists and arrays meet this requirement.
-### Implementation based on a linked list
+### Linked List Implementation
-As shown in the figure below, we can consider the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively. It is stipulated that nodes can only be added at the rear and removed at the front.
+As shown in the figure below, we can treat the "head node" and "tail node" of a linked list as the "front" and "rear" of the queue, respectively, with the rule that nodes can only be added at the rear and removed from the front.
=== "LinkedListQueue"
- 
+ 
=== "push()"

@@ -341,21 +389,21 @@ Below is the code for implementing a queue using a linked list:
[file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{}
```
-### Implementation based on an array
+### Array Implementation
-Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, this problem can be cleverly avoided as follows.
+Deleting the first element in an array has a time complexity of $O(n)$, which would make the dequeue operation inefficient. However, we can use the following clever method to avoid this problem.
-We use a variable `front` to indicate the index of the front element and maintain a variable `size` to record the queue's length. Define `rear = front + size`, which points to the position immediately following the tail element.
+We can use a variable `front` to point to the index of the front element and maintain a variable `size` to record the queue length. We define `rear = front + size`, which calculates the position right after the rear element.
-With this design, **the effective interval of elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below.
+Based on this design, **the valid interval containing elements in the array is `[front, rear - 1]`**. The implementation methods for various operations are shown in the figure below:
- Enqueue operation: Assign the input element to the `rear` index and increase `size` by 1.
- Dequeue operation: Simply increase `front` by 1 and decrease `size` by 1.
-Both enqueue and dequeue operations only require a single operation, each with a time complexity of $O(1)$.
+As you can see, both enqueue and dequeue operations require only one operation, with a time complexity of $O(1)$.
=== "ArrayQueue"
- 
+ 
=== "push()"

@@ -363,19 +411,19 @@ Both enqueue and dequeue operations only require a single operation, each with a
=== "pop()"

-You might notice a problem: as enqueue and dequeue operations are continuously performed, both `front` and `rear` move to the right and **will eventually reach the end of the array and can't move further**. To resolve this, we can treat the array as a "circular array" where connecting the end of the array back to its beginning.
+You may notice a problem: as we continuously enqueue and dequeue, both `front` and `rear` move to the right. **When they reach the end of the array, they cannot continue moving**. To solve this problem, we can treat the array as a "circular array" with head and tail connected.
-In a circular array, `front` or `rear` needs to loop back to the start of the array upon reaching the end. This cyclical pattern can be achieved with a "modulo operation" as shown in the code below:
+For a circular array, we need to let `front` or `rear` wrap around to the beginning of the array when they cross the end. This periodic pattern can be implemented using the "modulo operation," as shown in the code below:
```src
[file]{array_queue}-[class]{array_queue}-[func]{}
```
-The above implementation of the queue still has its limitations: its length is fixed. However, this issue is not difficult to resolve. We can replace the array with a dynamic array that can expand itself if needed. Interested readers can try to implement this themselves.
+The queue implemented above still has limitations: its length is immutable. However, this problem is not difficult to solve. We can replace the array with a dynamic array to introduce an expansion mechanism. Interested readers can try to implement this themselves.
-The comparison of the two implementations is consistent with that of the stack and is not repeated here.
+The comparison conclusions for the two implementations are consistent with those for stacks and will not be repeated here.
-## Typical applications of queue
+## Typical Applications of Queue
-- **Amazon orders**: After shoppers place orders, these orders join a queue, and the system processes them in order. During events like Singles' Day, a massive number of orders are generated in a short time, making high concurrency a key challenge for engineers.
-- **Various to-do lists**: Any scenario requiring a "first-come, first-served" functionality, such as a printer's task queue or a restaurant's food delivery queue, can effectively maintain the order of processing with a queue.
+- **Taobao orders**. After shoppers place orders, the orders are added to a queue, and the system subsequently processes the orders in the queue according to their sequence. During Double Eleven, massive orders are generated in a short time, and high concurrency becomes a key challenge that engineers need to tackle.
+- **Various to-do tasks**. Any scenario that needs to implement "first come, first served" functionality, such as a printer's task queue or a restaurant's order queue, can effectively maintain the processing order using queues.
diff --git a/en/docs/chapter_stack_and_queue/stack.md b/en/docs/chapter_stack_and_queue/stack.md
index 0548ec18e..32f8f90d6 100755
--- a/en/docs/chapter_stack_and_queue/stack.md
+++ b/en/docs/chapter_stack_and_queue/stack.md
@@ -1,292 +1,292 @@
# Stack
-A stack is a linear data structure that follows the principle of Last-In-First-Out (LIFO).
+A stack is a linear data structure that follows the Last In First Out (LIFO) logic.
-We can compare a stack to a pile of plates on a table. To access the bottom plate, one must first remove the plates on top. By replacing the plates with various types of elements (such as integers, characters, objects, etc.), we obtain the data structure known as a stack.
+We can compare a stack to a pile of plates on a table. If we specify that only one plate can be moved at a time, then to get the bottom plate, we must first remove the plates above it one by one. If we replace the plates with various types of elements (such as integers, characters, objects, etc.), we get the stack data structure.
-As shown in the figure below, we refer to the top of the pile of elements as the "top of the stack" and the bottom as the "bottom of the stack." The operation of adding elements to the top of the stack is called "push," and the operation of removing the top element is called "pop."
+As shown in the figure below, we call the top of the stacked elements the "top" and the bottom the "base." The operation of adding an element to the top is called "push," and the operation of removing the top element is called "pop."
-
+
-## Common operations on stack
+## Common Stack Operations
-The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use `push()`, `pop()`, and `peek()` as examples.
+The common operations on a stack are shown in the table below. The specific method names depend on the programming language used. Here, we use the common naming convention of `push()`, `pop()`, and `peek()`.
- Table Efficiency of stack operations
+ Table Efficiency of Stack Operations
-| Method | Description | Time Complexity |
-| -------- | ----------------------------------------------- | --------------- |
-| `push()` | Push an element onto the stack (add to the top) | $O(1)$ |
-| `pop()` | Pop the top element from the stack | $O(1)$ |
-| `peek()` | Access the top element of the stack | $O(1)$ |
+| Method | Description | Time Complexity |
+| -------- | ---------------------------------------------- | --------------- |
+| `push()` | Push element onto stack (add to top) | $O(1)$ |
+| `pop()` | Pop top element from stack | $O(1)$ |
+| `peek()` | Access top element | $O(1)$ |
-Typically, we can directly use the stack class built into the programming language. However, some languages may not specifically provide a stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations that are not related to stack logic in the program.
+Typically, we can directly use the built-in stack class provided by the programming language. However, some languages may not provide a dedicated stack class. In these cases, we can use the language's "array" or "linked list" as a stack and ignore operations unrelated to the stack in the program logic.
=== "Python"
```python title="stack.py"
- # Initialize the stack
- # Python does not have a built-in stack class, so a list can be used as a stack
+ # Initialize stack
+ # Python does not have a built-in stack class, can use list as a stack
stack: list[int] = []
- # Push elements onto the stack
+ # Push elements
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
- # Access the top element of the stack
+ # Access top element
peek: int = stack[-1]
- # Pop an element from the stack
+ # Pop element
pop: int = stack.pop()
- # Get the length of the stack
+ # Get stack length
size: int = len(stack)
- # Check if the stack is empty
+ # Check if empty
is_empty: bool = len(stack) == 0
```
=== "C++"
```cpp title="stack.cpp"
- /* Initialize the stack */
+ /* Initialize stack */
stack stack;
- /* Push elements onto the stack */
+ /* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
- /* Access the top element of the stack */
+ /* Access top element */
int top = stack.top();
- /* Pop an element from the stack */
+ /* Pop element */
stack.pop(); // No return value
- /* Get the length of the stack */
+ /* Get stack length */
int size = stack.size();
- /* Check if the stack is empty */
+ /* Check if empty */
bool empty = stack.empty();
```
=== "Java"
```java title="stack.java"
- /* Initialize the stack */
+ /* Initialize stack */
Stack stack = new Stack<>();
- /* Push elements onto the stack */
+ /* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
- /* Access the top element of the stack */
+ /* Access top element */
int peek = stack.peek();
- /* Pop an element from the stack */
+ /* Pop element */
int pop = stack.pop();
- /* Get the length of the stack */
+ /* Get stack length */
int size = stack.size();
- /* Check if the stack is empty */
+ /* Check if empty */
boolean isEmpty = stack.isEmpty();
```
=== "C#"
```csharp title="stack.cs"
- /* Initialize the stack */
+ /* Initialize stack */
Stack stack = new();
- /* Push elements onto the stack */
+ /* Push elements */
stack.Push(1);
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(4);
- /* Access the top element of the stack */
+ /* Access top element */
int peek = stack.Peek();
- /* Pop an element from the stack */
+ /* Pop element */
int pop = stack.Pop();
- /* Get the length of the stack */
+ /* Get stack length */
int size = stack.Count;
- /* Check if the stack is empty */
+ /* Check if empty */
bool isEmpty = stack.Count == 0;
```
=== "Go"
```go title="stack_test.go"
- /* Initialize the stack */
- // In Go, it is recommended to use a Slice as a stack
+ /* Initialize stack */
+ // In Go, it is recommended to use Slice as a stack
var stack []int
- /* Push elements onto the stack */
+ /* Push elements */
stack = append(stack, 1)
stack = append(stack, 3)
stack = append(stack, 2)
stack = append(stack, 5)
stack = append(stack, 4)
- /* Access the top element of the stack */
+ /* Access top element */
peek := stack[len(stack)-1]
- /* Pop an element from the stack */
+ /* Pop element */
pop := stack[len(stack)-1]
stack = stack[:len(stack)-1]
- /* Get the length of the stack */
+ /* Get stack length */
size := len(stack)
- /* Check if the stack is empty */
+ /* Check if empty */
isEmpty := len(stack) == 0
```
=== "Swift"
```swift title="stack.swift"
- /* Initialize the stack */
- // Swift does not have a built-in stack class, so Array can be used as a stack
+ /* Initialize stack */
+ // Swift does not have a built-in stack class, can use Array as a stack
var stack: [Int] = []
- /* Push elements onto the stack */
+ /* Push elements */
stack.append(1)
stack.append(3)
stack.append(2)
stack.append(5)
stack.append(4)
- /* Access the top element of the stack */
+ /* Access top element */
let peek = stack.last!
- /* Pop an element from the stack */
+ /* Pop element */
let pop = stack.removeLast()
- /* Get the length of the stack */
+ /* Get stack length */
let size = stack.count
- /* Check if the stack is empty */
+ /* Check if empty */
let isEmpty = stack.isEmpty
```
=== "JS"
```javascript title="stack.js"
- /* Initialize the stack */
- // JavaScript does not have a built-in stack class, so Array can be used as a stack
+ /* Initialize stack */
+ // JavaScript does not have a built-in stack class, can use Array as a stack
const stack = [];
- /* Push elements onto the stack */
+ /* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
- /* Access the top element of the stack */
+ /* Access top element */
const peek = stack[stack.length-1];
- /* Pop an element from the stack */
+ /* Pop element */
const pop = stack.pop();
- /* Get the length of the stack */
+ /* Get stack length */
const size = stack.length;
- /* Check if the stack is empty */
+ /* Check if empty */
const is_empty = stack.length === 0;
```
=== "TS"
```typescript title="stack.ts"
- /* Initialize the stack */
- // TypeScript does not have a built-in stack class, so Array can be used as a stack
+ /* Initialize stack */
+ // TypeScript does not have a built-in stack class, can use Array as a stack
const stack: number[] = [];
- /* Push elements onto the stack */
+ /* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
- /* Access the top element of the stack */
+ /* Access top element */
const peek = stack[stack.length - 1];
- /* Pop an element from the stack */
+ /* Pop element */
const pop = stack.pop();
- /* Get the length of the stack */
+ /* Get stack length */
const size = stack.length;
- /* Check if the stack is empty */
+ /* Check if empty */
const is_empty = stack.length === 0;
```
=== "Dart"
```dart title="stack.dart"
- /* Initialize the stack */
- // Dart does not have a built-in stack class, so List can be used as a stack
+ /* Initialize stack */
+ // Dart does not have a built-in stack class, can use List as a stack
List stack = [];
- /* Push elements onto the stack */
+ /* Push elements */
stack.add(1);
stack.add(3);
stack.add(2);
stack.add(5);
stack.add(4);
- /* Access the top element of the stack */
+ /* Access top element */
int peek = stack.last;
- /* Pop an element from the stack */
+ /* Pop element */
int pop = stack.removeLast();
- /* Get the length of the stack */
+ /* Get stack length */
int size = stack.length;
- /* Check if the stack is empty */
+ /* Check if empty */
bool isEmpty = stack.isEmpty;
```
=== "Rust"
```rust title="stack.rs"
- /* Initialize the stack */
+ /* Initialize stack */
// Use Vec as a stack
let mut stack: Vec = Vec::new();
- /* Push elements onto the stack */
+ /* Push elements */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
- /* Access the top element of the stack */
+ /* Access top element */
let top = stack.last().unwrap();
- /* Pop an element from the stack */
+ /* Pop element */
let pop = stack.pop().unwrap();
- /* Get the length of the stack */
+ /* Get stack length */
let size = stack.len();
- /* Check if the stack is empty */
+ /* Check if empty */
let is_empty = stack.is_empty();
```
@@ -299,7 +299,54 @@ Typically, we can directly use the stack class built into the programming langua
=== "Kotlin"
```kotlin title="stack.kt"
+ /* Initialize stack */
+ val stack = Stack()
+ /* Push elements */
+ stack.push(1)
+ stack.push(3)
+ stack.push(2)
+ stack.push(5)
+ stack.push(4)
+
+ /* Access top element */
+ val peek = stack.peek()
+
+ /* Pop element */
+ val pop = stack.pop()
+
+ /* Get stack length */
+ val size = stack.size
+
+ /* Check if empty */
+ val isEmpty = stack.isEmpty()
+ ```
+
+=== "Ruby"
+
+ ```ruby title="stack.rb"
+ # Initialize stack
+ # Ruby does not have a built-in stack class, can use Array as a stack
+ stack = []
+
+ # Push elements
+ stack << 1
+ stack << 3
+ stack << 2
+ stack << 5
+ stack << 4
+
+ # Access top element
+ peek = stack.last
+
+ # Pop element
+ pop = stack.pop
+
+ # Get stack length
+ size = stack.length
+
+ # Check if empty
+ is_empty = stack.empty?
```
=== "Zig"
@@ -308,24 +355,24 @@ Typically, we can directly use the stack class built into the programming langua
```
-??? pythontutor "Code Visualization"
+??? pythontutor "Visualize Execution"
https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
-## Implementing a stack
+## Stack Implementation
To gain a deeper understanding of how a stack operates, let's try implementing a stack class ourselves.
-A stack follows the principle of Last-In-First-Out, which means we can only add or remove elements at the top of the stack. However, both arrays and linked lists allow adding and removing elements at any position, **therefore a stack can be seen as a restricted array or linked list**. In other words, we can "shield" certain irrelevant operations of an array or linked list, aligning their external behavior with the characteristics of a stack.
+A stack follows the LIFO principle, so we can only add or remove elements at the top. However, both arrays and linked lists allow adding and removing elements at any position. **Therefore, a stack can be viewed as a restricted array or linked list**. In other words, we can "shield" some irrelevant operations of arrays or linked lists so that their external logic conforms to the characteristics of a stack.
-### Implementation based on a linked list
+### Linked List Implementation
-When implementing a stack using a linked list, we can consider the head node of the list as the top of the stack and the tail node as the bottom of the stack.
+When implementing a stack using a linked list, we can treat the head node of the linked list as the top of the stack and the tail node as the base.
-As shown in the figure below, for the push operation, we simply insert elements at the head of the linked list. This method of node insertion is known as "head insertion." For the pop operation, we just need to remove the head node from the list.
+As shown in the figure below, for the push operation, we simply insert an element at the head of the linked list. This node insertion method is called the "head insertion method." For the pop operation, we just need to remove the head node from the linked list.
=== "LinkedListStack"
- 
+ 
=== "push()"

@@ -333,18 +380,18 @@ As shown in the figure below, for the push operation, we simply insert elements
=== "pop()"

-Below is an example code for implementing a stack based on a linked list:
+Below is sample code for implementing a stack based on a linked list:
```src
[file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{}
```
-### Implementation based on an array
+### Array Implementation
-When implementing a stack using an array, we can consider the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, respectively, both with a time complexity of $O(1)$.
+When implementing a stack using an array, we can treat the end of the array as the top of the stack. As shown in the figure below, push and pop operations correspond to adding and removing elements at the end of the array, both with a time complexity of $O(1)$.
=== "ArrayStack"
- 
+ 
=== "push()"

@@ -352,38 +399,38 @@ When implementing a stack using an array, we can consider the end of the array a
=== "pop()"

-Since the elements to be pushed onto the stack may continuously increase, we can use a dynamic array, thus avoiding the need to handle array expansion ourselves. Here is an example code:
+Since elements pushed onto the stack may increase continuously, we can use a dynamic array, which eliminates the need to handle array expansion ourselves. Here is the sample code:
```src
[file]{array_stack}-[class]{array_stack}-[func]{}
```
-## Comparison of the two implementations
+## Comparison of the Two Implementations
**Supported Operations**
-Both implementations support all the operations defined in a stack. The array implementation additionally supports random access, but this is beyond the scope of a stack definition and is generally not used.
+Both implementations support all operations defined by the stack. The array implementation additionally supports random access, but this goes beyond the stack definition and is generally not used.
**Time Efficiency**
-In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and therefore higher efficiency. However, if the push operation exceeds the array capacity, it triggers a resizing mechanism, making the time complexity of that push operation $O(n)$.
+In the array-based implementation, both push and pop operations occur in pre-allocated contiguous memory, which has good cache locality and is therefore more efficient. However, if pushing exceeds the array capacity, it triggers an expansion mechanism, causing the time complexity of that particular push operation to become $O(n)$.
-In the linked list implementation, list expansion is very flexible, and there is no efficiency decrease issue as in array expansion. However, the push operation requires initializing a node object and modifying pointers, so its efficiency is relatively lower. If the elements being pushed are already node objects, then the initialization step can be skipped, improving efficiency.
+In the linked list-based implementation, list expansion is very flexible, and there is no issue of reduced efficiency due to array expansion. However, the push operation requires initializing a node object and modifying pointers, so it is relatively less efficient. Nevertheless, if the pushed elements are already node objects, the initialization step can be omitted, thereby improving efficiency.
-Thus, when the elements for push and pop operations are basic data types like `int` or `double`, we can draw the following conclusions:
+In summary, when the elements pushed and popped are basic data types such as `int` or `double`, we can draw the following conclusions:
-- The array-based stack implementation's efficiency decreases during expansion, but since expansion is a low-frequency operation, its average efficiency is higher.
-- The linked list-based stack implementation provides more stable efficiency performance.
+- The array-based stack implementation has reduced efficiency when expansion is triggered, but since expansion is an infrequent operation, the average efficiency is higher.
+- The linked list-based stack implementation can provide more stable efficiency performance.
**Space Efficiency**
-When initializing a list, the system allocates an "initial capacity," which might exceed the actual need; moreover, the expansion mechanism usually increases capacity by a specific factor (like doubling), which may also exceed the actual need. Therefore, **the array-based stack might waste some space**.
+When initializing a list, the system allocates an "initial capacity" that may exceed the actual need. Additionally, the expansion mechanism typically expands at a specific ratio (e.g., 2x), and the capacity after expansion may also exceed actual needs. Therefore, **the array-based stack implementation may cause some space wastage**.
-However, since linked list nodes require extra space for storing pointers, **the space occupied by linked list nodes is relatively larger**.
+However, since linked list nodes need to store additional pointers, **the space occupied by linked list nodes is relatively large**.
-In summary, we cannot simply determine which implementation is more memory-efficient. It requires analysis based on specific circumstances.
+In summary, we cannot simply determine which implementation is more memory-efficient and need to analyze the specific situation.
-## Typical applications of stack
+## Typical Applications of Stack
-- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to go back to the previous page through the back operation, which is essentially a pop operation. To support both back and forward, two stacks are needed to work together.
-- **Memory management in programs**. Each time a function is called, the system adds a stack frame at the top of the stack to record the function's context information. In recursive functions, the downward recursion phase keeps pushing onto the stack, while the upward backtracking phase keeps popping from the stack.
+- **Back and forward in browsers, undo and redo in software**. Every time we open a new webpage, the browser pushes the previous page onto the stack, allowing us to return to the previous page via the back operation. The back operation is essentially performing a pop. To support both back and forward, two stacks are needed to work together.
+- **Program memory management**. Each time a function is called, the system adds a stack frame to the top of the stack to record the function's context information. During recursion, the downward recursive phase continuously performs push operations, while the upward backtracking phase continuously performs pop operations.
diff --git a/en/docs/chapter_stack_and_queue/summary.md b/en/docs/chapter_stack_and_queue/summary.md
index a76a81433..ff8551353 100644
--- a/en/docs/chapter_stack_and_queue/summary.md
+++ b/en/docs/chapter_stack_and_queue/summary.md
@@ -1,31 +1,31 @@
# Summary
-### Key review
+### 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.
+- A stack is a data structure that follows the LIFO principle and can be implemented using arrays or linked lists.
+- In terms of time efficiency, the array implementation of a stack has higher average efficiency, but during expansion, the time complexity of a single push operation degrades to $O(n)$. In contrast, the linked list implementation of a stack provides more stable efficiency performance.
+- In terms of space efficiency, the array implementation of a stack may lead to some degree of space wastage. However, it should be noted that the memory space occupied by linked list nodes is larger than that of array elements.
+- A queue is a data structure that follows the FIFO principle and can also be implemented using arrays or linked lists. The conclusions regarding time efficiency and space efficiency comparisons for queues are similar to those for stacks mentioned above.
+- A deque is a queue with greater flexibility that allows adding and removing elements at both ends.
### 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.
+The forward and backward functionality of a browser is essentially a manifestation of a "stack." When a user visits a new page, that page is added to the top of the stack; when the user clicks the back button, that page is popped from the top of the stack. Using a deque can conveniently implement some additional operations, as mentioned in the "Deque" section.
-**Q**: After popping from a stack, is it necessary to free the memory of the popped node?
+**Q**: After popping from the stack, do we need 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.
+If the popped node will still be needed later, then memory does not need to be freed. If it won't be used afterward, languages like Java and Python have automatic garbage collection, so manual memory deallocation is not required; in C and C++, manual memory deallocation is necessary.
-**Q**: A double-ended queue seems like two stacks joined together. What are its uses?
+**Q**: A deque seems like two stacks joined together. What is its purpose?
-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.
+A deque is like a combination of a stack and a queue, or two stacks joined together. It exhibits the logic of both stack and queue, so it can implement all applications of stacks and queues, and is more flexible.
-**Q**: How exactly are undo and redo implemented?
+**Q**: How are undo and redo specifically implemented?
-Undo and redo operations are implemented using two stacks: Stack `A` for undo and Stack `B` for redo.
+Use 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`.
+1. Whenever the user performs an operation, push this operation onto stack `A` and clear stack `B`.
+2. When the user performs "undo," pop the most recent operation from stack `A` and push it onto stack `B`.
+3. When the user performs "redo," pop the most recent operation from stack `B` and push it onto stack `A`.
diff --git a/en/docs/chapter_tree/array_representation_of_tree.md b/en/docs/chapter_tree/array_representation_of_tree.md
index a2e814a58..5c1bc0433 100644
--- a/en/docs/chapter_tree/array_representation_of_tree.md
+++ b/en/docs/chapter_tree/array_representation_of_tree.md
@@ -1,6 +1,6 @@
# Array representation of binary trees
-Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, with nodes connected by pointers. The basic operations of binary trees under the linked list representation were introduced in the previous section.
+Under the linked list representation, the storage unit of a binary tree is a node `TreeNode`, and nodes are connected by pointers. The previous section introduced the basic operations of binary trees under the linked list representation.
So, can we use an array to represent a binary tree? The answer is yes.
@@ -8,7 +8,7 @@ So, can we use an array to represent a binary tree? The answer is yes.
Let's analyze a simple case first. Given a perfect binary tree, we store all nodes in an array according to the order of level-order traversal, where each node corresponds to a unique array index.
-Based on the characteristics of level-order traversal, we can deduce a "mapping formula" between the index of a parent node and its children: **If a node's index is $i$, then the index of its left child is $2i + 1$ and the right child is $2i + 2$**. The figure below shows the mapping relationship between the indices of various nodes.
+Based on the characteristics of level-order traversal, we can derive a "mapping formula" between parent node index and child node indices: **If a node's index is $i$, then its left child index is $2i + 1$ and its right child index is $2i + 2$**. The figure below shows the mapping relationships between various node indices.

@@ -16,7 +16,7 @@ Based on the characteristics of level-order traversal, we can deduce a "mapping
## Representing any binary tree
-Perfect binary trees are a special case; there are often many `None` values in the middle levels of a binary tree. Since the sequence of level-order traversal does not include these `None` values, we cannot solely rely on this sequence to deduce the number and distribution of `None` values. **This means that multiple binary tree structures can match the same level-order traversal sequence**.
+Perfect binary trees are a special case; in the middle levels of a binary tree, there are typically many `None` values. Since the level-order traversal sequence does not include these `None` values, we cannot infer the number and distribution of `None` values based on this sequence alone. **This means multiple binary tree structures can correspond to the same level-order traversal sequence**.
As shown in the figure below, given a non-perfect binary tree, the above method of array representation fails.
@@ -117,13 +117,15 @@ To solve this problem, **we can consider explicitly writing out all `None` value
```kotlin title=""
/* Array representation of a binary tree */
// Using null to represent empty slots
- val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )
+ val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )
```
=== "Ruby"
```ruby title=""
-
+ ### Array representation of a binary tree ###
+ # Using nil to represent empty slots
+ tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
```
=== "Zig"
@@ -134,7 +136,7 @@ To solve this problem, **we can consider explicitly writing out all `None` value

-It's worth noting that **complete binary trees are very suitable for array representation**. Recalling the definition of a complete binary tree, `None` appears only at the bottom level and towards the right, **meaning all `None` values definitely appear at the end of the level-order traversal sequence**.
+It's worth noting that **complete binary trees are very well-suited for array representation**. Recalling the definition of a complete binary tree, `None` only appears at the bottom level and towards the right, **meaning all `None` values must appear at the end of the level-order traversal sequence**.
This means that when using an array to represent a complete binary tree, it's possible to omit storing all `None` values, which is very convenient. The figure below gives an example.
@@ -142,8 +144,8 @@ This means that when using an array to represent a complete binary tree, it's po
The following code implements a binary tree based on array representation, including the following operations:
-- Given a node, obtain its value, left (right) child node, and parent node.
-- Obtain the pre-order, in-order, post-order, and level-order traversal sequences.
+- Given a certain node, obtain its value, left (right) child node, and parent node.
+- Obtain the preorder, inorder, postorder, and level-order traversal sequences.
```src
[file]{array_binary_tree}-[class]{array_binary_tree}-[func]{}
@@ -153,12 +155,12 @@ The following code implements a binary tree based on array representation, inclu
The array representation of binary trees has the following advantages:
-- Arrays are stored in contiguous memory spaces, which is cache-friendly and allows for faster access and traversal.
+- Arrays are stored in contiguous memory space, which is cache-friendly, allowing faster access and traversal.
- It does not require storing pointers, which saves space.
- It allows random access to nodes.
However, the array representation also has some limitations:
- Array storage requires contiguous memory space, so it is not suitable for storing trees with a large amount of data.
-- Adding or deleting nodes requires array insertion and deletion operations, which are less efficient.
+- Adding or removing nodes requires array insertion and deletion operations, which have lower efficiency.
- When there are many `None` values in the binary tree, the proportion of node data contained in the array is low, leading to lower space utilization.
diff --git a/en/docs/chapter_tree/avl_tree.md b/en/docs/chapter_tree/avl_tree.md
index 68bd5e088..1d6d1ca56 100644
--- a/en/docs/chapter_tree/avl_tree.md
+++ b/en/docs/chapter_tree/avl_tree.md
@@ -1,6 +1,6 @@
# AVL tree *
-In the "Binary Search Tree" section, we mentioned that after multiple insertions and removals, a binary search tree might degrade to a linked list. In such cases, the time complexity of all operations degrades from $O(\log n)$ to $O(n)$.
+In the "Binary Search Tree" section, we mentioned that after multiple insertion and removal operations, a binary search tree may degenerate into a linked list. In this case, the time complexity of all operations degrades from $O(\log n)$ to $O(n)$.
As shown in the figure below, after two node removal operations, this binary search tree will degrade into a linked list.
@@ -10,11 +10,11 @@ For example, in the perfect binary tree shown in the figure below, after inserti

-In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL Tree in their paper "An algorithm for the organization of information". The paper detailed a series of operations to ensure that after continuously adding and removing nodes, the AVL tree would not degrade, thus maintaining the time complexity of various operations at $O(\log n)$ level. In other words, in scenarios where frequent additions, removals, searches, and modifications are needed, the AVL tree can always maintain efficient data operation performance, which has great application value.
+In 1962, G. M. Adelson-Velsky and E. M. Landis proposed the AVL tree in their paper "An algorithm for the organization of information". The paper described in detail a series of operations ensuring that after continuously adding and removing nodes, the AVL tree does not degenerate, thus keeping the time complexity of various operations at the $O(\log n)$ level. In other words, in scenarios requiring frequent insertions, deletions, searches, and modifications, the AVL tree can always maintain efficient data operation performance, making it very valuable in applications.
## Common terminology in AVL trees
-An AVL tree is both a binary search tree and a balanced binary tree, satisfying all properties of these two types of binary trees, hence it is a balanced binary search tree.
+An AVL tree is both a binary search tree and a balanced binary tree, simultaneously satisfying all the properties of these two types of binary trees, hence it is a balanced binary search tree.
### Node height
@@ -180,7 +180,7 @@ Since the operations related to AVL trees require obtaining node heights, we nee
```c title=""
/* AVL tree node */
- TreeNode struct TreeNode {
+ typedef struct TreeNode {
int val;
int height;
struct TreeNode *left;
@@ -214,7 +214,18 @@ Since the operations related to AVL trees require obtaining node heights, we nee
=== "Ruby"
```ruby title=""
+ ### AVL tree node class ###
+ class TreeNode
+ attr_accessor :val # Node value
+ attr_accessor :height # Node height
+ attr_accessor :left # Left child reference
+ attr_accessor :right # Right child reference
+ def initialize(val)
+ @val = val
+ @height = 0
+ end
+ end
```
=== "Zig"
@@ -231,7 +242,7 @@ The "node height" refers to the distance from that node to its farthest leaf nod
### Node balance factor
-The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, with the balance factor of a null node defined as $0$. We will also encapsulate the functionality of obtaining the node balance factor into a function for easy use later on:
+The balance factor of a node is defined as the height of the node's left subtree minus the height of its right subtree, and the balance factor of a null node is defined as $0$. We also encapsulate the function to obtain the node's balance factor for convenient subsequent use:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor}
@@ -243,13 +254,13 @@ The balance factor of a node is defined as the height of the node's left
## Rotations in AVL trees
-The characteristic feature of an AVL tree is the "rotation" operation, which can restore balance to an unbalanced node without affecting the in-order traversal sequence of the binary tree. In other words, **the rotation operation can maintain the property of a "binary search tree" while also turning the tree back into a "balanced binary tree"**.
+The characteristic of AVL trees lies in the "rotation" operation, which can restore balance to unbalanced nodes without affecting the inorder traversal sequence of the binary tree. In other words, **rotation operations can both maintain the property of a "binary search tree" and make the tree return to a "balanced binary tree"**.
-We call nodes with an absolute balance factor $> 1$ "unbalanced nodes". Depending on the type of imbalance, there are four kinds of rotations: right rotation, left rotation, right-left rotation, and left-right rotation. Below, we detail these rotation operations.
+We call nodes with a balance factor absolute value $> 1$ "unbalanced nodes". Depending on the imbalance situation, rotation operations are divided into four types: right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. Below we describe these rotation operations in detail.
### Right rotation
-As shown in the figure below, the first unbalanced node from the bottom up in the binary tree is "node 3". Focusing on the subtree with this unbalanced node as the root, denoted as `node`, and its left child as `child`, perform a "right rotation". After the right rotation, the subtree is balanced again while still maintaining the properties of a binary search tree.
+As shown in the figure below, the value below the node is the balance factor. From bottom to top, the first unbalanced node in the binary tree is "node 3". We focus on the subtree with this unbalanced node as the root, denoting the node as `node` and its left child as `child`, and perform a "right rotation" operation. After the right rotation is completed, the subtree regains balance and still maintains the properties of a binary search tree.
=== "<1>"

@@ -283,42 +294,42 @@ Similarly, as shown in the figure below, when the `child` node has a left child

-It can be observed that **the right and left rotation operations are logically symmetrical, and they solve two symmetrical types of imbalance**. Based on symmetry, by replacing all `left` with `right`, and all `right` with `left` in the implementation code of right rotation, we can get the implementation code for left rotation:
+It can be observed that **right rotation and left rotation operations are mirror symmetric in logic, and the two imbalance cases they solve are also symmetric**. Based on symmetry, we only need to replace all `left` in the right rotation implementation code with `right`, and all `right` with `left`, to obtain the left rotation implementation code:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate}
```
-### Left-right rotation
+### Left rotation then right rotation
-For the unbalanced node 3 shown in the figure below, using either left or right rotation alone cannot restore balance to the subtree. In this case, a "left rotation" needs to be performed on `child` first, followed by a "right rotation" on `node`.
+For the unbalanced node 3 in the figure below, using either left rotation or right rotation alone cannot restore the subtree to balance. In this case, a "left rotation" needs to be performed on `child` first, followed by a "right rotation" on `node`.

-### Right-left rotation
+### Right rotation then left rotation
-As shown in the figure below, for the mirror case of the above unbalanced binary tree, a "right rotation" needs to be performed on `child` first, followed by a "left rotation" on `node`.
+As shown in the figure below, for the mirror case of the above unbalanced binary tree, a "right rotation" needs to be performed on `child` first, then a "left rotation" on `node`.

### Choice of rotation
-The four kinds of imbalances shown in the figure below correspond to the cases described above, respectively requiring right rotation, left-right rotation, right-left rotation, and left rotation.
+The four imbalances shown in the figure below correspond one-to-one with the above cases, requiring right rotation, left rotation then right rotation, right rotation then left rotation, and left rotation operations respectively.

-As shown in the table below, we determine which of the above cases an unbalanced node belongs to by judging the sign of the balance factor of the unbalanced node and its higher-side child's balance factor.
+As shown in the table below, we determine which case the unbalanced node belongs to by judging the signs of the balance factor of the unbalanced node and the balance factor of its taller-side child node.
Table Conditions for Choosing Among the Four Rotation Cases
-| Balance factor of unbalanced node | Balance factor of child node | Rotation method to use |
-| --------------------------------- | ---------------------------- | --------------------------------- |
-| $> 1$ (Left-leaning tree) | $\geq 0$ | Right rotation |
-| $> 1$ (Left-leaning tree) | $<0$ | Left rotation then right rotation |
-| $< -1$ (Right-leaning tree) | $\leq 0$ | Left rotation |
-| $< -1$ (Right-leaning tree) | $>0$ | Right rotation then left rotation |
+| Balance factor of the unbalanced node | Balance factor of the child node | Rotation method to apply |
+| -------------------------------------- | --------------------------------- | --------------------------------- |
+| $> 1$ (left-leaning tree) | $\geq 0$ | Right rotation |
+| $> 1$ (left-leaning tree) | $<0$ | Left rotation then right rotation |
+| $< -1$ (right-leaning tree) | $\leq 0$ | Left rotation |
+| $< -1$ (right-leaning tree) | $>0$ | Right rotation then left rotation |
-For convenience, we encapsulate the rotation operations into a function. **With this function, we can perform rotations on various kinds of imbalances, restoring balance to unbalanced nodes**. The code is as follows:
+For ease of use, we encapsulate the rotation operations into a function. **With this function, we can perform rotations for various imbalance situations, restoring balance to unbalanced nodes**. The code is as follows:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{rotate}
@@ -328,7 +339,7 @@ For convenience, we encapsulate the rotation operations into a function. **With
### Node insertion
-The node insertion operation in AVL trees is similar to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear along the path from that node to the root node. Therefore, **we need to start from this node and perform rotation operations upwards to restore balance to all unbalanced nodes**. The code is as follows:
+The node insertion operation in AVL trees is similar in principle to that in binary search trees. The only difference is that after inserting a node in an AVL tree, a series of unbalanced nodes may appear on the path from that node to the root. Therefore, **we need to start from this node and perform rotation operations from bottom to top, restoring balance to all unbalanced nodes**. The code is as follows:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper}
@@ -336,7 +347,7 @@ The node insertion operation in AVL trees is similar to that in binary search tr
### Node removal
-Similarly, based on the method of removing nodes in binary search trees, rotation operations need to be performed from the bottom up to restore balance to all unbalanced nodes. The code is as follows:
+Similarly, on the basis of the binary search tree's node removal method, rotation operations need to be performed from bottom to top to restore balance to all unbalanced nodes. The code is as follows:
```src
[file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper}
@@ -344,10 +355,10 @@ Similarly, based on the method of removing nodes in binary search trees, rotatio
### Node search
-The node search operation in AVL trees is consistent with that in binary search trees and will not be detailed here.
+The node search operation in AVL trees is consistent with that in binary search trees, and will not be elaborated here.
## Typical applications of AVL trees
-- Organizing and storing large amounts of data, suitable for scenarios with high-frequency searches and low-frequency intertions and removals.
+- Organizing and storing large-scale data, suitable for scenarios with high-frequency searches and low-frequency insertions and deletions.
- Used to build index systems in databases.
-- Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balancing conditions, require fewer rotations for node insertion and removal, and have a higher average efficiency for node addition and removal operations.
+- Red-black trees are also a common type of balanced binary search tree. Compared to AVL trees, red-black trees have more relaxed balance conditions, require fewer rotation operations for node insertion and deletion, and have higher average efficiency for node addition and deletion operations.
diff --git a/en/docs/chapter_tree/binary_search_tree.md b/en/docs/chapter_tree/binary_search_tree.md
index 881973801..d9fd02a0a 100755
--- a/en/docs/chapter_tree/binary_search_tree.md
+++ b/en/docs/chapter_tree/binary_search_tree.md
@@ -13,7 +13,7 @@ We encapsulate the binary search tree as a class `BinarySearchTree` and declare
### Searching for a node
-Given a target node value `num`, one can search according to the properties of the binary search tree. As shown in the figure below, we declare a node `cur`, start from the binary tree's root node `root`, and loop to compare the size between the node value `cur.val` and `num`.
+Given a target node value `num`, we can search according to the properties of the binary search tree. As shown in the figure below, we declare a node `cur` and start from the binary tree's root node `root`, looping to compare the node value `cur.val` with `num`.
- If `cur.val < num`, it means the target node is in `cur`'s right subtree, thus execute `cur = cur.right`.
- If `cur.val > num`, it means the target node is in `cur`'s left subtree, thus execute `cur = cur.left`.
@@ -31,7 +31,7 @@ Given a target node value `num`, one can search according to the properties of t
=== "<4>"

-The search operation in a binary search tree works on the same principle as the binary search algorithm, eliminating half of the cases in each round. The number of loops is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. The example code is as follows:
+The search operation in a binary search tree works on the same principle as the binary search algorithm, both eliminating half of the cases in each round. The number of loop iterations is at most the height of the binary tree. When the binary tree is balanced, it uses $O(\log n)$ time. The example code is as follows:
```src
[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search}
@@ -39,17 +39,17 @@ The search operation in a binary search tree works on the same principle as the
### Inserting a node
-Given an element `num` to be inserted, to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion operation proceeds as shown in the figure below.
+Given an element `num` to be inserted, in order to maintain the property of the binary search tree "left subtree < root node < right subtree," the insertion process is as shown in the figure below.
-1. **Finding insertion position**: Similar to the search operation, start from the root node, loop downwards according to the size relationship between the current node value and `num`, until the leaf node is passed (traversed to `None`), then exit the loop.
-2. **Insert the node at this position**: Initialize the node `num` and place it where `None` was.
+1. **Finding the insertion position**: Similar to the search operation, start from the root node and loop downward searching according to the size relationship between the current node value and `num`, until passing the leaf node (traversing to `None`) and then exit the loop.
+2. **Insert the node at that position**: Initialize node `num` and place it at the `None` position.

-In the code implementation, note the following two points.
+In the code implementation, note the following two points:
-- The binary search tree does not allow duplicate nodes to exist; otherwise, its definition would be violated. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed, and the node returns directly.
-- To perform the insertion operation, we need to use the node `pre` to save the node from the previous loop. This way, when traversing to `None`, we can get its parent node, thus completing the node insertion operation.
+- Binary search trees do not allow duplicate nodes; otherwise, it would violate its definition. Therefore, if the node to be inserted already exists in the tree, the insertion is not performed and it returns directly.
+- To implement the node insertion, we need to use node `pre` to save the node from the previous loop iteration. This way, when traversing to `None`, we can obtain its parent node, thereby completing the node insertion operation.
```src
[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert}
@@ -59,7 +59,7 @@ Similar to searching for a node, inserting a node uses $O(\log n)$ time.
### Removing a node
-First, find the target node in the binary tree, then remove it. Similar to inserting a node, we need to ensure that after the removal operation is completed, the property of the binary search tree "left subtree < root node < right subtree" is still satisfied. Therefore, based on the number of child nodes of the target node, we divide it into three cases: 0, 1, and 2, and perform the corresponding node removal operations.
+First, find the target node in the binary tree, then remove it. Similar to node insertion, we need to ensure that after the removal operation is completed, the binary search tree's property of "left subtree $<$ root node $<$ right subtree" is still maintained. Therefore, depending on the number of child nodes the target node has, we divide it into 0, 1, and 2 three cases, and execute the corresponding node removal operations.
As shown in the figure below, when the degree of the node to be removed is $0$, it means the node is a leaf node and can be directly removed.
@@ -69,12 +69,12 @@ As shown in the figure below, when the degree of the node to be removed is $1$,

-When the degree of the node to be removed is $2$, we cannot remove it directly, but need to use a node to replace it. To maintain the property of the binary search tree "left subtree $<$ root node $<$ right subtree," **this node can be either the smallest node of the right subtree or the largest node of the left subtree**.
+When the degree of the node to be removed is $2$, we cannot directly remove it; instead, we need to use a node to replace it. To maintain the binary search tree's property of "left subtree $<$ root node $<$ right subtree," **this node can be either the smallest node in the right subtree or the largest node in the left subtree**.
-Assuming we choose the smallest node of the right subtree (the next node in in-order traversal), then the removal operation proceeds as shown in the figure below.
+Assuming we choose the smallest node in the right subtree (the next node in the inorder traversal), the removal process is as shown in the figure below.
-1. Find the next node in the "in-order traversal sequence" of the node to be removed, denoted as `tmp`.
-2. Replace the value of the node to be removed with `tmp`'s value, and recursively remove the node `tmp` in the tree.
+1. Find the next node of the node to be removed in the "inorder traversal sequence," denoted as `tmp`.
+2. Replace the value of the node to be removed with the value of `tmp`, and recursively remove node `tmp` in the tree.
=== "<1>"

@@ -88,25 +88,25 @@ Assuming we choose the smallest node of the right subtree (the next node in in-o
=== "<4>"

-The operation of removing a node also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the in-order traversal successor node requires $O(\log n)$ time. Example code is as follows:
+The node removal operation also uses $O(\log n)$ time, where finding the node to be removed requires $O(\log n)$ time, and obtaining the inorder successor node requires $O(\log n)$ time. Example code is as follows:
```src
[file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove}
```
-### In-order traversal is ordered
+### Inorder traversal is ordered
-As shown in the figure below, the in-order traversal of a binary tree follows the traversal order of "left $\rightarrow$ root $\rightarrow$ right," and a binary search tree satisfies the size relationship of "left child node $<$ root node $<$ right child node."
+As shown in the figure below, the inorder traversal of a binary tree follows the "left $\rightarrow$ root $\rightarrow$ right" traversal order, while the binary search tree satisfies the "left child node $<$ root node $<$ right child node" size relationship.
-This means that when performing in-order traversal in a binary search tree, the next smallest node will always be traversed first, thus leading to an important property: **The sequence of in-order traversal in a binary search tree is ascending**.
+This means that when performing an inorder traversal in a binary search tree, the next smallest node is always traversed first, thus yielding an important property: **The inorder traversal sequence of a binary search tree is ascending**.
-Using the ascending property of in-order traversal, obtaining ordered data in a binary search tree requires only $O(n)$ time, without the need for additional sorting operations, which is very efficient.
+Using the property of inorder traversal being ascending, we can obtain ordered data in a binary search tree in only $O(n)$ time, without the need for additional sorting operations, which is very efficient.
-
+
## Efficiency of binary search trees
-Given a set of data, we consider using an array or a binary search tree for storage. Observing the table below, the operations on a binary search tree all have logarithmic time complexity, which is stable and efficient. Arrays are more efficient than binary search trees only in scenarios involving frequent additions and infrequent searches or removals.
+Given a set of data, we consider using an array or a binary search tree for storage. Observing the table below, all operations in a binary search tree have logarithmic time complexity, providing stable and efficient performance. Arrays are more efficient than binary search trees only in scenarios with high-frequency additions and low-frequency searches and deletions.
Table Efficiency comparison between arrays and search trees
@@ -116,7 +116,7 @@ Given a set of data, we consider using an array or a binary search tree for stor
| Insert element | $O(1)$ | $O(\log n)$ |
| Remove element | $O(n)$ | $O(\log n)$ |
-Ideally, the binary search tree is "balanced," allowing any node can be found within $\log n$ loops.
+In the ideal case, a binary search tree is "balanced," such that any node can be found within $\log n$ loop iterations.
However, if we continuously insert and remove nodes in a binary search tree, it may degenerate into a linked list as shown in the figure below, where the time complexity of various operations also degrades to $O(n)$.
diff --git a/en/docs/chapter_tree/binary_tree.md b/en/docs/chapter_tree/binary_tree.md
index b06570218..cbbe299e2 100644
--- a/en/docs/chapter_tree/binary_tree.md
+++ b/en/docs/chapter_tree/binary_tree.md
@@ -1,6 +1,6 @@
# Binary tree
-A binary tree is a non-linear data structure that represents the hierarchical relationship between ancestors and descendants and embodies the divide-and-conquer logic of "splitting into two". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node.
+A binary tree is a non-linear data structure that represents the derivation relationship between "ancestors" and "descendants" and embodies the divide-and-conquer logic of "one divides into two". Similar to a linked list, the basic unit of a binary tree is a node, and each node contains a value, a reference to its left child node, and a reference to its right child node.
=== "Python"
@@ -189,7 +189,16 @@ A binary tree is a non-linear data structure that represents the hierarch
=== "Ruby"
```ruby title=""
+ ### Binary tree node class ###
+ class TreeNode
+ attr_accessor :val # Node value
+ attr_accessor :left # Reference to left child node
+ attr_accessor :right # Reference to right child node
+ def initialize(val)
+ @val = val
+ end
+ end
```
=== "Zig"
@@ -432,7 +441,18 @@ Similar to a linked list, the initialization of a binary tree involves first cre
=== "Ruby"
```ruby title="binary_tree.rb"
-
+ # Initializing a binary tree
+ # Initializing nodes
+ n1 = TreeNode.new(1)
+ n2 = TreeNode.new(2)
+ n3 = TreeNode.new(3)
+ n4 = TreeNode.new(4)
+ n5 = TreeNode.new(5)
+ # Linking references (pointers) between nodes
+ n1.left = n2
+ n1.right = n3
+ n2.left = n4
+ n2.right = n5
```
=== "Zig"
@@ -594,7 +614,13 @@ Similar to a linked list, inserting and removing nodes in a binary tree can be a
=== "Ruby"
```ruby title="binary_tree.rb"
-
+ # Inserting and removing nodes
+ _p = TreeNode.new(0)
+ # Inserting node _p between n1 and n2
+ n1.left = _p
+ _p.left = n2
+ # Removing node _p
+ n1.left = n2
```
=== "Zig"
@@ -615,7 +641,7 @@ Similar to a linked list, inserting and removing nodes in a binary tree can be a
### Perfect binary tree
-As shown in the figure below, in a perfect binary tree, all levels are completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of $0$, while all other nodes have a degree of $2$. The total number of nodes can be calculated as $2^{h+1} - 1$, where $h$ is the height of the tree. This exhibits a standard exponential relationship, reflecting the common phenomenon of cell division in nature.
+As shown in the figure below, a perfect binary tree has all levels completely filled with nodes. In a perfect binary tree, leaf nodes have a degree of $0$, while all other nodes have a degree of $2$. If the tree height is $h$, the total number of nodes is $2^{h+1} - 1$, exhibiting a standard exponential relationship that reflects the common phenomenon of cell division in nature.
!!! tip
@@ -625,13 +651,13 @@ As shown in the figure below, in a perfect binary tree, all levels are co
### Complete binary tree
-As shown in the figure below, a complete binary tree is a binary tree where only the bottom level is possibly not completely filled, and nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree.
+As shown in the figure below, a complete binary tree only allows the bottom level to be incompletely filled, and the nodes at the bottom level must be filled continuously from left to right. Note that a perfect binary tree is also a complete binary tree.

### Full binary tree
-As shown in the figure below, a full binary tree, except for the leaf nodes, has two child nodes for all other nodes.
+As shown in the figure below, in a full binary tree, all nodes except leaf nodes have two child nodes.

@@ -643,10 +669,10 @@ As shown in the figure below, in a balanced binary tree, the absolute dif
## Degeneration of binary trees
-The figure below shows the ideal and degenerate structures of binary trees. A binary tree becomes a "perfect binary tree" when every level is filled; while it degenerates into a "linked list" when all nodes are biased toward one side.
+The figure below shows the ideal and degenerate structures of binary trees. When every level of a binary tree is filled, it reaches the "perfect binary tree" state; when all nodes are biased toward one side, the binary tree degenerates into a "linked list".
-- A perfect binary tree is an ideal scenario where the "divide and conquer" advantage of a binary tree can be fully utilized.
-- On the other hand, a linked list represents another extreme where all operations become linear, resulting in a time complexity of $O(n)$.
+- A perfect binary tree is the ideal case, fully leveraging the "divide and conquer" advantage of binary trees.
+- A linked list represents the other extreme, where all operations become linear operations with time complexity degrading to $O(n)$.

diff --git a/en/docs/chapter_tree/binary_tree_traversal.md b/en/docs/chapter_tree/binary_tree_traversal.md
index 08c104605..c0c1308c4 100755
--- a/en/docs/chapter_tree/binary_tree_traversal.md
+++ b/en/docs/chapter_tree/binary_tree_traversal.md
@@ -8,13 +8,13 @@ The common traversal methods for binary trees include level-order traversal, pre
As shown in the figure below, level-order traversal traverses the binary tree from top to bottom, layer by layer. Within each level, it visits nodes from left to right.
-Level-order traversal is essentially a type of breadth-first traversal, also known as breadth-first search (BFS), which embodies a "circumferentially outward expanding" layer-by-layer traversal method.
+Level-order traversal is essentially breadth-first traversal, also known as breadth-first search (BFS), which embodies a "expanding outward circle by circle" layer-by-layer traversal method.

### Code implementation
-Breadth-first traversal is usually implemented with the help of a "queue". The queue follows the "first in, first out" rule, while breadth-first traversal follows the "layer-by-layer progression" rule, the underlying ideas of the two are consistent. The implementation code is as follows:
+Breadth-first traversal is typically implemented with the help of a "queue". The queue follows the "first in, first out" rule, while breadth-first traversal follows the "layer-by-layer progression" rule; the underlying ideas of the two are consistent. The implementation code is as follows:
```src
[file]{binary_tree_bfs}-[class]{}-[func]{level_order}
@@ -22,16 +22,16 @@ Breadth-first traversal is usually implemented with the help of a "queue". The q
### Complexity analysis
-- **Time complexity is $O(n)$**: All nodes are visited once, taking $O(n)$ time, where $n$ is the number of nodes.
-- **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue can contain at most $(n + 1) / 2$ nodes simultaneously, occupying $O(n)$ space.
+- **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time, where $n$ is the number of nodes.
+- **Space complexity is $O(n)$**: In the worst case, i.e., a full binary tree, before traversing to the bottom level, the queue contains at most $(n + 1) / 2$ nodes simultaneously, occupying $O(n)$ space.
-## Preorder, in-order, and post-order traversal
+## Preorder, inorder, and postorder traversal
-Correspondingly, pre-order, in-order, and post-order traversal all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "proceed to the end first, then backtrack and continue" traversal method.
+Correspondingly, preorder, inorder, and postorder traversals all belong to depth-first traversal, also known as depth-first search (DFS), which embodies a "first go to the end, then backtrack and continue" traversal method.
-The figure below shows the working principle of performing a depth-first traversal on a binary tree. **Depth-first traversal is like "walking" around the entire binary tree**, encountering three positions at each node, corresponding to pre-order, in-order, and post-order traversal.
+The figure below shows how depth-first traversal works on a binary tree. **Depth-first traversal is like "walking" around the perimeter of the entire binary tree**, encountering three positions at each node, corresponding to preorder, inorder, and postorder traversal.
-
+
### Code implementation
@@ -45,13 +45,13 @@ Depth-first search is usually implemented based on recursion:
Depth-first search can also be implemented based on iteration, interested readers can study this on their own.
-The figure below shows the recursive process of pre-order traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return".
+The figure below shows the recursive process of preorder traversal of a binary tree, which can be divided into two opposite parts: "recursion" and "return".
-1. "Recursion" means starting a new method, the program accesses the next node in this process.
-2. "Return" means the function returns, indicating the current node has been fully accessed.
+1. "Recursion" means opening a new method, where the program accesses the next node in this process.
+2. "Return" means the function returns, indicating that the current node has been fully visited.
=== "<1>"
- 
+ 
=== "<2>"

@@ -86,4 +86,4 @@ The figure below shows the recursive process of pre-order traversal of a binary
### Complexity analysis
- **Time complexity is $O(n)$**: All nodes are visited once, using $O(n)$ time.
-- **Space complexity is $O(n)$**: In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches $n$, the system occupies $O(n)$ stack frame space.
+- **Space complexity is $O(n)$**: In the worst case, i.e., the tree degenerates into a linked list, the recursion depth reaches $n$, and the system occupies $O(n)$ stack frame space.
diff --git a/en/docs/chapter_tree/index.md b/en/docs/chapter_tree/index.md
index f65885391..f641a4a14 100644
--- a/en/docs/chapter_tree/index.md
+++ b/en/docs/chapter_tree/index.md
@@ -4,6 +4,6 @@
!!! abstract
- The towering tree exudes a vibrant essence, boasting profound roots and abundant foliage, yet its branches are sparsely scattered, creating an ethereal aura.
-
- It shows us the vivid form of divide-and-conquer in data.
\ No newline at end of file
+ Towering trees are full of vitality, with deep roots and lush leaves, spreading branches and flourishing.
+
+ They show us the vivid form of divide and conquer in data.
\ No newline at end of file
diff --git a/en/docs/chapter_tree/summary.md b/en/docs/chapter_tree/summary.md
index 93f64a4b9..22cea3590 100644
--- a/en/docs/chapter_tree/summary.md
+++ b/en/docs/chapter_tree/summary.md
@@ -2,17 +2,17 @@
### Key review
-- A binary tree is a non-linear data structure that reflects the "divide and conquer" logic of splitting one into two. Each binary tree node contains a value and two pointers, which point to its left and right child nodes, respectively.
-- For a node in a binary tree, its left (right) child node and the tree formed below it are collectively called the node's left (right) subtree.
-- Terms related to binary trees include root node, leaf node, level, degree, edge, height, and depth.
-- The operations of initializing a binary tree, inserting nodes, and removing nodes are similar to those of linked list operations.
-- Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree represents the ideal state, while the linked list is the worst state after degradation.
-- A binary tree can be represented using an array by arranging the node values and empty slots in a level-order traversal sequence and implementing pointers based on the index mapping relationship between parent nodes and child nodes.
-- The level-order traversal of a binary tree is a breadth-first search method, which reflects a layer-by-layer traversal manner of "expanding circle by circle." It is usually implemented using a queue.
-- Pre-order, in-order, and post-order traversals are all depth-first search methods, reflecting the traversal manner of "going to the end first, then backtracking to continue." They are usually implemented using recursion.
-- A binary search tree is an efficient data structure for element searching, with the time complexity of search, insert, and remove operations all being $O(\log n)$. When a binary search tree degrades into a linked list, these time complexities deteriorate to $O(n)$.
-- An AVL tree, also known as a balanced binary search tree, ensures that the tree remains balanced after continuous node insertions and removals through rotation operations.
-- Rotation operations in an AVL tree include right rotation, left rotation, right-left rotation, and left-right rotation. After node insertion or removal, the AVL tree rebalances itself by performing these rotations in a bottom-up manner.
+- A binary tree is a non-linear data structure that embodies the divide-and-conquer logic of "one divides into two". Each binary tree node contains a value and two pointers, which respectively point to its left and right child nodes.
+- For a certain node in a binary tree, the tree formed by its left (right) child node and all nodes below is called the left (right) subtree of that node.
+- Related terminology of binary trees includes root node, leaf node, level, degree, edge, height, and depth.
+- The initialization, node insertion, and node removal operations of binary trees are similar to those of linked lists.
+- Common types of binary trees include perfect binary trees, complete binary trees, full binary trees, and balanced binary trees. The perfect binary tree is the ideal state, while the linked list is the worst state after degradation.
+- A binary tree can be represented using an array by arranging node values and empty slots in level-order traversal sequence, and implementing pointers based on the index mapping relationship between parent and child nodes.
+- Level-order traversal of a binary tree is a breadth-first search method, embodying a layer-by-layer traversal approach of "expanding outward circle by circle", typically implemented using a queue.
+- Preorder, inorder, and postorder traversals all belong to depth-first search, embodying a traversal approach of "first go to the end, then backtrack and continue", typically implemented using recursion.
+- A binary search tree is an efficient data structure for element searching, with search, insertion, and removal operations all having time complexity of $O(\log n)$. When a binary search tree degenerates into a linked list, all time complexities degrade to $O(n)$.
+- An AVL tree, also known as a balanced binary search tree, ensures the tree remains balanced after continuous node insertions and removals through rotation operations.
+- Rotation operations in AVL trees include right rotation, left rotation, left rotation then right rotation, and right rotation then left rotation. After inserting or removing nodes, AVL trees perform rotation operations from bottom to top to restore the tree to balance.
### Q & A
@@ -24,21 +24,21 @@ Yes, because height and depth are typically defined as "the number of edges pass
Taking the binary search tree as an example, the operation of removing a node needs to be handled in three different scenarios, each requiring multiple steps of node operations.
-**Q**: Why are there three sequences: pre-order, in-order, and post-order for DFS traversal of a binary tree, and what are their uses?
+**Q**: Why does DFS traversal of binary trees have three orders: preorder, inorder, and postorder, and what are their uses?
-Similar to sequential and reverse traversal of arrays, pre-order, in-order, and post-order traversals are three methods of traversing a binary tree, allowing us to obtain a traversal result in a specific order. For example, in a binary search tree, since the node sizes satisfy `left child node value < root node value < right child node value`, we can obtain an ordered node sequence by traversing the tree in the "left $\rightarrow$ root $\rightarrow$ right" priority.
+Similar to forward and reverse traversal of arrays, preorder, inorder, and postorder traversals are three methods of binary tree traversal that allow us to obtain a traversal result in a specific order. For example, in a binary search tree, since nodes satisfy the relationship `left child node value < root node value < right child node value`, we only need to traverse the tree with the priority of "left $\rightarrow$ root $\rightarrow$ right" to obtain an ordered node sequence.
-**Q**: In a right rotation operation that deals with the relationship between the imbalance nodes `node`, `child`, `grand_child`, isn't the connection between `node` and its parent node and the original link of `node` lost after the right rotation?
+**Q**: In a right rotation operation handling the relationship between unbalanced nodes `node`, `child`, and `grand_child`, doesn't the connection between `node` and its parent node get lost after the right rotation?
-We need to view this problem from a recursive perspective. The `right_rotate(root)` operation passes the root node of the subtree and eventually returns the root node of the rotated subtree with `return child`. The connection between the subtree's root node and its parent node is established after this function returns, which is outside the scope of the right rotation operation's maintenance.
+We need to view this problem from a recursive perspective. The right rotation operation `right_rotate(root)` passes in the root node of the subtree and eventually returns the root node of the subtree after rotation with `return child`. The connection between the subtree's root node and its parent node is completed after the function returns, which is not within the maintenance scope of the right rotation operation.
**Q**: In C++, functions are divided into `private` and `public` sections. What considerations are there for this? Why are the `height()` function and the `updateHeight()` function placed in `public` and `private`, respectively?
-It depends on the scope of the method's use. If a method is only used within the class, then it is designed to be `private`. For example, it makes no sense for users to call `updateHeight()` on their own, as it is just a step in the insertion or removal operations. However, `height()` is for accessing node height, similar to `vector.size()`, thus it is set to `public` for use.
+It mainly depends on the method's usage scope. If a method is only used within the class, then it is designed as `private`. For example, calling `updateHeight()` alone by the user makes no sense, as it is only a step in insertion or removal operations. However, `height()` is used to access node height, similar to `vector.size()`, so it is set to `public` for ease of use.
**Q**: How do you build a binary search tree from a set of input data? Is the choice of root node very important?
-Yes, the method for building the tree is provided in the `build_tree()` method in the binary search tree code. As for the choice of the root node, we usually sort the input data and then select the middle element as the root node, recursively building the left and right subtrees. This approach maximizes the balance of the tree.
+Yes, the method for building a tree is provided in the `build_tree()` method in the binary search tree code. As for the choice of root node, we typically sort the input data, then select the middle element as the root node, and recursively build the left and right subtrees. This approach maximizes the tree's balance.
**Q**: In Java, do you always have to use the `equals()` method for string comparison?
@@ -47,7 +47,7 @@ In Java, for primitive data types, `==` is used to compare whether the values of
- `==`: Used to compare whether two variables point to the same object, i.e., whether their positions in memory are the same.
- `equals()`: Used to compare whether the values of two objects are equal.
-Therefore, to compare values, we should use `equals()`. However, strings initialized with `String a = "hi"; String b = "hi";` are stored in the string constant pool and point to the same object, so `a == b` can also be used to compare the contents of two strings.
+Therefore, if we want to compare values, we should use `equals()`. However, strings initialized via `String a = "hi"; String b = "hi";` are stored in the string constant pool and point to the same object, so `a == b` can also be used to compare the contents of the two strings.
**Q**: Before reaching the bottom level, is the number of nodes in the queue $2^h$ in breadth-first traversal?
diff --git a/en/docs/index.html b/en/docs/index.html
index 0a18da586..181fa0d27 100644
--- a/en/docs/index.html
+++ b/en/docs/index.html
@@ -26,7 +26,7 @@
- Hash table
+ Hashing
diff --git a/en/mkdocs.yml b/en/mkdocs.yml
index c98eeabb9..322b55d36 100644
--- a/en/mkdocs.yml
+++ b/en/mkdocs.yml
@@ -9,15 +9,14 @@ docs_dir: ../build/en/docs
site_dir: ../site/en
# Repository
edit_uri: tree/main/en/docs
-version: 1.0.0
+version: 1.2.0
# Configuration
theme:
custom_dir: ../build/overrides
language: en
font:
- text: Roboto
- code: Roboto Mono
+ text: Lato
palette:
- scheme: default
primary: white
@@ -44,9 +43,9 @@ nav:
# [icon: material/book-open-outline]
- chapter_preface/index.md
- 0.1 About this book: chapter_preface/about_the_book.md
- - 0.2 How to read: chapter_preface/suggestions.md
+ - 0.2 How to use this book: chapter_preface/suggestions.md
- 0.3 Summary: chapter_preface/summary.md
- - Chapter 1. Encounter with algorithms:
+ - Chapter 1. Introduction to algorithms:
# [icon: material/calculator-variant-outline]
- chapter_introduction/index.md
- 1.1 Algorithms are everywhere: chapter_introduction/algorithms_are_everywhere.md
@@ -55,7 +54,7 @@ nav:
- Chapter 2. Complexity analysis:
# [icon: material/timer-sand]
- chapter_computational_complexity/index.md
- - 2.1 Algorithm efficiency assessment: chapter_computational_complexity/performance_evaluation.md
+ - 2.1 Algorithm efficiency evaluation: chapter_computational_complexity/performance_evaluation.md
- 2.2 Iteration and recursion: chapter_computational_complexity/iteration_and_recursion.md
- 2.3 Time complexity: chapter_computational_complexity/time_complexity.md
- 2.4 Space complexity: chapter_computational_complexity/space_complexity.md
@@ -83,7 +82,7 @@ nav:
- 5.2 Queue: chapter_stack_and_queue/queue.md
- 5.3 Double-ended queue: chapter_stack_and_queue/deque.md
- 5.4 Summary: chapter_stack_and_queue/summary.md
- - Chapter 6. Hash table:
+ - Chapter 6. Hashing:
# [icon: material/table-search]
- chapter_hashing/index.md
- 6.1 Hash table: chapter_hashing/hash_map.md
@@ -95,8 +94,8 @@ nav:
- chapter_tree/index.md
- 7.1 Binary tree: chapter_tree/binary_tree.md
- 7.2 Binary tree traversal: chapter_tree/binary_tree_traversal.md
- - 7.3 Array Representation of tree: chapter_tree/array_representation_of_tree.md
- - 7.4 Binary Search tree: chapter_tree/binary_search_tree.md
+ - 7.3 Array representation of tree: chapter_tree/array_representation_of_tree.md
+ - 7.4 Binary search tree: chapter_tree/binary_search_tree.md
- 7.5 AVL tree *: chapter_tree/avl_tree.md
- 7.6 Summary: chapter_tree/summary.md
- Chapter 8. Heap:
@@ -110,7 +109,7 @@ nav:
# [icon: material/graphql]
- chapter_graph/index.md
- 9.1 Graph: chapter_graph/graph.md
- - 9.2 Basic graph operations: chapter_graph/graph_operations.md
+ - 9.2 Basic operations on graphs: chapter_graph/graph_operations.md
- 9.3 Graph traversal: chapter_graph/graph_traversal.md
- 9.4 Summary: chapter_graph/summary.md
- Chapter 10. Searching:
@@ -118,8 +117,8 @@ nav:
- chapter_searching/index.md
- 10.1 Binary search: chapter_searching/binary_search.md
- 10.2 Binary search insertion: chapter_searching/binary_search_insertion.md
- - 10.3 Binary search boundaries: chapter_searching/binary_search_edge.md
- - 10.4 Hashing optimization strategies: chapter_searching/replace_linear_by_hashing.md
+ - 10.3 Binary search edge cases: chapter_searching/binary_search_edge.md
+ - 10.4 Hash optimization strategy: chapter_searching/replace_linear_by_hashing.md
- 10.5 Search algorithms revisited: chapter_searching/searching_algorithm_revisited.md
- 10.6 Summary: chapter_searching/summary.md
- Chapter 11. Sorting:
@@ -141,31 +140,31 @@ nav:
- chapter_divide_and_conquer/index.md
- 12.1 Divide and conquer algorithms: chapter_divide_and_conquer/divide_and_conquer.md
- 12.2 Divide and conquer search strategy: chapter_divide_and_conquer/binary_search_recur.md
- - 12.3 Building binary tree problem: chapter_divide_and_conquer/build_binary_tree_problem.md
- - 12.4 Tower of Hanoi Problem: chapter_divide_and_conquer/hanota_problem.md
+ - 12.3 Building a binary tree problem: chapter_divide_and_conquer/build_binary_tree_problem.md
+ - 12.4 Hanoi tower problem: chapter_divide_and_conquer/hanota_problem.md
- 12.5 Summary: chapter_divide_and_conquer/summary.md
- Chapter 13. Backtracking:
# [icon: material/map-marker-path]
- chapter_backtracking/index.md
- - 13.1 Backtracking algorithms: chapter_backtracking/backtracking_algorithm.md
- - 13.2 Permutation problem: chapter_backtracking/permutations_problem.md
- - 13.3 Subset sum problem: chapter_backtracking/subset_sum_problem.md
- - 13.4 n queens problem: chapter_backtracking/n_queens_problem.md
+ - 13.1 Backtracking Algorithm: chapter_backtracking/backtracking_algorithm.md
+ - 13.2 Permutations Problem: chapter_backtracking/permutations_problem.md
+ - 13.3 Subset-Sum Problem: chapter_backtracking/subset_sum_problem.md
+ - 13.4 n-queens problem: chapter_backtracking/n_queens_problem.md
- 13.5 Summary: chapter_backtracking/summary.md
- Chapter 14. Dynamic programming:
# [icon: material/table-pivot]
- chapter_dynamic_programming/index.md
- 14.1 Introduction to dynamic programming: chapter_dynamic_programming/intro_to_dynamic_programming.md
- - 14.2 Characteristics of DP problems: chapter_dynamic_programming/dp_problem_features.md
- - 14.3 DP problem-solving approach¶: chapter_dynamic_programming/dp_solution_pipeline.md
- - 14.4 0-1 Knapsack problem: chapter_dynamic_programming/knapsack_problem.md
+ - 14.2 Characteristics of dynamic programming problems: chapter_dynamic_programming/dp_problem_features.md
+ - 14.3 Dynamic programming problem-solving approach: chapter_dynamic_programming/dp_solution_pipeline.md
+ - 14.4 0-1 knapsack problem: chapter_dynamic_programming/knapsack_problem.md
- 14.5 Unbounded knapsack problem: chapter_dynamic_programming/unbounded_knapsack_problem.md
- 14.6 Edit distance problem: chapter_dynamic_programming/edit_distance_problem.md
- 14.7 Summary: chapter_dynamic_programming/summary.md
- Chapter 15. Greedy:
# [icon: material/head-heart-outline]
- chapter_greedy/index.md
- - 15.1 Greedy algorithms: chapter_greedy/greedy_algorithm.md
+ - 15.1 Greedy algorithm: chapter_greedy/greedy_algorithm.md
- 15.2 Fractional knapsack problem: chapter_greedy/fractional_knapsack_problem.md
- 15.3 Maximum capacity problem: chapter_greedy/max_capacity_problem.md
- 15.4 Maximum product cutting problem: chapter_greedy/max_product_cutting_problem.md
@@ -173,8 +172,8 @@ nav:
- Chapter 16. Appendix:
# [icon: material/help-circle-outline]
- chapter_appendix/index.md
- - 16.1 Installation: chapter_appendix/installation.md
- - 16.2 Contributing: chapter_appendix/contribution.md
- - 16.3 Terminology: chapter_appendix/terminology.md
+ - 16.1 Programming environment installation: chapter_appendix/installation.md
+ - 16.2 Contributing Together: chapter_appendix/contribution.md
+ - 16.3 Terminology Table: chapter_appendix/terminology.md
- References:
- chapter_reference/index.md
diff --git a/ja/mkdocs.yml b/ja/mkdocs.yml
index 4a2906e09..af099f820 100644
--- a/ja/mkdocs.yml
+++ b/ja/mkdocs.yml
@@ -9,7 +9,7 @@ docs_dir: ../build/ja/docs
site_dir: ../site/ja
# Repository
edit_uri: tree/main/ja/docs
-version: 1.0.0
+version: 1.2.0
# Configuration
theme:
@@ -17,7 +17,6 @@ theme:
language: ja
font:
text: Noto Sans JP
- code: Fira Code
palette:
- scheme: default
primary: white
diff --git a/mkdocs.yml b/mkdocs.yml
index 7b96608f1..63feb0274 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,6 +19,9 @@ theme:
name: material
custom_dir: build/overrides
language: zh
+ font:
+ text: Noto Sans SC
+ code: JetBrains Mono
features:
# - announce.dismiss
- content.action.edit
@@ -56,9 +59,6 @@ theme:
toggle:
icon: material/theme-light-dark
name: 浅色模式
- font:
- text: Noto Sans SC
- code: Fira Code
favicon: assets/images/favicon.png
logo: assets/images/logo.svg
icon: