数据结构(三)——基于顺序存储结构的线性表

2023-06-20,,

数据结构(三)——基于顺序存储结构的线性表

一、基于顺序存储结构的线性表实现

1、顺序存储的定义

    线性表的顺序存储结构是用一段地址连续的存储单元依次存储线性表中的数据元素。

2、顺序存储结构的操作

    使用一维数组实现顺序存储结构。
template <typename T>
class SeqList:public List<T>
{
protected:
  T* m_array;//顺序存储空间
  int m_length;//当前线性表的长度
};
    一维顺序存储结构可以是原生数组或是动态分配的空间。因此,根据空间分配的不同,分为静态线性表和动态线性表。

(1)元素的获取
顺序存储结构的元素获取操作:
A、判断目标位置是否合法
B、将目标位置作为数组下标获取元素

bool get(int index, T& value)
{
  //判断目标位置是否合法
  bool ret = (0 <= index) && (index < m_length);
  if(ret)
  {
      //将目标位置作为下标获取元素
      value = m_array[index];
  }
  return ret;
}

(2)元素的插入操作
顺序存储结构的元素插入操作:
A、判断目标位置是否合法
B、将目标位置后的所有元素向后移一位
C、将新元素插入目标位置
D、线性表长度增加1

bool insert(int index, const T& value)
{
  //判断目标位置是否合法
  bool ret = (0 <= index) && (index <= m_length);
  ret = ret && ((m_length + 1) <= capacity());
  if(ret)
  {
      //将目标位置后的所有元素后移一个位置
      for(int i = m_length -1; index <= i; i--)
      {
          m_array[i+1] = m_array[i];
      }
      //将新元素插入到目标位置
      m_array[index] = value;
      //线性表长度加1
      m_length++;
  }
  return ret;
}

(3)元素的删除操作
顺序存储结构的元素删除操作:
A、判断目标位置是否合法
B、将目标位置后的所有元素前移一个位置
C、线性表长度减1

bool remove(int index)
{
  //判断目标位置是否合法
  bool ret = (0 <= index) && (index < m_length);
  if(ret)
  {
      //将目标位置后的所有元素前移一位
      for(int i = index; i < m_length-1; i++)
      {
          m_array[i] = m_array[i+1];
      }
      //将线性表长度减1
      m_length--;
  }
  return ret;
}

(4)元素值的修改
顺序存储结构中元素值的修改:
A、判断目标位置是否合法
B、修改目标位置元素的值

bool set(int index, const T& value)
{
  //判断目标位置是否合法
  bool ret = (0 <= index) && (index < m_length);
  if(ret)
  {
      //修改目标位置元素的值
      m_array[index] = value;
  }
  return ret;
}

(5)线性表长度的获取
顺序存储结构线性表的长度获取

int length()const
{
  //线性表长度
  return m_length;
}

(6)线性表的清空

void clear()
{
  //清空线性表
  m_length = 0;
}

(7)[]操作符重载

//[]操作符重载
T& operator[](int index)
{
  //判断目标位置是否合法
  if((0 <= index) && (index < m_length))
  {
      //返回下标相应的元素
      return m_array[index];
  }
  else
  {
      THROW_EXCEPTION(IndexOutOfBoudsException, "Paramenter index is invalid...");
  }
}
//const对象的[]重载
T operator[](int index)const
{
  //转换为非const对象后使用[]操作
  return (const_cast<SeqList<T>&>(*this))[index];
}

(8)顺序存储结构空间的大小
virtual int capacity()const = 0;
由于存储空间在子类分配,因此为纯虚函数。

