views:

1858

answers:

7

I have an array of a few million numbers.

double* const data = new double (3600000);

I need to iterate through the array and find the range (the largest value in the array minus the smallest value). However, there is a catch. I only want to find the range where the smallest and largest values are within 1,000 samples of each other.

So I need to find the maximum of: range(data + 0, data + 1000), range(data + 1, data + 1001), range(data + 2, data + 1002), ...., range(data + 3599000, data + 3600000).

I hope that makes sense. Basically I could do it like above, but I'm looking for a more efficient algorithm if one exists. I think the above algorithm is O(n), but I feel that it's possible to optimize. An idea I'm playing with is to keep track of the most recent maximum and minimum and how far back they are, then only backtrack when necessary.

I'll be coding this in C++, but a nice algorithm in pseudo code would be just fine. Also, if this number I'm trying to find has a name, I'd love to know what it is.

Thanks.

+5  A: 

The algorithm you describe is really O(N), but i think the constant is too high. Another solution which looks reasonable is to use O(N*log(N)) algorithm the following way:

* create sorted container (std::multiset) of first 1000 numbers
* in loop (j=1, j<(3600000-1000); ++j)
   - calculate range
   - remove from the set number which is now irrelevant (i.e. in index *j - 1* of the array)
   - add to set new relevant number  (i.e. in index *j+1000-1* of the array)

I believe it should be faster, because the constant is much lower.

Drakosha
I think in practice this will be no faster than the trivial method since you're moving the complexity into manipulating the sorted set. If the set implementation has any memory allocation it will be a significant overhead.
Skizz
How do people come up with these ingenious ideas? I would have never thought of using a set to find the max-min.In any case, this seems simple and your explanation is great. I'm going to try it out.
Imbue
This algorithm is also O(N), since maintaining the set should take constant time once it holds 1,000 items. I'm going to be benchmarking this vs the naive solution today.
Imbue
Skizz - Instead of the 1 heap allocation per node with std::multiset you can use boost::intrusive::multiset and only allocate the space for the initial 1000 elements and reuse the space from the deleted elem for the inserted elem.
Greg Rogers
+6  A: 

This type of question belongs to a branch of algorithms called streaming algorithms. It is the study of problems which require not only an O(n) solution but also need to work in a single pass over the data. the data is inputted as a stream to the algorithm, the algorithm can't save all of the data and then and then it is lost forever. the algorithm needs to get some answer about the data, such as for instance the minimum or the median.

Specifically you are looking for a maximum (or more commonly in literature - minimum) in a window over a stream.

Here's a presentation on an article that mentions this problem as a sub problem of what they are trying to get at. it might give you some ideas.

I think the outline of the solution is something like that - maintain the window over the stream where in each step one element is inserted to the window and one is removed from the other side (a sliding window). The items you actually keep in memory aren't all of the 1000 items in the window but a selected representatives which are going to be good candidates for being the minimum (or maximum).

read the article. it's abit complex but after 2-3 reads you can get the hang of it.

shoosh
+5  A: 

This is a good application of a min-queue - a queue (First-In, First-Out = FIFO) which can simultaneously keep track of the minimum element it contains, with amortized constant-time updates. Of course, a max-queue is basically the same thing.

