This is the lecture note of CS61B - Lecture 5.
Review the IntList
we implemented in lecture 4. We call it "naked linked list".
1 | public class IntList { |
While it works, the above “naked” linked lists are hard to use.
Inspired by our experience with the IntList, we'll now build a new class called SLList
, which much more closely resembles the list implementations that programmers use in modern languages. We'll do so by iteratively adding a sequence of improvements.
Improvement 1: Rebranding and culling
Firstly, rename the IntList
class as IntNode
and remove all methods except the constructor.
1 | public class IntNode { |
Then, create a class called SLList
. Let IntNode be its instance variable, and add "addFirst" and "getFirst" methods to it.
1 | /** An SLList is a list of integers, which hides the terrible |
Until now, I bet you have already realized what's the strength of SLList - it is easier to instantiate and use.
Actually, it is the data structure that makes it easier to use.
Improvement 2: The private Keyword
However, the above implementation has some flaws. One of them is the abuse of public. For example, users of this class might to do some unexpected operations, like this:
1 | SLList L = new SLList(15); |
We can prevent programmers from making such mistakes with the private keyword. The private keyword restricts access. It prevents code in other classes from using members (or constructors) of a class.
1 | public class SLList { |
Improvement 3: Nested Classes
The IntNode class doesn't stand on its own, and is obviously subordinate to SLList. We can make it as a nested class.
1 | public class SLList { |
Here, we use private
and static
modifier.
- private: if other external classes never use the nested class, declare it private
- static: if the nested class never uses any instance variables or methods of the outer class, declare it static (results in a minor savings of memory)
Improvement 4: Recursive private helper Methods
To motivate our remaining improvements, and to give more functionality to our SLList class, let's add .addLast
and .size
methods.
1 | /** Adds an item to the end of the list. */ |
You may notice we have two .size
methods. They have the same function name, but one has parmaters while another does not have. We call it Overload.
Improvement 5: Caching
There are some issues of addLast
and size
methods - both of them are pretty slow.
Can you figure out why?
This is becauses we need to traverse the whole linked-list.
In this lecture, we will focus on modifying size method so that the execution time of it is always fast. In the next lecture, we will get inspiration for quick addLast method.
Our solution is maintaining a special size
variable that caches the size of the list. With this improvement, the execution time of size() method is independent of the size of the list.
Caching: putting aside data to speed up retrieval.
1 | public class SLList { |
Improvement 6: Sentinel Nodes
Let's add a new constructor so that we can represent empty SLList.
1 | // add a new constructor |
It Seems pretty good, right?
Well, actually there is a subtle bug, can you find it?
1 | // If you create an empty SLList, then using addLast method, the code will crash. |
Let's do our last improvement of SLList to fix this bug.
Naive Solution
One possible solution is adding a special case for the empty list.
1 | public void addLast(int x) { |
In this case, it's ok. But in some other complex cases, such as tree data structure, it will cause vast amount of complexity, and make the code ugly. 🤮
There is a better way!
Sentinel Solution
We avoid special cases by making all SLLists (even empty) the same!
The following is our final implementation of SLList
.
1 | /** An SLList is a list of integers, which hides the terrible |
Invariants
