Data structure -- linear table based on sequential storage structure

Posted by Centrek on Wed, 02 Mar 2022 17:06:41 +0100

1, Implementation of linear table based on sequential storage structure

1. Definition of sequential storage

The sequential storage structure of linear table is to store the data elements in the linear table in sequence with a section of storage units with continuous addresses.

2. Operation of sequential storage structure

Use one-dimensional array to realize sequential storage structure.

template <typename T>
class SeqList:public List<T>
{
protected:
  T* m_array;//Sequential storage space
  int m_length;//Length of current linear table
};

One dimensional sequential storage structure can be a native array or a dynamically allocated space. Therefore, according to the different space allocation, it is divided into static linear table and dynamic linear table.

(1) Element acquisition

Element acquisition operation of sequential storage structure:

A. Judge whether the target location is legal

B. Gets the element with the target position as an array subscript

bool get(int index, T& value)
{
  //Judge whether the target location is legal
  bool ret = (0 <= index) && (index < m_length);
  if(ret)
  {
      //Get element with target location as subscript
      value = m_array[index];
  }
  return ret;
}

(2) Insert operation of element

Element insertion operation of sequential storage structure:

A. Judge whether the target location is legal

B. Move all elements after the target position back one bit

C. Insert the new element into the target location

D. Linear table length increased by 1

bool insert(int index, const T& value)
{
  //Judge whether the target location is legal
  bool ret = (0 <= index) && (index <= m_length);
  ret = ret && ((m_length + 1) <= capacity());

  if(ret)
  {
      //Move all elements after the target position back one position
      for(int i = m_length -1; index <= i; i--)
      {
          m_array[i+1] = m_array[i];
      }

      //Insert the new element into the target location
      m_array[index] = value;
      //Linear meter length plus 1
      m_length++;
  }
  return ret;
}

(3) Element deletion

Element deletion of sequential storage structure:

A. Judge whether the target location is legal

B. Move all elements after the target position forward one position

C. Linear table length minus 1

bool remove(int index)
{
  //Judge whether the target location is legal
  bool ret = (0 <= index) && (index < m_length);

  if(ret)
  {
      //Move all elements after the target position forward one bit
      for(int i = index; i < m_length-1; i++)
      {
          m_array[i] = m_array[i+1];
      }
      //Subtract the length of the linear table by 1
      m_length--;
  }
  return ret;
}

(4) Modification of element value

Modification of element values in sequential storage structure:

A. Judge whether the target location is legal

B. Modify the value of the target location element

bool set(int index, const T& value)
{
  //Judge whether the target location is legal
  bool ret = (0 <= index) && (index < m_length);
  if(ret)
  {
      //Modify the value of the target location element
      m_array[index] = value;
  }
  return ret;
}

(5) Acquisition of linear table length

Length acquisition of linear table with sequential storage structure

int length()const
{
  //Linear table length
  return m_length;
}

(6) Emptying of linear table

void clear()
{
  //Empty linear table
  m_length = 0;
}

(7) [] operator overload

//[] operator overload
T& operator[](int index)
{
  //Judge whether the target location is legal
  if((0 <= index) && (index < m_length))
  {
      //Returns the element corresponding to the subscript
      return m_array[index];
  }
  else
  {
      THROW_EXCEPTION(IndexOutOfBoudsException, "Paramenter index is invalid...");
  }
}

//[] overload of const object
T operator[](int index)const
{
  //Use [] operation after converting to non const object
  return (const_cast<SeqList<T>&>(*this))[index];
}

(8) Size of sequential storage structure space

virtual int capacity()const = 0;

Since the storage space is allocated as a pure subspace, it is a pure function.

3. Abstract implementation of sequential storage structure

  template <typename T>
  class SeqList:public List<T>
  {
  public:
    bool insert(int index, const T& value)
    {
      //Judge whether the target location is legal
      bool ret = (0 <= index) && (index <= m_length);
      ret = ret && ((m_length + 1) <= capacity());
      if(ret)
      {
          //Move all elements after the target position back one position
          for(int i = m_length -1; index <= i; i--)
          {
              m_array[i+1] = m_array[i];
          }
          //Insert the new element into the target location
          m_array[index] = value;
          //Linear meter length plus 1
          m_length++;
      }
      return ret;
    }

    bool remove(int index)
    {
      //Judge whether the target location is legal
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //Move all elements after the target position forward one bit
          for(int i = index; i < m_length-1; i++)
          {
              m_array[i] = m_array[i+1];
          }

          //Subtract the length of the linear table by 1
          m_length--;
      }

      return ret;
    }

    bool set(int index, const T& value)
    {
      //Judge whether the target location is legal
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //Modify the value of the target location element
          m_array[index] = value;
      }

      return ret;
    }

    bool get(int index, T& value)const
    {
      //Judge whether the target location is legal
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //Gets the element with the target location as a subscript
          value = m_array[index];
      }

      return ret;
    }

    int length()const
    {
      //Linear table length
      return m_length;
    }

    void clear()
    {
      //Empty linear table

      m_length = 0;
    }

    int find(const T& value)const
    {
        int ret = -1;
        //Ergodic linear table
        for(int i = 0; i < m_length; i++)
        {
            //If the element is found, exit the loop
            if(m_array[i] = value)
            {
               ret = i;
               break;
            }
        }
        return ret;
    }

    //[] operator overload
    T& operator[](int index)
    {
      //Judge whether the target location is legal
      if((0 <= index) && (index < m_length))
      {
          //Returns the element corresponding to the subscript
          return m_array[index];
      }
      else
      {
          THROW_EXCEPTION(IndexOutOfBoudsException, "Paramenter index is invalid...");
      }
    }

    //[] overload of const object
    T operator[](int index)const
    {
      //Use [] operation after converting to non const object
      return (const_cast<SeqList<T>&>(*this))[index];
    }

    virtual int capacity()const = 0;

  protected:
    T* m_array;//Sequential storage space
    int m_length;//Length of current linear table
  };

