Lecture 05 DLLs and Arrays
Doubly Linked Lists (In Brief)
Draw box and pointer notation on paper if you find this part a bit hard to understand.
One Downside of SLLists
Inserting at the back of an
SLList
is much slower than the front.
public void addFirst(int x) {
size += 1;
sentinel.next = new IntNode(x, sentinel.next);
}
public void addLast(int x) {
size += 1;
IntNode p = sentinel;
while (p.next != null) {
p = p.next;
}
p.next = new IntNode(x, null);
}
Improvement #7: .last
and .prev
We added .last. What other changes might we make so that remove is also fast?
-
Add backwards links from every node.
-
This yields a “doubly linked list” or DLList, as opposed to our earlier “singly linked list” or SLList.
Doubly Linked Lists
A Naive Version
Reverse pointers allow all operations (add, get, remove) to be fast.
- We call such a list a “doubly linked list” or
DLList
.
Non-obvious fact: This approach has an annoying special case: last sometimes points at the sentinel, and sometimes points at a ‘real’ node.
Improve: Double Sentinel Version
One solution: Have two sentinels.
Improve: Circular Sentinel Version
Even better topology (IMO) :
Use one sentinel as both the front and last sentinel, creating a circle.
Improvement #8: Fancier Sentinel Node(s)
While fast, adding .last
and .prev
introduces lots of special cases.
To avoid these, either:
- Add an additional
sentBack
sentinel at the end of the list. - Make your linked list
circular
, with a single sentinel in the middle.
DLList Summary
Methods | Non-Obvious Improvements |
---|---|
addFirst(int x) | Rebranching: IntList -> IntNode |
getFirst() | Bureaucracy: SLList |
size() | Access Control: public -> private |
addLast(int x) | Nested Class: Bringring IntNode into SLList |
removeLast() | Caching: Saving size as an int. |
Generalizing: Adding a sentinel node to allow representation of the empty list. | |
Looking back: .last and .prev allow fast removeLast | |
Sentinel upgrade: Avoiding special cases with sentBack or circular list |
Generic Lists
One issue with our list classes: They only supports integers.
SLists
Java allows us to defer type selection until declaration.
public class SLList<BleepBlorp> {
private IntNode sentinel;
private int size;
public class IntNode {
public BleepBlorp item;
public IntNode next;
...
}
...
}
SLList<Integer> s1 = new SLList<>(5);
s1.addFirst=(10);
SLList<String> s2 = new SLList<>("hi");
s2.addFirst("apple");
Generics
DLList<Double> s1 = new DLList<>(5.3);
double x = 9.3 + 15.2;
s1.insertFront(x);
We’ll spend a lot more time with generics later, but here are the rules of thumb you’ll need:
-
In the
.java
file implementing your data structure, specify your “generic type” only once at the very top of the file. -
In
.java
files that use your data structure, specify desired type once:- Write out desired type during declaration.
- Use the empty diamond operator <> during instantiation.
-
When declaring or instantiating your data structure, use the reference type.
- int: Integer
- double: Double
- char: Character
- boolean: Boolean
- long: Long
- etc.
Arrays
Getting Memory Boxes
To store information, we need memory boxes, which we can get in Java by declaring variables or instantiating objects. Examples:
int x;
/* Gives us a memory box of 32 bits that stores ints. */
Walrus w1;
/* Gives us a memory box of 64 bits that stores Walrus references. */
Walrus w2 = new Walrus(30, 5.6);
/* Gives us a memory box of 64 bits that stores Walrus references,
* and also gives us 96 bits for storing the int size (32 bits) and
* double tuskSize (64 bits) of our Walrus. */
Arrays are a special kind of object which consists of a numbered sequence of memory boxes.
-
To get ith item of array A, use A[i].
-
Unlike class instances which have have named memory boxes.
Arrays
Arrays consist of:
-
A fixed integer length (cannot change!)
-
A sequence of N memory boxes where N=length, such that:
- All of the boxes hold the same type of value (and have same # of bits).
- The boxes are numbered 0 through length-1.
Like instances of classes:
-
You get one reference when its created.
-
If you reassign all variables containing that reference, you can never get the array back.
Unlike classes, arrays do not have methods.
Like classes, arrays are (almost always) instantiated with new.
Three valid notations:
x = new int[3];
/* Creates array containing 3 int boxes (32 x 3 = 96 bits total).
* Each container gets a default value. */
y = new int[]{1, 2, 3, 4, 5};
int[] z = {9, 10, 11, 12, 13};
/* Can omit the new if you are also declaring a variable. */
All three notations create an array, which we saw before comprises:
- A length field.
- A sequence of N boxes, where N = length.
As an aside: In Oracle’s implementation of Java, all Java objects also have some overhead. Total size of an array=192 + KN bits, where K is the number of bits per item.
Array Basics:
Just figure out the syntax by yourself through the following example.
int[] z = null;
int[] x, y;
x = new int[]{1, 2, 3, 4, 5};
y = x;
x = new int[]{-1, 2, 5, 4, 99};
y = new int[3];
z = new int[0];
int xL = x.length;
String[] s = new String[6];
s[4] = "ketchup";
s[x[3] - x[1]] = "muffins";
int[] b = {9, 10, 11};
System.arraycopy(b, 0, x, 3, 2);
Arraycopy
System.arraycopy(b, 0, x, 3, 2); //(In Java)
#(In python):
x[3:5] = b[0:2] #List Slice Assignment
Two ways to copy arrays:
-
Item by item using a loop.
-
Using
System.arraycopy
, takes 5 parameters:- Source array
- Start position in source
- Target array
- Start position in target
- Number to copy
arraycopy
is (likely to be) faster, particularly for large arrays. More compact code.
- Code is (arguably) harder to read.
2D Arrays
Arrays of Array Addresses
Array Boxes Can Contain References to Arrays!
int[][] pascalsTriangle;
/* Array of int array references. */
pascalsTriangle = new int[4][];
/* Create four boxes, each can store an int array reference */
int[] rowZero = pascalsTriangle[0];
pascalsTriangle[0] = new int[]{1};
pascalsTriangle[1] = new int[]{1, 1};
pascalsTriangle[2] = new int[]{1, 2, 1};
/* Create a new array with three boxes, storing integers 1, 2, 1,
* respectively.
* Store a reference to this array in pascalsTriangle box #2. */
pascalsTriangle[3] = new int[]{1, 3, 3, 1};
int[] rowTwo = pascalsTriangle[2];
rowTwo[1] = -5;
int[][] matrix;
matrix = new int[4][]; //Creates 1 total array.
matrix = new int[4][4]; //Creates 5 total arrays.
int[][] pascalAgain = new int[][]{{1}, {1, 1},{1, 2, 1}, {1, 3, 3, 1}};
- Syntax for arrays of arrays can be a bit confounding. You’ll learn through practice (much later).
matrix = new int[4][4]; //Creates 5 total arrays.
Arrays vs. Classes
Arrays and Classes can both be used to organize a bunch of memory boxes.
-
Array boxes are accessed using [] notation.
-
Class members are accessed using dot notation.
-
Array boxes must all be of the same type.
-
Class members may be of different types.
-
Both have a fixed number of boxes/members.
Array indices can be computed at runtime.
int[] x = new int[]{100, 101, 102, 103};
int indexOfInterest = askUser();
int k = x[indexOfInterest];
System.out.println(k);
$ javac ArrayDemo.java
$ java ArrayDemo
What index do you want? 2
102
Class member variable names CANNOT be computed and used at runtime.
String fieldOfInterest = "mass";
Planet earth = new Planet(6e24, "earth");
double mass = earth[fieldOfInterest];
System.out.println(mass);
$ javac ClassDemo.java
ClassDemo.java:5: error: array required,
but Planet found.
double mass = earth[fieldOfInterest];
^
- Dot notation doesn’t work either.
String fieldOfInterest = "mass";
Planet earth = new Planet(6e24, "earth");
double mass = earth.fieldOfInterest;
System.out.println(mass);
$ javac ClassDemo.java
ClassDemo.java:5: error: cannot find Symbol
double mass = earth.fieldOfInterest;
^
symbol: variable fieldOfInterest
location: variable earth of type Planet
Another view
The only (easy) way to access a member of a class is with hard-coded dot notation.
int k = x[indexOfInterest]; /* no problem */
double m = p.fieldOfInterest; /* won't work */
double z = p[fieldOfInterest]; /* won't work */
/* No (sane) way to use field of interest */
double w = p.mass; /* works fine */
The Java compiler does not treat text on either side of a dot as an expression, and thus it is not evaluated.