目录
1、线性表的本质和操作
线性表( List )的表现形式
-零个或多个数据元素组成的集合
-数据元素在位置上是有序排列的
-数据元素的个数是有限的,数据元素的类型必须相同
线性表( List )的抽象定义
线性表是具有相同类型的n(>=0)个数据元素的有限序列 ( a₀,a₁,...,an-1 )
ai 是表项(数据元素),n 是表长度
线性表(List)的性质
-a₀为线性表的第一个元素,只有一个后继
-an-1为线性表的最后一个元素,只有一个前驱
-除a₀和an-1外的其它元素 ai ,既有前驱,又有后继
-直接支持逐项访问和顺序存取
线性表的—些常用操作
-将元素插入线性表
-将元素从线性表中删除
-获取目标位置处元素的值
-设置目标位置处元素的值
-查找指定元素的位置
-获取线性表的长度
-清空线性表
2、编程实验
线性表抽象类的创建 List.h
#ifndef LIST_H
#define LIST_H
#include "Object.h"
namespace DTLib
{
template <typename T>
class List : public Object
{
List(const List& e);
List& operator= (const List& e);
// 对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。
// 如:存放的数据为堆空间地址时,赋值操作后,最后可能两次delete同一片空间
// 如:s1指向堆空间一片空间,被拷贝构造后s2也指向这片空间
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 find(const T& e) const = 0;
virtual int length() const = 0;
virtual void clear() = 0;
};
}
#endif // LIST_H
main.cpp
#include <iostream>
#include "List.h"
using namespace std;
using namespace DTLib;
int main()
{
List<int>* l = NULL;
return 0;
}
3、线性表的顺序存储结构和抽象实现
顺序存储的定义
- 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表中的数据元素。
这片空间可能是栈空间也可能是堆空间
设计思路
- 可以用一维数组来实现顺序存储结构SeqList
- SeqList设计要点 :抽象类模板,存储空间的位置和大小由子类完成
4、编程实验
顺序存储线性表 SeqList.h
#ifndef SEQLIST_H
#define SEQLIST_H
#include "List.h"
#include "Exception.h"
namespace DTLib
{
template <typename T>
class SeqList : public List<T>
{
protected:
T* m_array; // 顺序存储空间的位置和大小由子类决定
int m_length; // 线性表的长度
public:
/* 这里的i都是下标,[m_length-1]为最后一个元素 */
bool insert(const T& e)
{
return insert(m_length, e);
}
bool insert(int i, const T& e)
{
bool ret = (0 <= i) && (i <= m_length);
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; 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 find(const T& e) const
{
int ret = -1;
for(int p = 0; p < m_length; p++)
{
if(m_array[p] == e)
{
ret = p;
break;
}
}
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(IndexOutOfBoundsExpception, "Parameter i is invalid...");
}
}
T operator[] (int i) const
{
return const_cast<T&>(*this)[i];
}
// 顺序存储空间的大小由子类完成
virtual int capacity() const = 0;
};
}
#endif // SEQLIST_H
main.cpp
#include <iostream>
#include "SeqList.h"
using namespace std;
using namespace DTLib;
int main()
{
SeqList<int>* l = NULL;
return 0;
}
5、顺序存储线性表的效率分析
template <typename T>
class SeqList : public List<T>
{
public:
bool insert(const T& e)
bool insert(int i, const T& e);// O(n)
bool remove(int i); // O(n)
bool set(int i, const T& e); // O(1)
bool get(int i, T& e) const; // O(1)
int find(const T& e) const // O(n)
int length() const; // O(1)
void clear(); // O(1)
T& operator[] (int i); // O(1)
T operator[] (int i) const; // O(1)
virtual int capacity() const = 0;
};
顺序存储线性表的插入和删除操作存在重大效率隐患
问: 长度相同的两个SeqList , 插入和删除 操作的平均耗时是否相同?
答:取决于线性表存储数据元素类型,如插入字符串和整形差距很大
6、StaticList 和 DynamicList
1、StaticList
目标:完成SeqList的子类StaticList,DynamicList类的具体实现
StaticList设计要点 :使用原生数组作为顺序存储空间 使用模板参数决定数组大小
2、编程实验
StaticList的实现 StaticList. h
#ifndef STATICLIST_H
#define STATICLIST_H
#include "SeqList.h"
namespace DTLib
{
template <typename T, int N>
class StaticList : public SeqList<T>
{
T m_space[N];
public:
StaticList()
{
this->SeqList<T>::m_array = m_space; // this->m_array = m_space;
this->m_length = 0;
}
int capacity() const
{
return N;
}
};
}
#endif // STATICLIST_H
main.cpp
#include <iostream>
#include "StaticList.h"
using namespace std;
using namespace DTLib;
int main()
{
StaticList<int, 5> l;
for(int i=0; i < l.capacity(); i++)
{
l.insert(0, i);
}
for(int i = 0; i < l.length(); i++)
{
cout << l[i] << endl;
}
l[0] *= l[0];
for(int i = 0; i < l.length(); i++)
{
cout << l[i] << endl;
}
try
{
l[5];
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
}
return 0;
}
3、DynamicList设计要点
申请连续堆空间作为顺序存储空间 ,动态设置顺序存储空间的大小 ,保证重置顺序存储空间时的异常安全性
函数异常安全的概念 :不泄漏任何资源,不允许破坏数据
函数异常安全的基本保证 :如果异常被抛出
★ 对象内的任何成员仍然能保持有效状态
★ 没有数据的破坏及资源泄漏
4、编程实验
DynamicList的实现 DynamicList.h
#ifndef DYNAMICLIST_H
#define DYNAMICLIST_H
#include "SeqList.h"
#include "Exception.h"
namespace DTLib
{
template <typename T>
class DynamicList : public SeqList<T>
{
protected:
int 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(m_capacity != 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; // 标记原来的堆空间
/*若在这里直接delete调用析构函数,可能抛出异常,函数直接返回,顺序表不可用了*/
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"
using namespace std;
using namespace DTLib;
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;
}
l[0] *= l[0];
for(int i = 0; i < l.length(); i++)
{
cout << l[i] <<endl;
}
try
{
l[5];
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
l.resize(10);
l.insert(5, 50);
}
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;
}
5、易错点
下面的代码正确吗?为什么?
StaticList<int, 5> list;
for(int i = 0; i < list.capacity(); i++)
{
list[i] = i * i; // error 将线性表当数组使用
}
线性表必须先插入元素,才能使用操作符[]访问元素
7、小结
StaticList通过模板参数定义顺序存储空间
DynamicList通过动态内存申请定义顺序存储空间
DynamicList支持动态重置顺序存储空间的大小
DynamicList中的resize()函数实现需要保证异常安全
顺序存储线性表可能被当成数组误用
工程开发中可以考虑使用数组类代替原生数组使用