4. Skills of moving data elements in sequential storage structure

Forward movement of data elements:

To move the following data elements forward, you need to move the previous data elements forward first and move them successively until the last data element moves forward. It is generally used to move the following data elements forward after deleting a data element.

      //Move all elements after the target position forward one bit
      for(int i = index; i < m_length-1; i++)
      {
          m_array[i] = m_array[i+1];
      }

Backward movement of data elements:

To move the front data element backward, first move the last data element backward and move it successively until the ith data element is moved backward. It is generally used to insert a new data element. First move all the data elements after the insertion position backward and put a new data element at the position.

      //Move all elements after the target position back one position
      for(int i = m_length -1; index <= i; i--)
      {
          m_array[i+1] = m_array[i];
      }

5. Linear table implemented by native array

The native array is used as the sequential storage space, and the template parameter is used to determine the size of the array.

  template <typename T, int N>
  class StaticList:public SeqList<T>
  {
  public:
    StaticList()
    {
      this->m_array = m_space;//Specifies the space that the parent class pointer points to
      this->m_length = 0;//Set initial length
    }

    int capacity() const
    {
      return N;
    }
  protected:
    T m_space[N];//Sequential storage space, N is the template parameter
  };

You need to implement the pure virtual function capacity() of the parent class.

6. Linear table realized by dynamically allocating space

Use the dynamically applied heap space as the sequential storage space, dynamically set the size of the sequential storage space, and ensure the abnormal safety when resetting the size of the sequential storage space.

Function exception security requires that no resources should be disclosed and data destruction is not allowed. In order to ensure the safety of exceptions, when throwing exceptions, you must ensure that:

A. Any member within the object can still remain in a valid state

B. No data destruction and resource leakage.

template <typename T>
class DynamicList:public SeqList<T>
{
public:
  DynamicList(int capacity)
  {
    //Apply for dynamic space
    this->m_array = new T[capacity];
    if(this->m_array)
    {
        this->m_length = 0;//Initial length
        this->m_capacity = capacity;//Space size
    }
    else
    {

        THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");

    }
  }

  int capacity() const
  {
    return m_capacity;
  }

  void resize(int capacity)
  {
    if(capacity != m_capacity)
    {
        T* array = new T[capacity];
        if(array)
        {
            int length = (this->length() < capacity ? this->length():capacity);
            for(int i = 0; i < length; i++)
            {
                array[i] = this->m_array[i];
                //If an exception is thrown, the original array state does not change
            }

            T* temp = this->m_array;
            this->m_array = array;
            this->m_capacity = capacity;
            this->m_length = length;
            delete[] temp;
            //If an exception is thrown, the reset array state has changed
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
        }
      }
  }

  ~DynamicList()
  {
    delete[] this->m_array;//Free up the dynamic space of the application
  }

protected:
  int m_capacity;//Size of sequential storage space
};

2, Efficiency analysis of linear table with sequential storage structure

1. Time complexity analysis of linear table with sequential storage structure

2. Efficiency analysis of linear table with sequential storage structure

The time complexity of the insertion and deletion operations of the linear table with sequential storage structure is O (n). However, since the insertion and deletion operations will involve the movement of data elements in the linear table, they will frequently involve the call of copy constructor and assignment operator. If there are many resources in the data element object, the copying and assignment between objects will be very time-consuming, At the same time, because the linear table is a generic template, it is impossible to determine whether the actual instantiated object type is the class type occupying resources. Therefore, it is necessary to disable the copy constructor and assignment operator. The disabled method only needs to declare the copy constructor and assignment operator protected.

