mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-10 14:36:06 +00:00
build
This commit is contained in:
@@ -133,7 +133,7 @@ Elements in an array are stored in contiguous memory spaces, making it simpler t
|
||||
|
||||
<p align="center"> Figure 4-2 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. 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.
|
||||
|
||||
{ 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 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. 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. 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 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user