数据结构(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.概念和设计思路

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

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

    // 存储空间: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。

    15.3 List实现

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;
};

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的子类实现
    这两者的差异在于,后者可以动态指定线性表的大小和存储空间,由于两者的性质完全不同,所以不能实现为彼此的子类

    16.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;
};

}

#endif // SEQLIST_H

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.1.1 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;
    }
};

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

    17.2.1 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;
    }
};

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

18.1.时间复杂度

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

18.2.问题

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

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

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

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

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

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

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

原文地址:http://blog.51cto.com/11134889/2126733

时间: 2024-10-09 09:24:34

数据结构(03)_顺序存储结构线性表的相关文章

【学习总结】《大话数据结构》- 第3章-线性表

[学习总结]<大话数据结构>- 总 启示: 线性表:零个或多个数据元素的有限序列. 目录 3.1 开场白 3.2 线性表的定义 3.3 线性表的抽象数据类型 3.4 线性表的顺序存储结构 3.5 顺序存储结构的插入与删除 3.6 线性表的链式存储结构 3.7 单链表的读取 3.8 单链表的插入与删除 3.9 单链表的整表创建 3.10 单链表的整表删除 3.11 单链表结构与顺序存储结构优缺点 3.12 静态链表 3.13 循环链表 3.14 双向链表 3.15 总结回顾 3.16 结尾语 -

顺序存储的线性表的基本操作

刚开始学数据结构,几乎算是什么都不会,想记录一下学习的东西,所以就学别人开始写博客. 刚学了顺序存储的线性表的基本操作,把操作写了一遍.可能会有错误. 顺序存储的线性表,用结构体类型.注意:结构体并不是用来存储元素的,elem才是存储元素的首地址 1 typedef struct 2 { 3 ElemType *elem;//存储空间基地址 6 int length;//表长 7 int listsize;//表容量 8 }SqList; 初始化:构造空表L,返回一个状态,需要带回一个表给基地址

小猪的数据结构辅助教程——2.7 线性表中的双向循环链表

小猪的数据结构辅助教程--2.7 线性表中的双向循环链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.了解引入双向循环链表的原因 2.熟悉双向循环链表的特点以及存储结构 3.掌握双向循环链表的一些基本操作的实现逻辑 4.掌握逆序输出双向循环链表元素逻辑 1.双向循环链表的引入 2.双向循环链表的存储结构 双向循环链表的特点: 上面也说了,空间换时间,比起循环链表只是多了一个指向前驱的指针 特点的话: 判断空表:L ->next = L -> prior = L; 存

小猪的数据结构辅助教程——2.4 线性表中的循环链表

小猪的数据结构辅助教程--2.4 线性表中的循环链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.了解单链表存在怎样的缺点,暴露出来的问题 2.知道什么是循环单链表,掌握单链表的特点以及存储结构 3.掌握循环链表的一些基本操作的实现逻辑,最好能手撕代码 1.循环单链表的引入 2.循环链表的特点以及存储结构 循环链表的特点: 上面也说了,比单链表稍微高比格一点的地方就是: 链表最后一个结点的指针域指向了头结点而已,这样形成所谓的环,就是循环单链表了,呵呵! 特点的话有:

小猪的数据结构辅助教程——2.1 线性表中的顺序表

小猪的数据结构辅助教程--2.1 线性表中的顺序表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.抽象数据类型(ADT)的概念,三要素:数据,数据元素间的关系和数据的操作 2.线性表的特点:按照一条线排列的数据集合,1对1,除了首元和尾元,其他元素都有直接前驱和直接后继 3.牢记线性表的存储结构,要理解并熟悉12个基本操作的逻辑,最好能徒手撕出代码 4.求并集,顺序表的经典例子,必须掌握! 1.抽象的数据类型 简单点说: 抽象:有点像我们面向对象语言中的类的思想,将事物

小猪的数据结构辅助教程——2.2 线性表中的单链表

小猪的数据结构辅助教程--2.2 线性表中的单链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.理解顺序表以及单链表各自的有点以及缺点! 2.熟悉单链表的形式,对于头指针,头结点,尾结点,数据域和指针域这些名词要知道是什么! 3.熟悉单链表的结点结构 4.区分头指针与头结点! 5.熟悉创建单链表的两种方式:头插法和尾插法 6.了解单链表12个基本操作的逻辑 7.有趣的算法题:查找单链表的中间结点~ 1.单链表的引入(顺序表与单链表的PK) 2.单链表的结构图以及一些名

数据结构与算法 Chapter 2 线性表

2.1 线性表定义 线性表是由长度为n的一组节点组成的有限序列,其中除了首末结点之外,每个结点都有直接的前驱结点和后继结点. 2.2 线性表的顺序存储结构 顺序存储结构使用一组连续的存储单元来存储线性表. 其特点有:线性表的逻辑顺序与物理顺序一致.数据元素之间的关系采用物理位置的相邻来表示.其可以随机存取,常用一维数组表示: #include <iostream> using namespace std; template<class Elem> class Alist { pri

[数据结构 - 第3章补充] 线性表之双向链表(C语言实现)

一.什么是循环链表? 双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域.所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱. 既然单链表也可以有循环链表,那么双向链表当然也可以是循环表. 线性表的双向链表存储结构如下: typedef int ElemType; typedef struct DulNode { ElemType data; //数据域 DulNode *prior; //指向前驱结点的指针 DulN

【Java数据结构学习笔记之一】线性表的存储结构及其代码实现

应用程序后在那个的数据大致有四种基本的逻辑结构: 集合:数据元素之间只有"同属于一个集合"的关系 线性结构:数据元素之间存在一个对一个的关系 树形结构:数据元素之间存在一个对多个关系 图形结构或网状结构:数据元素之间存在多个对多个的关系 对于数据不同的逻辑结构,计算机在物理磁盘上通常有两种屋里存储结构 顺序存储结构 链式存储结构 本篇博文主要讲的是线性结构,而线性结构主要是线性表,非线性结构主要是树和图. 线性表的基本特征: 总存在唯一的第一个数据元素 总存在唯一的最后一个数据元素 除