Once you have this data structure in place, you can consider CurrentMax (of the past 1000 elements) minus CurrentMin, store that as the BestSoFar, and then push a new value and pop the old value, and check again. In this way, keep updating BestSoFar until the final value is the solution to your question. Each single step takes amortized constant time, so the whole thing is linear, and the implementation I know of has a good scalar constant (it's fast).

I don't know of any documentation on min-queue's - this is a data structure I came up with in collaboration with a coworker. You can implement it by internally tracking a binary tree of the least elements within each contiguous sub-sequence of your data. It simplifies the problem that you'll only pop data from one end of the structure.

If you're interested in more details, I can try to provide them. I was thinking of writing this data structure up as a paper for arxiv. Also note that Tarjan and others previously arrived at a more powerful min-deque structure that would work here, but the implementation is much more complex. You can google for "mindeque" to read about Tarjan et al.'s work.

Tyler
The data structure you describe sounds a lot like a heap: http://en.wikipedia.org/wiki/Heap_(data_structure%29
Greg Hewgill
It's similar, but not the same. Heaps don't allow you to delete elements in amortized constant time.
Tyler
"Each single step takes amortized constant time, so the whole thing is linear". Does it mean that by iteratively popping the min element you can sort the elements in linear time?
Rafał Dowgird
No, because you can only pop from the back of the structure, and _find_ the minimum element (not delete/pop it). Sorry I was vague with my description.
Tyler
A: 

Idea of alg:
Take the first 1000 values of data and sort them
The last in the sort - the first is range(data + 0, data + 999).
Then remove from the sort pile the first element with the value data[0]
and add the element data[1000]
Now, the last in the sort - the first is range(data + 1, data + 1000).
Repeat until done

// This should run in (DATA_LEN - RANGE_WIDTH)log(RANGE_WIDTH)
#include <set>
#include <algorithm>
using namespace std;

const int DATA_LEN = 3600000;
double* const data = new double (DATA_LEN);

....
....

const int RANGE_WIDTH = 1000;
double range = new double(DATA_LEN - RANGE_WIDTH);
multiset<double> data_set;
data_set.insert(data[i], data[RANGE_WIDTH]);

for (int i = 0 ; i < DATA_LEN - RANGE_WIDTH - 1 ; i++)
{
   range[i] = *data_set.end() - *data_set.begin();
   multiset<double>::iterator iter = data_set.find(data[i]);
   data_set.erase(iter);
   data_set.insert(data[i+1]);
}
range[i] = *data_set.end() - *data_set.begin();

// range now holds the values you seek

You should probabally check this for off by 1 errors, but the idea is there.
--James

James Caccese
A: 
std::multiset<double> range;
double currentmax = 0.0;
for (int i = 0;  i < 3600000;  ++i)
{
    if (i >= 1000)
        range.erase(range.find(data[i-1000]));
    range.insert(data[i]);
    if (i >= 999)
        currentmax = max(currentmax, *range.rbegin());
}

Note untested code.

Edit: fixed off-by-one error.

Mark Ransom
A: 
  1. read in the first 1000 numbers.
  2. create a 1000 element linked list which tracks the current 1000 number.
  3. create a 1000 element array of pointers to linked list nodes, 1-1 mapping.
  4. sort the pointer array based on linked list node's values. This will rearrange the array but keep the linked list intact.
  5. you can now calculate the range for the first 1000 numbers by examining the first and last element of the pointer array.
  6. remove the first inserted element, which is either the head or the tail depending on how you made your linked list. Using the node's value perform a binary search on the pointer array to find the to-be-removed node's pointer, and shift the array one over to remove it.
  7. add the 1001th element to the linked list, and insert a pointer to it in the correct position in the array, by performing one step of an insertion sort. This will keep the array sorted.
  8. now you have the min. and max. value of the numbers between 1 and 1001, and can calculate the range using the first and last element of the pointer array.
  9. it should now be obvious what you need to do for the rest of the array.

The algorithm should be O(n) since the delete and insertion is bounded by log(1e3) and everything else takes constant time.

freespace
Your insertion sort and item removal (shifting the higher values down one place) will kill the performance here - lots of memory copying involved which will be the major bottleneck.
Skizz
Since the entire array should fit into the L2 cache of a modern CPU, I don't see how this is much of an issue.
freespace
A: 

I decided to see what the most efficient algorithm I could think of to solve this problem was using actual code and actual timings. I first created a simple solution, one that tracks the min/max for the previous n entries using a circular buffer, and a test harness to measure the speed. In the simple solution, each data value is compared against the set of min/max values, so that's about window_size * count tests (where window size in the original question is 1000 and count is 3600000).

I then thought about how to make it faster. First off, I created a solution that used a fifo queue to store window_size values and a linked list to store the values in ascending order where each node in the linked list was also a node in the queue. To process a data value, the item at the end of the fifo was removed from the linked list and the queue. The new value was added to the start of the queue and a linear search was used to find the position in the linked list. The min and max values could then be read from the start and end of the linked list. This was quick, but wouldn't scale well with increasing window_size (i.e. linearly).

So I decided to add a binary tree to the system to try to speed up the search part of the algorithm. The final timings for window_size = 1000 and count = 3600000 were:

Simple: 106875
Quite Complex: 1218
Complex: 1219

which was both expected and unexpected. Expected in that using a sorted linked list helped, unexpected in that the overhead of having a self balancing tree didn't offset the advantage of a quicker search. I tried the latter two with an increased window size and found that the were always nearly identical up to a window_size of 100000.

Which all goes to show that theorising about algortihms is one thing, implementing them is something else.

Anyway, for those that are interested, here's the code I wrote (there's quite a bit!):

