Return to the Lecture Notes Index

15-111 Lecture 22 (Wednesday, March 13, 2003)

Overview

Today, in preparation for the lab, we will cover the insertion sort, algorithm. We will also continue the thread form last class, and discuss the implementation of quick sort.

Insertion Sort

A couple classes ago we talked about selection sort, where you walk through the list to find the smallest item, and then insert it into the lowest index. In this case, the index for each iteration is known, while the value is not. Insertion sort is the opposite of that: We know the value, but not the index where it goes. What does this mean?

Let's look at an example:

59 51 39 3 14 27 213

We look at the first number, 59, and put it in our sorted list, which we keep at the front of our current list. Since 59 is the only element in that sub-list, we know it's sorted:

59 51 39 3 14 27 213

Now we take out the 51, and compare it to what's in our sorted list. Since it's smaller than 59, it goes before it:

51 59 39 3 14 27 213

Next we take out the 39, and see that it goes before 51, so we insert it there in our sorted list-so-far:

39 51 59 3 14 27 213

We keep doing this with each value until the list is sorted- the idea is that you compare the latest value with each value in the sorted list until you find the right place for it, and then you insert it there.

with a linked list, this would be pretty easy because you can just insert before or after. But with an array or a vector, you would have to keep shifting elements.

Another way to do the comparison is to always check against the largest element in the sorted list to see if it needs shifting. If necessary, shift the larger item, and continue checking, otherwise, insert element in the space that's been created. Let's take a look at what the runtime for this sort would be.

Runtime of Insertion Sort

There are n items in the list, so there will be n passes through the list. The worst case for a pass is that we will have to push n-1 elements to the right and then we have to move the element that we're placing, so the total time would be n for one pass. Since there are n passes, the runtime would be O(n2).

Recursive Quicksort using Linked Lists

Here is the some code for Quicksort using LinkedLists:

//why doubly linked list? because you need to be able to go from left to 
// right as well as right to left.

class SortableDoublyLinkedList extends DoublyLinkedList{

  public void quickSort(){
    

    recursiveQuickSort(head, tail);

  }

  private void recursiveQuickSort(Node left, Node right){
    if (right == left)  //covers if only one item, or if list is empty
      return;

    Node savedLeft = left;
    Node savedRight = right;

    //now we know there are at least 2 elements.
    //now we should pick a pivot point. Since doubly linked lists would be inefficient in picking the 
      center element, we're just going to pick the left one out of convenience.

    Node pivot = left;

    //remove pivot from partition
    remove(left); //easy enough to write

    //reset left
    left = pivot.getNext();

    while(left != right){

      //look for an item on left that doesn't belong there:
      while((left.getData().compareTo(pivot.getData())<=0) && (left!=right))
        left = left.getNext();

      while((right.getData().compareTo(pivot.getData())>=0) && (left!=right))
        right = right.getPrev();

      if (right != left)
        swap(left, right);
    }


    insertBefore(pivot, left);
    
    if(pivot != savedLeft)  // why do we need these if clauses? 
      recursiveQuickSort(savedLeft, pivot.getPrev());

    if(pivot != savedRight)
      recursiveQuickSort(pivot.getNext(), savedRight);
  }
}

So why do we need the "if (pivot != savedLeft)" and "if (pivot != savedRight)" tests above? If we didn't, pivot.getPrev() or pivot.getNext() might end up going out of the partition we're trying to sort. What does this mean? Let's look at the following picture:

So that's how you would implement Quicksort with linkedLists.

Quicksort using Vectors

How different would the code be if we used Vectors instead of linkedLists? Not much:

  class SortableVector extends Vector{

    public void quickSort(){
    
      recursiveQuickSort(0, size());

    }

    private void recursiveQuickSort(int left, int right){
      if (right == left)  //covers if only one item, or if list is empty
        return;

      int savedLeft = left;
      int savedRight = right;

      //now we know there are at least 2 elements.
      //now we should pick a pivot point. Since doubly linked lists would be inefficient in picking the 
        center element, we're just going to pick the left one out of convenience.

      int pivot = (right-left)/2;
  
      //remove pivot from partition -> swap to end of list
      Comparable temp = (Comparable)getElement(right);
      setElement(right, getElement(pivot));
      setElement(pivot, temp);

      //reset right and pivot for new positions
      pivot = right;
      right--;

      while(left != right){
  
        //look for an item on left that doesn't belong there:
        while((getElement(left).compareTo(getElement(pivot))<=0) && (left!=right))
          left++;

        while((getElement(right).compareTo(getElement(pivot))<=0) && (left!=right))
          right--;
 
        if (right != left)
          swap(left, right); /*the swap code would look just like the swap we did above to swap pivot and right*/
      }

      swap(pivot, right);

      insertBefore(pivot, left);
     
      if(pivot != savedLeft)
        recursiveQuickSort(savedLeft, pivot-1);

      if(pivot != savedRight)
        recursiveQuickSort(pivot+1, savedRight);
    }  
  }