3、顺序存储结构的抽象实现

 template <typename T>
  class SeqList:public List<T>
  {
  public:
    bool insert(int index, const T& value)
    {
      //判断目标位置是否合法
      bool ret = (0 <= index) && (index <= m_length);
      ret = ret && ((m_length + 1) <= capacity());
      if(ret)
      {
          //将目标位置后的所有元素后移一个位置
          for(int i = m_length -1; index <= i; i--)
          {
              m_array[i+1] = m_array[i];
          }
          //将新元素插入到目标位置
          m_array[index] = value;
          //线性表长度加1
          m_length++;
      }
      return ret;
    }
    bool remove(int index)
    {
      //判断目标位置是否合法
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //将目标位置后的所有元素前移一位
          for(int i = index; i < m_length-1; i++)
          {
              m_array[i] = m_array[i+1];
          }
          //将线性表长度减1
          m_length--;
      }
      return ret;
    }
    bool set(int index, const T& value)
    {
      //判断目标位置是否合法
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //修改目标位置元素的值
          m_array[index] = value;
      }
      return ret;
    }
    bool get(int index, T& value)const
    {
      //判断目标位置是否合法
      bool ret = (0 <= index) && (index < m_length);
      if(ret)
      {
          //将目标位置作为下标获取元素
          value = m_array[index];
      }
      return ret;
    }
    int length()const
    {
      //线性表长度
      return m_length;
    }
    void clear()
    {
      //清空线性表
      m_length = 0;
    }

    int find(const T& value)const
    {
        int ret = -1;
        //遍历线性表
        for(int i = 0; i < m_length; i++)
        {
            //如果找到元素,退出循环
            if(m_array[i] == value)
            {
               ret = i;
               break;
            }
        }
        return ret;
    }

    //[]操作符重载
    T& operator[](int index)
    {
      //判断目标位置是否合法
      if((0 <= index) && (index < m_length))
      {
          //返回下标相应的元素
          return m_array[index];
      }
      else
      {
          THROW_EXCEPTION(IndexOutOfBoudsException, "Paramenter index is invalid...");
      }
    }
    //const对象的[]重载
    T operator[](int index)const
    {
      //转换为非const对象后使用[]操作
      return (const_cast<SeqList<T>&>(*this))[index];
    }
    virtual int capacity()const = 0;

  protected:
    T* m_array;//顺序存储空间
    int m_length;//当前线性表的长度
  };

4、顺序存储结构中数据元素移动的技巧

    数据元素的前移:
    将后面的数据元素向前移动,需要先将前面的数据元素前移,依次移动,直到最后一个数据元素前移,一般用于删除一个数据元素后将后面的数据元素前移。
  //将目标位置后的所有元素前移一位
  for(int i = index; i < m_length-1; i++)
  {
      m_array[i] = m_array[i+1];
  }
    数据元素的后移:
    将前面的数据元素向后移动,需要先将最后的数据元素后移,依次移动,直到第i个数据元素被后移,一般用于插入一个新的数据元素,需要先将插入位置后的所有数据元素后移,在位置处放入新的数据元素。
  //将目标位置后的所有元素后移一个位置
  for(int i = m_length -1; index <= i; i--)
  {
      m_array[i+1] = m_array[i];
  }

5、原生数组实现的线性表

    使用原生数组作为顺序存储空间,使用模板参数确定数组大小。
 template <typename T, int N>
  class StaticList:public SeqList<T>
  {
  public:
    StaticList()
    {
      this->m_array = m_space;//指定父类指针指向的空间
      this->m_length = 0;//设置初始长度
    }
    int capacity() const
    {
      return N;
    }
  protected:
    T m_space[N];//顺序存储空间,N为模板参数
  };
    需要父类的实现纯虚函数capacity()。

6、动态分配空间实现的线性表

        使用动态申请的堆空间作为顺序存储空间,动态设置顺序存储空间的大小并确保重置顺序存储空间大小时的异常安全。

函数异常安全要求不泄漏任何资源,不允许破坏数据。为了确保异常安全,在异常抛出时,必须确保:
A、对象内的任何成员仍然能保持有效状态
B、没有数据的破坏及资源泄漏。

template <typename T>
class DynamicList:public SeqList<T>
{
public:
  DynamicList(int capacity)
  {
    //申请动态空间
    this->m_array = new T[capacity];
    if(this->m_array)
      {
        this->m_length = 0;//初始长度
        this->m_capacity = capacity;//空间大小
      }
    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];
                //如果抛出异常,原数组状态没有改变
              }
            T* temp = this->m_array;
            this->m_array = array;
            this->m_capacity = capacity;
            this->m_length = length;
            delete[] temp;
            //如果抛出异常,重置后的数组状态已经改变
          }
        else
          {
            THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
          }
      }
  }
  ~DynamicList()
  {
    delete[] this->m_array;//释放申请的动态空间
  }