Range.h:

#include <algorithm>
#include <iostream>
#include <ctime>

using namespace std;

//  Callback types.
typedef void (*OutputCallback) (int min, int max);
typedef int (*GeneratorCallback) ();

//  Declarations of the test functions.
clock_t Simple (int, int, GeneratorCallback, OutputCallback);
clock_t QuiteComplex (int, int, GeneratorCallback, OutputCallback);
clock_t Complex (int, int, GeneratorCallback, OutputCallback);

main.cpp:

#include "Range.h"

int
  checksum;

//  This callback is used to get data.
int CreateData ()
{
  return rand ();
}

//  This callback is used to output the results.
void OutputResults (int min, int max)
{
  //cout << min << " - " << max << endl;
  checksum += max - min;
}

//  The program entry point.
void main ()
{
  int
    count = 3600000,
    window = 1000;

  srand (0);
  checksum = 0;
  std::cout << "Simple: Ticks = " << Simple (count, window, CreateData, OutputResults) << ", checksum = " << checksum << std::endl;
  srand (0);
  checksum = 0;
  std::cout << "Quite Complex: Ticks = " << QuiteComplex (count, window, CreateData, OutputResults) << ", checksum = " << checksum << std::endl;
  srand (0);
  checksum = 0;
  std::cout << "Complex: Ticks = " << Complex (count, window, CreateData, OutputResults) << ", checksum = " << checksum << std::endl;
}

Simple.cpp:

#include "Range.h"

//  Function to actually process the data.
//  A circular buffer of min/max values for the current window is filled
//  and once full, the oldest min/max pair is sent to the output callback
//  and replaced with the newest input value. Each value inputted is 
//  compared against all min/max pairs.
void ProcessData
(
  int count,
  int window,
  GeneratorCallback input,
  OutputCallback output,
  int *min_buffer,
  int *max_buffer
)
{
  int
    i;

  for (i = 0 ; i < window ; ++i)
  {
    int
      value = input ();

    min_buffer [i] = max_buffer [i] = value;

    for (int j = 0 ; j < i ; ++j)
    {
      min_buffer [j] = min (min_buffer [j], value);
      max_buffer [j] = max (max_buffer [j], value);
    }
  }

  for ( ; i < count ; ++i)
  {
    int
      index = i % window;

    output (min_buffer [index], max_buffer [index]);

    int
      value = input ();

    min_buffer [index] = max_buffer [index] = value;

    for (int k = (i + 1) % window ; k != index ; k = (k + 1) % window)
    {
      min_buffer [k] = min (min_buffer [k], value);
      max_buffer [k] = max (max_buffer [k], value);
    }
  }

  output (min_buffer [count % window], max_buffer [count % window]);
}

//  A simple method of calculating the results.
//  Memory management is done here outside of the timing portion.
clock_t Simple
(
  int count,
  int window,
  GeneratorCallback input,
  OutputCallback output
)
{
  int
    *min_buffer = new int [window],
    *max_buffer = new int [window];

  clock_t
    start = clock ();

  ProcessData (count, window, input, output, min_buffer, max_buffer);

  clock_t
    end = clock ();

  delete [] max_buffer;
  delete [] min_buffer;

  return end - start;
}

