第6课 - 线性表的顺序存储结构及实现

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

【顺序存储结构定义】

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

【设计思路】

可以用一维数组来实现顺序存储结构的线性表。

1 template <typename T>
2 class SeqList : public List<T>
3 {
4 protected:
5     T *m_array;    //顺序存储空间
6     int m_length;  //当前线性表长度
7 };

【顺序存储线性表类的组成】

DTLib顺序存储结构线性表相关的类有三个:

- SeqList:顺序存储结构线性表的抽象父类

- StaticList:SeqList的子类,使用原生数组作为顺序存储空间

- DynamicList:SeqList的子类,申请连续堆空间作为顺序存储空间

2. SeqList类的实现

【设计要点】

- 抽象类模板

- 仅实现顺序存储结构线性表的关键操作(增,删,改,等)

- 存储空间的位置和大小由子类完成

- 提供数组操作符,方便快速获取元素

【接口设计】

 1 template <typename T>
 2 class SeqList : public List<T>
 3 {
 4 public:
 5     bool insert(int i, const T &e);    //在位置i处插入新元素e
 6     bool insertTail(const T &e);       //在尾部插入新元素e
 7     bool remove(int i);                //删除位置i处的元素
 8     bool set(int i, const T &e);       //设置位置i处的元素为e
 9     bool get(int i, T &e) const;       //获取位置i处的元素,通过参数e返回
10     T get(int i) const;                //获取位置i处的元素,通过返回值返回
11     int length() const;                //获取线性表长度
12     void clear();                      //清空线性表
13
14     //数组访问形式,提供const和非const版本
15     T &operator[] (int i);
16     T &operator[] (int i) const;
17
18     //顺序存储空间的大小,由子类实现
19     virtual int capacity() const = 0;
20 };

【插入功能实现】

  1. 检查目标位置是否合法
  2. 检查是否有剩余存储空间可供插入
  3. 将目标位置处元素及目标位置之后的所有元素后移一个位置
  4. 将新元素插入目标位置
  5. 线性表长度加1
 1 //在位置i处插入新元素e
 2 bool insert(int i, const T &e)
 3 {
 4     bool ret = (0 <= i) && (i <= m_length);       //目标位置可以为m_length,表示在尾部插入
 5     ret = ret && ((m_length + 1) <= capacity());  //确保有剩余空间可供插入
 6
 7     if (ret)
 8     {
 9         //将目标位置处元素及目标位置之后的所有元素后移一个位置
10         for (int p = m_length - 1; p >= i; p--)
11         {
12             m_array[p + 1] = m_array[p];
13         }
14
15         m_array[i] = e;
16         m_length++;
17     }
18
19     return ret;
20 }
21
22 //在尾部插入新元素e
23 bool insertTail(const T &e)
24 {
25     return insert(m_length, e);
26 }

【删除功能实现】

  1. 检查目标位置是否合法
  2. 将目标位置之后的所有元素前移一个位置
  3. 线性表长度减一
 1 //删除位置i处的元素
 2 bool remove(int i)
 3 {
 4     bool ret = (0 <= i) && (i < m_length);
 5
 6     if (ret)
 7     {
 8         //将目标位置之后的所有元素前移一个位置
 9         for (int p = i + 1; p < m_length; p++)
10         {
11             m_array[p - 1] = m_array[p];
12         }
13
14         m_length--;
15     }
16
17     return ret;
18 }

【设置功能实现】

  1. 检查目标位置合法性
  2. 将目标位置处的元素替换为要设置的值
 1 //设置位置i处的元素为e
 2 bool set(int i, const T &e)
 3 {
 4     bool ret = (0 <= i) && (i < m_length);
 5
 6     if (ret)
 7     {
 8         m_array[i] = e;
 9     }
10
11     return ret;
12 }

【获取功能实现】

  1. 检查目标位置合法性
  2. 将目标位置作为数组下标获取元素
 1 //获取位置i处的元素,通过参数e返回
 2 bool get(int i, T &e) const
 3 {
 4     bool ret = (0 <= i) && (i < m_length);
 5
 6     if (ret)
 7     {
 8         e = m_array[i];
 9     }
10
11     return ret;
12 }
13
14 //获取位置i处的元素,通过返回值返回
15 T get(int i) const
16 {
17     T ret;
18
19     if (get(i, ret))
20     {
21         return ret;
22     }
23     else
24     {
25         THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
26     }
27 }

