Cs61b Experiment Record (2)

project1A

LinkedListDeque

circular sentinel

Incorrect writing:

public LinkedListDeque(a){
        sentinel=new Node(sentinel,null,sentinel.next);
        size=0;
    }
Copy the code

At the time of instantiation of sentinel, sentinel itself was still null. Using sentinel and sent.next to instantiate sentinel would not work.

We should:

public LinkedListDeque(a){
        sentinel=new Node(null.null.null);
        sentinel.next=sentinel;
        sentinel.prev=sentinel;
        size=0;
    }
Copy the code

The next and prev fields are assigned after the sentinel is instantiated.

Complete implementation:

public class LinkedListDeque<T> {

    private class Node{
        Node prev;
        T item;
        Node next;
        Node(Node prev,T item,Node next){
            this.item=item;
            this.prev=prev;
            this.next=next; }}private int size;
    private Node sentinel;
    public LinkedListDeque(a){
        sentinel=new Node(null.null.null);
        sentinel.next=sentinel;
        sentinel.prev=sentinel;
        size=0;
    }
    public void addFirst(T item) {
        sentinel.next=new Node(sentinel,item,sentinel.next);
        sentinel.next.next.prev=sentinel.next;
        size++;
    }
    public void addLast(T item) {
        sentinel.prev=new Node(sentinel.prev,item,sentinel);
        sentinel.prev.prev.next=sentinel.prev;
        size++;
    }

    public boolean isEmpty(a) {
        return size==0;
    }

    public int size(a) {
        return size;
    }

    public void printDeque(a) {
        Node pos=sentinel.next;
        for (int i=0; i<size-1; i++){ System.out.print(pos.item+"");
            pos=pos.next;
        }
        System.out.print(pos.item);
    }

    public T removeFirst(a) {
        if (sentinel.next==sentinel)
            return null;
        Node temp=sentinel.next;
        sentinel.next.next.prev=sentinel;
        sentinel.next=sentinel.next.next;
        size--;
        return temp.item;
    }

    public T removeLast(a) {
        if (sentinel.prev==sentinel)
            return null;
        Node temp=sentinel.prev;
        sentinel.prev.prev.next=sentinel;
        sentinel.prev=sentinel.prev.prev;
        size--;
        return temp.item;
    }

    public T get(int index) {
        Node pos=sentinel.next;
        if (index>=size)
            return null;
        for (int i=0; i<index; i++){ pos=pos.next; }return pos.item;
    }
    public T getRecursive(int index){
        if (index>=size)
            return null;
        return getRecursive(0,index,sentinel.next);
    }
    private T getRecursive(int pos,int index,Node x){
        if (pos==index)
            return x.item;
        return getRecursive(pos+1,index,x.next); }}Copy the code

ArrayDeque

Step by step, the complexity of the data structure implementation should be reduced and its implementation layered.

Resize should not be considered first, and the most basic data structure should be implemented first

public class ArrayDeque<T> {

        private int nextFirst;
        private int nextLast;
        private T[]items;
        private int size;
        public ArrayDeque(a){
            items=(T[])new Object[8];
            nextFirst=0;
            nextLast=1;
            size=0;
        }
        public void addFirst(T item) {
            items[nextFirst]=item;
            size++;
            nextFirst=nextFirst==0? items.length-1:nextFirst-1;
        }

        public void addLast(T item) {
            items[nextLast]=item;
            size++;
            nextLast=nextLast==items.length-1?0:nextLast+1;
        }

        public boolean isEmpty(a) {
            return size==0;
        }

        public int size(a) {
            return size;
        }

        public void printDeque(a) {
            for (int i=nextFirst+1; i! =nextLast; i=(i+1)%items.length)
                System.out.print(items[i]);
        }

        public T removeFirst(a) {
            if (nextFirst==0)return null;
            nextFirst++;
            T temp=items[nextFirst];
            items[nextFirst]=null;
            size--;
            return temp;
        }

        public T removeLast(a) {
            if (nextLast==1)return null;
            nextLast--;
            T temp=items[nextLast];
            items[nextLast]=null;
            size--;
            return temp;
        }

        public T get(int index) {
            if (index>=size)
                return null;
            return items[(nextFirst+1+index)%items.length]; }}Copy the code

In fact, we can find that since nextFirst is 0 and nextLast is 1, special judgment should be made when the inflection point of the array is encountered, which causes trouble for the implementation of our method. We can further think, why not set nextFirst to the last element of the array and nextLast to 0? We were surprised to find that there were fewer special judgments.

public class ArrayDeque<T> {

        private int nextFirst;
        private int nextLast;
        private T[]items;
        private int size;
        public ArrayDeque(a){
            items=(T[])new Object[8];
            nextFirst=items.length-1;
            nextLast=0;
            size=0;
        }
        public void addFirst(T item) {
            items[nextFirst]=item;
            size++;
            nextFirst--;
        }

        public void addLast(T item) {
            items[nextLast]=item;
            size++;
            nextLast++;
        }

        public boolean isEmpty(a) {
            return size==0;
        }

        public int size(a) {
            return size;
        }

        public void printDeque(a) {
            for (int i=nextFirst+1; i! =nextLast; i=(i+1)%items.length)
                System.out.print(items[i]);
        }

        public T removeFirst(a) {
            if (nextFirst==items.length-1)return null;
            nextFirst++;
            T temp=items[nextFirst];
            items[nextFirst]=null;
            size--;
            return temp;
        }

        public T removeLast(a) {
            if (nextLast==0)return null;
            nextLast--;
            T temp=items[nextLast];
            items[nextLast]=null;
            size--;
            return temp;
        }

        public T get(int index) {
            if (index>=size)
                return null;
            return items[(nextFirst+1+index)%items.length]; }}Copy the code