QuiteComplex.cpp:

#include "Range.h"

template <class T>
class Range
{
private:
  //  Class Types

  //  Node Data
  //  Stores a value and its position in various lists.
  struct Node
  {
    Node
      *m_queue_next,
      *m_list_greater,
      *m_list_lower;

    T
      m_value;
  };

public:
  //  Constructor
  //  Allocates memory for the node data and adds all the allocated
  //  nodes to the unused/free list of nodes.
  Range
  (
    int window_size
  ) :
    m_nodes (new Node [window_size]),
    m_queue_tail (m_nodes),
    m_queue_head (0),
    m_list_min (0),
    m_list_max (0),
    m_free_list (m_nodes)
  {
    for (int i = 0 ; i < window_size - 1 ; ++i)
    {
      m_nodes [i].m_list_lower = &m_nodes [i + 1];
    }

    m_nodes [window_size - 1].m_list_lower = 0;
  }

  //  Destructor
  //  Tidy up allocated data.
  ~Range ()
  {
    delete [] m_nodes;
  }

  //  Function to add a new value into the data structure.
  void AddValue
  (
    T value
  )
  {
    Node
      *node = GetNode ();

    //  clear links
    node->m_queue_next = 0;

    //  set value of node
    node->m_value = value;

    //  find place to add node into linked list
    Node
      *search;

    for (search = m_list_max ; search ; search = search->m_list_lower)
    {
      if (search->m_value < value)
      {
        if (search->m_list_greater)
        {
          node->m_list_greater = search->m_list_greater;
          search->m_list_greater->m_list_lower = node;
        }
        else
        {
          m_list_max = node;
        }

        node->m_list_lower = search;
        search->m_list_greater = node;
      }
    }

    if (!search)
    {
      m_list_min->m_list_lower = node;
      node->m_list_greater = m_list_min;
      m_list_min = node;
    }
  }

  //  Accessor to determine if the first output value is ready for use.
  bool RangeAvailable ()
  {
    return !m_free_list;
  }

  //  Accessor to get the minimum value of all values in the current window.
  T Min ()
  {
    return m_list_min->m_value;
  }

  //  Accessor to get the maximum value of all values in the current window.
  T Max ()
  {
    return m_list_max->m_value;
  }

private:
  //  Function to get a node to store a value into.
  //  This function gets nodes from one of two places:
  //    1. From the unused/free list
  //    2. From the end of the fifo queue, this requires removing the node from the list and tree
  Node *GetNode ()
  {
    Node
      *node;

    if (m_free_list)
    {
      //  get new node from unused/free list and place at head
      node = m_free_list;

      m_free_list = node->m_list_lower;

      if (m_queue_head)
      {
        m_queue_head->m_queue_next = node;
      }

      m_queue_head = node;
    }
    else
    {
      //  get node from tail of queue and place at head
      node = m_queue_tail;

      m_queue_tail = node->m_queue_next;
      m_queue_head->m_queue_next = node;
      m_queue_head = node;

      //  remove node from linked list
      if (node->m_list_lower)
      {
        node->m_list_lower->m_list_greater = node->m_list_greater;
      }
      else
      {
        m_list_min = node->m_list_greater;
      }

      if (node->m_list_greater)
      {
        node->m_list_greater->m_list_lower = node->m_list_lower;
      }
      else
      {
        m_list_max = node->m_list_lower;
      }
    }

    return node;
  }

  //  Member Data.
  Node
    *m_nodes,
    *m_queue_tail,
    *m_queue_head,
    *m_list_min,
    *m_list_max,
    *m_free_list;
};

