zoukankan      html  css  js  c++  java
  • 数据结构开发(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() 函数实现需要保证异常安全
    • 顺序存储线性表的插入和删除操作存在重大效率隐患
    • 线性表作为容器类,应该避免拷贝构造和拷贝赋值
    • 顺序存储线性表可能被当成数组误用
    • 工程开发中可以考虑使用数组类代替原生数组使用
  • 相关阅读:
    53. Maximum Subarray
    64. Minimum Path Sum
    28. Implement strStr()
    26. Remove Duplicates from Sorted Array
    21. Merge Two Sorted Lists
    14. Longest Common Prefix
    7. Reverse Integer
    412. Fizz Buzz
    linux_修改域名(centos)
    linux_redis常用数据类型操作
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10114009.html
Copyright © 2011-2022 走看看