61b 一拖再拖,课内学得稀烂。把 61b 的仓库 public 了,搬了一堆屎到博客上,希望得到存在或不存在的人的监督🙏✊ 又听到 Josh Hug 的声音,感觉好幸福。

Lists in Java#

date: 2025-8-20 23:35:07

public class SLList {
    private static class Int Node {
        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);
    }
    
    public void addFirst(int x) {
        first = new IntNode(x, first);
    }
    public int getFirst() {
        return first.item;
    }
    
    // Why is it `static`?
    private static int size(IntNode p) {
        if (p.next == null) {
            return 1;
        }
        return 1 + size(p.netx);
    }
    public int size() {
        return size(first);
    }
}

更强的封装性,更面向对象。留意 .size() 的实现。


The meaning of a sentinel#

统一的逻辑,严谨的结构(空表)


addLast is slow#

需要将倒数第二个节点的 next 置空,要遍历链表。


If we just maintain a lastNode, the moveLast would be slow#

多维护一个 lastNode 也没有用(思考)。


So the solution is… Doubly Linked List!#

因此想到双向链表。


Two sentinel? Can be more perfect. One Sentinel!#

image-20250820151743741

image-20250820231056714

public class DLList<T> {
	private class Node {
		public T item;
		public Node next;
        public Node prev;
		
		public Node(T i, Node n, Node p) {
			item = i;
			next = n;	
			prev = p;
		}	
	}
	
	private Node sentinel;
	private int size;
	
	public DLList() {
		size = 0;
		sentinel = new Node(null, null, null);\
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
	}
	
    public void addFirst(T x) {
        Node newNode = new Node(x, sentinel.next, sentinel);
        sentinel.next.prev = newNode;
        sentinel.next = newNode;
        ++size;
    }
    
    public void removeFirst() {
        if (size == 0) {
            throw new IllegalStateException("Cannot remove from an empty list");
        }
        sentinel.next.next.prev = sentinel;
        sentinel.next = sentinel.next.next;
        --size;
    }
	
    public T getFirst() {
        if (size == 0) {
            throw new IllegalStateException("Cannot get first element of an empty list");
        }
        return sentinel.next.item;
    }
	
    public void addLast(T x) {
        Node newNode = new Node(x, sentinel, sentinel.prev);
        sentinel.prev.next = newNode;
        sentinel.prev = newNode;
        ++size;
    }
    
    public void removeLast() {
        if (size == 0) {
            throw new IllegalStateException("Cannot remove from an empty list");
        }
        sentinel.prev.prev.next = sentinel;
        sentinel.prev = sentinel.prev.prev;
        --size;
    }
    
    public T getLast() {
        if (size == 0) {
            throw new IllegalStateException("Cannot get last element of an empty list");
        }
        return sentinel.prev.item;
    }
    
	public int size() { return size; }
}

Lecture8-10#

date: 2025-9-26 9:40:27

接口继承和实现继承#

Java 中, 继承分为接口继承 implements 和实现继承 entends.

前者提供一个接口 interface , 包含所有子类应实现的功能, 但没有具体实现, 子类用 implements 关键字声明.

public interface List61B<Item> {
    public void addFirst(Item x);
    public void add Last(Item y);
    public Item getFirst();
    public Item getLast();
    public Item removeLast();
    public Item get(int i);
    public void insert(Item x, int position);
    public int size();
}
public class AList<Item> implements List61B<Item> {
    @Override
    public void addFirst(Item x) { ... }
    
    ...
}

后者的使用情景为子类需要重写父类的某些方法, 对于这些方法, 加入 default 关键字声明, 子类用 extends 关键字声明.

default public void print() { // 需要重写的方法
    for (int i = 0; i < size(); ++i) {
        System.out.print(get(i) + " ");
    }
    System.out.println();
}
@Override
public void print() {
    for (Node p = sentinel.next; p != null; p = p.next) {
        System.out.print(p.item + " ");
    }
}

使用 extends 关键字, 子类继承父类构造和析构函数外的所有成员(虽然不继承, 但在子类对象构造时也会被显示/隐式调用), 并且 private variables 不能被子类直接访问.


静态类型和动态类型#

省流: 编译检查看静态类型, 为了通过编译检查会用到强制类型转换; 虚表查找(…就是那个意思)看动态类型


高阶函数和子类多态性#

Java 中高阶函数的实现.

