数据结构(03)_顺序存储结构线性表

本节我们基于前面实现的数据结构类模板基础,继续完成基于顺序存储结构的线性表的实现,话不多说,继承关系图如下:
数据结构(03)_顺序存储结构线性表

14.线性表的本质和操作

14.1.线性表的表现形式

  • 零个多多个数据元素组成的集合
  • 数据元素在位置上是有序排列的
  • 数据元素的个数是有限的
  • 数据元素的类型必须相同

    14.2.线性表的抽象定义、性质

    线性表是具有相同类型的n(>=)个数据元素的有限序列,(a0, a1, a2... an-1),其中ai是表项,n是表长度。
    性质:

  • a0为线性表的第一个元素,只有一个后继
  • an-1为线性表的最后一个元素,只有一个前驱
  • 其他数据项既有后继,也有前驱
  • 支持逐项和顺序存储

    14.3.线性表的一些常用操作

  • 插入、删除数据元素
  • 获取、设置目标位置元素的值
  • 获取线性表的长度
  • 清空线性表
    示例代码:
    template <typename T>
    class list : public Object
    {
    public:
    virtual bool insert(int index, const T& e) = 0;
    virtual bool remove(int index) = 0;
    virtual bool set(int index, const T& e) = 0;
    virtual bool get(int index, T& e) const = 0;
    virtual bool length() const = 0;
    virtual bool clear() = 0;
    }

    14.4.总结:

    线性表是数据元素的有序并且有限的集合,其中的数据元素类型相同,在程序中表现为一个特殊的数据结构,可以使用C++中的抽象类来表示,用来描述排队关系的问题。

    15.线性表的顺序存储结构

    15.1.概念和设计思路

    定义:
    线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表中的数据元素。
    数据结构(03)_顺序存储结构线性表
    设计思路:
    使用一维数组来实现存储结构:

    // 存储空间:T* m_array; 当前长度:int m_length;
    template <typename T>
    class SeqList : public List<T>
    {
    protected:
    T* m_array;
    int m_length;
    };

    15.2.顺序存储结构的元素操作

  • 获取:判断目标位置是否合法,将目标位置做为数组下标获取元素。
  • 插入:1.判断目标位置是否合法,将目标位置之后的元素后移一个位置,3.将新元素插入目标位置,4.线性表长度加1。(注意插入的点永远比元素会多一个)
  • 删除:1.判断目标位置是否合法,将目标位置之后的元素前移一个位置,3.线性表长度减1。

    16.SeqList的设计要点

  • 抽象类模板,存储空间的大小和位置由子类完成;
  • 实现顺序存储结构线性表的关键操作(增、删、查、等);
  • 提供数组操作符重载,方面快速获取元素;

    template <typename T>
    class SeqList : public List<T>
    {
    protected:
    T* m_array;      // 顺序存储空间
    int m_length;        // 当前线性长度
    public:
    bool insert(int index, const T& e);
    bool remove(int index);
    bool set(int index, const T& e);
    bool get(int index, T& e) const;
    int length() const;
    void clear();
    
    // 顺序存储表的数组访问方式
    T& operator [] (int index);
    T operator [] (int index) const;
    
    // 顺序存储表的的容量
    virtual int capacity() const = 0;
    };

    思考:StaticList和DynamicList如何实现,差异在那里?是否可以将DynamicList做为StaticList的子类实现
    这两者的差异在于,后者可以动态指定线性表的大小和存储空间,由于两者的性质完全不同,所以不能实现为彼此的子类

    17.StaticList和DynamicList

    17.1.StaticList的设计要点:

    类模板

  • 使用原生数组做为顺序存储空间
  • 使用模板参数决定数组的大小
    template < typename T, int N >
    class StaticList : public SeqList <T>
    {
    protected:
    T m_space[];        // 顺序存储空间,N为模板参数
    public:
    StaticList();       // 指定父类成员的具体值
    int capacity() const;
    };

    17.2.DynamicList的设计要点:

    类模板

  • 申请连续堆空间做为顺序存储空间
  • 保证重置顺序存储空间的异常安全性
    函数异常安全的概念:
  • 不允许任何内存泄露,不允许破坏数据
  • 函数异常安全的基本保证:
  • 如果有异常抛出,对象内的任何成员任然能保持有效状态,没有数据破话或者资源泄露。
    template < typename T>
    class DynamicList : public SeqList <T>
    {
    protected:
    int capacity;       // 顺序存储空间的大小
    public:
    DynamicList(int capacity);   // 申请空间
    int capacity(void) const         // 返回capacity的值 
    // 重置存储空间的大小
    void reset(int capacity);
    ~DynamicList();             // 归还空间
    };

    18.顺序存储结构线性表分析

    18.1.时间复杂度

    顺序存储结构线性表的效率为O(n),主要受其插入和删除操作的影响(譬如插入操作时,要插入位置之后的数据要向后挪动) 。

    18.2.问题

    两个长度相同的顺序存储结构线性表,插入、删除操作的耗时是否相同?
    不相同,对顺序存储结构线性表,其插入、删除操作的复杂度还取决于存储的数据类型,譬如一个普通类型和一个字符串类型/类类型就完全不同(对于复杂数据类型,元素之间移动时必然耗时很多)。从这个角度考虑,线性表的效率存在隐患。

    18.3.禁用拷贝构造和赋值操作。

    贝构造和赋值操作会导致两个指针指向同一个地址,导致内存重复释放。对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。
    原因: 1、对于生活中容器类的东西,我们无法对其进行赋值(譬如生活中我们不可能将杯子中的水进行复制,只能使用另一个杯子重新去获取等量的水)。
    实现:将拷贝构造和赋值操作函数定义为proteced成员,在类的外部,不能使用。

    protected:
    List(const List&){}
    List& operator = (const List&){}

    18.4.线性表不能直接当做数组来使用

    顺序存储结构线性表提供了数组操作符的重载,可以直接像数组一样,同过下标直接获取目标位置的元素,在具体的使用上类似数组,但是本质上不同,不能代替数组使用:

  • 必须先进行插入操作,才能对其内部的数据进行操作。
  • 原生数组是自带空间的,可以直接操作。

猜你喜欢

转载自blog.51cto.com/11134889/2126733