【获取长度与清空功能实现】

  1. 获取线性表长度,直接将m_length返回
  2. 清空线性表,只需将m_length清0
 1 //获取线性表长度
 2 int length() const
 3 {
 4     return m_length;
 5 }
 6
 7 //清空线性表
 8 void clear()
 9 {
10     m_length = 0;
11 }

【数组访问方式实现】

  1. 检查目标位置合法性
  2. 对于非const版本,直接将目标位置处元素返回
  3. 对于const版本,先对当前对象进行const_cast强制类型转换,去除其const属性,然后再调用非const版本的数组访问函数
 1 //数组访问,用于非const对象
 2 T &operator[] (int i)
 3 {
 4    if ((0 <= i) && (i < m_length))
 5    {
 6        return m_array[i];
 7    }
 8    else
 9    {
10        THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
11    }
12 }
13
14 //数组访问,用于const对象
15 T &operator[] (int i) const
16 {
17     return (const_cast<SeqList<T> &>(*this))[i];
18 }

【SeqList完整实现】

以下是SeqList.h的完整源码。

  1 #ifndef SEQLIST_H
  2 #define SEQLIST_H
  3
  4 #include "List.h"
  5 #include "Exception.h"
  6
  7 namespace DTLib
  8 {
  9
 10 template <typename T>
 11 class SeqList : public List<T>
 12 {
 13 protected:
 14     T *m_array;    //顺序存储空间
 15     int m_length;  //当前线性表长度
 16 public:
 17     //在位置i处插入新元素e
 18     bool insert(int i, const T &e)
 19     {
 20         bool ret = (0 <= i) && (i <= m_length);       //目标位置可以为m_length,表示在尾部插入
 21         ret = ret && ((m_length + 1) <= capacity());  //确保有剩余空间可供插入
 22
 23         if (ret)
 24         {
 25             //将目标位置处元素及目标位置之后的所有元素后移一个位置
 26             for (int p = m_length - 1; p >= i; p--)
 27             {
 28                 m_array[p + 1] = m_array[p];
 29             }
 30
 31             m_array[i] = e;
 32             m_length++;
 33         }
 34
 35         return ret;
 36     }
 37
 38     //在尾部插入新元素e
 39     bool insertTail(const T &e)
 40     {
 41         return insert(m_length, e);
 42     }
 43
 44     //删除位置i处的元素
 45     bool remove(int i)
 46     {
 47         bool ret = (0 <= i) && (i < m_length);
 48
 49         if (ret)
 50         {
 51             //将目标位置之后的所有元素前移一个位置
 52             for (int p = i + 1; p < m_length; p++)
 53             {
 54                 m_array[p - 1] = m_array[p];
 55             }
 56
 57             m_length--;
 58         }
 59
 60         return ret;
 61     }
 62
 63     //设置位置i处的元素为e
 64     bool set(int i, const T &e)
 65     {
 66         bool ret = (0 <= i) && (i < m_length);
 67
 68         if (ret)
 69         {
 70             m_array[i] = e;
 71         }
 72
 73         return ret;
 74     }
 75
 76     //获取位置i处的元素,通过参数e返回
 77     bool get(int i, T &e) const
 78     {
 79         bool ret = (0 <= i) && (i < m_length);
 80
 81         if (ret)
 82         {
 83             e = m_array[i];
 84         }
 85
 86         return ret;
 87     }
 88
 89     //获取位置i处的元素,通过返回值返回
 90     T get(int i) const
 91     {
 92         T ret;
 93
 94         if (get(i, ret))
 95         {
 96             return ret;
 97         }
 98         else
 99         {
100             THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
101         }
102     }
103
104     //获取线性表长度
105     int length() const
106     {
107         return m_length;
108     }
109
110     //清空线性表
111     void clear()
112     {
113         m_length = 0;
114     }
115
116     //数组访问,用于非const对象
117     T &operator[] (int i)
118     {
119        if ((0 <= i) && (i < m_length))
120        {
121            return m_array[i];
122        }
123        else
124        {
125            THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
126        }
127     }
128
129     //数组访问,用于const对象
130     T &operator[] (int i) const
131     {
132         return (const_cast<SeqList<T> &>(*this))[i];
133     }
134
135     //顺序存储空间的最大容量,由子类实现
136     virtual int capacity() const = 0;
137 };
138
139 }
140
141 #endif // SEQLIST_H