//  A reasonable complex but more efficent method of calculating the results.
//  Memory management is done here outside of the timing portion.
clock_t QuiteComplex
(
  int size,
  int window,
  GeneratorCallback input,
  OutputCallback output
)
{
  Range <int>
    range (window);

  clock_t
    start = clock ();

  for (int i = 0 ; i < size ; ++i)
  {   
    range.AddValue (input ());

    if (range.RangeAvailable ())
    {
      output (range.Min (), range.Max ());
    }
  }

  clock_t
    end = clock ();

  return end - start;
}

Complex.cpp:

#include "Range.h"

template <class T>
class Range
{
private:
  //  Class Types

  //  Red/Black tree node colours.
  enum NodeColour
  {
    Red,
    Black
  };

  //  Node Data
  //  Stores a value and its position in various lists and trees.
  struct Node
  {
    //  Function to get the sibling of a node.
    //  Because leaves are stored as null pointers, it must be possible
    //  to get the sibling of a null pointer. If the object is a null pointer
    //  then the parent pointer is used to determine the sibling.
    Node *Sibling
    (
      Node *parent
    )
    {
      Node
        *sibling;

      if (this)
      {
        sibling = m_tree_parent->m_tree_less == this ? m_tree_parent->m_tree_more : m_tree_parent->m_tree_less;
      }
      else
      {
        sibling = parent->m_tree_less ? parent->m_tree_less : parent->m_tree_more;
      }

      return sibling;
    }

    //  Node Members
    Node
      *m_queue_next,
      *m_tree_less,
      *m_tree_more,
      *m_tree_parent,
      *m_list_greater,
      *m_list_lower;

    NodeColour
      m_colour;

    T
      m_value;
  };

public:
  //  Constructor
  //  Allocates memory for the node data and adds all the allocated
  //  nodes to the unused/free list of nodes.
  Range
  (
    int window_size
  ) :
    m_nodes (new Node [window_size]),
    m_queue_tail (m_nodes),
    m_queue_head (0),
    m_tree_root (0),
    m_list_min (0),
    m_list_max (0),
    m_free_list (m_nodes)
  {
    for (int i = 0 ; i < window_size - 1 ; ++i)
    {
      m_nodes [i].m_list_lower = &m_nodes [i + 1];
    }

    m_nodes [window_size - 1].m_list_lower = 0;
  }

  //  Destructor
  //  Tidy up allocated data.
  ~Range ()
  {
    delete [] m_nodes;
  }

  //  Function to add a new value into the data structure.
  void AddValue
  (
    T value
  )
  {
    Node
      *node = GetNode ();

    //  clear links
    node->m_queue_next = node->m_tree_more = node->m_tree_less = node->m_tree_parent = 0;

    //  set value of node
    node->m_value = value;

    //  insert node into tree
    if (m_tree_root)
    {
      InsertNodeIntoTree (node);
      BalanceTreeAfterInsertion (node);
    }
    else
    {
      m_tree_root = m_list_max = m_list_min = node;
      node->m_tree_parent = node->m_list_greater = node->m_list_lower = 0;
    }

    m_tree_root->m_colour = Black;
  }

  //  Accessor to determine if the first output value is ready for use.
  bool RangeAvailable ()
  {
    return !m_free_list;
  }

  //  Accessor to get the minimum value of all values in the current window.
  T Min ()
  {
    return m_list_min->m_value;
  }

  //  Accessor to get the maximum value of all values in the current window.
  T Max ()
  {
    return m_list_max->m_value;
  }

private:
  //  Function to get a node to store a value into.
  //  This function gets nodes from one of two places:
  //    1. From the unused/free list
  //    2. From the end of the fifo queue, this requires removing the node from the list and tree
  Node *GetNode ()
  {
    Node
      *node;

    if (m_free_list)
    {
      //  get new node from unused/free list and place at head
      node = m_free_list;

      m_free_list = node->m_list_lower;

      if (m_queue_head)
      {
        m_queue_head->m_queue_next = node;
      }

      m_queue_head = node;
    }
    else
    {
      //  get node from tail of queue and place at head
      node = m_queue_tail;

      m_queue_tail = node->m_queue_next;
      m_queue_head->m_queue_next = node;
      m_queue_head = node;

      //  remove node from tree
      node = RemoveNodeFromTree (node);
      RebalanceTreeAfterDeletion (node);

      //  remove node from linked list
      if (node->m_list_lower)
      {
        node->m_list_lower->m_list_greater = node->m_list_greater;
      }
      else
      {
        m_list_min = node->m_list_greater;
      }

      if (node->m_list_greater)
      {
        node->m_list_greater->m_list_lower = node->m_list_lower;
      }
      else
      {
        m_list_max = node->m_list_lower;
      }
    }

    return node;
  }