public interface IntUnaryFunction {
    int apply(int x);
}
public TenX implements IntUnaryFunction {
    /* Returns ten times the argument. */
    @Override
    public int apply(int x) {
        return 10 * x;
    }
}
public static int do_twice(IntUnaryFunction f, int x) {
    return f.apply(f.apply(x));
}

比较器#

public class Dog implements Comparable<Dog> {
    ...
    public int compareTo(Dog uddaDog) {
        return this.size - uddaDog.size;
    }
}

上述代码通过 Comparable 实现了运算符重载.

需要实现多种不同的比大小方法时, 用到另一个类 Comparator

import java.util.Comparator;

public class Dog implements Comparable<Dog> {
    ...
    public int compareTo(Dog uddaDog) {
        return this.size - uddaDog.size;
    }
    
    private static class NameComparator implements Comparator<Dog> {
        public int compare(Dog a, Dog b) {
            return a.name.compareTo(b.name);
        }
    }
    
    public static Comparator<Dog> getNameComparator() {
        return new NameComparator();
    }
}

Eceptions and Literators#

date: 2025-9-26 11:07:16

本节实现数据结构 ArraySet

Exceptions#

throw new ExceptionObject(parameter1, ...)
/* Associates pthe specified value with the specified key in this map.
   Throws an IllegalArgumentException if the key is null. */
public void add(T x) {
    if (x == null) {
        throw new IllegalArgumentException("can't add null");
    }
    if (contains(x)) {
        return;
    }
    items[size] = x;
    size += 1;
}

Iterators and Iterables#

public interface Iterable<T> {
    Iterator<T> iterator();
}
public interface Iterator<T> {
    boolean hasNext();
    T next();
}
import java.util.Iterator:

public class ArraySet<T> implements Iterable<T> {
    private T[] items;
    private int size; // the next item to be added will be at position `size`
    
    public ArraySet() {
        items = (T[]) new Object[100];
        size = 0;
    }
    
    /* Returns true if this map contains a mapping for specified key. */
    public boolean contains(T x) {
        for (int i = 0; i < size; ++i) {
            if (items[i].equals(x)) {
                return true;
            }
        }
        return false;
    }
    
    /* Associates the specified value with the specified key in this map.
       Throws an IllegalArgumentException if the key is null. */
    public void add(T x) {
        if (x == null) {
            throw new IllegalArgumentException("can't add null");
        }
        if (contains(x)) {
            return;
        }
        items[size] = x;
        size += 1;
    }
    
    /* Returns the number of key-value mappings in this map */
    public int size() {
        return size;
    }
    
    /* Returns an iterator into ME */
    public Iterator<T> iterator() {
        return new ArraySetIterator();
    }
    
    private class ArraySetIterator implements Iterator<T> {
        private int wizPos;
        
        public ArraySetIterator() {
            wizPos = 0;
        }
        
        public boolean hasNext() {
            return wizPos < size;
        }
        
        public T next() {
            T returnItem = items[wizPos];
            wizPos += 1;
            return returnItem;
        }
    }
    
    public static void main(String[] args) {
        ArraySet<Integer> aset = new ArraySet<>();
        aset.add(5);
        aset.add(23);
        aset.add(42);

        // iteration
        for (int i : aset) {
            System.out.println(i);
        }
}
    }

JUnit Check#

date: 2025-9-26 14:17:05

public class MaxArrayDequeTest {
    @Test
    /** Adds a few things to the list, checking isEmpty() and size() are correct,
     * finally printing the results.
     *
     */
    public void addIsEmptySizeTest() {
        Comparator<String> c = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        };
       MaxArrayDeque<String> mad1 = new MaxArrayDeque<>(c);

       assertTrue("A newly initialized MADeque should be empty", mad1.isEmpty());
       mad1.addFirst("front");

       assertEquals(1, mad1.size());
       assertFalse("mad1 should now contain 1 item", mad1.isEmpty());

       mad1.addLast("middle");
       assertEquals(2, mad1.size());

       mad1.addLast("back");
       assertEquals(3, mad1.size());