protected:
  int m_capacity;//顺序存储空间的大小
};

二、顺序存储结构线性表的效率分析

1、顺序存储结构线性表的时间复杂度分析

2、顺序存储结构线性表的效率分析

顺序存储结构线性表的插入和删除操作的时间复杂度都是O(n),但是由于插入和删除操作都会涉及到线性表中数据元素的移动,因此会频繁涉及拷贝构造函数和赋值操作符的调用,如果数据元素对象内部的资源较多,对象间的复制、赋值将是非常耗时的,同时由于线性表是泛型模板,无法确定实际实例化的对象类型是否是占有资源的类类型,因此需要禁用拷贝构造函数和赋值操作符,禁用的方式只需要将拷贝构造函数和赋值操作符声明为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、顺序存储结构线性表的缺陷

顺序存储结构线性表重载了[]操作符,因此可以使用[]操作符访问顺序存储结构线性表中的元素。但是线性表中必须存在要访问的元素,即先插入元素才能使用[]操作符访问元素。

三、数组类的工程实现

1、数组类的抽象实现

    要创建一个数组类取代原生数组,数组类需要避免原生数组的缺陷:

A、数组类包含长度信息
B、数组类能够主动发现越界访问
数组类的设计如下:
A、抽象类模板,存储空间的位置和大小由子类完成
B、重载数组操作符,判断访问下标是否越界
C、提供数组长度的抽象访问函数
D、拷贝构造函数和赋值操作符需要在子类实现
数组的实现:

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、静态数组类实现

指定原生数组作为数组类的存储空间实现静态数组类,使用模板参数指定数组大小,实现函数返回数组的长度,实现重载拷贝构造函数和赋值操作符。

template &lt;typename T,int N&gt;
class StaticArray:public Array&lt;T&gt;
{
public:
StaticArray()
{
this-&gt;m_array = m_space;
}
//拷贝构造函数
StaticArray(const StaticArray&lt;T,N&gt;& other)
{
this-&gt;m_array = m_space;
for(int i = 0; i &lt; N; i++)
{
m_space[i] = other.m_space[i];
}
}
//赋值操作符
StaticArray& operator=(const StaticArray&lt;T,N&gt;& other)
{
if(this != &other)
{
for(int i = 0; i&lt; N; i++)
{
m_space[i] = other.m_space[i];
}
}
return *this;
}
int length() const
{
return N;
}
protected:
T m_space[N];//存储空间
};

3、动态分配数组实现

  使用动态分配的堆空间作为数组类的存储空间实现动态分配数组类。
  类模板设计需要动态确定分配存储空间的大小,实现函数返回数组大小,实现拷贝构造函数和赋值操作符。

template &lt;typename T&gt;
class DynamicArray:public Array&lt;T&gt;
{
public:
//构造函数
DynamicArray(int length)
{
this-&gt;m_array = new T[length];
if(this-&gt;m_array != NULL)
{
this-&gt;m_length = length;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
}
}
//拷贝构造函数
DynamicArray(const DynamicArray&lt;T&gt;& other)
{
this-&gt;m_array = new T[other.m_length];
if(this-&gt;m_array != NULL)
{
this-&gt;m_length = other.m_length;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
}
}
//赋值操作符
DynamicArray&lt;T&gt;& operator=(const DynamicArray&lt;T&gt;& other)
{
if(this != &other)
{
T* array = new T[other.m_length];
if(array != NULL)
{
for(int i = 0; i &lt; other.m_length; i++)
{
array[i] = other.m_array[i];
}
T* temp = this-&gt;m_array;
this-&gt;m_array = array;
this-&gt;m_length = other.m_length;
delete[] temp;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No enough memory...");
}
}
return *this;
}

int length()const
{
  return m_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;//数组长度
};