3. StaticList类的实现

【设计要点】

- 类模板

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

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

【设计实现】

StaticList实现比较简单,就不多说了,只需要注意一点:在构造函数中将父类的m_array指针指向子类的m_space数组。

 1 #ifndef STATICLIST_H
 2 #define STATICLIST_H
 3
 4 #include "SeqList.h"
 5
 6 namespace DTLib
 7 {
 8
 9 template <typename T, int N>
10 class StaticList : public SeqList<T>
11 {
12 protected:
13     T m_space[N];
14 public:
15     StaticList()
16     {
17         this->m_array = m_space;
18         this->m_length = 0;
19     }
20
21     int capacity() const
22     {
23         return N;
24     }
25 };
26
27 }
28
29 #endif // STATICLIST_H

【StaticList类简单测试】

下面写一点代码对StaticList类进行简单的功能测试。

 1 #include "StaticList.h"
 2 #include <iostream>
 3
 4 using namespace DTLib;
 5 using namespace std;
 6
 7 int main()
 8 {
 9     StaticList<int, 5> list;
10
11     for (int i = 0; i < (list.capacity() - 1); i++)
12     {
13         list.insert(list.length(), i);
14     }
15
16     list.insertTail(4);
17
18     for (int i = 0; i < list.length(); i++)
19     {
20         cout << list.get(i) << endl;
21     }
22
23     cout << endl;
24
25     list[4] = 16;
26     cout << list[4] << endl;
27
28     int v = 0;
29     list.set(3, 9);
30     list.get(3, v);
31     cout << v << endl;
32
33     cout << endl;
34
35     try
36     {
37         list[5] = 5;
38     }
39     catch(const Exception &e)
40     {
41         cout << e.location() << endl;
42         cout << e.message() << endl;
43     }
44
45     return 0;
46 }

运行结果如下,初步测试功能正常。

4. DynamicList类的实现

【设计要点】

- 类模板

- 申请连续堆空间作为顺序存储空间

- 支持动态设置(resize)顺序存储空间的大小

 1 class DynamicList : public SeqList<T>
 2 {
 3 protected:
 4     int m_capacity;
 5 public:
 6     DynamicList(int capacity); //动态申请存储空间
 7     int capacity() const;      //获取存储空间大小
 8     void resize(int capacity); //重设存储空间大小
 9     ~DynamicList();            //释放存储空间
10 };

【设计实现】

首先实现构造和析构函数,分别用于申请和释放顺序存储空间。

 1 DynamicList(int capacity)
 2 {
 3     this->m_array = new T[capacity];
 4
 5     if (this->m_array != NULL)
 6     {
 7         this->m_length = 0;
 8         this->m_capacity = capacity;
 9     }
10     else
11     {
12         THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicList object ...");
13     }
14 }
15
16 ~DynamicList()
17 {
18     delete[] this->m_array;
19 }

然后实现父类的纯虚函数capaticy()。

1 int capacity() const
2 {
3     return m_capacity;
4 }

最后实现DynamicList的关键函数resize(),该函数用于在当前存储空间过大或过小时重设存储空间。

 1 void resize(int capacity)
 2 {
 3     if (capacity != m_capacity)
 4     {
 5         T *array = new T[capacity]; //申请新的存储空间
 6
 7         if (array != NULL)
 8         {
 9             /*拷贝数据到新的存储空间*/
10             int length = (this->m_length < capacity) ? this->m_length : capacity;
11
12             for (int i = 0; i < length; i++)
13             {
14                 /*
15                  * 若T是类类型,且[]操作符被重载,那么array[i]是有可能产生异常的;
16                  * 但DTLib无法顾全这一点,只能由使用者保证。
17                 */
18                 array[i] = this->m_array[i];
19             }
20
21             /*保证异常安全*/
22             T *temp = this->m_array;
23
24             this->m_array = array;
25             this->m_length = length;
26             this->m_capacity = capacity;
27
28             delete[] temp;
29         }
30         else
31         {
32             THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicList object ...");
33         }
34     }
35 }

其中,resize()第22~28行的设计可以保证函数异常安全。所谓函数异常安全,指的是在调用函数时如果有异常被抛出,那么:

- 对象内的任何成员仍然能保持有效状态

- 没有数据的破坏以及资源的泄露