Since nextLast and nextFirst are unlikely to cross the inflection point, we no longer need special judgments in the ADD approach

Resize is also easy to implement by copying the beginning and end of the original array into the new array.

Complete implementation:

public class ArrayDeque<T> {

        private int nextFirst;
        private int nextLast;
        private T[]items;
        private int size;
        public ArrayDeque(a){
            items=(T[])new Object[8];
            nextFirst=items.length-1;
            nextLast=0;
            size=0;
        }
        private void resize(int capacity){
            T[]a=(T[])new Object[capacity];
            System.arraycopy(items,0,a,0,nextLast);
            int length=items.length-1-nextFirst;
            System.arraycopy(items,nextFirst+1,a,capacity-length,length);
            nextFirst=capacity-length-1;
            items=a;
        }
        public void addFirst(T item) {
            if (nextFirst-1==nextLast)
                resize(items.length*2);
            items[nextFirst]=item;
            size++;
            nextFirst--;
        }

        public void addLast(T item) {
            if (nextFirst-1==nextLast)
                resize(items.length*2);
            items[nextLast]=item;
            size++;
            nextLast++;
        }

        public boolean isEmpty(a) {
            return size==0;
        }

        public int size(a) {
            return size;
        }

        public void printDeque(a) {
            for (int i=nextFirst+1; i! =nextLast-1; i=(i+1)%items.length)
                System.out.print(items[i]+"");
            System.out.print(items[nextLast-1]);
        }

        public T removeFirst(a) {
            if (nextFirst==items.length-1)return null;
            nextFirst++;
            T temp=items[nextFirst];
            items[nextFirst]=null;
            size--;
            if (items.length>=16&&size<items.length/4)
                resize(items.length/2);
            return temp;
        }

        public T removeLast(a) {
            if (nextLast==0)return null;
            nextLast--;
            T temp=items[nextLast];
            items[nextLast]=null;
            size--;
            if (items.length>=16&&size<items.length/4)
                resize(items.length/2);
            return temp;
        }

        public T get(int index) {
            if (index>=size)
                return null;
            return items[(nextFirst+1+index)%items.length]; }}Copy the code

Just by having two Pointers to the beginning and the end of an array, which is a completely different data structure, you can use arrays to implement a two-way queue, and you can go from linear time to constant time. We went from linear time to constant time.


Updated March 22, 2021:

All of the above ArrayDeque is wrong!

The previous approach ended up getting a 40/50 score on autoGrader and half the score on ArrayDeque. I thought there were only some minor special cases that I didn’t take into account, so I didn’t check and correct them.

I went back and looked at the code and found that it was too naive, and most of ArrayDeque was wrong! The corrections are as follows:

I naively assumed that if I placed nextLast and nextFirst at the beginning and end of the array, respectively, they would never cross the inflection point. Obviously wrong! Because even if add does not cross the inflection point, nextLast and nextFirst can still cross the inflection point when remove is executed!

So, since nextFirst and nextLast are likely to pass the inflection point anyway, it doesn’t matter where they point.

Complete code:

public class ArrayDeque<T> {

    private int nextFirst;
    private int nextLast;
    private int capacity;
    private T[]items;
    private int size;
    public ArrayDeque(a){
        items=(T[])new Object[8];
        this.capacity=items.length;
        nextFirst=capacity-1;
        nextLast=0;
        size=0;
    }
    private void resize(int capacity){
        T[]a=(T[])new Object[capacity];
        // Because the positions of nextFirst and nextLast are uncertain, we can only copy them into the new array one by one
        // Start copying from the first point to the right of nextFirst
        // Copy ends at the first point to the left of nextLast
        for (int i=1; i<=size; i++) a[i]=items[(++nextFirst)%this.capacity];
        this.capacity=capacity;
        // It doesn't matter where these two Pointers point to
        nextFirst=0;
        nextLast=size+1;
        items=a;
    }
    public void addFirst(T item) {
        // Resize when size equals capacity instead of looking at the relative positions of two Pointers
        if (size==capacity)
            resize(capacity*2);
        items[nextFirst]=item;
        size++;
        //nextFirst may be out of bounds
        nextFirst=nextFirst==0? capacity-1:nextFirst-1;
    }

    public void addLast(T item) {
        if (size==capacity)
            resize(capacity*2);
        items[nextLast]=item;
        size++;
        //nextLast may be out of bounds
        nextLast=(nextLast+1)%capacity;
    }

    public boolean isEmpty(a) {
        return size==0;
    }

    public int size(a) {
        return size;
    }

    public void printDeque(a) {
        //nextFirst may point to the last position
        for (int i=(nextFirst+1)%capacity; i! =nextLast-1; i=(i+1)%capacity)
            System.out.print(items[i]+"");
        System.out.print(items[nextLast-1]);
    }

    public T removeFirst(a) {
        // Remove cannot be performed when the contents of the array are empty, not depending on the location of nextFirst.
        if (size==0)return null;
        nextFirst=(nextFirst+1)%capacity;
        T temp=items[nextFirst];
        items[nextFirst]=null;
        size--;
        if (capacity>=16&&size<capacity/4)
            resize(capacity/2);
        return temp;
    }

    public T removeLast(a) {
        if (size==0)return null;
        nextLast=nextLast==0? capacity-1:nextLast-1;
        T temp=items[nextLast];
        items[nextLast]=null;
        size--;
        if (capacity>=16&&size<capacity/4)
            resize(capacity/2);
        return temp;
    }

    public T get(int index) {
        if (index>=size)
            return null;
        return items[(nextFirst+1+index)%capacity]; }}Copy the code

Autograder score:

project1B

Nothing special

See my GitHub for the solution

Github.com/BoL0150/Ber…