  //  Rebalances the tree after insertion
  void BalanceTreeAfterInsertion
  (
    Node *node
  )
  {
    node->m_colour = Red;

    while (node != m_tree_root && node->m_tree_parent->m_colour == Red)
    {
      if (node->m_tree_parent == node->m_tree_parent->m_tree_parent->m_tree_more)
      {
        Node
          *uncle = node->m_tree_parent->m_tree_parent->m_tree_less;

        if (uncle && uncle->m_colour == Red)
        {
          node->m_tree_parent->m_colour = Black;
          uncle->m_colour = Black;
          node->m_tree_parent->m_tree_parent->m_colour = Red;
          node = node->m_tree_parent->m_tree_parent;
        }
        else
        {
          if (node == node->m_tree_parent->m_tree_less)
          {
            node = node->m_tree_parent;
            LeftRotate (node);
          }

          node->m_tree_parent->m_colour = Black;
          node->m_tree_parent->m_tree_parent->m_colour = Red;
          RightRotate (node->m_tree_parent->m_tree_parent);
        }
      }
      else
      {
        Node
          *uncle = node->m_tree_parent->m_tree_parent->m_tree_more;

        if (uncle && uncle->m_colour == Red)
        {
          node->m_tree_parent->m_colour = Black;
          uncle->m_colour = Black;
          node->m_tree_parent->m_tree_parent->m_colour = Red;
          node = node->m_tree_parent->m_tree_parent;
        }
        else
        {
          if (node == node->m_tree_parent->m_tree_more)
          {
            node = node->m_tree_parent;
            RightRotate (node);
          }

          node->m_tree_parent->m_colour = Black;
          node->m_tree_parent->m_tree_parent->m_colour = Red;
          LeftRotate (node->m_tree_parent->m_tree_parent);
        }
      }
    }
  }

  //  Adds a node into the tree and sorted linked list
  void InsertNodeIntoTree
  (
    Node *node
  )
  {
    Node
      *parent = 0,
      *child = m_tree_root;

    bool
      greater;

    while (child)
    {
      parent = child;
      child = (greater = node->m_value > child->m_value) ? child->m_tree_more : child->m_tree_less;
    }

    node->m_tree_parent = parent;

    if (greater)
    {
      parent->m_tree_more = node;

      //  insert node into linked list
      if (parent->m_list_greater)
      {
        parent->m_list_greater->m_list_lower = node;
      }
      else
      {
        m_list_max = node;
      }

      node->m_list_greater = parent->m_list_greater;
      node->m_list_lower = parent;
      parent->m_list_greater = node;
    }
    else
    {
      parent->m_tree_less = node;

      //  insert node into linked list
      if (parent->m_list_lower)
      {
        parent->m_list_lower->m_list_greater = node;
      }
      else
      {
        m_list_min = node;
      }

      node->m_list_lower = parent->m_list_lower;
      node->m_list_greater = parent;
      parent->m_list_lower = node;
    }
  }

