Java
Java¶
Introduction¶
Work Flow¶
Basic Java Features¶
Basic Structure¶
public class ClassName { // Class name = filename (ClassName.java)
public static void main(String[] args) { // Entry point
System.out.println("Hello, World!");
}
}
Static Typing¶
All variables, parameters, and methods must have a declared type. Expressions also have an implicit type.
The compiler checks that types are compatible before the program even runs.
However, when used with mixed types (String + number), Java converts the number to a String and performs concatenation
Functions¶
In Java, all code is in class, so all functions are methods.
public class Larger {
public static int larger(int x, int y) {
if (x > y) {
return x;
}
return y;
}
public static void main(String[] args) {
System.out.println(larger(8, 10));
}
}
Code Style, Comments, Javadoc¶
Defining and Using Classes¶
dot notation either side of a dot is not treated as expression thus it is not evaluated. → use hard-coded dot notation.
Constructors¶
public class Dog {
public int weightInPounds;
public Dog(int w) { // constructor
weightInPounds = w;
}
}
d = new Dog(20); // initialize
this
keyword can be used to access the current object's instance variables or methods, which is not mandatory (as long as variable names are unique)
however,
Variables¶
public class Dog {
public int weightInPounds; // instance variable / non-static variable
public static String binomen = "Canis familiaris"; // class variable / static variable
...
}
d = new Dog(20)
w = d.weightInPouns //invoked by instance
b = Dog.binomen //invoked by class
Methods¶
Class methods / static methods taken by the class itself
Instance methods / non-static methods taken only by a specific instance of a classpublic class Dog {
public int weightInPounds;
public void makeNoise() {
if (weightInPounds < 10) { // take instance variable
System.out.println("yipyipyip!");
} else if (weightInPounds < 30) {
System.out.println("bark. bark.");
} else {
System.out.println("woof!");
}
}
}
References¶
Declaring a Variable¶
- When you declare a variable of a certain type, Java finds a contiguous block with exactly enough bits to hold a thing of that type
- Java provides no way for you to know the location of the box
- There are no default values.
The Golden Rule of Equals (GRoE)¶
y = x
: copy the bits from x into y
Primitive Types¶
- numeric:
- short (1B), byte (2B), int (4B), long (8B)
- float (4B), double (8B)
- char (2B) -- can express Chinese charactors
- boolean (1B):
true
&false
Reference Types¶
Reference Variable Declaration¶
When we declare a variable of any reference type, Java allocates a box of 64 bits, no matter what type of object.Object Instantiation & Assignment¶
Initialization instantiate withnew
: allocates a box for each instance variable of the class, and fills them with a default value.
| object type | default value |
|---|---|
| numeric | 0 / 0.0 |
| char | null char |
|boolean|false|
| reference type | null |
public class Person {
int age; // default: 0
String name; // default: null
boolean isStudent; // default: false
}
Assignment
assign with =
: store the address of the instance
assign null
to a reference variable: all zeros
About
null
: If we try to access an instance member or call an instance method from a value of null, we will see an error called aNullPointerException
.
Parameter Passing¶
Copying the bits is usually called "pass by value". In Java, we always pass by value.
Lists¶
IntList¶
constructor: first
& rest
IntList
correctly, the programmer must understand and utilize recursion even for simple list related tasks.
SLList (single linked list)¶
Invariants (a fact about a data structure that is guaranteed to be true): - The sentinel reference always points to a sentinel node. - The front item (if it exists), is always at sentinel.next.item. - The size variable is always the total number of items that have been added.
public class SLList {
// nested classes definition
public static class IntNode { // rename
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode sentinel; // private
private int size; // caching: saving important data to speed up retrieval
public SLList(int x){
sentinel = new IntList(0, null);
sentinel.next = new IntList(x, null);
size = 1;
}
public SLList(){
// overload: the same method name, different argument signature
sentinel = new IntList(0, null);
size = 0;
}
public int size(){
return size;
}
public void addFirst(int x){
size += 1; // keep current track
sentinel.next = new IntList(x, first);
}
public int getFirst(){
return sentinel.next.item;
}
public void addLast(int x){
size += 1;
/* implement without sentinel:
if (first == null){ // cope with special cases -- not pretty
first = new IntList(x);
return;
}
*/
IntNode p = sentinel; // sentinel: to avoid special cases
while (p.next != null){
p = p.next;
}
p.next = new IntNode(x, null);
/* wrong:
while (p != null){
p = p.next;
}
p = new...
*/
}
Nested Class¶
Having a nested class has no meaningful effect on code performance, and is simply a tool for keeping code organized.
public class SLList {
public static class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode sentinel;
...
reference to static nested classes - in the closing class:
- out of the closing class:DLList (double linked list)¶
solutions | addLast | removeLast | cases of last |
---|---|---|---|
SLList: | |||
first sentinel | slow | ||
+ last pointer | quick | slow | |
DLList: | |||
first sentinel + last node | quick | quick | special (first sentinel or real node) |
first sentinel + last sentinel | quick | quick | generel |
circular | quick | quick | general |
- sentBack:
- circular:
Generic Lists¶
public class DLList<BleepBlorp> {
private IntNode sentinel;
private int size;
public class Node {
public Node prev;
public BleepBlorp item;
public Node next;
...
}
...
}
int
or double
inside of angle brackets.
Instead, we use the reference version of the primitive type
Arrays¶
Array Creation¶
int[] x;
x = new int[]{1, 2, 3, 4, 5};
x = new int[5]; // get default value
x[0] = 1;
x[1] = 2;
int[] x = {1, 2, 3, 4, 5}
x = null; // null
x = int[0]; // empty
int n = 5;
x = new int[x]; // can be evaluated
== vs. Arrays.equals¶
==
compare the literal bits in memory boxes x and yArray.equals
compare the two content of the two arrays
Array Access and Modification¶
Array Copy
- System.arraycopy()
2D Arrays¶

int[][] pascalsTriangle;
pascalsTriangle = new int[4][];
int[] rowZero = pascalsTriangle[0]; // default value: null
pascalsTriangle[0] = new int[]{1};
pascalsTriangle[1] = new int[]{1, 1};
pascalsTriangle[2] = new int[]{1, 2, 1};
pascalsTriangle[3] = new int[]{1, 3, 3, 1};
int[] rowTwo = pascalsTriangle[2];
rowTwo[1] = -5;
int[][] pascalAgain = new int[][]{{1}, {1, 1},{1, 2, 1}, {1, 3, 3, 1}};
Arrays vs. Classes¶
Both arrays and classes can be used to organize a bunch of memory boxes. In both cases, the number of memory boxes is fixed, - the length of an array cannot be changed - class fields cannot be added or removed.
differences - Array boxes are numbered and accessed using [] notation, and class boxes are named and accessed using dot notation. - Array boxes must all be the same type. Class boxes can be different types.
ArrayList¶
Invarianats¶
-
The position of the next item to be inserted (using addLast) is always size.
-
The number of items in the AList is always size.
-
The position of the last item in the list is always size - 1.
The list is an abstract idea, and thepublic class AList{ private int[] item; private int size; public AList(){ item = int[100]; size = 0; } }
size
,items
, anditems[i]
memory boxes are the concrete representation of that idea. Any change to our list must be reflected in a change in one or more memory boxes in our implementation. Our invariants provide us with a guide for what those changes should look like.
removeLast¶
no need to actually zero the element
Resizing¶
Naive Resizing Arrays¶
Geometric Resizing¶
Memory Performance¶
"usage ratio" R In a typical implementation, we halve the size of the array when R falls to less than 0.25.
Generic ALists¶
Java does not allow us to create an array of generic objects due to an obscure issue with the way generics are implemented.
changes of remove¶
Whereas before, we had no reason to zero out elements that were deleted, with generic objects, we do want to null out references to the objects. This is to avoid "loitering". If we fail to null out the reference, then Java will not garbage collect the objects that have been added to the list.
Inheritance¶
Hierarchy¶
- Superclass: Hypernyms
- Subclass: Hyponyms
Static & Dynamic Type¶
Static Type | Dynamic Type | |
---|---|---|
specified in | declaration | initialization |
checked at | compilie time | runtime |
dynamic method selection: When Java runs a method that is overriden, it searches for the appropriate method signature in it's dynamic type and runs it.
Compile-Time Type Checking¶
Compiler allows method calls & assignment based on compile-time type of variable & expressions.
An expression using the new
keyword has the specified compile-time type.
- RHS expression:
VengefulSLList
- LHS variable:
SLList
- A VengefulSLList is-an SLList, so assignment is allowed.
Method calls have compile-time type equal to their declared type.
Poodle frank = new Poodle("Frank", 5);
Poodle frankJr = new Poodle("Frank Jr.", 15);
Dog largerDog = maxDog(frank, frankJr); // succeed
Poodle largerPoodle = maxDog(frank, frankJr); // error
Casting¶
specify the compile-time type of any expression.
powerful but dangerous:
Poodle frank = new Poodle("Frank", 5);
Malamute frankSr = new Malamute("Frank Sr.", 100);
Poodle largerPoodle = (Poodle) maxDog(frank, frankSr); // can compile, but crash at runtime
¶
Poodle frank = new Poodle("Frank", 5);
Malamute frankSr = new Malamute("Frank Sr.", 100);
Poodle largerPoodle = (Poodle) maxDog(frank, frankSr); // can compile, but crash at runtime
Interface Inheritance¶
Interface It is essentially a contract that specifies what a subclass must be able to do, but it doesn't provide any implementation for those behaviors. - what √ - how ×
public interface List61B<Item> {
void addFirst(Item x);
void add Last(Item y);
Item getFirst();
Item getLast();
Item removeLast();
Item get(int i);
void insert(Item x, int position);
int size();
}
public
Class If a “subclass” has a method with the exact same signature as in the “superclass”, we say the subclass overrides the method. - should assign the same or stronger access privileges
public class AList<Item> implements List61B<Item>{
...
@Override
public void addFirst(Item x) {
insert(x, 0);
}
...
// must override all the method (wihtout implementation) in the interface
}
Implementation Inheritance¶
superclass: - interface → default method - class → extend
Default Methods¶
public interface List61B<Item> {
...
default public void print() { // must include the `default` keyword
for (int i = 0; i < size(); i += 1) {
System.out.print(get(i) + " ");
}
System.out.println();
}
}
SLList
to print a different way than the way specified in its interface. To do this, override.
@Override
public void print() {
for (Node p = sentinel.next; p != null; p = p.next) {
System.out.print(p.item + " ");
}
}
Extend¶
RotatingSLList
Because of extends,public class RotatingSLList<Blorp> extends SLList<Blorp>{ public void rotateRight() { Blorp oldBack = removeLast(); addFirst(oldBack); } }
RotatingSLList
inherits all members ofSLList
: - All instance and static variables. - All methods. - All nested classes.
imexplixit contructor:
VengefulSllist
the constructor behavior: rule: All constructors must start with a call to one of the super class’s constructorspublic class VengefulSLList<Item> extends SLList<Item> { /* Remembers all Items that have been destroyed by removeLast. */ private SLList<Item> deletedItems; public VengefulSLList() { deletedItems = new SLList<Item>(); } @Override public Item removeLast() { Item oldBack = super.removeLast(); // calls Superclass’s version of removeLast() deletedItems.addLast(oldBack); return oldBack; } public void printLostItems() { deletedItems.print(); } }
The Object Class¶
Every type in Java is a descendant of the Object
class
Encapsulation¶
Some tools for managing complexity:
-
Hierarchical abstraction.
- Create layers of abstraction, with clear abstraction barriers!
-
“Design for change”
- Organize program around objects.
- Let objects decide how things are done.
- Hide information others don’t need.
Module: A set of methods that work together as a whole to perform some task or set of related tasks. A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface.
Implementation Inheritance Breaks Encapsulation¶
public class Dog{
...
public void bark() {
System.out.println("bark");
}
public void barkMany(int N) {
for (int i = 0; i < N; i += 1) {
bark();
}
}
}
public class VerboseDog extends Dog{
...
@Override
public void barkMany(int N) {
System.out.println("As a dog, I say: ");
for (int i = 0; i < N; i += 1) {
bark();
}
}
}
Higher Order Functions¶
Higher Order Function: A function that treats another function as data.
Fundamental issue: Memory boxes (variables) cannot contain pointers to functions. use an interface (to realize generalization) instead:
public interface IntUnaryFunction {
int apply(int x);
}
public class TenX implements IntUnaryFunction {
public int apply(int x) {
return 10 * x;
}
}
public class HoFDemo {
public static int do_twice(IntUnaryFunction f, int x) {
return f.apply(f.apply(x));
}
public static void main(String[] args) {
System.out.println(do_twice(new TenX(), 2));
// wrong: do_twice(TenX, 2) -- pass an instance!
}
}
Subtype Polymorphism¶
Polymorphism, at its core, means 'many forms'. In Java, polymorphism refers to how objects can have many forms or types -- an instance of its own class, its superclass, its superclass's superclass...
larger
Explicit HoF Approach:
def print_larger(x, y, compare, stringify):
if compare(x, y):
return stringify(x)
return stringify(y)
Comparable & Comparator¶
Comparable¶
OurComparable
public class Dog implements OurComparable{ ... @Override public int compareTo(Object o){ // the same signature! Dog otherDog = (Dog) o; // casting, or cannot compile return this.size - otherDog.size; } }
- Setbacks: - casting (verbose and dangerous) - not concludes other types (Integers, Strings...)public class Maximizer { public static OurComparable max(OurComparable[] items) { int maxDex = 0; for (int i = 0; i < items.length; i += 1) { int cmp = items[i].compareTo(items[maxDex]); if (cmp > 0) { maxDex = i; } } return items[maxDex]; } }
Comparable
Comparable
is already defined by Java and is used by countless libraries.
public class Dog implements Comparable<Dog> {
...
@Override
public int compareTo(Dog uddaDog) {
return this.size - uddaDog.size;
}
}
public class Maximizer {
public static Comparable max(Comparable[] items) {
int maxDex = 0;
for (int i = 0; i < items.length; i += 1) {
int cmp = items[i].compareTo(items[maxDex]);
if (cmp > 0) {
maxDex = i;
}
}
return items[maxDex];
}
}
Comparator¶
Natural order: the ordering implied in the compareTo
method of a particular class.
What if we'd like to sort in a different way?
-- using Comparator
's
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> { // nested static class
@Override
public int compare(Dog a, Dog b) {
return a.name.compareTo(b.name); // String is comparable
}
}
public static Comparator<Dog> getNameComparator() {
return new NameComparator();
}
}
public static void main(String[] args){
Dog d1 = new Dog("Cheems", 100);
Dog d2 = new Dog("Lucy", 99);
Comparator<Dog> nc = Dog.getNameComparator();
/* Dog.NameComparator nc = new Dog.NameComparator() */
int cmp = nc.compare(d1, d2);
if (cmp > 0){
d1.bark();
} else {
d2.bark();
}
}
Iterable & Iterator¶
Enhenced For Loop¶
equivalent toIterator¶
interface
implementArraySetIterator
A common convention is to throw aprivate class ArraySetIterator implements Iterator<T> { private int wizPos; public ArraySetIterator() { wizPos = 0; } @Override public boolean hasNext() { return wizPos < size; } @Override public T next() { T returnItem = items[wizPos]; wizPos += 1; return returnItem; } }
NoSuchElementException
if someone callsnext
whenhasNext
returns false
Iterable¶
interface
implementArraySet
now we can use the enhanced for loop on ourimport java.util.Iterator; public class ArraySet<T> implements Iterable<T> { private T[] items; private int size; public ArraySet() { items = (T[]) new Object[100]; size = 0; } public boolean contains(T x) {...} public void add(T x) { if (x == null) { throw new IllegalArgumentException("can't add null"); } ... } public int size() {...} @Override public Iterator<T> iterator() { return new ArraySetIterator(); } private class ArraySetIterator implements Iterator<T> { ... } }
ArraySet
Object Methods¶
String toString()
¶
When you run System.out.println(dog)
, it's actually doing this:
toString()
method prints the hashcode of the object.
will get something like:
ArraySet
-
naive string concatenation usign
+
: creates an entirely new string sinceString
is immutable -
using
StringBuilder
: creates a string object that is mutable - use
String.join
method convert list of strings into a single string
boolean equals(Object obj)
¶
Object class' equals
method:
ArraySet
@Override
public equals(Object o){ // the same signeture!!!
if (o instanceof ArraySet oas) {
if (this.size != oas.size) {
return false;
}
for (T i : this) {
if (!oas.contains(i)) {
return false
}
}
return true;
}
return false; // if o is not an ArraySet
}
instanceof
keyword checks the dynamic type of the object
equivalent to
ADT¶
An Abstract Data Type (ADT) is defined only by its operations, not by its implementation.
Some commonly used ADT's:
- Stacks: Structures that support last-in first-out retrieval of elements
push(int x)
int pop()
- Lists: an ordered set of elements
add(int i)
int get(int i)
- Sets: an unordered set of unique elements (no repeats)
add(int i)
contains(int i)
- Maps: set of key/value pairs
put(K key, V value)
V get(K key)
Disjoint Set¶
Interface¶
Implementation¶
Implementation | isConnected |
connect |
---|---|---|
Quick Find | Θ(1) | Θ(N) |
Quick Union | O(N) | O(N) |
Weighted Quick Union (WQU) | O(log N) | O(log N) |
WQU with Path Compression | O(α(N)) | O(α(N)) |
public class WQUDSWithPathCompression implements DisjointSets {
private int[] parent;
private int[] size;
public WQUDSWithPathCompression(int N) {
parent = new int[N];
size = new int[N];
for (int i = 0; i < N; i++) {
parent[i] = i;
size[i] = 1;
}
}
private int find(int p) {
if (p != parent[p]) {
parent[p] = find(parent[p]);
}
return parent[p];
}
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
public void connect(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP != rootQ) {
if (size[rootP] < size[rootQ]) {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
} else {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
}
}
}
}
¶
public class WQUDSWithPathCompression implements DisjointSets {
private int[] parent;
private int[] size;
public WQUDSWithPathCompression(int N) {
parent = new int[N];
size = new int[N];
for (int i = 0; i < N; i++) {
parent[i] = i;
size[i] = 1;
}
}
private int find(int p) {
if (p != parent[p]) {
parent[p] = find(parent[p]);
}
return parent[p];
}
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
public void connect(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP != rootQ) {
if (size[rootP] < size[rootQ]) {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
} else {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
}
}
}
}
BST¶
Operations¶
Search
static BST find(BST T, Key sk) {
if (T == null)
return null;
if (sk.equals(T.key))
return T;
else if (sk ≺ T.key)
return find(T.left, sk);
else
return find(T.right, sk);
}
static BST insert(BST T, Key ik) {
if (T == null)
return new BST(ik);
if (ik ≺ T.key)
T.left = insert(T.left, ik);
else if (ik ≻ T.key)
T.right = insert(T.right, ik);
return T;
}
Delete Hibbard deletion
Performance¶
worst case: spindly tree \(\(H = O(N)\)\)
as Sets and Maps¶
- as Sets: decrease the runtime of
contains
to \(log(n)\) - as Maps: each node holds a
(key,value)
pair
B-Tree (Spliting Tree)¶
Operations¶
B-Trees with a limit of 3 items per node are also called 2-3-4 trees or 2-4 trees (a node can have 2, 3, or 4 children). Setting a limit of 2 items per node results in a 2-3 tree.
eg. 2-4 tree:
Invariants:
- All leaves are the same distance from the root.
- A non-leaf node with k items must have exactly k + 1 children.
Performance¶
\(\(H = Θ(logN)\)\)
Red Black Tree¶
Tree Rotation¶
left rotation
- G's right child, P, merges with G, bringing its children along.
- P then passes its left child to G and G goes down to the left to become P's left child.
private Node rotateRight(Node h) {
// assert (h != null) && isRed(h.left);
Node x = h.left;
h.left = x.right;
x.right = h;
return x;
}
// make a right-leaning link lean to the left
private Node rotateLeft(Node h) {
// assert (h != null) && isRed(h.right);
Node x = h.right;
h.right = x.left;
x.left = h;
return x;
}
LLRB Tree¶
- Left-Leaning Red-Black trees have a 1-1 correspondence with 2-3 trees
- As for 2-3-4 trees, they maintain correspondence with standard Red-Black trees.
Performance¶
Height is no more than \(2 * height + 1\) of the corresponding 2-3 tree. so \(\(H = Θ(logN)\)\)
Operations¶
Search the same as BST Insert Always insert the new node with a red link to its parent node, because in a 2-3 tree, we are always inserting by adding to a leaf node.
- If there is a right-leaning “3-node”, we have a Left Leaning Violation.
Rotate left the appropriate node to fix.
- If there are two consecutive left links, we have an Incorrect 4 Node Violation.
Rotate right the appropriate node to fix.
- If there are any nodes with two red children, we have a Temporary 4 Node.
Color flip the node to emulate the split operation.
public class RedBlackTree<T extends Comparable<T>> { RBTreeNode<T> root; static class RBTreeNode<T> { final T item; boolean isBlack; RBTreeNode<T> left; RBTreeNode<T> right; RBTreeNode(boolean isBlack, T item, RBTreeNode<T> left, RBTreeNode<T> right) { this.isBlack = isBlack; this.item = item; this.left = left; this.right = right; } } public RedBlackTree() { root = null; } void flipColors(RBTreeNode<T> node) { node.isBlack = false; node.left.isBlack = true; node.right.isBlack = true; } RBTreeNode<T> rotateRight(RBTreeNode<T> node) { if (node.left == null) { throw new IllegalArgumentException("no left child"); } RBTreeNode newRoot = node.left; RBTreeNode subT = newRoot.right; newRoot.right = node; node.left = subT; flipColors(newRoot); // change color!! return newRoot; } RBTreeNode<T> rotateLeft(RBTreeNode<T> node) { if (node.right == null) { throw new IllegalArgumentException("no right child"); } node.isBlack = false; RBTreeNode newRoot = node.right; newRoot.isBlack = false; // red!! RBTreeNode subT = newRoot.left; newRoot.left = node; node.right = subT; return newRoot; } private boolean isRed(RBTreeNode<T> node) { return node != null && !node.isBlack; } public void insert(T item) { root = insert(root, item); root.isBlack = true; } private RBTreeNode<T> insert(RBTreeNode<T> node, T item) { /* ordinary insert */ if (node == null) { RBTreeNode newNode = new RBTreeNode(false, item, null, null); return newNode; } if (item.compareTo(node.item) < 0) { node.left = insert(node.left, item); } else if (item.compareTo(node.item) == 0) { return node; } else { node.right = insert(node.right, item); } /* adjust */ if (!isRed(node.left) && isRed(node.right)) { return rotateLeft(node); } if (isRed(node.left) && isRed(node.left.left)) { return rotateRight(node); } if (isRed(node.left) && isRed(node.right)) { flipColors(node); } return node; } }
Hashing¶
Motivation -- Set / Map¶
interface: - search (boolean contain) - insert (add / put)
implement: | | search |insert| |---|---|---| | Array | O(N) | O(N) (search first) | | BST | O(N) |O(N)| |B-Tree / RBT | O(logN)|O(logN)|
setbacks: - search tree require items to be comparable - runtime
Solution -- Categorize¶
suppose N items and M buckets runtime (evenly distrubuted): \(Θ(N/M)\) - M is contant: \(Θ(N)\) - N / M is constant: \(Θ(1)\) √ - load factor: Java picks 0.75
-> adjust M as N increases to maintain N / M as a constant
Categorizing Strategies¶
Principle¶
- can apply on every item
- allow resizing
- evenly distributed
Specified Strategies¶
Integers: - LSB: the last digit -> the lase two digit -> ... (can't apply on 1, 2...) - Modular: % m -> % 2 * m -> ...
lowercase letters: - the first letter -> the first two letter -> ... (can't apply on "a") - integerization: base 26
String: - integerization by ascii: base 126 (can't apply on Chinese...)
General Strategy -- Hashcode¶
We want a general categorizing strategy that applies to every object. Idea: integerization -> reduced to a valid index (module...)
Default hashcode Function¶
Integer Overflow In Java, there are only finitely many integers, the largest 2,147,483,647. If you go over this limit, you overflow, starting back over at the smallest integer, which is -2,147,483,648.
to calculate the modulo of a negative number: Math.floorMod(x, m)
default hashCode() Object method: related to the memory address
Distribution: a good spread, since the memory address is effectively random
Strings method:
@Override
public int hashCode(String s) {
int intRep = 0;
for (int i = 0; i < s.length(); i += 1) {
intRep = intRep * 31;
intRep = intRep + s.charAt(i);
}
return intRep;
}
Custom hashCode Function¶
hashcode() work with equals()
@Override
public boolean equals(Object o) {
if (o instanceof ColoredNumber otherCn) {
return this.num == otherCn.num;
}
return false;
}
int N = 20;
HashSet<ColoredNumber> hs = new HashSet<>();
for (int i = 0; i < N; i += 1) {
hs.add(new ColoredNumber(i));
}
ColoredNumber twelve = new ColoredNumber(12);
hs.contains(twelve); // returns ??
hs.add(twelve); // what happens ??
false
→ add a duplicated value
- find the same bin, return true
by custom hashcode():
returntrue
Hashing a Collection
@Override
public int hashCode() {
int hashCode = 1;
for (Object o : this) {
hashCode = hashCode * 31;
hashCode = hashCode + o.hashCode();
}
return hashCode;
}
@Override
public int hashCode() {
if (this.value == null) {
return 0;
}
return this.value.hashCode() +
31 * this.left.hashCode() +
31 * 31 * this.right.hashCode();
}
Avoid Mutable Keys¶
List<Integer> items = new ArrayList<>();
items.add(0);
items.add(1);
HashSet<List<Integer>> hs = new HashSet<>();
hs.add(items);
items.add(7);
System.out.println(hs.contains(items)); // return false!
handling collisions¶
- External Chaining: Dynamic Array of ListsSet
- Linear Probing
Priority Queue & Heap¶
PQ¶
/** (Min) Priority Queue: Allowing tracking and removal of
* the smallest item in a priority queue. */
public interface MinPQ<Item> {
/** Adds the item to the priority queue. */
public void add(Item x);
/** Returns the smallest item in the priority queue. */
public Item getSmallest();
/** Removes the smallest item from the priority queue. */
public Item removeSmallest();
/** Returns the size of the priority queue. */
public int size();
}
Implements | add | getSmallest | removeSmallest |
---|---|---|---|
Ordered Array | \(\theta(N)\) | \(\theta(1)\) | \(\theta(1)\) |
Bushy BST | \(\theta(logN)\) | \(\theta(logN)\) | \(\theta(logN)\) |
HashTable | \(\theta(1)\) | \(\theta(N)\) | \(\theta(N)\) |
Heap | \(\theta(logN)\) | \(\theta(1)\) | \(\theta(logN)\) |
Heap¶
- complete
- min-heap property
Array-based heaps take around ⅓rd the memory it takes to represent a heap using approach 1A (direct pointers to children)
Graph¶
API¶
public class Graph {
public Graph(int V): Create empty graph with v vertices
public void addEdge(int v, int w): add an edge v-w
Iterable<Integer> adj(int v): vertices adjacent to v
int V(): number of vertices
int E(): number of edges
...
public class Paths {
public Paths(Graph G, int s): Find all paths from G
boolean hasPathTo(int v): is there a path from s to v?
Iterable<Integer> pathTo(int v): path from s to v (if any)
}
Traversal¶
DFS¶
public class DepthFirstPaths {
private boolean[] marked;
private int[] edgeTo;
private int s;
public DepthFirstPaths(Graph G, int s) {
...
dfs(G, s);
}
private void dfs(Graph G, int v) {
marked[v] = true;
for (int w : G.adj(v)) {
if (!marked[w]) {
edgeTo[w] = v;
dfs(G, w);
}
}
}
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) return null;
List<Integer> path = new ArrayList<>();
for (int x = v; x != s; x = edgeTo[x]) {
path.add(x);
}
path.add(s);
Collections.reverse(path);
return path;
}
public boolean hasPathTo(int v) {
return marked[v];
}
}
BFS¶
public class BreadthFirstPaths {
private boolean[] marked;
private int[] edgeTo;
...
private void bfs(Graph G, int s) {
Queue<Integer> fringe =
new Queue<Integer>();
fringe.enqueue(s);
marked[s] = true;
while (!fringe.isEmpty()) {
int v = fringe.dequeue();
for (int w : G.adj(v)) {
if (!marked[w]) {
fringe.enqueue(w);
marked[w] = true;
edgeTo[w] = v;
}
}
}
}
Representations¶
Adj List | Adj Matrix | |
---|---|---|
runtime (print graph) | \(O(V + E)\) | \(O(V^2)\) |
suitable for | sparse | dense |
Shortest Path¶
- unweighted: BFS
- weighted:
- no negative edge: Dijkstra Algorithm
- no negative edge: A* Algorithm
- DAG: topological
Dijkstra Algorithm¶
- Vertices: PQ
def relax(edge p, q):
if q is visited (i.e., q is not in PQ):
return
if distTo[p] + weight(edge) < distTo[q]:
distTo[q] = distTo[p] + w
edgeTo[q] = p
PQ.changePriority(q, distTo[q])
question -- negative edge¶
Shortest Path Algorithm for DAGs¶
Since we visit vertices in topological order, a vertex is visited only when all possible info about it has been considered.
Longest Path Algorithm for DAGs¶
reduction: DAG LPT -> DAG SPT
MST¶
Prim Algorithm¶
def prim_mst(graph):
start_vertex = 0
mst = []
visited = set()
pq = PriorityQueue()
pq.put((0, start_vertex))
while not pq.empty():
weight, current_vertex = pq.get()
if current_vertex in visited:
continue
visited.add(current_vertex)
mst.append((weight, current_vertex))
for neighbor, weight in graph[current_vertex]:
if neighbor not in visited:
pq.put((weight, neighbor))
return mst
Kruskal Algorithm¶
- Vertices: Disjoint Set
- Edges: Min Heap / Ordered List
public class KruskalMST {
private List<Edge> mst = new ArrayList<Edge>();
public KruskalMST(EdgeWeightedGraph G) {
MinPQ<Edge> pq = new MinPQ<Edge>();
for (Edge e : G.edges()) {
pq.insert(e);
} // or O(N) approach
WeightedQuickUnionPC uf = new WeightedQuickUnionPC(G.V());
while (!pq.isEmpty() && mst.size() < G.V() - 1) {
Edge e = pq.delMin();
int v = e.from();
int w = e.to();
if (!uf.connected(v, w)) {
uf.union(v, w);
mst.add(e);
}
}
}
}
Try¶
If we know that our keys all have some common special property, we can sometimes get even better implementations.
when the keys are always ASCII characters / chars -- Simply use an array!
public class DataIndexedCharMap<V> {
private V[] items;
public DataIndexedCharMap(int R) {
items = (V[]) new Object[R];
}
...
}
public class TrieSet {
private static final int R = 128; // ASCII
private Node root; // root of trie
private static class Node {
private boolean isKey;
private DataIndexedCharMap<Node> next;
private Node(char c, boolean b, int R) {
ch = c; isKey= b;
next = new DataIndexedCharMap<>(R);
}
}
}
space: - DataIndexedCharMap: wasteful other implements: - list - BST - hash table