Skip to content

Commit c0676c1

Browse files
authored
Merge pull request #24 from yeoshuheng/main
linked list
2 parents be4b255 + a73908f commit c0676c1

File tree

4 files changed

+581
-62
lines changed

4 files changed

+581
-62
lines changed

src/dataStructures/linkedList/LinkedList.java

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public LinkedList(Node<T> head) {
4040
this.head = head;
4141
Node<T> trav = head;
4242
int count = 0;
43-
while (trav != null) {
43+
while (trav != null) { // We count the size of our linked list through iteration.
4444
count++;
4545
trav = trav.next;
4646
}
@@ -60,24 +60,6 @@ public int size() {
6060
return this.size;
6161
}
6262

63-
/**
64-
* Inserts the object at the front of the linked list
65-
* @param object to be inserted
66-
* @return boolean representing whether insertion was successful
67-
*/
68-
public boolean insertFront(T object) {
69-
return insert(object, 0);
70-
}
71-
72-
/**
73-
* Inserts the object at the end of the linked list
74-
* @param object to be inserted
75-
* @return boolean representing whether insertion was successful
76-
*/
77-
public boolean insertEnd(T object) {
78-
return insert(object, this.size);
79-
}
80-
8163
/**
8264
* inserts the object at the specified index of the linked list
8365
* @param object to be inserted
@@ -92,7 +74,7 @@ public boolean insert(T object, int idx) {
9274

9375
Node<T> newNode = new Node<>(object);
9476

95-
if (head == null) {
77+
if (head == null) { // Linked list empty; We just need to add the head;
9678
head = newNode;
9779
size++;
9880
return true;
@@ -105,7 +87,7 @@ public boolean insert(T object, int idx) {
10587
trav = trav.next;
10688
}
10789

108-
if (prev != null) {
90+
if (prev != null) { // Reset the pointer at the index.
10991
prev.next = newNode;
11092
newNode.next = trav;
11193
} else { // case when inserting at index 0; need to update head
@@ -116,6 +98,24 @@ public boolean insert(T object, int idx) {
11698
return true;
11799
}
118100

101+
/**
102+
* Inserts the object at the front of the linked list
103+
* @param object to be inserted
104+
* @return boolean representing whether insertion was successful
105+
*/
106+
public boolean insertFront(T object) {
107+
return insert(object, 0);
108+
}
109+
110+
/**
111+
* Inserts the object at the end of the linked list
112+
* @param object to be inserted
113+
* @return boolean representing whether insertion was successful
114+
*/
115+
public boolean insertEnd(T object) {
116+
return insert(object, this.size);
117+
}
118+
119119
/**
120120
* remove the node at the specified index
121121
* @param idx of the node to be removed
@@ -133,7 +133,7 @@ public T remove(int idx) {
133133
for (int i = 0; i < idx; i++) {
134134
prev = trav;
135135
trav = trav.next;
136-
}
136+
} // This iteration allows us to store a copy of nodes before and after idx.
137137

138138
if (prev != null) {
139139
prev.next = trav.next;
@@ -144,15 +144,38 @@ public T remove(int idx) {
144144
return trav.val;
145145
}
146146

147+
/**
148+
* search for the 1st encounter of the node that holds the specified object
149+
* @param object
150+
* @return index of the node found
151+
*/
152+
public int search(T object) {
153+
Node<T> trav = head;
154+
int idx = 0;
155+
if (trav == null) { // Empty linked list.
156+
return -1;
157+
}
158+
159+
while (trav != null) {
160+
if (trav.val.equals(object)) {
161+
return idx;
162+
}
163+
idx++;
164+
trav = trav.next;
165+
}
166+
167+
return -1;
168+
}
169+
147170
/**
148171
* delete the 1st encounter of the specified object from the linked list
149172
* @param object to search and delete
150173
* @return boolean whether the delete op was successful
151174
*/
152175
public boolean delete(T object) {
153-
int idx = search(object);
176+
int idx = search(object); // Get index of object to remove.
154177
if (idx != -1) {
155-
remove(idx);
178+
remove(idx); // Remove based on that index.
156179
return true;
157180
}
158181
return false;
@@ -174,28 +197,6 @@ public T poll() {
174197
return remove(0);
175198
}
176199

177-
/**
178-
* search for the 1st encounter of the node that holds the specified object
179-
* @param object
180-
* @return index of the node found
181-
*/
182-
public int search(T object) {
183-
Node<T> trav = head;
184-
int idx = 0;
185-
if (trav == null) {
186-
return -1;
187-
}
188-
189-
while (trav != null) {
190-
if (trav.val.equals(object)) {
191-
return idx;
192-
}
193-
idx++;
194-
trav = trav.next;
195-
}
196-
197-
return -1;
198-
}
199200

200201
/**
201202
* get the node at the specified index
@@ -204,7 +205,7 @@ public int search(T object) {
204205
*/
205206
public Node<T> get(int idx) {
206207
Node<T> trav = head;
207-
if (idx < this.size && trav != null) {
208+
if (idx < this.size && trav != null) { // Check: idx is valid & linked list not empty.
208209
for (int i = 0; i < idx; i++) {
209210
trav = trav.next;
210211
}
@@ -214,29 +215,38 @@ public Node<T> get(int idx) {
214215
}
215216

216217
/**
217-
* reverse the linked list
218+
* reverse the linked list.
219+
* A good video to visualise this algorithm can be found
220+
* <a = https://www.youtube.com/watch?v=D7y_hoT_YZI, href = "url">here</a>.
218221
*/
219222
public void reverse() {
220-
if (head == null || head.next == null) {
223+
if (head == null || head.next == null) { // No need to reverse if list is empty or only 1 element.
221224
return;
222225
}
223226

224227
Node<T> prev = head;
225228
Node<T> curr = head.next;
226-
Node<T> newHead = curr;
227-
prev.next = null;
229+
Node<T> newHead = curr; // Store the next head.
230+
prev.next = null; // Reset to null, representing end of list.
228231
while (curr.next != null) {
229-
newHead = curr.next;
230-
curr.next = prev;
232+
newHead = curr.next; // We set the next element as the newHead.
233+
curr.next = prev; // Replace our current node as the previous node.
231234
prev = curr;
232-
curr = newHead;
235+
curr = newHead; // Set our current node as the next element (newHead) to look at.
233236
}
234237
newHead.next = prev;
235-
head = newHead;
238+
head = newHead; // newHead is last ele from org. list -> First ele of our reversed list.
236239
}
237240

238241
/**
239-
* sorts the linked list by their natural order
242+
* Sorts the linked list by the natural order of the elements.
243+
* Generally, merge sort is the most efficient sorting algorithm for linked lists.
244+
* Addressing a random node in the linked list incurrs O(n) time complexity.
245+
* This makes sort algorithms like quicksort and heapsort inefficient since they rely
246+
* on the O(1) lookup time of an array.
247+
*
248+
* A good video to visualise this algorithm can be found
249+
* <a = https://www.youtube.com/watch?v=JSceec-wEyw, href = "url">here</a>.
240250
*/
241251
public void sort() {
242252
if (this.size <= 1) {
@@ -245,11 +255,11 @@ public void sort() {
245255
int mid = (this.size - 1) / 2;
246256
Node<T> middle = this.get(mid);
247257
Node<T> nextHead = middle.next;
248-
LinkedList<T> next = new LinkedList<>(nextHead, this.size - 1 - mid);
258+
LinkedList<T> next = new LinkedList<>(nextHead, this.size - 1 - mid); // Split the list into 2.
249259
middle.next = null;
250-
this.size = mid + 1; // update size of original LL after split
260+
this.size = mid + 1; // update size of original list after split
251261

252-
next.sort();
262+
next.sort(); // Recursively sort the list.
253263
this.sort();
254264
head = merge(this, next);
255265
}
@@ -268,6 +278,7 @@ private Node<T> merge(LinkedList<T> first, LinkedList<T> second) {
268278

269279
while (headFirst != null && headSecond != null) {
270280
if (headFirst.val.compareTo(headSecond.val) < 0) {
281+
// Note that first & second either only have 2 values or are already sorted.
271282
trav.next = headFirst;
272283
headFirst = headFirst.next;
273284
} else {
@@ -277,10 +288,9 @@ private Node<T> merge(LinkedList<T> first, LinkedList<T> second) {
277288
trav = trav.next;
278289
}
279290

280-
if (headFirst != null) {
291+
if (headFirst != null) { // Add any remaining nodes from first.
281292
trav.next = headFirst;
282-
}
283-
if (headSecond != null) {
293+
} else { // We know loop terminated because of second; Add any remaining nodes from second.
284294
trav.next = headSecond;
285295
}
286296
return dummy.next;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Linked Lists
2+
Linked lists are a linear structure used to store data elements.
3+
It consists of a collection of objects, used to store our data elements, known as nodes.
4+
5+
![Linked list image](https://media.geeksforgeeks.org/wp-content/cdn-uploads/20230726162542/Linked-List-Data-Structure.png)
6+
7+
*Source: GeeksForGeeks*
8+
9+
### Linked Lists vs Arrays
10+
Linked lists are similar to arrays in
11+
terms of used and purpose, but there are considerations when deciding which structure to use.
12+
13+
Unlike arrays, which are stored in contiguous locations in memory,
14+
linked lists are stored across memory and are connected to each other via pointers.
15+
16+
![Array image](https://beginnersbook.com/wp-content/uploads/2018/10/array.jpg)
17+
18+
*Source: BeginnersBook*
19+
20+
## Analysis
21+
Some common operations involved in a linked list includes looking up elements in a linked list and inserting elements into a linked list.
22+
23+
Searching a linked list requires O(n) time complexity whereas inserting into a linked list from a specified index requires O(n) time complexity.
24+
25+
## Notes / Further Details / Conclusion
26+
27+
### Memory Requirements & Flexibility
28+
As a contiguous block of memory must be allocated for an array, its size is fixed.
29+
If we declare a array of size *n*, but only allocate *x* elements, where *x < n*,
30+
we will be left with unused, wasted memory.
31+
32+
This waste does not occur with linked lists, which only take up memory as new elements are added.
33+
However, additional space will be used to store the pointers.
34+
35+
As the declared size of our array is static (done at compile time), we are also given less flexibility if
36+
we end up needing to store more elements at run time.
37+
38+
However, linked list gives us the option of adding new nodes at run time based on our requirements,
39+
allowing us to allocate memory to store items dynamically, giving us more flexibility.
40+
41+
### Conclusion
42+
You should aim to use linked list in scenarios where you cannot predict how many elements you need to store
43+
or if you require constant time insertions to the list.
44+
45+
However, arrays would be preferred if you already know the amount of elements you need to store ahead of time.
46+
It would also be preferred if you are conducting a lot of look up operations.
47+
48+
## Linked List Variants
49+
The lookup time within a linked list is its biggest issue.
50+
However, there are variants of linked lists designed to speed up lookup time.
51+
52+
### Doubly Linked List
53+
54+
![Doubly Linked List](https://media.geeksforgeeks.org/wp-content/cdn-uploads/gq/2014/03/DLL1.png)
55+
56+
*Source: GeeksForGeeks*
57+
58+
This is a variant of the linked list with each node containing the pointer to not just the next note, but also the previous node.
59+
60+
Unlike the standard linked list, this allows us to traverse the list in the backwards direction too, this makes it a good data structure to use when implementing undo / redo functions. However, when implementing a doubly linked list, it is vital to ensure that you consider and maintain **both** pointers.
61+
62+
It is also worth noting that insertion from the front and back of the linked list is a O(1) operation. (Since we now only need to change the pointers in the node.)
63+
64+
### Skip list
65+
66+
![Skip List](https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Skip_list.svg/800px-Skip_list.svg.png)
67+
68+
*Source: Brilliant*
69+
70+
This is a variant of a linked list with additional pointer paths that does not lead to the next node
71+
but allow you to skip through nodes in a "express pointer path" which can be user configured.
72+
If we know which nodes each express pointer path stops at, we would be able to conduct faster lookup.
73+
74+
This would also be ideal in situations where we want to store a large amount
75+
of data which we do not need to access regularly that we are not willing to delete.
76+
77+
### Unrolled Linked Lists
78+
79+
![Unrolled Linked List](https://ds055uzetaobb.cloudfront.net/brioche/uploads/5LFjevVjNy-ull-new-page.png?width=2400)
80+
81+
*Source: Brilliant*
82+
83+
Unrolled linked lists stores multiple consecutive elements into a single bucket node.
84+
This allows us to avoid constantly travelling down nodes to get to the element we need.

0 commit comments

Comments
 (0)