[Happy DSA] Heap Structure

A heap is a partially sorted binary tree. Although a heap is not completely in order, it conforms to a sorting principle: every node has a value less (for the sake of simplicity, we will assume that all orderings are from least to greatest) than either of its children. Additionally, a heap is a "complete tree" -- a complete tree is one in which there are no gaps between leaves. For instance, a tree with a root node that has only one child must have its child as the left node. More precisely, a complete tree is one that has every level filled in before adding a node to the next level, and one that has the nodes in a given level filled in from left to right, with no breaks.


[Happy DSA] Heap Structure_第1张图片

Why use a heap?


A heap can be thought of as a priority queue; the most important node will always be at the top, and when removed, its replacement will be the most important. This can be useful when coding algorithms that require certain things to processed in a complete order, but when you don't want to perform a full sort or need to know anything about the rest of the nodes. For instance, a well-known algorithm for finding the shortest distance between nodes in a graph, Dijkstra's Algorithm, can be optimized by using a priority queue.


Heaps can also be used to sort data. A heap sort is O(nlogn) efficiency, though it is not the fastest possible sorting algorithm. Check out this tutorial heap sort for more information related to heap sort. 


How do you implement a heap?


Although the concept of a heap is simple, the actual implementation can appear tricky. How do you remove the root node and still ensure that it is eventually replaced by the correct node? How do you add a new node to a heap and ensure that it is moved into the proper spot?


The answers to these questions are more straight forward than meets the eye, but to understand the process, let's first take a look at two operations that are used for adding and removing nodes from a heap: upheaping and downheaping. 


Upheap: The upheap process is used to add a node to a heap. When you upheap a node, you compare its value to its parent node; if its value is less than its parent node, then you switch the two nodes and continue the process. Otherwise the condition is met that the parent node is less than the child node, and so you can stop the process. Once you find a parent node that is less than the node being upheaped, you know that the heap is correct--the node being upheaped is greater than its parent, and its parent is greater than its own parent, all the way up to the root. 


Downheap: The downheap process is similar to the upheaping process. When you downheap a node, you compare its value with its two children. If the node is less than both of its children, it remains in place; otherwise, if it is greater than one or both of its children, then you switch it with the child of lowest value, thereby ensuring that of the three nodes being compared, the new parent node is lowest. Of course, you cannot be assured that the node being downheaped is in its proper position -- it may be greater than one or both of its new children; the downheap process must be repeated until the node is less than both of its children. 

[Happy DSA] Heap Structure_第2张图片  [Happy DSA] Heap Structure_第3张图片

When you add a new node to a heap, you add it to the rightmost unoccupied leaf on the lowest level. Then you upheap that node until it has reached its proper position. In this way, the heap's order is maintained and the heap remains a complete tree.


Removing the root node from a heap is almost as simple: when you take the node out of the tree, you replace it with "last" node in the tree: the node on the last level and rightmost on that level.


Once the top node has been replaced, you downheap the node that was moved until it reaches its proper position. As usual, the result will be a proper heap, as it will be complete, and even if the node in the last position happens to be the greatest node in the entire heap, it will do no worse than end up back where it started. 


Efficiency of a heap


Whenever you work with a heap, most of the time taken by the algorithm will be in upheaping and downheaping. As it happens, the maximum number of levels of a complete tree is log(n)+1, where n is the number of nodes in the tree. Because upheap or downheap moves an element from one level to another, the order of adding to or removing from a heap is O(logn), as you can make switches only log(n) times, or one less time than the number of levels in the tree (consider that a two level tree can have only one switch).


By Eric Suh 

This source code is an implementation of the Heap Tree class and the Heap Sort algorithm. The class is implemented with templates

For the templated class, the elements must have the operators >, =, and < defined. 

To use the Heap sort that is built into the class, two separate steps must be taken. The first is to call the constructor, which organizes the array into a heap:

HeapTree<TYPE> HeapName(Array, Num, MaxNum);

