第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
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 //删除位置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 //设置位置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
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 //获取位置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 }
【获取长度与清空功能实现】
- 获取线性表长度,直接将m_length返回
- 清空线性表,只需将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 }
【数组访问方式实现】
- 检查目标位置合法性
- 对于非const版本,直接将目标位置处元素返回
- 对于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
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. 小结
- StaticList通过模板参数定义顺序存储空间
- DynamicList通过动态内存申请定义顺序存储空间
- DynamicList支持动态重置顺序存储空间的大小
- DynamicList中的resize()函数需要保证异常安全
注:本文整理于狄泰《数据结构开发实战教程》课程内容