1 Priority Queues (ADT)
2 Lists
2.1 Removing the Minimum Element: Accumulators
2.2 Why Do Lists This Way?

Priority Queues and Lists

Yesterday, we looked at heaps. They are good for accessing the smallest element, but not great as a general representation for sets. If we needed to access the smallest element frequently, heaps could provide a good implementation data structure.

1 Priority Queues (ADT)

Priority queues are an ADT with three required operations:
  • addElt: PriorQ, int -> PriorQ // adds element

  • remMinElt: PriorQ -> PriorQ // remove smallest element

  • getMinElt: PriorQ -> int // return, but don’t remove, smallest elt

Think for a few moments: what axioms do you expect on priority queues?

Let’s contrast heaps with another possible data structure to implement priority queues: sorted lists. Sorted lists are a data structure, defined as follows:

A sorted list is a list in which the first element is smaller than all elements in the rest of the list and the rest of the list is a sorted list.

We have said that when we implement a data structure, we must build on its core shape. So before we can implement sorted lists, we need to implement plain lists in Java. We will work through the basic implementation of lists in class today. You will build on this to implement sorted lists in lab tomorrow (unless you’d rather implement tree rotation and AVL trees):

2 Lists

Recall what lists looked like in Racket:

  ; A list-of-number is

  ; - empty, or

  ; - (cons number list-of-number)

Let’s convert this data definition to Java following the approach from the first week of the course: we should get an interface for the list-of-number type and classes for each of empty and cons:

  interface IListNum {}

  

  class MtListNum implements IListNum {

    MtListNum(){}

  }

  

  class ConsListNum implements IListNum {

    int first;

    IListNum rest;

  

    ConsListNum(int first, IListNum rest) {

       this.first = first;

       this.rest = rest;

    }

  }

The only substantive different with the migration on trees is that we had to create a class for the empty list, since every datum in Java is an object of a class.

To those with Java experience: we realize this probably isn’t how you learned to do lists. We’ll talk about the approach you’ve seen towards the end of these notes. For now, ask yourself why we might be doing things this way.

As an example of a method on lists, here’s the code for remElt:

    interface IListNum {

      IListNum remElt (int elt);

    }

  

    class MtListNum implements IListNum {

      MtListNum(){}

  

     // element not in list, so nothing to remove

      MtListNum remElt (int elt) {

        return this;

      }

    }

  

    class ConsListNum implements IListNum {

      int first;

      IListNum rest;

  

      ConsListNum(int first, IListNum rest) {

         this.first = first;

         this.rest = rest;

      }

  

      // removes one occurrence of given element from list

      IListNum remElt(int elt) {

        if (elt == this.first)

          return this.rest;

        else

          return this.rest.remElt(elt);

    }

  }

2.1 Removing the Minimum Element: Accumulators

How would we remove the minimum element? First, we have to locate it, then we have to remove it. How do we locate the minimum element?

One common approach is to recur down the list with an extra parameter that holds the smallest element seen so far. When we get to the empty list, we return that minimum value. This extra parameter that holds a value determined over the course of the computation is called an accumulator (it accumulates a value over the computation). We will write a separate method findMinEltAccum to find the smallest element, then have the findMinElt method call this function initialized with the first element in the list. The code follows:

  interface IListNum {

      IListNum remElt (int elt);

      IListNum remMinElt ();

      int findMinElt ();

      int findMinEltAccum (int min);

    }

  

  class MtListNum implements IListNum {

    MtListNum(){}

  

    // element not in list, so nothing to remove

    public MtListNum remElt (int elt) {

      return this;

    }

  

    public MtListNum remMinElt () {

      return this;

    }

  

    public int findMinElt () {

      throw new RuntimeException("can't find min in empty list");

    }

  

    public int findMinEltAccum (int min) {

      return min;

    }

  }

  

  class ConsListNum implements IListNum {

    int first;

    IListNum rest;

  

    ConsListNum(int first, IListNum rest) {

      this.first = first;

      this.rest = rest;

    }

  

    // removes one occurrence of given element from list

    public IListNum remElt(int elt) {

      if (elt == this.first)

        return this.rest;

      else

        return new ConsListNum(this.first, this.rest.remElt(elt));

    }

  

    public IListNum remMinElt() {

      return this.remElt(this.findMinElt());

    }

  

    // initialize accumulator to first, find min in rest

    public int findMinElt () {

      return this.rest.findMinEltAccum(this.first);

    }

  

    public int findMinEltAccum (int min) {

      if (this.first < min)

        return this.rest.findMinEltAccum(this.first);

      else

        return this.rest.findMinEltAccum(min);

    }

  }

  

One potential problem with this code is that it doesn’t limit findMinEltAccum to being used as a helper for findMinElt. We’ll deal with that issue next week.

2.2 Why Do Lists This Way?

If you’ve seen Java before, you almost certainly saw lists done differently, using one of the following two ways:

To wrap up, write out the recurrences for the priority queue operations on lists. Compare to the recurrence for remMinElt on heaps. This motivates preferring sorted lists to regular lists.