This commit is contained in:
krahets
2026-04-03 18:46:15 +08:00
parent 377736b1bd
commit 9d21ca86b0
352 changed files with 46563 additions and 11262 deletions
@@ -14,7 +14,7 @@ An <u>array</u> is a linear data structure that stores elements of the same type
### 1. &nbsp; Initializing Arrays
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$:
We can choose between two array initialization methods based on our needs: with or without initial values. When no initial values are specified, most programming languages initialize array elements to $0$:
=== "Python"
@@ -320,13 +320,13 @@ Accessing elements in an array is highly efficient; we can randomly access any e
### 3. &nbsp; Inserting Elements
Array elements are stored "tightly adjacent" in memory, with no space between them to store any additional data. As shown in Figure 4-3, 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.
Array elements are packed tightly together in memory, with no extra space between them for additional data. As shown in Figure 4-3, if we want to insert an element in the middle of an array, we need to shift all subsequent elements one position to the right and then assign the value at that index.
![Example of inserting an element into an array](array.assets/array_insert_element.png){ class="animation-figure" }
<p align="center"> Figure 4-3 &nbsp; Example of inserting an element into an array </p>
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.
It is worth noting that since the length of an array is fixed, inserting an element will inevitably push the last element out of the array. We will leave the solution to this problem for discussion in the "List" chapter.
=== "Python"
@@ -517,7 +517,7 @@ Similarly, as shown in Figure 4-4, to delete the element at index $i$, we need t
<p align="center"> Figure 4-4 &nbsp; Example of removing an element from an array </p>
Note that after the deletion is complete, the original last element becomes "meaningless", so we do not need to specifically modify it.
Note that after the deletion is complete, the original last element is no longer meaningful, so we do not need to modify it explicitly.
=== "Python"
@@ -678,7 +678,7 @@ Overall, array insertion and deletion operations have the following drawbacks:
- **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.
- **Memory waste**: We can initialize a relatively long array and use only the front portion, so that any overwritten tail elements are merely unused placeholders, but this wastes some memory space.
### 5. &nbsp; Traversing Arrays
@@ -3,20 +3,20 @@ comments: true
icon: material/view-list-outline
---
# Chapter 4. &nbsp; Array and Linked List
# Chapter 4. &nbsp; Arrays and Linked Lists
![Array and Linked List](../assets/covers/chapter_array_and_linkedlist.jpg){ class="cover-image" }
![Arrays and Linked Lists](../assets/covers/chapter_array_and_linkedlist.jpg){ class="cover-image" }
!!! abstract
The world of data structures is like a solid brick wall.
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.
The bricks of an array are neatly aligned, each pressed tightly against the next. The bricks of a linked list are scattered about, with connecting vines weaving freely through the gaps between them.
## Chapter contents
- [4.1 &nbsp; Array](array.md)
- [4.2 &nbsp; Linked List](linked_list.md)
- [4.3 &nbsp; List](list.md)
- [4.4 &nbsp; Memory and Cache *](ram_and_cache.md)
- [4.4 &nbsp; Random-Access Memory and Cache *](ram_and_cache.md)
- [4.5 &nbsp; Summary](summary.md)
@@ -4,11 +4,11 @@ comments: true
# 4.2 &nbsp; Linked List
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.
Memory is a shared resource for all programs. In a complex runtime environment, free memory may be scattered throughout the address space. We know that arrays require contiguous memory, and when an array is very large, the system may not be able to provide such a large contiguous block. This is where the flexibility of linked lists becomes apparent.
A <u>linked list</u> 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 nodes to be stored scattered throughout the memory, and their memory addresses do not need to be contiguous.
This design allows linked-list nodes to be stored in different locations in memory, and their addresses do not need to be contiguous.
![Linked list definition and storage method](linked_list.assets/linkedlist_definition.png){ class="animation-figure" }
@@ -424,7 +424,7 @@ Building a linked list involves two steps: first, initializing each node object;
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%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&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%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&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=3&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Full Screen ></a></div>
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`.
An array is a single variable; for example, an array `nums` contains elements `nums[0]`, `nums[1]`, and so on. A linked list, by contrast, is composed of multiple independent node objects. **We usually use the head node as a stand-in for the entire linked list**; for example, the linked list in the above code can be referred to as linked list `n0`.
### 2. &nbsp; Inserting a Node
@@ -1221,7 +1221,7 @@ Table 4-1 summarizes the characteristics of arrays and linked lists and compares
As shown in Figure 4-8, there are three common types of linked lists:
- **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`.
- **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; the tail node points to `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.
+7 -7
View File
@@ -4,22 +4,22 @@ comments: true
# 4.3 &nbsp; List
<u>A list</u> 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.
<u>A list</u> is an abstract data structure that represents an ordered collection of elements. It supports operations such as element access, modification, insertion, deletion, and traversal, without requiring users to worry about capacity limits. Lists can be implemented using linked lists or arrays.
- 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.
- A linked list can naturally be viewed as a list: it supports insertion, deletion, search, and update, and can grow flexibly as needed.
- An array also supports insertion, deletion, search, and update, but because its length is fixed, it can only be regarded as a list with a capacity limit.
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.
When a list is implemented with an array, **its fixed length makes it less practical**. This is because we usually cannot determine in advance how much data we need to store, making it difficult to choose an appropriate capacity. If the capacity is too small, it may fail to meet our needs; if it is too large, memory space will be wasted.
To solve this problem, we can use a <u>dynamic array</u> to implement a list. It inherits all the advantages of arrays and can dynamically expand during program execution.
To solve this problem, we can use a <u>dynamic array</u> to implement a list. It inherits all the advantages of arrays while supporting dynamic resizing during program execution.
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.
In fact, **the list types provided by the standard libraries of many programming languages are implemented with 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.
## 4.3.1 &nbsp; Common List Operations
### 1. &nbsp; Initialize a List
We typically use two initialization methods: "without initial values" and "with initial values":
We typically initialize a list in one of two ways: empty or with predefined values:
=== "Python"
@@ -4,7 +4,7 @@ comments: true
# 4.4 &nbsp; Random-Access Memory and Cache *
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 the first two sections of this chapter, we explored arrays and linked lists, two fundamental and important data structures that represent two physical layouts: "contiguous storage" and "distributed storage", respectively.
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.
@@ -22,11 +22,11 @@ Computers include three types of storage devices: <u>hard disk</u>, <u>random-ac
| 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 |
| Cost (CNY/GB) | Inexpensive, from a few tenths of a yuan to a few yuan per GB | Expensive, from tens to hundreds of yuan per GB | Very expensive, effectively bundled with the CPU package |
</div>
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.
We can imagine the computer storage system as a pyramid, as shown in the diagram below. Storage devices closer to the top are faster, have smaller capacity, and are more expensive. This multi-layered design is deliberate, the result of careful consideration by computer scientists and engineers.
- **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.
@@ -39,9 +39,9 @@ We can imagine the computer storage system as a pyramid structure as shown in th
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.
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.
In summary, **hard disks are used for long-term storage of large amounts of data, RAM is used to temporarily store the data being processed during program execution, and cache is used to store frequently accessed data and instructions**, thereby improving program execution efficiency. The three work together to keep the computer system running efficiently.
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.
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. **By intelligently loading data from RAM**, it provides the CPU with high-speed access to data, significantly improving program execution efficiency and reducing reliance on slower RAM.
![Data Flow Among Hard Disk, RAM, and Cache](ram_and_cache.assets/computer_storage_devices.png){ class="animation-figure" }
@@ -68,9 +68,9 @@ To achieve the highest efficiency possible, cache employs the following data loa
- **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 efficiencies in utilizing cache**, manifested in the following aspects.
In fact, **arrays and linked lists differ in how efficiently they utilize cache**, mainly in the following respects.
- **Space occupied**: Linked list elements occupy more space than array elements, resulting in fewer effective data in the cache.
- **Space occupied**: Linked-list elements occupy more space than array elements, so less useful data can fit 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.
@@ -6,11 +6,11 @@ comments: true
### 1. &nbsp; Key Review
- 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 and linked lists are two fundamental data structures, representing two different ways data can be stored in computer memory: contiguous storage and scattered storage. Their strengths and weaknesses 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.
- The emergence of lists has greatly improved the practicality of arrays, but it may also waste some 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.
@@ -29,17 +29,17 @@ Arrays stored on the stack and on the heap are both stored in contiguous memory
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, 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".
In contrast, array elements must be of the same type so that their positions can be determined by calculating offsets. 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 sizes".
```shell
# Element Memory Address = Array Memory Address (first Element Memory address) + Element Length * Element Index
# element address = array base address (address of the first element) + element size * element index
```
**Q**: After deleting node `P`, do we need to set `P.next` to `None`?
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 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.
From an algorithms-and-problem-solving perspective, leaving the pointer connected is fine as long as the program logic is correct. From a standard-library implementation perspective, explicitly disconnecting it is safer and clearer. If it is not disconnected and the deleted node is not reclaimed properly, it may affect the reclamation of successor nodes.
**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)$?
@@ -83,8 +83,8 @@ If we want each `[0]` in the 2D list to be independent, we can use `res = [[0] f
**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.
In this list, all the integer zeros reference 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.
Although they all reference the same object, we can still modify each element in the list independently. This is because Python integers are "immutable objects". When we modify an element, we actually switch that element to reference a different 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.