This commit is contained in:
krahets
2024-05-06 14:40:36 +08:00
parent 7e7eb6047a
commit 5c7d2c7f17
54 changed files with 3456 additions and 215 deletions
+55 -8
View File
@@ -133,7 +133,7 @@ Elements in an array are stored in contiguous memory spaces, making it simpler t
<p align="center"> Figure 4-2 &nbsp; Memory address calculation for array elements </p>
As observed in the above illustration, 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$.
As observed in Figure 4-2, 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$.
Accessing elements in an array is highly efficient, allowing us to randomly access any element in $O(1)$ time.
@@ -152,7 +152,14 @@ Accessing elements in an array is highly efficient, allowing us to randomly acce
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{randomAccess}
/* Random access to elements */
int randomAccess(int *nums, int size) {
// Randomly select a number in the range [0, size)
int randomIndex = rand() % size;
// Retrieve and return a random element
int randomNum = nums[randomIndex];
return randomNum;
}
```
=== "Java"
@@ -236,7 +243,7 @@ Accessing elements in an array is highly efficient, allowing us to randomly acce
### 3. &nbsp; Inserting elements
Array elements are tightly packed in memory, with no space available to accommodate additional data between them. Illustrated in 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 tightly packed in memory, with no space available to accommodate additional data between them. As illustrated in Figure 4-3, 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 element insertion example](array.assets/array_insert_element.png){ class="animation-figure" }
@@ -259,7 +266,15 @@ It's important to note that due to the fixed length of an array, inserting an el
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{insert}
/* Insert element num at `index` */
void insert(int *nums, int size, int num, int index) {
// Move all elements after `index` one position backward
for (int i = size - 1; i > index; i--) {
nums[i] = nums[i - 1];
}
// Assign num to the element at index
nums[index] = num;
}
```
=== "Java"
@@ -365,7 +380,13 @@ Please note that after deletion, the former last element becomes "meaningless,"
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{remove}
/* Remove the element at `index` */
void remove(int *nums, int size, int index) {
// Move all elements after `index` one position forward
for (int i = index; i < size - 1; i++) {
nums[i] = nums[i + 1];
}
}
```
=== "Java"
@@ -477,7 +498,14 @@ In most programming languages, we can traverse an array either by using indices
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{traverse}
/* Traverse array */
void traverse(int *nums, int size) {
int count = 0;
// Traverse array by index
for (int i = 0; i < size; i++) {
count += nums[i];
}
}
```
=== "Java"
@@ -583,7 +611,14 @@ Because arrays are linear data structures, this operation is commonly referred t
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{find}
/* Search for a specified element in the array */
int find(int *nums, int size, int target) {
for (int i = 0; i < size; i++) {
if (nums[i] == target)
return i;
}
return -1;
}
```
=== "Java"
@@ -688,7 +723,19 @@ To expand an array, it's necessary to create a larger array and then copy the e
=== "C++"
```cpp title="array.cpp"
[class]{}-[func]{extend}
/* Extend array length */
int *extend(int *nums, int size, int enlarge) {
// Initialize an extended length array
int *res = new int[size + enlarge];
// Copy all elements from the original array to the new array
for (int i = 0; i < size; i++) {
res[i] = nums[i];
}
// Free memory
delete[] nums;
// Return the new array after expansion
return res;
}
```
=== "Java"
@@ -14,7 +14,7 @@ The design of linked lists allows for their nodes to be distributed across memor
<p align="center"> Figure 4-5 &nbsp; Linked list definition and storage method </p>
As shown in the figure, we see that the basic building block of a linked list is the <u>node</u> object. Each node comprises two key components: the node's "value" and a "reference" to the next node.
As shown in Figure 4-5, we see that the basic building block of a linked list is the <u>node</u> object. Each node comprises two key components: 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.
@@ -412,7 +412,7 @@ The array as a whole is a variable, for instance, the array `nums` includes elem
### 2. &nbsp; Inserting nodes
Inserting a node into a linked list is very easy. As shown in the figure, 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)$.
Inserting a node into a linked list is very easy. As shown in Figure 4-6, 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)$.
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.
@@ -433,7 +433,12 @@ By comparison, inserting an element into an array has a time complexity of $O(n)
=== "C++"
```cpp title="linked_list.cpp"
[class]{}-[func]{insert}
/* Insert node P after node n0 in the linked list */
void insert(ListNode *n0, ListNode *P) {
ListNode *n1 = n0->next;
P->next = n1;
n0->next = P;
}
```
=== "Java"
@@ -515,7 +520,7 @@ By comparison, inserting an element into an array has a time complexity of $O(n)
### 3. &nbsp; Deleting nodes
As shown in the figure, 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 Figure 4-7, deleting a node from a linked list is also very easy, **involving only the modification of a single 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.
@@ -539,7 +544,17 @@ It's important to note that even though node `P` continues to point to `n1` afte
=== "C++"
```cpp title="linked_list.cpp"
[class]{}-[func]{remove}
/* Remove the first node after node n0 in the linked list */
void remove(ListNode *n0) {
if (n0->next == nullptr)
return;
// n0 -> P -> n1
ListNode *P = n0->next;
ListNode *n1 = P->next;
n0->next = n1;
// Free memory
delete P;
}
```
=== "Java"
@@ -641,7 +656,15 @@ It's important to note that even though node `P` continues to point to `n1` afte
=== "C++"
```cpp title="linked_list.cpp"
[class]{}-[func]{access}
/* Access the node at `index` in the linked list */
ListNode *access(ListNode *head, int index) {
for (int i = 0; i < index; i++) {
if (head == nullptr)
return nullptr;
head = head->next;
}
return head;
}
```
=== "Java"
@@ -745,7 +768,17 @@ Traverse the linked list to locate a node whose value matches `target`, and then
=== "C++"
```cpp title="linked_list.cpp"
[class]{}-[func]{find}
/* Search for the first node with value target in the linked list */
int find(ListNode *head, int target) {
int index = 0;
while (head != nullptr) {
if (head->val == target)
return index;
head = head->next;
index++;
}
return -1;
}
```
=== "Java"
@@ -851,7 +884,7 @@ Table 4-1 summarizes the characteristics of arrays and linked lists, and it also
## 4.2.3 &nbsp; Common types of linked lists
As shown in the figure, there are three common types of linked lists.
As shown in Figure 4-8, 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.
+110 -1
View File
@@ -989,7 +989,116 @@ To enhance our understanding of how lists work, we will attempt to implement a s
=== "C++"
```cpp title="my_list.cpp"
[class]{MyList}-[func]{}
/* List class */
class MyList {
private:
int *arr; // Array (stores list elements)
int arrCapacity = 10; // List capacity
int arrSize = 0; // List length (current number of elements)
int extendRatio = 2; // Multiple for each list expansion
public:
/* Constructor */
MyList() {
arr = new int[arrCapacity];
}
/* Destructor */
~MyList() {
delete[] arr;
}
/* Get list length (current number of elements)*/
int size() {
return arrSize;
}
/* Get list capacity */
int capacity() {
return arrCapacity;
}
/* Access element */
int get(int index) {
// If the index is out of bounds, throw an exception, as below
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
return arr[index];
}
/* Update element */
void set(int index, int num) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
arr[index] = num;
}
/* Add element at the end */
void add(int num) {
// When the number of elements exceeds capacity, trigger the expansion mechanism
if (size() == capacity())
extendCapacity();
arr[size()] = num;
// Update the number of elements
arrSize++;
}
/* Insert element in the middle */
void insert(int index, int num) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
// When the number of elements exceeds capacity, trigger the expansion mechanism
if (size() == capacity())
extendCapacity();
// Move all elements after `index` one position backward
for (int j = size() - 1; j >= index; j--) {
arr[j + 1] = arr[j];
}
arr[index] = num;
// Update the number of elements
arrSize++;
}
/* Remove element */
int remove(int index) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
int num = arr[index];
// Move all elements after `index` one position forward
for (int j = index; j < size() - 1; j++) {
arr[j] = arr[j + 1];
}
// Update the number of elements
arrSize--;
// Return the removed element
return num;
}
/* Extend list */
void extendCapacity() {
// Create a new array with a length multiple of the original array by extendRatio
int newCapacity = capacity() * extendRatio;
int *tmp = arr;
arr = new int[newCapacity];
// Copy all elements from the original array to the new array
for (int i = 0; i < size(); i++) {
arr[i] = tmp[i];
}
// Free memory
delete[] tmp;
arrCapacity = newCapacity;
}
/* Convert the list to a Vector for printing */
vector<int> toVector() {
// Only convert elements within valid length range
vector<int> vec(size());
for (int i = 0; i < size(); i++) {
vec[i] = arr[i];
}
return vec;
}
};
```
=== "Java"
@@ -48,7 +48,7 @@ If an element is searched first and then deleted, the time complexity is indeed
**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?
The diagram is just a qualitative representation; quantitative analysis depends on specific situations.
The figure is just a qualitative representation; quantitative analysis depends on specific situations.
- Different types of node values occupy different amounts of space, such as int, long, double, and object instances.
- The memory space occupied by pointer variables depends on the operating system and compilation environment used, usually 8 bytes or 4 bytes.