template <typename T>
class List:public Object
{
protected:
  List(const List<T>& other);
  List<T>& operator=(const List<T>& other);
public:
  List(){}
  virtual bool insert(int index, const T& value) = 0;
  virtual bool remove(int index) = 0;
  virtual bool set(int index, const T& value) = 0;
  virtual bool get(int index, T& value) = 0;
  virtual int length()const = 0;
  virtual void clear() = 0;
};

3. Defects of linear table with sequential storage structure

The [] operator is overloaded in the sequential storage structure linear table, so the [] operator can be used to access the elements in the sequential storage structure linear table. However, the element to be accessed must exist in the linear table, that is, the element must be inserted first before the [] operator can be used to access the element.

3, Engineering implementation of array class

1. Abstract implementation of array class

To create an array class to replace the native array, the array class needs to avoid the defects of the native array:

A. The array class contains length information

B. The array class can actively discover out of bounds access

The design of array class is as follows:

A. Abstract class template. The location and size of storage space are completed by subclasses

B. Overload the array operator to judge whether the access subscript is out of bounds

C. Abstract access function that provides array length

D. Copy constructors and assignment operators need to be implemented in subclasses

Implementation of array:

  template <typename T>
  class Array:public Object
  {
  public:
    virtual bool set(int index, const T& value)
    {
      bool ret = (0 <= index) && (index < length());
      if(ret)
      {
          m_array[index] = value;
      }
      return ret;
    }

    virtual bool get(int index, T& value)
    {
      bool ret = (0 <= index) && (index < length());
      if(ret)
      {
          value = m_array[index];
      }
      return ret;
    }

    T& operator[](int index)
    {
      if((0 <= index) && (index < length()))
      {
          return m_array[index];
      }
      else
      {
          THROW_EXCEPTION(IndexOutOfBoudsException, "Parameter index is valid...");
      }
    }

    T operator[](int index)const
    {
      return const_cast<T&>(*this)[index];
    }

    virtual int length()const = 0;
  protected:
    T* m_array;
  };
}

2. Static array class implementation

Specify the native array as the storage space of the array class, implement the static array class, specify the array size with template parameters, realize the length of the array returned by the function, and overload the copy constructor and assignment operator.

  template <typename T,int N>
  class StaticArray:public Array<T>
  {
  public:
    StaticArray()
    {
      this->m_array = m_space;
    }

    //copy constructor 
    StaticArray(const StaticArray<T,N>& other)
    {
      this->m_array = m_space;
      for(int i = 0; i < N; i++)
      {
          m_space[i] = other.m_space[i];
      }
    }

    //Assignment operator
    StaticArray& operator=(const StaticArray<T,N>& other)
    {
      if(this != &other)
      {
          for(int i = 0; i< N; i++)
          {
              m_space[i] = other.m_space[i];
          }
      }
      return *this;
    }

    int length() const
    {
      return N;
    }

  protected:
    T m_space[N];//storage space 
  };

3. Dynamic allocation array implementation

The dynamically allocated heap space is used as the storage space of the array class to realize the dynamic allocation of the array class.

Class template design needs to dynamically determine the size of the allocated storage space, realize the size of the array returned by the function, and realize the copy constructor and assignment operator.

  template <typename T>
  class DynamicArray:public Array<T>
  {
  public:
    //Constructor
    DynamicArray(int length)
    {
      this->m_array = new T[length];
      if(this->m_array != NULL)
      {
          this->m_length = length;
      }
      else
      {
          THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
      }
    }

    //copy constructor 
    DynamicArray(const DynamicArray<T>& other)
    {
      this->m_array = new T[other.m_length];
      if(this->m_array != NULL)
      {
          this->m_length = other.m_length;
      }
      else
      {
          THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
      }
    }

    //Assignment operator
    DynamicArray<T>& operator=(const DynamicArray<T>& other)
    {
      if(this != &other)
      {
          T* array = new T[other.m_length];
          if(array != NULL)
          {
              for(int i = 0; i < other.m_length; i++)
              {
                  array[i] = other.m_array[i];
              }

              T* temp = this->m_array;
              this->m_array = array;
              this->m_length = other.m_length;
              delete[] temp;
          }
          else
          {
              THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
          }
        }
      return *this;
    }

    int length()const
    {
      return m_length;
    }

    //Reset array length
    void resize(int length)
    {
      if(this->m_length != length)
      {
          T* array = new T[length];
          if(array != NULL)
          {
              int size = (length < this->m_length)?length:this->m_length;
              for(int i = 0; i < size; i++)
              {
                  array[i] = this->m_array[i];
              }
              T* temp = this->m_array;
              this->m_array = array;
              this->m_length = length;
              delete[] temp;
          }
          else
          {
              THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
          }
      }
    }

    ~DynamicArray()
    {
      delete[] this->m_array;
    }
  protected:
    int m_length;//Array length
  };

Topics: data structure