Hello scholars.
In this video, we're going to be learning
about linked lists.
A linked list is a data structure that stores
a sequence of values - similar to an array,
but with some key differences that make them
better suited to certain applications.
Each element in a linked list is a node, which
in its simplest form stores a value and a
pointer to the next element in the list.
So in this example, we have the first element
of the list, 4, which we call the head of
the list, pointing to the next element, 2,
which points to 7, which point sto the final
element in the list, 3, which we call the
tail.
The last element in the list points to nothing.
This box with a line through it is simply
a visual representation of the null pointer.
When working with a linked list, it's sufficient
to store a reference to the head of the list,
since each element points to the next one
in the list.
Another type of linked list is the doubly-linked
list, where each node points not only to the
next element, but also the previous element.
This can be a powerful modification, and we'll
see why in a minute.
Both singly and doubly-linked lists can be
made circular by pointing the tail back to
the head, and vice-versa in a doubly-linked
list.
This is particularly useful for doubly-linked
lists, because it means you can access the
tail of the list in constant time by just
looking at the previous pointer of the head.
If the list weren't circular, you would need
to traverse the entire list to reach the tail
or store a separate tail pointer for easy
access.
Let's look at common operations on lists to
understand the differences between the various
types.
First, let's look at a singly-linked list.
Accessing an element in a linked list takes
O(n) time, because you can't just index into
a list like you could with an array, since
you have to follow the pointers through to
each element until you get to the index you're
looking for.
Insertion into a linked list, however, is
much quicker than in an array, because all
you have to do is hook up a few pointers.
If we want to insert 1 after 7 here, all we
have to do is make 1 point to 2 and then 7
point to 1.
Since all we're doing is changing two pointers,
the operation runs in constant time.
However, you must already have access to the
element after which you're trying to insert
for the operation to take O(1) time, because
otherwise you need to take O(n) time to find
it.
You might expect removal to take constant
time as well, because we're just going to
be moving around a few pointers.
But in a singly-linked list, it's actually
going to take O(n) time.
Let's say we're trying to remove 2 from the
list.
That means we want to have 7 pointing to 3.
But in order to do that, we need access to
the previous element [7], which in a singly-linked
list isn't something we can do.
So if we're trying to remove an element in
a singly-linked list, we'll actually have
to traverse the entire list until we get to
the previous element, which is going to take
O(n) time, and then once we finally do have
the previous element, we can hook that up
to the next one.
So it's an O(n) time operation.
Searching for elements in linked lists will
also be an O(n) time operation, because you
have to compare against each element.
Binary search does not speed things up for
linked lists even when they're sorted, because
you don't have constant time access to elements.
The key difference between a singly-linked
list and a doubly-linked list is that a doubly-linked
list allows for constant time removal anywhere
in the list.
This is because we can always access the previous
element in constant time.
So if we're trying to remove 2, then we need
to hook 7 up to 3 and 3 up to 7, but we already
have access to both elements.
So all we have to do is take 7, point it to
3, take 3, point it to 7, and we're done.
Constant time.
All we need to do is move two pointers around.
Is there an advantage to making a linked list
circular?
Not really.
At least, not in terms of time complexity
of these operations.
Access in a circular list is still going to
take O(n) time because you're not going to
be able to access into it and it's not going
to speed up search, either.
The only slight advantage to having a circular
linked list is [that] if you have a doubly
linked list, then you don't need to keep track
of a tail pointer, because you can just use
the previous pointer of the head to get to
the tail.
Let's compare lists to arrays.
Accessing an element in a list is much slower,
because you can't index into it the way you
can with an array: O(n) vs O(1).
Insertion and removal, however, happen much
more quickly with lists than arrays: O(1)
vs O(n).
Searching a list is going to take O(n) time
no matter what, even if the list is sorted.
In contrast, with arrays, while unsorted arrays
are going to take O(n) time to search, a sorted
array only takes O(log n) time because you
can use binary search.
This means that you'll want to use lists instead
of arrays if you're doing a lot of insertion
and removal into your sequence.
However, if you need to be able to access
elements quickly, or you want to be able to
search for a large number of elements and
have the time to sort your data beforehand,
an array would be better suited to the job.
Guaranteed worst case constant time insertion
and removal into lists means that they're
much better suited for implementing things
like stacks and queues.
Of course, you could use a dynamic array or
a circular array to implement an infinitely-sized
stack or queue, but you will have to contend
with the fact that in the worst case, insertion
will take O(n) time, because you have to copy
over every element, even if the average case
is still going to be constant.
What do you need to remember?
A list is a sequence of nodes, with each node
storing a value and pointing to the next - and
sometimes the previous - element in the list.
Accessing an element in a list takes O(n)
time because you can't index into it.
You have to follow the pointers until you
find the element.
Insertion and removal into a doubly-linked
list takes O(1) time no matter where in the
list you're inserting or removing.
However, removal in a singly-linked list takes
O(n) time because you don't have previous
pointers and thus need to traverse until the
previous elemnet.
Circular linked lists have the tail pointing
to the head and vice versa in the case of
a doubly-linked list.
These don't change the time complexities of
operations, but it does mean that in a doubly-linked
list you don't need to keep track of a tail
pointer.
Arrays should be used when your primary operations
are access or search, whereas lists should
be used if your primary operations are insert
and delete.
Thank you for watching.
My goal with these videos is to help you learn,
so if there's anything you'd like to see me
do differently, please let me know.
If you have questions or feedback, please
leave a comment or come to my office hours
on Twitch.
If you found this video helpful, please share
it online or with your friends and classmates
to spread the knowledge.
Subscribe to continue learning.
This has been cubesmarching.
Thank you."
