cs61b week2--The SLList

Posted by lanbor on Tue, 23 Nov 2021 09:37:59 +0100

1. Naked recursive write linked list

public class IntList {
    public int first;
    public IntList rest;

    public IntList(int f, IntList r) {
        first = f;
        rest = r;
    }
...

Imagine writing a list node in the above way, then every time you call it in public static void main(),
use
IntList L1 = new IntList(5, null);
When we want to insert a new node before the current node,

IntList L = new IntList(5, L);
IntList L = new IntList(15, L);
IntList L = new IntList(50, L);

You need to write this every time. The first parameter of IntList() is the data item, and the second is the node pointer. Using the above statement, the pointer of the current new node points to the previous node every time, so as to complete the header insertion. But in terms of readability, it is very unfriendly for novices in Java.

2. Improved linked list writing method

In essence, it encapsulates some repetitive operations in the class, thus making it more clear and concise in main().
First, encapsulate the node class, which is no different from the naked encapsulation above

public class IntNode{
    public int item;
    public IntNode next;

    public IntNode(int f,IntList r){
        item = f;
        next = r;
    }
}

Then build an SLList class to encapsulate some methods, such as constructors:

    public SLList(int x){
        first = new IntNode(x,null);
    }

Then our parameters are reduced to only one x, which eliminates the need to add null each time,
Add a new node before the current node:

    public void addFirst(int x){
        first = new IntNode(x,first);
    }

Similar to the method of adding a new node in main() above, it is used every time
IntList L = new IntList(data,L)
We encapsulate the operation of each new node's rest pointer pointing to the previous node in the class, eliminating the operation that needs to be written once every call
Return the data of the current node:

    public int getFirst(){
        return first.item;
    }

Full code:

public class SLList{
    public IntNode first;

    public SLList(int x){
        first = new IntNode(x,null);
    }

    public void addFirst(int x){
        first = new IntNode(x,first);
    }

    public int getFirst(){
        return first.item;
    }
}

What's wonderful is that when we call it in main(), it no longer looks messy and easier to understand. For example, the operations of building a chain header node and adding a new node:

SLList L = new SLList(15);
L.addFirst(10);
L.addFirst(5);

You can see at a glance that the two nodes 10 and 5 are added.

3.Private statement

Suppose someone has a stupid practice, write this

public class SLLTroubleMaker {
    public static void main(String[] args) {
        SLList L = new SLList(15);
        L.addFirst(10);
        L.first.next.next = L.first.next;
    }
}

When you use
L.first.next.next = L.first.next;
This will cause the node to point to itself, resulting in an infinite loop. In order to avoid outsiders calling the first member, we can make it private, that is
private IntNode first;
Private variables often need to be understood by users. For example, for a car, we don't need to know how its engine burns gasoline. We only need to operate the public steering wheel,
When you declare public, it means that the world has permanent access to it

4. Nested classes

Because the name of the java main class must be the same as the file name of the.java, it can not be compiled. In the past, we always wrote two class in two.java files, and then called them, for example, the creation of the linked list in the above. We first wrote public class IntNode in a.java file, and then called it in public.java class in another.java file.
Obviously, this is troublesome, so we can write class IntNode into class SLList as a nested class

public class SLList {
       public class IntNode {
            public int item;
            public IntNode next;
            public IntNode(int i, IntNode n) {
                item = i;
                next = n;
            }
       }

       private IntNode first; 

       public SLList(int x) {
           first = new IntNode(x, null);
       } 
...

Contact the private mentioned above. We can set class IntNode to private and prohibit access to its internal members
private class IntNode
static
When the nested class does not need to use any methods and members of the external class at all, static can be added and declared as static, which means that the methods in the static class cannot access any members of the closed class. In this case, this means that IntNode will not be able to access first, addFirst() or getFirst().
private static class IntNode
The premise of using static is to ensure that nested classes do not need to access external elements at all. Adding static can save memory space

5.addLast() and size()

Next, you need to add two methods to add a new node at the end of the linked list and calculate the total length of the linked list,
Since each default new node is a header insertion, and each new node is inserted before the current node, the first is always the header node. To add a new node at the end of the linked list, you need to traverse the linked list until you reach the last node, and then add it

public void addLast(int x) {
    IntNode p = first;

    while (p.next != null) {
        p = p.next;
    }
    p.next = new IntNode(x, null);
}

Since our main class is SLList and has no next pointer, we need to use the next in IntNode to add auxiliary methods

private static int size(IntNode p) {
    if (p.next == null) {
        return 1;
    }

    return 1 + size(p.next);
}

This is a recursive method to find the length, which is very similar to finding the depth of a binary tree. It returns 1 + the length of the remaining linked list, i.e. the length of the current node. As Josh Hu said, this is the language of God, which we convert into the language of mortals

public int size() {
    return size(first);
}

Java allows two functions to have the same name, as long as the parameter definitions of the two functions are different, which is called function overloading

6. Improving size() -- caching

When the amount of data is large enough, the time complexity is O(n). When it takes two seconds to calculate a 100 length linked list, it may take 2000 seconds to calculate a 100000 length linked list. For this, we need to improve, that is, add a size variable to record the length of the current linked list

public class SLList {
    private IntNode first;
    private int size;

    public SLList(int x) {
        first = new IntNode(x, null);
        size = 1;
    }

    public void addFirst(int x) {
        first = new IntNode(x, first);
        size += 1;
    }

    public int size() {
        return size;
    }
    ...
}

At the beginning, there is only one header node, size=1. After that, you can increase it by size+1. The time complexity will be O(1) level.

7. Empty linked list and sentinel node

Suppose we want to create an empty linked list

public SLList() {
    first = null;
    size = 0;
}

So when we call the previous addLast() method, the

p=first;
while(p.next!=null)

Here, p=first=null. Calling p.next is equivalent to calling null.next. Obviously, NullPointerException will be triggered. The simple solution is to add a special condition to addLast()

    if (first == null) {
        first = new IntNode(x, null);
        return;
    }

However, when we are faced with more complex data structures, such as trees and graphs, this special judgment will be very troublesome. Therefore, in order to unify, we set a sentinel node, that is, it is equivalent to the head node. Its data field does not put any value (it can also be assigned arbitrarily, don't care), and then the real data items are stored from the next item of the node, In this way, the judgment condition of the empty linked list is head.next=null, and the null pointer exception will not be triggered

The code after all sentinel nodes are added is

public class SLList{

    private static class IntNode{
    public int item;
    public IntNode next;

    public IntNode(int i,IntList n){
        item = i;
        next = n;
    }
}

    public IntNode sentinel;
    private int size;

    public SLList(){
        sentinel = new IntNode(??,null);
        size = 0;
    }

    public SLList(int x){
        sentinel = new IntNode(??,null);
        sentinel.next = new IntNode(x,null);
        size = 1;
    }

    public void addFirst(int x){
        sentinel.next = new IntNode(x,sentinel.next);
        size++;
    }

    public int getFirst(){
        return sentinel.next.item;
    }

    public void addLast(int x){
        IntNode p = sentinel;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new IntNode(x,null);
        size++;
    }
}

8.Invariants

SLList with sentinel nodes has at least the following variables:

  • Sentinel references always point to sentinel nodes.
  • The first real node (if any) is always located at sentinel.next.item.
  • The size variable of SSList is always the total number of items added.

Topics: Java