如果将第22~28行代码改为如下方式(非异常安全),那么当T为类类型的时候delete[] this->m_array会触发析构函数调用,析构函数中有可能抛出异常,

使后面三行代码无法执行,最终造成DynamicList对象的数据被破坏。

1 delete[] this->m_array;
2
3 this->m_array = array;
4 this->m_length = length;
5 this->m_capacity = capacity;

【DynamicList完整实现】

 1 #ifndef DYNAMICLIST_H
 2 #define DYNAMICLIST_H
 3
 4 #include "SeqList.h"
 5 #include "Exception.h"
 6
 7 namespace DTLib
 8 {
 9
10 template <typename T>
11 class DynamicList : public SeqList<T>
12 {
13 protected:
14     int m_capacity;
15 public:
16     DynamicList(int capacity)
17     {
18         this->m_array = new T[capacity];
19
20         if (this->m_array != NULL)
21         {
22             this->m_length = 0;
23             this->m_capacity = capacity;
24         }
25         else
26         {
27             THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicList object ...");
28         }
29     }
30
31     int capacity() const
32     {
33         return m_capacity;
34     }
35
36     void resize(int capacity)
37     {
38         if (capacity != m_capacity)
39         {
40             T *array = new T[capacity]; //申请新的存储空间
41
42             if (array != NULL)
43             {
44                 /*拷贝数据到新的存储空间*/
45                 int length = (this->m_length < capacity) ? this->m_length : capacity;
46
47                 for (int i = 0; i < length; i++)
48                 {
49                     /*
50                      * 若T是类类型,且[]操作符被重载,那么array[i]是有可能产生异常的;
51                      * 但DTLib无法顾全这一点,只能由使用者保证。
52                     */
53                     array[i] = this->m_array[i];
54                 }
55
56                 /*保证异常安全*/
57                 T *temp = this->m_array;
58
59                 this->m_array = array;
60                 this->m_length = length;
61                 this->m_capacity = capacity;
62
63                 delete[] temp;
64             }
65             else
66             {
67                 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicList object ...");
68             }
69         }
70     }
71
72     ~DynamicList()
73     {
74         delete[] this->m_array;
75     }
76 };
77
78 }
79
80 #endif // DYNAMICLIST_H

【DynamicList功能测试】

测试代码:

 1 #include "DynamicList.h"
 2 #include <iostream>
 3
 4 using namespace DTLib;
 5 using namespace std;
 6
 7 int main()
 8 {
 9     DynamicList<int> list(5);
10
11     for (int i = 0; i < list.capacity(); i++)
12     {
13         list.insert(0, i);
14     }
15
16     for (int i = 0; i < list.length(); i++)
17     {
18         cout << list[i] << endl;
19     }
20
21     cout << endl;
22
23     try
24     {
25         list[5] = 5;
26     }
27     catch(const Exception &e)
28     {
29         cout << e.location() << endl;
30         cout << e.message() << endl;
31
32         list.resize(10);
33         list.insert(5, 50);
34
35         for (int i = 0; i < list.length(); i++)
36         {
37             cout << list[i] << endl;
38         }
39
40         cout << endl;
41     }
42
43     list.resize(3);
44
45     for (int i = 0; i < list.length(); i++)
46     {
47         cout << list[i] << endl;
48     }
49
50     return 0;
51 }

测试结果:

5. 小结

  1. StaticList通过模板参数定义顺序存储空间
  2. DynamicList通过动态内存申请定义顺序存储空间
  3. DynamicList支持动态重置顺序存储空间的大小
  4. DynamicList中的resize()函数需要保证异常安全

注:本文整理于狄泰《数据结构开发实战教程》课程内容

原文地址:https://www.cnblogs.com/songhe364826110/p/9195820.html

时间: 2024-10-23 13:30:13

第6课 - 线性表的顺序存储结构及实现的相关文章

第7课 - 线性表的顺序存储结构

第7课 - 线性表的顺序存储结构 1. 顺序存储定义 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素. 根据上面的定义,在 C 语言中可以用一维数组来实现顺序存储结构: #define MAXSIZE xx /* xx为存储空间初始分配量 */ typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */ typedef struct _tag_List { ElemType node[MAXSIZE]; /* 数组

线性表的顺序存储结构