TYPE is the data type of the elements, Array is the actual array to be sorted, and Num is the number of elements that are to be sorted. MaxNum normally sets the limit on the number of data nodes that the Heap can have. If you are only using the heap for sorting, you can set MaxNum to Num. (However, MaxNum should not be set to anything less than Num). 

When the constructor is called, the Heap copies the Array. Thus, neither the Array variable nor what it points to will be modified by the Heap. 

The second step is to call the actual sort, which will organize the heap into a sorted array:

NewArray *Sort();

This Sort() function will return a pointer to another array which is sorted. Any modifications done to NewArray or its contents will not affect the heap. 

/*
     -------------------------------------------------------------------
    |                                                                   |
    |    HeapTree Class                                                 |
    |    ===========================================================    |
    |    This HeapTree Class has been implemented with templates.       |
    |                                                                   |
    |    To use the HeapSort that is built into the class, two          |
    |    separate steps must be taken. The first is the constructor:    |
    |                                                                   |
    |        HeapTree<Type> HeapName(Array, Num, MaxNum);               |
    |                                                                   |
    |    'Type' is the data type of the Array elements, 'Array' is a    |
    |    standard C++ array to be sorted and 'Num' is the number of     |
    |    elements in the array. MaxNum sets the limit on the number     |
    |    of data nodes that the Heap can have. If you are only using    |
    |    the heap for sorting, you can set MaxNum to Num. (However,     |
    |    MaxNum should not be set less than Num).                       |
    |                                                                   |
    |    When the constructor is called, the Heap *copies* the Array.   |
    |    Thus, neither the Array variable nor what it points to will    |
    |    be modified.                                                   |
    |                                                                   |
    |    The second step is to call the actual sort:                    |
    |                                                                   |
    |        NewArray *Sort();                                          |
    |                                                                   |
    |    This sort will return a pointer to another array, which is     |
    |    the sorted array. Any modifications done to NewArray or its    |
    |    contents will not affect the heap.                             |
    |                                                                   |
     -------------------------------------------------------------------
*/

#ifndef __HeapTreeClassH__
#define __HeapTreeClassH__

#include <assert.h>    // For error-checking purposes

//-------------------------------------------------
// Main structure of HeapTree Class:
//-------------------------------------------------

template <class Elem>
class HeapTree
{
  public:
    HeapTree(int MaxSize=500);
    HeapTree(const HeapTree<Elem> &OtherTree);
    HeapTree(Elem *Array, int ElemNum, int MaxSize);
    Elem *Sort(void); // Built-in HeapSort Algorithm
    ~HeapTree(void);

    bool Add(const Elem &Item);  // Add the Item to Heap
    Elem Remove(void);           // Remove and return Item from Heap

    inline int GetSize(void);    // Returns the number of nodes in the Heap

  protected:
    Elem     *Data;              // Actual Data array
    int       CurrentNum;        // Current number of elements
    const int MAX_SIZE;          // The maximum number of elements

    void ShiftUp(int Node);      // Shift Node up into place
    void ShiftDown(int Node);    // Shift Node down into place

    inline int ParentOf(int Node);      // Returns Parent location
    inline int LeftChildOf(int Node);   // Returns Left Child location
};

//-------------------------------------------------
// Implementation of HeapTree Class:
//-------------------------------------------------

// HeapTree constructor function
template <class Elem>
HeapTree<Elem>::HeapTree(int MaxSize)
    : MAX_SIZE(MaxSize)
{
  Data       = new Elem[MAX_SIZE];
  CurrentNum = 0;
}

// HeapTree copy constructor function
template <class Elem>
HeapTree<Elem>::HeapTree(const HeapTree<Elem> &OtherTree)
    : MAX_SIZE(OtherTree.MAX_SIZE)
{
  Data       = new Elem[MAX_SIZE];
  CurrentNum = OtherTree.CurrentNum;

  // Copy the array
  for (int i = 0; i < OtherTree.CurrentNum; ++i)
    Data[i] = OtherTree.Data[i];
}

