The abstract data type Priority Queue


The ADT Priority Queue keeps track of numerical costs of the objects it holds; it is specified by The data types pq_object and cost are provided by the ADT user; the data type pq is chosen by the designer of the data structure that implements the ADT. In C, the data type boolean is not provided explicitly, but may be created by the definitions
#define TRUE  1
#define FALSE 0
typedef char boolean;
In implementing Priority Queue, we will assume that the data type pq_object is a structure including an element pq_cost of type cost and that the ADT designer may choose to include additional elements in this structure.


Jump to:


Implementing Priority Queue as a linked list

typedef struct list_item
{
  pq_object *content;
  struct list_item *next;
} list_item;

typedef list_item pq;


pq *create_pq(void)
{
  pq *p;
  
  p = (pq *) malloc(sizeof(pq));
  if(p==NULL)    
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }

  p->next=NULL;
  return p;
}

boolean pq_is_empty(pq *p)
{
  return( (p->next==NULL) ? TRUE : FALSE ); 
}

pq_object *find_min(pq *p)
{
  list_item *winner, *candidate;
  
  if(pq_is_empty(p)==TRUE)
    {
      printf("TRIED TO FIND A MIN-OBJECT IN AN EMPTY PRIORITY QUEUE!\n");
      exit(1);
    }

  winner=p->next;
  for(candidate=p->next->next; candidate; candidate=candidate->next)
    {
      if(candidate->content->pq_cost < winner->content->pq_cost)
        winner=candidate;
    }
  return winner->content;
}

void pq_insert(pq *p, pq_object *object, cost object_cost)
{
  list_item *new_item;
  
  new_item = (list_item *) malloc(sizeof(list_item));
  if(new_item==NULL)
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }

  object->pq_cost=object_cost;
  new_item->content=object;

  new_item->next=p->next;
  p->next=new_item;
}

void delete_min(pq *p)
{
  list_item *winner, *candidate, *previous;
  
  if(pq_is_empty(p)==TRUE)
    {
      printf("TRIED TO FIND A MIN-OBJECT IN AN EMPTY PRIORITY QUEUE!\n");
      exit(1);
    }

  winner=p->next;
  for(candidate=p->next->next; candidate; candidate=candidate->next)
    {
      if(candidate->content->pq_cost < winner->content->pq_cost)
	winner=candidate;
    }
  for(previous=p; previous->next != winner; previous=previous->next);

  previous->next=winner->next;
  free(winner);
}

void reduce_cost(pq *p, pq_object *object, cost smaller_cost)
{
  object->pq_cost=smaller_cost;
}


Jump to:


Implementing Priority Queue as a binary heap

Just as in
Heapsort, the heap structure on n nodes is the binary tree with nodes 0,1,...,n-1 such that the parent of each node j other than 0 is the floor of (j-1)/2 (and such that 0 is the root of the tree). A binary heap is a heap structure of pointers to the objects in the priority queue that satisfies the min-heap condition,
heap[x]->pq_cost <= heap[y]->pq_cost
whenever y is a descendant of x.

In the objects of the priority queue, we shall maintain integer elements pq_location that reverse the pointers stored in the heap: if x is a nonnegative integer smaler than n, then

heap[x]->pq_location equals x
and if y is a pointer stored in the heap, then
heap[y->pq_location] equals y.
We shall maintain a pointer to the root of the heap and define the data type pq by
typedef struct pq
{
  pq_object **heap;
  int capacity;
  int count;
} pq;
much as we defined the data type stack when implementing the ADT Stack as an array. In fact, we implement
pq *create_pq(void)
{
  pq *p;
  pq_object **object;

  p = (pq *) malloc(sizeof(pq));
  if(p==NULL)
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }

  object = (pq_object **) malloc(sizeof(pq_object *));
  if(object==NULL)
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }

  p->heap = object;
  p->count = 0;
  p->capacity = 1;

  return p;
}

boolean pq_is_empty(pq *p)
{
  return( (p->count==0) ? TRUE : FALSE );
}

pq_object *find_min(pq *p)
{
  if(pq_is_empty(p)==TRUE)
    {
      printf("TRIED TO FIND A MIN-OBJECT IN AN EMPTY PRIORITY QUEUE!\n");
      exit(1);
    }

  return *(p->heap);
}

