C/C++知识点之数据结构(03)_顺序存储结构线性表
小标 2019-06-10 来源 : 阅读 874 评论 0

摘要:本文主要向大家介绍了C/C++知识点之数据结构(03)_顺序存储结构线性表,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

本文主要向大家介绍了C/C++知识点之数据结构(03)_顺序存储结构线性表,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

C/C++知识点之数据结构(03)_顺序存储结构线性表

基于前面实现的数据结构类模板基础,继续完成基于顺序存储结构的线性表的实现,继承关系图如下:


1.线性表简介


1.1.线性表的表现形式


零个多多个数据元素组成的集合


数据元素在位置上是有序排列的


数据元素的个数是有限的


数据元素的类型必须相同

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


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


a0为线性表的第一个元素,只有一个后继


an-1为线性表的最后一个元素,只有一个前驱


其他数据项既有后继,也有前驱


支持逐项和顺序存储

1.3.线性表的抽象实现


插入、删除数据元素


获取、设置目标位置元素的值


获取线性表的长度


清空线性表


template <typename T>
class List:public Object
{
protected:
    List(const List&);
    List& operator ==(const List&);

public:
    List(){}
    virtual bool insert(const T& e) = 0;
    virtual bool insert(int i,const T& e) = 0;
    virtual bool remove(int i) = 0;
    virtual bool set(int i,const T& e) = 0;
    virtual bool get(int i,T& e) const  = 0;
    virtual int length() const = 0;
    virtual void clear() = 0;
};


1.4.总结:


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


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


2.1.概念和设计思路


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

设计思路:
使用一维数组来实现存储结构:


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


3.SeqList的设计要点


抽象类模板,存储空间的大小和位置由子类完成;


实现顺序存储结构线性表的关键操作(增、删、查、等);


提供数组操作符重载,方面快速获取元素;

3.1SeqList实现


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

    bool insert(int index, const T& e)
    {
        bool ret = ( (index>=0) && (index<=m_length) ); // <= 因为可以插入的点,必然比当前元素多1

        if(ret && ( m_length < capacity() ))    // 当前至少有一个空间可插入
        {
            for(int p=m_length-1; p>=index; p--)
            {
                m_array[p + 1] = m_array[p];
            }

            m_array[index] = e;
            m_length++;
        }
        return ret;
    }

    bool insert(const T& e)
    {
        return insert(m_length, e);
    }

    bool remove(int index)
    {
        bool ret = ( (index>=0) && (index<m_length) );  // 目标位置合法 <m_length

        if(ret)
        {
            for(int p=index; p<m_length-1; p++)        // 注意思考此处的边界条件
            {
                m_array[p] = m_array[p+1];
            }

             m_length--;
        }

        return ret;
    }

    bool set(int index, const T& e)
    {
        bool ret = ( (index>=0) && (index<m_length) );

        if(ret)
        {
            m_array[index] = e;
        }

        return ret;
    }

    bool get(int index, T& e) const
    {
        bool ret = ( (index>=0) && (index<m_length) );

        if(ret)
        {
            e = m_array[index];
        }

        return ret;
    }

    int length() const
    {
        return m_length;
    }

    void clear()
    {
        m_length = 0;
    }

    // 顺序存储表的数组访问方式
    T& operator [] (int index)
    {
        if( (index>=0) && (index<m_length) )
        {
            return m_array[index];
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "index out of range...");
        }
    }

    T operator [] (int index) const
    {
        static_cast<SeqList<T>&>(*this)[index];    // 去除const属性,然后调用非const版本实现
    }

    // 顺序存储表的的容量
    virtual int capacity() const = 0;
};


4.StaticList和DynamicList


4.1.StaticList的设计要点:


类模板


使用原生数组做为顺序存储空间


使用模板参数决定数组的大小

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


4.2. StaticList实现


template < typename T, int N >
class StaticList : public SeqList <T>
{
protected:
    T m_space[N];       // 顺序存储空间,N为模板参数
public:
    StaticList()        // 指定父类成员的具体值
    {
        this->m_array = m_space;
        this->m_length = 0;
    }
    int capacity() const
    {
        return N;
    }
};


4.3.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();             // 归还空间
};


4.4. DynamicList实现


template <typename T>
class DynamicList : public SeqList<T>
{

protected:
    int m_capacity;
public:
    DynamicList(int capacity)
    {
        this->m_array = new T[capacity];
        if(this->m_array != NULL)
        {
            this->m_length = 0;
            this->m_capacity = capacity;
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create DynamicList object ...");
        }
    }

    int capacity()const
    {
        return m_capacity;
    }

    void resize(int capacity)
    {
        if(capacity != m_capacity)
        {
            T* array = new T[capacity];
            if(array != NULL)
            {
                int length = (this->m_length < capacity ? this->m_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_length = length;
                this->m_capacity = capacity;
                delete[] temp;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create DynamicList object ...");
            }
        }
    }

    ~DynamicList()
    {
        delete[] this->m_array;
    }
};


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


5.1.时间复杂度


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


5.2.问题


两个长度相同的顺序存储结构线性表,插入、删除操作的耗时是否相同?


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

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


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


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


5.4.注意事项


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


必须先进行插入操作,才能对其内部的数据进行操作。


原生数组是自带空间的,可以直接操作。


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程