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

假装没事ソ 提交于 2020-05-05 01:15:04

0.目录

1.线性表的本质和操作

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

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

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

5.小结

<span id = "jump1">1.线性表的本质和操作</span>

线性表 ( <font color=blue>List</font> ) 的表现形式:

  • <font color=red>零个</font>或<font color=red>多个</font>数据元素组成的集合
  • 数据元素<font color=purple>在位置上是有序排列的</font>
  • 数据元素的个数是有限的
  • 数据元素的类型必须相同

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

线性表 ( List ) 的性质:

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

线性表的一些常用操作:

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

线性表<font color=blue>在程序中</font>表现为一种<font color=red>特殊的</font>数据类型:

(在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

<span id = "jump2">2.线性表的顺序存储结构</span>

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

设计思路——可以用<font color=red>一维数组</font>来实现顺序存储结构:

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

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

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

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

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

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

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

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

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

<span id = "jump3">3.顺序存储结构的抽象实现和具体实现</span>

<span id = "jump3.1">3.1 SeqList</span>

本节目标:

  • 完成<font color=red>顺序存储结构线性表</font>的抽象实现

SeqList设计要点:

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

(在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

<span id = "jump3.2">3.2 StaticList 和 DynamicList</span>

本节目标:

  • 完成 <font color=red>StaticList</font> 类的具体实现
  • 完成 <font color=purple>DynamicList</font> 类的具体实现

StaticList 设计要点:

  • 类模板
    1. 使用<font color=red>原生数组</font>作为顺序存储空间
    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:\allcode\qtcreator\datastructure\stlib\SeqList.h:97

DynamicList 设计要点:

  • 类模板
    1. 申请<font color=blue>连续堆空间</font>作为顺序存储空间
    2. <font color=red>动态设置</font>顺序存储空间的大小
    3. <font color=purple>保证重置顺序存储空间时的异常安全性</font>
  • 函数异常安全的概念
    1. 不泄漏任何资源
    2. 不允许破坏数据
  • <font color=purple>函数异常安全的基本保证</font>
    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:\allcode\qtcreator\datastructure\stlib\SeqList.h:97
16
3
2
1
0
5

16
3
2

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

<span id = "jump4">4.顺序存储线性表的分析</span>

<span id = "jump4.1">4.1 效率分析</span>

效率分析:

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

<span id = "jump4.2">4.2 功能分析</span>

下面的代码正确吗?为什么? 下面的代码正确吗?为什么? 功能分析: 对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。

代码优化(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

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

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

<span id = "jump5">5.小结</span>

  • 线性表是数据元素的<font color=blue>有序</font>并且<font color=red>有限</font>的集合
  • 线性表中的数据元素必须是类型相同的
  • 线性表可用于<font color=purple>描述排队关系</font>的问题
  • 线性表在程序中表现为一种特殊的数据类型
  • 线性表在C++中表现为一个抽象类
  • StaticList 通过<font color=red>模板参数</font>定义顺序存储空间
  • DynamicList 通过<font color=purple>动态内存申请</font>定义顺序存诸空间
  • DynamicList 支持<font color=purple>动态重置</font>顺序存储空间的大小
  • Dynamiclist 中的 resize() 函数实现需要保证异常安全
  • 顺序存储线性表的插入和删除操作<font color=red>存在重大效率隐患</font>
  • 线性表作为容器类,应该避免拷贝构造和拷贝赋值
  • 顺序存储线性表<font color=purple>可能</font>被当成数组<font color=purple>误用</font>
  • 工程开发中可以考虑<font color=purple>使用数组类代替原生数组</font>使用
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!