Manipulator functions pq_insert and reduce_cost rely on the function
void bubbleup(pq_object *heap[], int n, int vacant, pq_object *missing)
which is much like the function bubbleup(heap,n,vacant,missing) in Heapsort(with "small" and "big" reversed):
void bubbleup(pq_object *heap[], int n, int vacant, pq_object *missing)
{
  int parent=(vacant-1)/2;
  while(vacant>0 && missing->pq_cost < heap[parent]->pq_cost)
    {

      /* the actual value of heap[vacant] is irrelevant;
         if   heap[vacant] were overwritten with missing,
         then the min-heap condition would be satisfied
              everywhere except at j=vacant          */

      heap[vacant]=heap[parent], heap[vacant]->pq_location=vacant;
      vacant=parent, parent=(vacant-1)/2;
    }

  heap[vacant]=missing, heap[vacant]->pq_location=vacant;
}            

void pq_insert(pq *p, pq_object *object, cost object_cost)
{
  pq_object **newheap;

  if(p->count == p->capacity)
    /** DOUBLE THE LENGTH OF THE HEAP **/
    {
      (p->capacity)*=2;
      newheap = (pq_object **)
        realloc(p->heap, (p->capacity)*sizeof(pq_object *));
      if(newheap)
        p->heap = newheap;
      else
        {
          printf("OUT OF MEMORY!\n");
          exit(1);
        }
    }
 
  object->pq_cost=object_cost;
  (p->count)++;
  bubbleup(p->heap, p->count, (p->count)-1, object);
}

void reduce_cost(pq *p, pq_object *object, cost smaller_cost)
{
  object->pq_cost=smaller_cost;
  bubbleup(p->heap, p->count, object->pq_location, object);
}

Manipulator function delete_min relies on the function
void siftdown_from_root(pq_object *heap[], int n, pq_object *missing)
which is much like the function siftdown(heap,n,0,missing) in Heapsort (with "small" and "big" reversed):
void siftdown_from_root(pq_object *heap[], int n, pq_object *missing)
{
  int vacant=0, child=2;
  while(child<n)
    {

      /* the actual value of heap[vacant] is irrelevant;
         the min-heap condition is satisfied everywhere
         except possibly at i=vacant or j=vacant     */

      if(heap[child-1]->pq_cost < heap[child]->pq_cost)
        child--;
      heap[vacant]=heap[child], heap[vacant]->pq_location=vacant;
      vacant=child, child=2*(vacant+1);
    }
  if(child==n)
    {
      heap[vacant]=heap[n-1], heap[vacant]->pq_location=vacant;
      vacant=n-1;
    }

  bubbleup(heap, n, vacant, missing);
}

void delete_min(pq *p)
{
  (p->count)--;
  siftdown_from_root(p->heap, p->count, *((p->heap)+(p->count)));
}
A nice animation of a sequence of operations pq_insert and delete_max in the related data structure max-heap (where "small" and "big" are reversed) has been written by Woi Ang.


Jump to:

Implementing Priority Queue as a pairing heap

The data structure called pairing heap was introduced by M. L. Fredman, R. Sedgewick, D. D. Sleator, and R. E. Tarjan ("The pairing heap: a new form of self­adjusting heap" Algorithmica 1 (1986), pp. 111-129); its implementation presented here follows the high-level description given by M. L. Fredman in "On the efficiency of pairing heaps and related data structures", Journal of the ACM 46 (1999), pp. 473 - 501.

A pairing heap is an ordered tree of pointers to the objects; the tree is represented by a pair of pointers for each of its nodes,

its pointers satisfy the min-heap condition,
x.content->pq_cost <= y.content->pq_cost
whenever y is a child of x.

In the objects of the priority queue, we shall maintain elements pq_location that point to the tree nodes of the pairing heap and reverse the pointers content: if x points to a tree node, then

x->content->pq_location equals x
and if y points to an object in the priority queue, then
y->pq_location->content equals y.
We shall maintain a pointer to the root of the pairing heap and define the data type pq as a pointer to a tree-node.


The constructor create_pq constructs a pointer to a tree-node, sets its value at NULL and returns a pointer to this pointer:
pq *create_pq(void)
{
  pq *p;
  p = (pq *) malloc(sizeof(pq));
  if(p==NULL)    
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }
      
  *p=NULL;
  return p;
}

The access function pq_is_empty, given a pointer p to a pointer *p to the root of a pairing heap, returns TRUE if the pairing heap is empty and returns FALSE otherwise; the access function find_min, given a pointer p to a pointer *p to the root of a nonempty pairing heap, returns a pointer to the object in the priority queue that the root of the pairing heap points to.

boolean pq_is_empty(pq *p)
{
  return( (*p==NULL) ? TRUE : FALSE ); 
}

pq_object *find_min(pq *p)
{
  if(pq_is_empty(p)==TRUE)
    {
      printf("TRIED TO FIND A MIN-OBJECT IN AN EMPTY PRIORITY QUEUE!\n");
      exit(1);
    }

  return (*p)->content;
}                