  //  Red/Black tree manipulation routine, used for removing a node
  Node *RemoveNodeFromTree
  (
    Node *node
  )
  {
    if (node->m_tree_less && node->m_tree_more)
    {
      //  the complex case, swap node with a child node
      Node
        *child;

      if (node->m_tree_less)
      {
        // find largest value in lesser half (node with no greater pointer)
        for (child = node->m_tree_less ; child->m_tree_more ; child = child->m_tree_more)
        {
        }
      }
      else
      {
        // find smallest value in greater half (node with no lesser pointer)
        for (child = node->m_tree_more ; child->m_tree_less ; child = child->m_tree_less)
        {
        }
      }

      swap (child->m_colour, node->m_colour);

      if (child->m_tree_parent != node)
      {
        swap (child->m_tree_less, node->m_tree_less);
        swap (child->m_tree_more, node->m_tree_more);
        swap (child->m_tree_parent, node->m_tree_parent);

        if (!child->m_tree_parent)
        {
          m_tree_root = child;
        }
        else
        {
          if (child->m_tree_parent->m_tree_less == node)
          {
            child->m_tree_parent->m_tree_less = child;
          }
          else
          {
            child->m_tree_parent->m_tree_more = child;
          }
        }

        if (node->m_tree_parent->m_tree_less == child)
        {
          node->m_tree_parent->m_tree_less = node;
        }
        else
        {
          node->m_tree_parent->m_tree_more = node;
        }
      }
      else
      {
        child->m_tree_parent = node->m_tree_parent;
        node->m_tree_parent = child;

        Node
          *child_less = child->m_tree_less,
          *child_more = child->m_tree_more;

        if (node->m_tree_less == child)
        {
          child->m_tree_less = node;
          child->m_tree_more = node->m_tree_more;
          node->m_tree_less = child_less;
          node->m_tree_more = child_more;
        }
        else
        {
          child->m_tree_less = node->m_tree_less;
          child->m_tree_more = node;
          node->m_tree_less = child_less;
          node->m_tree_more = child_more;
        }

        if (!child->m_tree_parent)
        {
          m_tree_root = child;
        }
        else
        {
          if (child->m_tree_parent->m_tree_less == node)
          {
            child->m_tree_parent->m_tree_less = child;
          }
          else
          {
            child->m_tree_parent->m_tree_more = child;
          }
        }
      }

      if (child->m_tree_less)
      {
        child->m_tree_less->m_tree_parent = child;
      }

      if (child->m_tree_more)
      {
        child->m_tree_more->m_tree_parent = child;
      }

      if (node->m_tree_less)
      {
        node->m_tree_less->m_tree_parent = node;
      }

      if (node->m_tree_more)
      {
        node->m_tree_more->m_tree_parent = node;
      }
    }

    Node
      *child = node->m_tree_less ? node->m_tree_less : node->m_tree_more;

    if (node->m_tree_parent->m_tree_less == node)
    {
      node->m_tree_parent->m_tree_less = child;
    }
    else
    {
      node->m_tree_parent->m_tree_more = child;
    }

    if (child)
    {
      child->m_tree_parent = node->m_tree_parent;
    }

    return node;
  }

