C++ Initial - deque + stack queue simulation implementation (not finished)

Posted by transfield on Tue, 23 Nov 2021 18:34:45 +0100

Deque (double-ended queue)

concept

Deque (double-ended queue): is a data structure with two openings in a "continuous" space (independent of the queue). Double openings mean that insertions and deletions can be made at both ends of the head and the tail with an O(1) time complexity. Head insertions are more efficient than vector s and do not require elements to be moved. Higher space utilization than list

structure


A dequeue is not really a continuous space, but a splice of small, continuous spaces. The actual dequeue is like a dynamic two-dimensional array with the underlying structure shown in the following figure

The map:map array is a pointer array that points to multiple buff arrays to store data. When the head or tail of the buff array is full, a new buff array is created with the corresponding position of the map pointer. When the map array is full, it expands the map array (the expansion of the pointer array is not inefficient), reallocate_reallocate in the STL30 source code. The map() function expands the map array
Stl_in STL30 The deque.h code is as follows:

template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add,
                                             bool add_at_front) {
 size_type old_num_nodes = finish.node - start.node + 1;
 size_type new_num_nodes = old_num_nodes + nodes_to_add;

 map_pointer new_nstart;
 if (map_size > 2 * new_num_nodes) {
   new_nstart = map + (map_size - new_num_nodes) / 2 
                    + (add_at_front ? nodes_to_add : 0);
   if (new_nstart < start.node)
     copy(start.node, finish.node + 1, new_nstart);
   else
     copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
 }
 else {
   size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;

   map_pointer new_map = map_allocator::allocate(new_map_size);
   new_nstart = new_map + (new_map_size - new_num_nodes) / 2
                        + (add_at_front ? nodes_to_add : 0);
   copy(start.node, finish.node + 1, new_nstart);
   map_allocator::deallocate(map, map_size);

   map = new_map;
   map_size = new_map_size;
 }

 start.set_node(new_nstart);
 finish.set_node(new_nstart + old_num_nodes - 1);
}

DequeIterator

deque's so-called continuous space is an illusion, a complex iterator implementation at his bottom
The iterator for deque in the STL30 source encapsulates four pointers:

typedef T** map_pointer;
T* cur;
T* first;
T* last;
map_pointer node;
  1. cur: is the address currently pointed to by the buff array currently pointed to by the node
  2. First: is the buff array to which the current node points, the address of the first element
  3. Last: is the buff array to which the current node points, and the address of the last element
  4. node: is a pointer to the corresponding position of the map array. Since it also points to a pointer, the type is a secondary pointer


The STL30 source also defines a class for this iterator ( Reference list iterator (which also defines a class) implementation)

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
}

In the deque class: there are two types of iterator variables:

  1. start points to the first buff array
  2. finish points to the last buff array
typedef __deque_iterator<T, T&, T*, BufSiz>             iterator;
typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
protected:                      // Data members
	iterator start;
	iterator finish;
 public:                         // Basic accessors
	iterator begin() { return start; }
	iterator end() { return finish; }

When we use it like this:

deque<int> dp;
iterator it=dp.begin();
while(it!=db.end())
{
	*it;         //Yes *cur
	it++;        //
}
//Here are the member functions in the u deque_iterator class
self& operator++() {
   ++cur;
   if (cur == last) {
     set_node(node + 1);
     cur = first;
   }
   return *this; 
}
bool operator==(const self& x) const { return cur == x.cur; }

When cur==last, it resets its four pointers to the next buff array

Application (advantages and disadvantages)

Advantage:

  1. Double-ended queue, indicating that he is very suitable for head plug removal, tail plug removal, he is good for stack and queue default adapter containers.

Disadvantages:

  1. Inserting delete data in the middle of a double-ended queue is cumbersome. Not efficient
  1. Scenario 1: Moving data is inefficient (intermediate insertion is as inefficient as sequential tables, and random access is more efficient)
  2. Scenario 2: Move the current buf data, record the size of each buff array, and record the size of each buff array is inconsistent (slightly more efficient intermediate insertion, less efficient random access)
  1. Dequeis a compromise design, not extreme enough. Random access is less efficient than vector. Inserting and deleting anywhere is less efficient than list ing

stack

queue

Topics: C++