天天看点

ADS-Lecture 06 Arrays and Lists

Lecture 06 Arrays and Lists

A Last Look at Linked Lists

Doubly Linked Lists

Behold. The state of the art as we arrived at in previous lecture. Through various improvements, we made all of the following operations fast:

  • addFirst

    ,

    addLast

  • getFirst

    ,

    getLast

  • removeFirst

    ,

    removeLast

Arbitrary Retrieval

Suppose we added

get(int i)

, which returns the ith item from the list.

Why would get be slow for long lists compared to getLast()? For what inputs?

  • Have to scan to desired position. Slow for any i not near the sentinel node.
  • How do we fix this?
    • For now: We’ll take a different tack: Using an array instead (no links!).

Naive Array Lists

Random Access in Arrays

Retrieval from any position of an array is very fast.

  • Independent of array size.
  • Ultra fast random access results from the fact that memory boxes are the same size (in bits), we won’t talk about this in detail in this course about Algorithm and Data Structure.

Our Goal:

AList.java

Want to figure out how to build an array version of a list:

  • In lecture we’ll only do back operations. Front operations are left for your practice.

Naive AList Code

/** Array based list. */

public class Alist {
    private int[] items;
    private int size;

    /** Create an empty list. */
    public AList() {
        items = new int[100];
        size = 0;
    }

    /** Insert X into the back of the list. */
    public void addLast(int x) {
        items[size] = x;
        size += 1;
    }

    /** Returns the item from the back of the lists. */
    public int getLast() {
        return items[size - 1];
    }

    /** Gets the ith item in the list (0 is the front). */
    public int get(int i) {
        return items[i];
    }

    /** Returns the size of the list. */
    public int size() {
        return size;
    }

    /** Deletes item from back of the lisk and
     *  returns deleted item. */
    public int removeLast() {
        int x = getLast();
        //items[size - 1] = 0;
        /* Setting deleted item to zero is not necessary to
         * preserve invariants, and thus not necessary for
         * correctness. */
        size -= 1;
        return x;
    }
}
           

AList Invariants:

  • The position of the next item to be inserted is always size.
  • size is always the number of items in the AList.
  • The last item in the list is always in position size - 1.
Invariants: things that must be true.

Resizing Arrays

Array Resizing

When the array gets too full (

size == items.length

), e.g. addLast(11), just make a new array:

int[] a = new int[size+1];
System.arraycopy(...)
a[size] = 11;
items = a;  size +=1;
// We call this process "resizing"
           

Resizing Array Code

public void addLast(int x) {
  if (size == items.length) {
    int[] a = new int[size + 1];
    System.arraycopy(items, 0, a, 0, size);
    items = a;  	
  }
  items[size] = x;
  size += 1;
}
// Works.
           
private void resize(int capacity) {
  int[] a = new int[capacity];
  System.arraycopy(items, 0, a, 0, size);
  items = a;
}
 
public void addLast(int x) {
  if (size == items.length) {
	resize(size + 1);
  }
  items[size] = x;
  size += 1;
} 
// Much Better !
           

Resizing Slowness

Inserting 100,000 items requires roughly 5,000,000,000 new containers.

  • Computers operate at the speed of GHz (due billions of things per second).
  • No huge surprise that 100,000 items took seconds.
ADS-Lecture 06 Arrays and Lists
ADS-Lecture 06 Arrays and Lists
How do we fix the Resizing Performance Bug?

(Probably) Surprising Fact

Geometric resizing is much faster: Just how much better will have to wait.
public void addLast(int x) {
  if (size == items.length) {
	resize(size + RFACTOR);
  }
  items[size] = x;
  size += 1;
}
// Unsuably Bad!
           
public void addLast(int x) {
  if (size == items.length) {
	resize(size * RFACTOR);
  }
  items[size] = x;
  size += 1;
}
// Great performance!
/* This is how the Python list is implemented.

           

Memory Efficiency

An AList should not only be efficient in time, but also efficient in space.

  • Define the “usage ratio” R = size / items.length;
  • Typical solution: Half array size when R < 0.25.
  • More details later.
Later we will consider tradeoffs between time and space efficiency for a variety of algorithms and data structures.

Generic ALists

Similar to generic SLists.
public class AList<Glorp> {
    private Glorp[] items;    
    private int size;
 
    public AList() {
        items = (Glorp []) new Object[8];  
        size = 0;
    }
 
    private void resize(int cap) {
        Glorp[] a = (Glorp []) new Object[cap];
        System.arraycopy(items, 0, a, 0, size);
        items = a;
    }

    public Glorp get(int i) {
        return items[i];
    }
...
           

When creating an array of references to Glorps:

  • (Glorp []) new Object[cap];

  • Causes a compiler warning, which you should ignore.

Why not just

new Glorp[cap]

;

  • Will cause a “generic array creation” error.

Nulling Out Deleted Items

Unlike integer based ALists, we actually want to null out deleted items.

  • Java only destroys unwanted objects when the last reference has been lost.
  • Keeping references to unneeded objects is sometimes called loitering.
  • Save memory. Don’t loiter.
public Glorp deleteBack() {
    Glorp returnItem = getBack();
    items[size - 1] = null;
    size -= 1;  	
    return returnItem;
}
           

Loitering Example

Changing

size

to

size - 1

yields a correct AList.

  • But memory is wasted storing a reference to the unwanted object.
    ADS-Lecture 06 Arrays and Lists

By nulling out

items[size]

, Java is free to destroy the unneeded object from memory, which could be potentially megabytes in size.

Obscurantism in Java

We talk of “layers of abstraction” often in computer science.

  • Related concept: obscurantism. The user of a class does not and should not know how it works.
    • The Java language allows you to enforce this with ideas like

      private

      !
  • A good programmer obscures details from themselves, even within a class.
    • Example:

      addFirst

      and

      resize

      should be written totally independently. You should not be thinking about the details of one method while writing the other. Simply trust that the other works.
    • Breaking programming tasks down into small pieces (especially functions) helps with this greatly!
    • Through judicious use of testing, we can build confidence in these small pieces, as we’ll see later.