// HeapTree array constructor
template <class Elem>
HeapTree<Elem>::HeapTree(Elem *Array, int ElemNum, int MaxSize)
    : MAX_SIZE(MaxSize)
{
  Data       = new Elem[MAX_SIZE];
  CurrentNum = ElemNum;

  // This copies the array into the heap's internal array
  for (int i = 0; i < ElemNum; ++i)
        Data[i] = Array[i];

  // This organizes the Array into a proper HeapTree
  for (int i = ParentOf(CurrentNum - 1); i >= 0; --i)
    ShiftDown(i);
}

// Built-in Heap Sort algorithm
template <class Elem>
Elem *HeapTree<Elem>::Sort(void)
{
  // This is the array that will be returned
  Elem *NewArray = new Elem[CurrentNum];

  // The algorithm works back to front, with the sorted
  // elements being stored in NewArray
  for (int ElemNum = CurrentNum-1; ElemNum >=0; --ElemNum)
  {
    // Since the Remove() function alters CurrentNum by subtracting 1
    // from it each time, we must use a seperate variable to
    // index NewArray.
    NewArray[ElemNum] = Remove();
  }
  return NewArray;
}

// HeapTree destructor function
template <class Elem>
HeapTree<Elem>::~HeapTree(void)
{
  if (Data)
    delete Data;
}

// Add() function
template <class Elem>
bool HeapTree<Elem>::Add(const Elem &Item)
{
  if (CurrentNum >= MAX_SIZE)    // If we have reached our maximum capacity
    return false;
  Data[ CurrentNum ] = Item;
  ShiftUp(CurrentNum++);
  return true;
}

// Remove() function
template <class Elem>
Elem HeapTree<Elem>::Remove(void)
{
  assert(CurrentNum > 0);

  Elem Temp = Data[0];
  Data[0] = Data[--CurrentNum];  // Replace with the last element
  ShiftDown(0);
  return Temp;
}

// GetSize() function
template <class Elem>
inline int HeapTree<Elem>::GetSize(void)
{
  return CurrentNum;
}

// ShiftUp() function
template <class Elem>
void HeapTree<Elem>::ShiftUp(int Node)
{
  int  Current = Node,
       Parent  = ParentOf(Current);
  Elem Item    = Data[Current];

  while (Current > 0)  // While Current is not the RootNode
  {
    if (Data[Parent] < Item)
    {
      Data[Current] = Data[Parent];
      Current = Parent;
      Parent = ParentOf(Current);
    }
    else
      break;
  }
  Data[Current] = Item;
}

// ShiftDown() function
template <class Elem>
void HeapTree<Elem>::ShiftDown(int Node)
{
  int Current = Node,
      Child   = LeftChildOf(Current);
  Elem Item   = Data[Current];    // Used to compare values

  while (Child < CurrentNum)
  {
    if (Child < (CurrentNum - 1))
      if (Data[Child] < Data[Child+1])  // Set Child to largest Child node
        ++Child;

    if (Item < Data[Child])
    {    // Switch the Current node and the Child node
      Data[Current] = Data[Child];
      Current       = Child;
      Child         = LeftChildOf(Current);
    }
    else
      break;
  }
  Data[Current] = Item;
}

// ParentOf() function
template <class Elem>
inline int HeapTree<Elem>::ParentOf(int Node)
{
  assert(Node > 0);
  // This uses the fact that decimals are truncated during
  // the division of integers. Thus, (12 - 1) / 2 == 5
  return (Node - 1) / 2;
}

// LeftChildOf() function
template <class Elem>
inline int HeapTree<Elem>::LeftChildOf(int Node)
{
  return (Node * 2) + 1;
}

#endif /*__HeapTreeClassH__*/


你可能感兴趣的:([Happy DSA] Heap Structure)