  //  Red/Black tree manipulation routine, used for rebalancing a tree after a deletion
  void RebalanceTreeAfterDeletion
  (
    Node *node
  )
  {
    Node
      *child = node->m_tree_less ? node->m_tree_less : node->m_tree_more;

    if (node->m_colour == Black)
    {
      if (child && child->m_colour == Red)
      {
        child->m_colour = Black;
      }
      else
      {
        Node
          *parent = node->m_tree_parent,
          *n = child;

        while (parent)
        {
          Node
            *sibling = n->Sibling (parent);

          if (sibling && sibling->m_colour == Red)
          {
            parent->m_colour = Red;
            sibling->m_colour = Black;

            if (n == parent->m_tree_more)
            {
              LeftRotate (parent);
            }
            else
            {
              RightRotate (parent);
            }
          }

          sibling = n->Sibling (parent);

          if (parent->m_colour == Black &&
            sibling->m_colour == Black &&
            (!sibling->m_tree_more || sibling->m_tree_more->m_colour == Black) &&
            (!sibling->m_tree_less || sibling->m_tree_less->m_colour == Black))
          {
            sibling->m_colour = Red;
            n = parent;
            parent = n->m_tree_parent;
            continue;
          }
          else
          {
            if (parent->m_colour == Red &&
              sibling->m_colour == Black &&
              (!sibling->m_tree_more || sibling->m_tree_more->m_colour == Black) &&
              (!sibling->m_tree_less || sibling->m_tree_less->m_colour == Black))
            {
              sibling->m_colour = Red;
              parent->m_colour = Black;
              break;
            }
            else
            {
              if (n == parent->m_tree_more &&
                sibling->m_colour == Black &&
                (sibling->m_tree_more && sibling->m_tree_more->m_colour == Red) &&
                (!sibling->m_tree_less || sibling->m_tree_less->m_colour == Black))
              {
                sibling->m_colour = Red;
                sibling->m_tree_more->m_colour = Black;
                RightRotate (sibling);
              }
              else
              {
                if (n == parent->m_tree_less &&
                  sibling->m_colour == Black &&
                  (!sibling->m_tree_more || sibling->m_tree_more->m_colour == Black) &&
                  (sibling->m_tree_less && sibling->m_tree_less->m_colour == Red))
                {
                  sibling->m_colour = Red;
                  sibling->m_tree_less->m_colour = Black;
                  LeftRotate (sibling);
                }
              }

              sibling = n->Sibling (parent);
              sibling->m_colour = parent->m_colour;
              parent->m_colour = Black;

              if (n == parent->m_tree_more)
              {
                sibling->m_tree_less->m_colour = Black;
                LeftRotate (parent);
              }
              else
              {
                sibling->m_tree_more->m_colour = Black;
                RightRotate (parent);
              }
              break;
            }
          }
        }
      }
    }
  }

  //  Red/Black tree manipulation routine, used for balancing the tree
  void LeftRotate
  (
    Node *node
  )
  {
    Node
      *less = node->m_tree_less;

    node->m_tree_less = less->m_tree_more;

    if (less->m_tree_more)
    {
      less->m_tree_more->m_tree_parent = node;
    }

    less->m_tree_parent = node->m_tree_parent;

    if (!node->m_tree_parent)
    {
      m_tree_root = less;
    }
    else
    {
      if (node == node->m_tree_parent->m_tree_more)
      {
        node->m_tree_parent->m_tree_more = less;
      }
      else
      {
        node->m_tree_parent->m_tree_less = less;
      }
    }

    less->m_tree_more = node;
    node->m_tree_parent = less;
  }

  //  Red/Black tree manipulation routine, used for balancing the tree
  void RightRotate
  (
    Node *node
  )
  {
    Node
      *more = node->m_tree_more;

    node->m_tree_more = more->m_tree_less;

    if (more->m_tree_less)
    {
      more->m_tree_less->m_tree_parent = node;
    }

    more->m_tree_parent = node->m_tree_parent;

    if (!node->m_tree_parent)
    {
      m_tree_root = more;
    }
    else
    {
      if (node == node->m_tree_parent->m_tree_less)
      {
        node->m_tree_parent->m_tree_less = more;
      }
      else
      {
        node->m_tree_parent->m_tree_more = more;
      }
    }

    more->m_tree_less = node;
    node->m_tree_parent = more;
  }

  //  Member Data.
  Node
    *m_nodes,
    *m_queue_tail,
    *m_queue_head,
    *m_tree_root,
    *m_list_min,
    *m_list_max,
    *m_free_list;
};

//  A complex but more efficent method of calculating the results.
//  Memory management is done here outside of the timing portion.
clock_t Complex
(
  int count,
  int window,
  GeneratorCallback input,
  OutputCallback output
)
{
  Range <int>
    range (window);

  clock_t
    start = clock ();

  for (int i = 0 ; i < count ; ++i)
  {   
    range.AddValue (input ());

    if (range.RangeAvailable ())
    {
      output (range.Min (), range.Max ());
    }
  }

  clock_t
    end = clock ();

  return end - start;
}

Skizz

Skizz