• 数据结构开发(3):线性表的顺序存储结构


    0.目录

    1.线性表的本质和操作

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

    3.顺序存储结构的抽象实现和具体实现

    4.顺序存储线性表的分析

    5.小结

    1.线性表的本质和操作

    线性表 ( List ) 的表现形式:

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

    线性表 ( List ) 的抽象定义——线性表是具有相同类型的n(≥0)个数据元素的有限序列:

    线性表 ( List ) 的性质:

    线性表只是一个单纯的概念吗?如何在程序中描述和使用一个线性表?

    线性表的一些常用操作:

    • 将元素插入线性表
    • 将元素从线性表中删除
    • 获取目标位置处元素的值
    • 设置目标位置处元素的值
    • 获取线性表的长度
    • 清空线性表

    线性表在程序中表现为一种特殊的数据类型:

    (在StLib中实现List.h)

    #ifndef LIST_H
    #define LIST_H
    
    #include "Object.h"
    
    namespace StLib
    {
    
    template <typename T>
    class List : public Object
    {
    public:
        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;
    };
    
    }
    
    #endif // LIST_H
    

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

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

    设计思路——可以用一维数组来实现顺序存储结构:

    • 存储空间:T* m_array;
    • 当前长度:int m_length;

    顺序存诸结构的元素获取操作:

    • 判断目标位置是否合法
    • 将目标位置作为数组下标获取元素

    顺序存储结构的元素插入操作:

    1. 判断目标位置是否合法
    2. 将目标位置之后的所有元素后移一个位置
    3. 将新元素插入目标位置
    4. 线性表长度加 1

    顺序存储结构的元素插入示例:

    顺序存储结构的元素删除操作:

    1. 判断目标位置是否合法
    2. 将目标位置后的所有元素前移一个位置
    3. 线性表长度减 1

    顺序存储结构的元素删除示例:

    3.顺序存储结构的抽象实现和具体实现

    3.1 SeqList

    本节目标:

    • 完成顺序存储结构线性表的抽象实现

    SeqList设计要点:

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

    (在StLib中实现SeqList.h)

    #ifndef SEQLIST_H
    #define SEQLIST_H
    
    #include "List.h"
    #include "Exception.h"
    
    namespace StLib
    {
    
    template <typename T>
    class SeqList : public List<T>
    {
    protected:
        T* m_array;   // 顺序存储空间
        int m_length; // 当前线性表长度
    public:
        bool insert(int i, const T& e)
        {
            bool ret = ( (0 <= i) && (i <= m_length) );
    
            ret = ret && ( (m_length + 1) <= capacity() );
    
            if( ret )
            {
                for(int p=m_length-1; p>=i; p--)
                {
                    m_array[p+1] = m_array[p];
                }
    
                m_array[i] = e;
                m_length++;
            }
    
            return ret;
        }
    
        bool remove(int i)
        {
            bool ret = ( (0 <= i) && (i <= m_length) );
    
            if( ret )
            {
                for(int p=i; p<m_length-1; p++)
                {
                    m_array[p] = m_array[p+1];
                }
    
                m_length--;
            }
    
            return ret;
        }
    
        bool set(int i, const T& e)
        {
            bool ret = ( (0 <= i) && (i < m_length) );
    
            if( ret )
            {
                m_array[i] = e;
            }
    
            return ret;
        }
    
        bool get(int i, T& e) const
        {
            bool ret = ( (0 <= i) && (i < m_length) );
    
            if( ret )
            {
                 e = m_array[i];
            }
    
            return ret;
        }
    
        int length() const
        {
            return m_length;
        }
    
        void clear()
        {
            m_length = 0;
        }
    
        // 顺序存储线性表的数组访问方式
        T& operator[] (int i)
        {
            if( (0 <= i) && (i < m_length) )
            {
                return m_array[i];
            }
            else
            {
                THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
            }
        }
    
        T operator[] (int i) const
        {
            return (const_cast<SeqList<T>&>(*this))[i];
        }
    
        // 顺序存储空间的容量
        virtual int capacity() const = 0;
    };
    
    }
    
    #endif // SEQLIST_H
    

    3.2 StaticList 和 DynamicList

    本节目标:

    • 完成 StaticList 类的具体实现
    • 完成 DynamicList 类的具体实现

    StaticList 设计要点:

    • 类模板
      1. 使用原生数组作为顺序存储空间
      2. 使用模板参数决定数组大小

    (在StLib中实现StaticList.h)

    #ifndef STATICLIST_H
    #define STATICLIST_H
    
    #include "SeqList.h"
    
    namespace StLib
    {
    
    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;
        }
    };
    
    }
    
    #endif // STATICLIST_H
    

    main.cpp测试

    #include <iostream>
    #include "StaticList.h"
    #include "Exception.h"
    
    using namespace std;
    using namespace StLib;
    
    int main()
    {
        StaticList<int, 5> l;
    
        for(int i=0; i<l.capacity(); i++)
        {
            l.insert(0, i);
        }
    
        for(int i=0; i<l.capacity(); i++)
        {
            cout << l[i] << endl;
        }
    
        l[0] *= l[0];
    
        for(int i=0; i<l.capacity(); i++)
        {
            cout << l[i] << endl;
        }
    
        try
        {
            l[5] = 5;
        }
        catch(const Exception& e)
        {
            cout << e.message() << endl;
            cout << e.location() << endl;
        }
    
        return 0;
    }
    

    运行结果为:

    4
    3
    2
    1
    0
    16
    3
    2
    1
    0
    Parameter i is invalid ...
    f:allcodeqtcreatordatastructurestlibSeqList.h:97
    

    DynamicList 设计要点:

    • 类模板
      1. 申请连续堆空间作为顺序存储空间
      2. 动态设置顺序存储空间的大小
      3. 保证重置顺序存储空间时的异常安全性
    • 函数异常安全的概念
      1. 不泄漏任何资源
      2. 不允许破坏数据
    • 函数异常安全的基本保证
      1. 如果异常被抛出
        1. 对象内的任何成员仍然能保持有效状态
        2. 没有数据的破坏及资源泄漏

    (在StLib中实现DynamicList.h)

    #ifndef DYNAMICLIST_H
    #define DYNAMICLIST_H
    
    #include "SeqList.h"
    
    namespace StLib
    {
    
    template <typename T>
    class DynamicList : public SeqList<T>
    {
    protected:
        T 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 resize DynamicList object ...");
                }
            }
        }
    
        ~DynamicList() // 归还空间
        {
            delete[] this->m_array;
        }
    };
    
    }
    
    #endif // DYNAMICLIST_H
    

    main.cpp测试

    #include <iostream>
    #include "DynamicList.h"
    #include "Exception.h"
    
    using namespace std;
    using namespace StLib;
    
    int main()
    {
        DynamicList<int> l(5);
    
        for(int i=0; i<l.capacity(); i++)
        {
            l.insert(0, i);
        }
    
        for(int i=0; i<l.length(); i++)
        {
            cout << l[i] << endl;
        }
        cout << endl;
    
        l[0] *= l[0];
    
        for(int i=0; i<l.length(); i++)
        {
            cout << l[i] << endl;
        }
    
        try
        {
            l[5] = 5;
        }
        catch(const Exception& e)
        {
            cout << e.message() << endl;
            cout << e.location() << endl;
    
            l.resize(10);
            l.insert(5, 50);
        }
    
        l[5] = 5;
    
        for(int i=0; i<l.length(); i++)
        {
            cout << l[i] << endl;
        }
        cout << endl;
    
        l.resize(3);
    
        for(int i=0; i<l.length(); i++)
        {
            cout << l[i] << endl;
        }
    
        return 0;
    }
    

    运行结果为:

    4
    3
    2
    1
    0
    
    16
    3
    2
    1
    0
    Parameter i is invalid ...
    f:allcodeqtcreatordatastructurestlibSeqList.h:97
    16
    3
    2
    1
    0
    5
    
    16
    3
    2
    

    问题:
    是否可以将 DynamicList 作为 StaticList 的子类实现?
    (不能将 DynamicList 作为 StaticList 的子类实现,反之也是不可以的,因为这两个类对于顺序存储空间的指定是截然不同没有任何关系的,因此它们两个的地位必然是位于同一层次的。)

    4.顺序存储线性表的分析

    4.1 效率分析

    效率分析:

    问题:
    长度相同的两个SeqList插入删除操作的平均耗时是否相同?
    (insert操作最耗时的部分是for循环,究竟有多耗时取决于线性表里面存储的数据元素的类型是什么。如果存储的数据元素的类型是一个自定义的类,并且这个类还非常的庞大,那么插入操作就真的非常耗时了,因为涉及了对象之间的拷贝。)

    4.2 功能分析

    下面的代码正确吗?为什么?

    下面的代码正确吗?为什么?

    功能分析:
    对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。

    代码优化(List.h和SeqList.h):
    优化List.h

    #ifndef LIST_H
    #define LIST_H
    
    #include "Object.h"
    
    namespace StLib
    {
    
    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;
    };
    
    }
    
    #endif // LIST_H
    

    优化SeqList.h

    #ifndef SEQLIST_H
    #define SEQLIST_H
    
    #include "List.h"
    #include "Exception.h"
    
    namespace StLib
    {
    
    template <typename T>
    class SeqList : public List<T>
    {
    protected:
        T* m_array;   // 顺序存储空间
        int m_length; // 当前线性表长度
    public:
        bool insert(int i, const T& e)
        {
            bool ret = ( (0 <= i) && (i <= m_length) );
    
            ret = ret && ( (m_length + 1) <= capacity() );
    
            if( ret )
            {
                for(int p=m_length-1; p>=i; p--)
                {
                    m_array[p+1] = m_array[p];
                }
    
                m_array[i] = e;
                m_length++;
            }
    
            return ret;
        }
    
        bool insert(const T& e)
        {
            return insert(m_length, e);
        }
    
        bool remove(int i)
        {
            bool ret = ( (0 <= i) && (i <= m_length) );
    
            if( ret )
            {
                for(int p=i; p<m_length-1; p++)
                {
                    m_array[p] = m_array[p+1];
                }
    
                m_length--;
            }
    
            return ret;
        }
    
        bool set(int i, const T& e)
        {
            bool ret = ( (0 <= i) && (i < m_length) );
    
            if( ret )
            {
                m_array[i] = e;
            }
    
            return ret;
        }
    
        bool get(int i, T& e) const
        {
            bool ret = ( (0 <= i) && (i < m_length) );
    
            if( ret )
            {
                 e = m_array[i];
            }
    
            return ret;
        }
    
        int length() const
        {
            return m_length;
        }
    
        void clear()
        {
            m_length = 0;
        }
    
        // 顺序存储线性表的数组访问方式
        T& operator[] (int i)
        {
            if( (0 <= i) && (i < m_length) )
            {
                return m_array[i];
            }
            else
            {
                THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
            }
        }
    
        T operator[] (int i) const
        {
            return (const_cast<SeqList<T>&>(*this))[i];
        }
    
        // 顺序存储空间的容量
        virtual int capacity() const = 0;
    };
    
    }
    
    #endif // SEQLIST_H
    

    main.cpp测试

    #include <iostream>
    #include "DynamicList.h"
    
    using namespace std;
    using namespace StLib;
    
    int main()
    {
        DynamicList<int> l(5);
    
        for(int i=0; i<l.capacity(); i++)
        {
            l.insert(i);
        }
    
        for(int i=0; i<l.length(); i++)
        {
            cout << l[i] << endl;
        }
    
        return 0;
    }
    

    运行结果为:

    0
    1
    2
    3
    4
    

    下面的代码正确吗?为什么?

    问题分析:
    顺序存储结构线性表提供了数组操作符重载,通过重载能够快捷方便的获取目标位置处的数据元素,在具体的使用形式上类似数组,但是由于本质不同,不能代替数组使用

    5.小结

    • 线性表是数据元素的有序并且有限的集合
    • 线性表中的数据元素必须是类型相同的
    • 线性表可用于描述排队关系的问题
    • 线性表在程序中表现为一种特殊的数据类型
    • 线性表在C++中表现为一个抽象类
    • StaticList 通过模板参数定义顺序存储空间
    • DynamicList 通过动态内存申请定义顺序存诸空间
    • DynamicList 支持动态重置顺序存储空间的大小
    • Dynamiclist 中的 resize() 函数实现需要保证异常安全
    • 顺序存储线性表的插入和删除操作存在重大效率隐患
    • 线性表作为容器类,应该避免拷贝构造和拷贝赋值
    • 顺序存储线性表可能被当成数组误用
    • 工程开发中可以考虑使用数组类代替原生数组使用
  • 相关阅读:
    windows快捷键
    android录音实现不再担心—一个案例帮你解决你的问题
    区块链到底是个什么鬼?一幅漫画让你秒懂!
    10个优秀个android项目,精选|快速开发
    精文推荐,12个开源项目开发必备,绝对干货
    送书拉! 08年新开始|福利近在咫尺
    用Kotlin破解Android版微信小游戏-跳一跳
    聊一聊正则表达式,最全最常用总结
    11个优秀的Android开发开源项目
    一招教你打造一个滑动置顶的视觉特效
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10114009.html
Copyright © 2020-2023  润新知