1. 程式人生 > >數據結構(三)——基於順序存儲結構的線性表

數據結構(三)——基於順序存儲結構的線性表

img pro 存儲空間 沒有 順序存儲 聲明 操作符重載 cas turn

數據結構(三)——基於順序存儲結構的線性表

一、基於順序存儲結構的線性表實現

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 <typename T,int N>
  class StaticArray:public Array<T>
  {
  public:
    StaticArray()
    {
      this->m_array = m_space;
    }
    //拷貝構造函數
    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];
      }
    }
    //賦值操作符
    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];//存儲空間
  };

3、動態分配數組實現

    使用動態分配的堆空間作為數組類的存儲空間實現動態分配數組類。
    類模板設計需要動態確定分配存儲空間的大小,實現函數返回數組大小,實現拷貝構造函數和賦值操作符。
template <typename T>
  class DynamicArray:public Array<T>
  {
  public:
    //構造函數
    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...");
      }
    }
    //拷貝構造函數
    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...");
      }
    }
    //賦值操作符
    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;
    }
    //重置數組長度
    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;//數組長度
  };

數據結構(三)——基於順序存儲結構的線性表