1.顺序表: 线性表占用的存储空间=n*sizeof(ElemType) n为线性表的长度,ElemType为线性表的元素类型,sizeof(ElemType)为每个元素所占存储空间大小(即字节数) 线性表的顺序存储结构利用数组实现,数组的基本类型是线性表中元素类型,数组大小大于等于线性表的长度. 基本运算实现: (1)建立顺序表 void CreateList(SqList *&L,ElemType a[],int n)    //由a中的n个元素建立顺序表 {  int i;    L=(S

线性表之顺序存储结构实现(上)

一,线性表的概念以及数学定义 1.线性表的概念 零个或多个数据元素的有限序列.首先说明这是一个序列,也就是说数据元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且仅有一个前驱和后继. 2.数学定义 若将线性表记为(a1...ai-1,ai,ai+1....an),则线性表中,ai-1领先于ai,ai领先于ai+1,则称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素,当i=1,2....n-1的时候,ai有且仅有一个直接后继元素,当i=2,3

线性表之顺序存储结构(C语言动态数组实现)

线性表的定义:N个数据元素的有限序列 线性表从存储结构上分为:顺序存储结构(数组)和 链式存储结构(链表) 顺序存储结构:是用一段连续的内存空间存储表中的数据 L=(a1,a2,a3....an) 链式存储结构:是用一段一段连续的内存空间存储表中每一行的数据,段与段之间通过一个引用(指针)相互连接来,形成一个链式的存储结构 看到顺序存储结构的图示,我们可能会马上联想到C语言的数组.是的,数组就是一种典型的顺序存储数据结构.下面我通过一个实例,来实现对顺序存储结构中的数据增.删.改.查的操作. 首

线性表的顺序存储结构之顺序表类的实现_Java

在上一篇博文--线性表接口的实现_Java中,我们实现了线性表的接口,今天让我们来实现线性表的顺序存储结构--顺序表类. 首先让我们来看下顺序表的定义: 线性表的顺序存储是用一组连续的内存单元依次存放线性表的数据元素,元素在内存的物理存储次序与它们在线性表中的逻辑次序相同,即元素ai与其直接前驱ai-1及直接后继ai+1的存储位置相邻.顺序存储的线性表也成为顺序表(sequential list). 顺序表类SeqList提供线性表基于顺序存储结构的一种实现,它有两个私有成员变量table和n,

02.线性表(一)顺序存储结构

顺序存储结构 一.线性表基本概念 1.线性表定义 线性表(list)是指零个或多个数据元素的有限序列,所有数据元素为相同数据类型且一个数据元素可以由多个数据项组成.若将线性表记为(a1,..ai-1,ai,ai+1...,an),线性表元素的个数n(n>0,n=0时为空表)定义为线性表的长度,其中ai-1是ai 的直接前驱元素,ai+1是ai的直接后继元素. 2.线性表的抽象数据类型 ADT 线性表(List) Data 线性表的数据对象集合为{a1,a2,....an},每个元素的类型均匀Da

数据结构之线性表之顺序存储结构(3)

1 前言 经过前两张的理论基础,我相信大家都能简单的明白了数据结构和算法一些常用的概念了,从今天开始我们开始学习数据结构中线性表,一起叩响数据结构的大门. 2 详述 线性表(List):零个或者多个数据元素的有限序列. 如果用数学语言来进行定义: (声明:由于下标不是很好弄出来,所以只能表示下面这种方式了,请见谅!) 若线性表记作(a1,...ai-1,ai,ai+1,...,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素.当i

2.2_线性表的顺序存储结构

[线性表的顺序存储从结构] 指的是用一段连续的存储单元一次储存线性表的数据元素. [线性表的顺序存储的结构代码] #define MAXSIZE 20 /*存储空间初始分配量*/ typedef int ElemType; /*ElemType类型依实际情况而定*/ typedef struct { Element data[MAXSIZE]; /*数组存储数据元素,最大值为MAXSIZE*/ int length; /*线性表当前长度为length*/ }SqlList; [线性表的顺序存储结

简要比较线性表的顺序存储结构和链式存储结构

我们分别从存储分配方式.时间性能.空间性能三方面来做对比. 存储分配方式 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素. 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素. 时间性能 <1>查找 顺序存储结构O(1) 单链表O(n) <2>插入和删除 顺序存储结构需要平均移动表长一半的元素,时间为O(n) 单链表在计算出某位置的指针后,插入和删除时间仅为O(1) 空间性能 顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易发生溢出. 单链