The three manipulator functions use the primitive operation of linking: given pointers to the two roots of two nonempty pairing heaps, function link will make one of the two roots the leftmost child of the other, so as to create a single pairing heap, and it will return a pointer to the root of this resulting pairing heap.
tree_node *link(tree_node *a, tree_node *b)
{
  if(a->content->pq_cost < b->content->pq_cost)
    {
      b->across=a->down;
      a->down=b;
      a->across=NULL;
      return a;
    }
  else
    {
      a->across=b->down;
      b->down=a;
      b->across=NULL;
      return b;
    }
}

Manipulator function pq_insert, given a pointer p to a pointer *p to the root of a pairing heap, given a pointer object to an object, and given the cost object_cost of this object, inserts the object into the priority queue.
void pq_insert(pq *p, pq_object *object, cost object_cost)
{
  tree_node *new_node;
  new_node = (tree_node *) malloc(sizeof(tree_node));
  if(new_node==NULL)
    {
      printf("OUT OF MEMORY!\n");
      exit(1);
    }
  new_node->content=object;
  new_node->down=NULL;
  new_node->across=NULL;

  object->pq_location=new_node;
  object->pq_cost=object_cost;

  *p = (pq_is_empty(p)==TRUE ? new_node : link(*p,new_node));
}

Manipulator function reduce_cost, given a pointer p to a pointer *p to the root of a pairing heap, given a pointer object to an object, and given a cost value smaller_cost that is smaller than the current cost of the object, reduces the cost of the object to smaller_cost. When the object is pointed to from the root of the pairing heap,
reduce_cost(p, object, smaller_cost)
amounts to setting
object->pq_cost=smaller_cost;
in all other cases,
  1. the node object->pq_location that points to the object is removed from the list of its siblings and then
  2. the subtree of the pairing heap that is rooted at object->pq_location is reinserted into the pairing heap
    by a sequence of operations much like pq_insert(p, object, smaller_cost) except that
    new_node->down is set at object->pq_location->down rather than at NULL.
The first of these two steps is implemented in constant time by setting
object->pq_location->content=NULL;
this signals the fact that node object->pq_location has turned into garbage, whose presence in the list of its siblings will be disregarded.
void reduce_cost(pq *p, pq_object *object, cost smaller_cost)
{
  tree_node *new_node;

  object->pq_cost=smaller_cost;

  if(object->pq_location!=*p)
    {
      new_node = (tree_node *) malloc(sizeof(tree_node));
      if(new_node==NULL)
	{
	  printf("OUT OF MEMORY!\n");
	  exit(1);
	}
      new_node->content=object;
      new_node->down=object->pq_location->down;      
      new_node->across=NULL;

      object->pq_location->content=NULL;
      object->pq_location=new_node;      
      
      *p = link(*p,new_node);
    }
}

Manipulator function delete_min, given a pointer p to a pointer *p to the root of a nonempty pairing heap, removes the root as well as all the children of the root that have turned into garbage and, by repeated linking of the remaining children of the root, combines the pairing heaps rooted at these children into a single pairing heap. To separate the children of the root that should be removed as meaningless (since their pointers content are NULL) from the children of the root that should be kept as meaningful (since their pointers content point at objects in the priority queue), we will use a function find_sibling. This function, given a pointer child to a (meaningless or meaningful) child of the root, either or
tree_node *find_sibling(tree_node *child)
{
  tree_node *memo;
  
  while(child && child->content==NULL)
    {
      memo=child, child=child->across;
      free(memo);
    }
  return child;
}
The linking of the meaningful children of the root proceeds in two phases. In the first phase, In the second phase,
void delete_min(pq *p)
{
  tree_node *a, *b, *memo;

  if(pq_is_empty(p)==TRUE)
    {
      printf("TRIED TO DELETE A MIN-OBJECT FROM AN EMPTY PRIORITY QUEUE!\n");
      exit(1);
    }

  a=find_sibling((*p)->down);
  (*p)->down = NULL;
  while(a)
    {
      b=find_sibling(a->across);
      if(b)
        {
          memo=find_sibling(b->across);
          a=link(a,b);
          a->across=(*p)->down, (*p)->down=a;
          a=memo;                                                              
        }
      else
        {
          a->across=(*p)->down, (*p)->down=a;
          break;
        }
    }

  if((*p)->down)
    {
      for(a=(*p)->down->across; a; a=memo)
        {
          memo=a->across;
          (*p)->down=link((*p)->down,a);
        }
    }

  memo=*p;
  *p=(*p)->down;
  free(memo);
}


Jump to:

Back to the table of contents of Vašek Chvátal's course notes