第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     int find(const T &e) const;        //查找e在线性表中第一次出现的位置;若e不存在,则返回-1
10     bool get(int i, T &e) const;       //获取位置i处的元素,通过参数e返回
11     T get(int i) const;                //获取位置i处的元素,通过返回值返回
12     int length() const;                //获取线性表长度
13     void clear();                      //清空线性表
14 
15     //数组访问形式,提供const和非const版本
16     T &operator[] (int i);
17     T &operator[] (int i) const;
18 
19     //顺序存储空间的大小,由子类实现
20     virtual int capacity() const = 0;
21 };

【插入功能实现】

  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
 1 //查找e在线性表中第一次出现的位置;若e不存在,则返回-1
 2 int find(const T &e) const
 3 {
 4     int ret = -1;
 5 
 6     for (int i = 0; i < m_length; i++)
 7     {
 8         if (m_array[i] == e)
 9         {
10             ret = i;
11             break;
12         }
13     }
14 
15     return ret;
16 }

【获取功能实现】

  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     //查找e在线性表中第一次出现的位置;若e不存在,则返回-1
 77     int find(const T &e) const
 78     {
 79         int ret = -1;
 80 
 81         for (int i = 0; i < m_length; i++)
 82         {
 83             if (m_array[i] == e)
 84             {
 85                 ret = i;
 86                 break;
 87             }
 88         }
 89 
 90         return ret;
 91     }
 92 
 93     //获取位置i处的元素,通过参数e返回
 94     bool get(int i, T &e) const
 95     {
 96         bool ret = (0 <= i) && (i < m_length);
 97 
 98         if (ret)
 99         {
100             e = m_array[i];
101         }
102 
103         return ret;
104     }
105 
106     //获取位置i处的元素,通过返回值返回
107     T get(int i) const
108     {
109         T ret;
110 
111         if (get(i, ret))
112         {
113             return ret;
114         }
115         else
116         {
117             THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
118         }
119 
120         return ret;
121     }
122 
123     //获取线性表长度
124     int length() const
125     {
126         return m_length;
127     }
128 
129     //清空线性表
130     void clear()
131     {
132         m_length = 0;
133     }
134 
135     //数组访问,用于非const对象
136     T &operator[] (int i)
137     {
138        if ((0 <= i) && (i < m_length))
139        {
140            return m_array[i];
141        }
142        else
143        {
144            THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
145        }
146     }
147 
148     //数组访问,用于const对象
149     T &operator[] (int i) const
150     {
151         return (const_cast<SeqList<T> &>(*this))[i];
152     }
153 
154     //顺序存储空间的最大容量,由子类实现
155     virtual int capacity() const = 0;
156 };
157 
158 }
159 
160 #endif // SEQLIST_H
View Code

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
View Code

【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()函数需要保证异常安全

 

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

posted @ 2018-06-24 17:20  原野追逐  阅读(1613)  评论(0编辑  收藏  举报