- Homework
- Dynamic Array
-
Deadline
- OCT 22, 2023 11:59 PM
-
Grade
-
- test_submitted_test_cases
-
- test_build_for_grading
- test_linked_list (60/60)
- test_stack (15/15)
- test_queue (15/15)
- test_memory_safety (10/10)
-
Recap of Fixed-sized Array & Linked List
Operation | Fixed-sized Array (e.g. std::array ) |
Linked List (e.g. std::list ) |
---|---|---|
Random Access ([] ) |
||
Append | Not supported | |
Insert | Not supported | |
Remove | Not supported | |
Memory | Continuous | Not Continuous |
Recap of Fixed-sized Array & Linked List
Array | LinkedList | |
---|---|---|
Memory | Contiguous location & Less memory needed | Non-contiguous location & more memory needed (value and pointer) |
Size | Fixed size | Dynamic size |
Element access |
|
|
Insert/Deletion element | Slow | Fast |
A dynamic array can automatically grow or shrink in size as needed.
- We may not know the size of the array at the time of array declaration
- Elements can be added or removed at anytime
- Then, why not linked list?
- We need efficient random access
- Dynamic Sizing: Dynamic arrays can grow or shrink in size during runtime. When you need to add more elements than the array can currently hold, it can allocate additional memory, copying the existing elements into the new space. This allows for flexible memory management.
-
Random Access: Like traditional arrays, dynamic arrays provide constant-time
$O(1)$ access to elements by index, which makes them efficient for random access operations. -
Efficient Appends: Appending an element to the end of a dynamic array typically has an amortized constant-time
$O(1)$ complexity, as long as you have sufficient capacity. This makes dynamic arrays suitable for scenarios where you frequently add elements to the end.
-
Memory Overhead:
- Reserve more memory than their current contents require to accommodate potential future growth.
- Memory wastage if the array size significantly exceeds the actual data size.
- Resizing Overhead: When a dynamic array needs to grow beyond its current capacity, it may need to allocate a new block of memory, copy the existing elements, and deallocate the old memory. This operation can be time-consuming and may lead to a sudden spike in time complexity.
- **Limited Insertion/Deletion Efficiency of elements in the middle **: While dynamic arrays offer fast random access and efficient appending to the end, inserting or deleting elements in the middle can be less efficient. Such operations may require shifting elements, leading to linear time
$O(n)$ complexity.
What's the difference between array and list?
There is no universal definition of array and list.
Different languages have different definitions & implementations.
In C++,
std::list
is a double-linked liststd::vector
is a dynamic arraystd::array
is a fixed-size array- C-style array is also a fixed-size array
- Array often refers fixed-size array
- List usually refers to dynamic array
When we append to the end of the array, we create a new array with size + 1, copy all the elements from the old array to the new array, and then append the new element to the new array.
- Con:
$O(n)$ time complexity for append
When we append to the end of the array, we check if the array is full. If it is full, we create a new array with size + 4, copy all the elements from the old array to the new array, and then append the new element to the new array.
- Pro: less memory allocation & copying for append than the naive approach
- Con: still
$O(n)$ time complexity for append
Operation | Capacity | Size | Memory Alloc/Dealloc | Elements Copied |
---|---|---|---|---|
Initialize | 4 | 0 | 1 | 0 |
Append | 4 | 1 | 0 | 1 |
Append | 4 | 2 | 0 | 1 |
Append | 4 | 3 | 0 | 1 |
Append | 4 | 4 | 0 | 1 |
Append | 8 | 5 | 1 | 4 + 1 |
Append | 8 | 6 | 0 | 1 |
Append | 8 | 7 | 0 | 1 |
Append | 8 | 8 | 0 | 1 |
Append | 12 | 9 | 1 | 8 + 1 |
Append | 12 | 10 | 0 | 1 |
Append | ... | ... | ... | ... |
Total time to append
Amortized time complexity for append:
For any fixed
Amortized time complexity for append:
When we append to the end of the array, we check if the array is full. If it is full, we create a new array with size * 2, copy all the elements from the old array to the new array, and then append the new element to the new array.
- Pro: even less memory allocation & copying for append
- Con: average memory utilization is lower
Operation | Capacity | Size | Memory Alloc/Dealloc | Elements Copied |
---|---|---|---|---|
Initialize | 1 | 0 | 1 | 0 |
Append | 1 | 1 | 0 | 1 |
Append | 2 | 2 | 1 | 1 + 1 |
Append | 4 | 3 | 1 | 2 + 1 |
Append | 4 | 4 | 0 | 1 |
Append | 8 | 5 | 1 | 4 + 1 |
Append | 8 | 6 | 0 | 1 |
Append | 8 | 7 | 0 | 1 |
Append | 8 | 8 | 0 | 1 |
Append | 16 | 9 | 1 | 8 + 1 |
Append | 16 | 10 | 0 | 1 |
Append | ... | ... | ... | ... |
Time for appending
$$ \begin{align*} T_n(n)
&= n + \sum_{k=1}^{\log_2 n} 2^{k-1} \ &= n + \frac{1}{2} \sum_{k=1}^{\log_2 n} 2^{k} \ &= n + \frac{1}{2} \cdot \frac{2 (2^{\log_2 n} - 1)}{2 - 1} \ &= n + (2^{\log_2 n} - 1) \ &= n + (n - 1) = O(n) \ \end{align*} $$
Amortized time complexity for append:
Operation | Dynamic Array (e.g. std::vector ) |
Fixed-sized Array (e.g. std::array ) |
Doubly-linked List (e.g. std::list ) |
---|---|---|---|
Random Access ([] ) |
|||
Append |
|
Not supported | |
Insert | Not supported | ||
Remove | Not supported | ||
Memory | Continuous | Continuous | Not Continuous |
In practice, Dynamic Array is usually faster than Linked List in all operations when the number of elements is less than 20,000.
Why?
Actual Performance
- big O only tells us the asymptotic performance
- continuous memory locations for elements makes dynamic array practically faster than linked list