       System.out.println("Printing out deque: ");
       mad1.printDeque();
    }

    @Test
    public void addRemoveTest() {
        Comparator<Integer> c = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
        MaxArrayDeque<Integer> mad1 = new MaxArrayDeque<>(c);
        assertTrue("mad1 should be empty upon initialization", mad1.isEmpty());

        mad1.addFirst(1);
        assertFalse("mad1 should contain 1 item", mad1.isEmpty());

        mad1.removeFirst();
        assertTrue("mad1 should be empty after removal", mad1.isEmpty());
    }

    @Test
    public void removeEmptyTest() {
        Comparator<Integer> c = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
        MaxArrayDeque<Integer> mad1 = new MaxArrayDeque<>(c);
        mad1.addFirst(1);

        mad1.removeLast();
        mad1.removeFirst();
        mad1.removeLast();
        mad1.removeFirst();

        int size = mad1.size();
        String errorMsg = "  Bad size returned when removing from empty deque.\n";
        errorMsg += "  student size() returned " + size + "\n";
        errorMsg += "  actual size() returned 0\n";

        assertEquals(errorMsg, 0, size);
    }

    @Test
    public void multipleParamTest() {
        MaxArrayDeque<String>  mad1 = new MaxArrayDeque<String>();
        MaxArrayDeque<Double>  mad2 = new MaxArrayDeque<Double>();
        MaxArrayDeque<Boolean> mad3 = new MaxArrayDeque<Boolean>();

        mad1.addFirst("string");
        mad2.addFirst(3.14159);
        mad3.addFirst(true);

        String s = mad1.removeFirst();
        double d = mad2.removeFirst();
        boolean b = mad3.removeFirst();
    }

    @Test
    public void bigMADequeTest() {
        MaxArrayDeque<Integer> mad1 = new MaxArrayDeque<>();
        for (int i = 0; i < 1000000; i++) {
            mad1.addLast(i);
        }

        for (double i = 0; i < 500000; i++) {
            assertEquals("Should have the same value", i, (double) mad1.removeFirst(), 0.0);
        }

        for (double i = 999999; i > 500000; i--) {
            assertEquals("Should have the same value", i, (double) mad1.removeLast(), 0.0);
        }
    }

    @Test
    public void maxTest() {
        Comparator<Integer> c = new  Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
        MaxArrayDeque<Integer> mad1 =  new MaxArrayDeque<>(c);

        mad1.addFirst(1);
        mad1.addLast(29);
        mad1.addLast(18);
        mad1.addLast(2912);
        mad1.addLast(292);
        mad1.addFirst(2912);

        assertEquals((Integer) 2912, mad1.max());
    }

}

toString and equals#

date: 2025-10-3 15:06:53

toString#

@Override
publiic String toString() {
    String returnString = new String();
    for (int i = 0; i < size - 1; ++i) {
        returnString += items[i.toString();
        returnString += ", ";
    }
    returnString += items[size - 1].toString;
    returnString += ", ";
	return returnString;
}

利用迭代拼接字符串 String 效率特别低(每次迭代会复制一个字符串), 复杂度为 O(n²), 故改用 StringBuilder

@Override
public String toString() {
    StringBuilder returnSB = new StringBuilder("{");
    for (int i = 0; i < size - 1; ++i) {
        returnSB.append(items[i].toString());
        returnSB.append(", ");
    }
    returnSB.append(items[size - 1]);
    returnSB.append("}");
    return returnSB.toString();
}

Shorter toString:

@Override
public String toString() {
    List<String> listOfItems = new ArrayList<>();
    for (T x : this) {
        listOfItems.add(x.toString());
    }
    return "{" + String.join(", ", listOfItems) + "}";
}

这样的实现更简洁, 但还是效率低下, 如果不需要频繁调用 toString, 这样写更好看.


equals#

@Override
public boolean equals(Object o) { // the parameter must be Object, not ArraySet or something, because this is a overriding method
    if (o instanceof Dog uddaDog) {
        return this.size == uddaDog.size;
    }
    return false;
}

instanceof:

  • checks to see if o’s dynamic type is Dog
  • implicitly casts o as a Dog into a variable called uddaDog
@Override
public boolean equals(Object o) {
    if (this == o) {
        return true; 
    } 
    if (o instanceof ArraySet oas) {
        if (this.size != oas.size) {
            return false;
        }
        for (T x : this) {
            if (!oas.contains(x)) {
                return false;
            }
        }
        return true;
    }
    return false;
}

.of#

var arg (variable number of arguments)

new pattern of generic (of is a static method which can not access generic param T)

public static <Glerp> ArraySet<Glerp> of(Glerp... stuff) { // Glerp has nothing to do with T, it's just the declaration that this is a method which is generic
    ArraySet<Glerp> returnSet = new ArraySet<Glerp>();
    for (Glerp x : stuff) {
        returnSet.add(x);
    }
    return returnSet;
}

腰斩了,对不起我曾经成为算法高手的理想