前言
本笔记是自己根据网上课程学习一起学知识「一起学知识」的知识服务平台,由小鹅通一键生成https://appd872nnyh9503.pc.xiaoe-tech.com/detail/p_60f24709e4b08f7ad23db8ef/6所做的笔记,基于C++类模板实现的数据结构,如在笔记中有误,欢迎批评指出。
概述
线性表:线性结构,保存的数据象线一样按顺序排列,数据之间一对一的关系。
线性表是具有相同数据类型的n(n>=0)个数据的有限序列。n:线性表长度。
- 一般线性表表示为:(a1,a2,......ai,a i+1,....,an);
- 每个元素都有直接前趋和直接后继(首元素只有直接后继,尾元素只有直接前趋)
数组、链表、栈、队列等都属于线性表的一种(或者理解为 数组,链表,栈,队列都可以用来表达线性表)。
非线性表:就是一对多的关系,如树、图、堆等。
顺序表
线性表的顺序存储:指的是用一段连续的内存空间一次存储线性表中的数据。
线性表的顺序存储一般就会采用一维数组来实现。STL,vecotr。
采用一维数组实现的线性表也被称为顺序表。
随机访问:通过数组首地址和所给元素下标可以最快的找到任意数组元素。而且这种通过下标随机访问数组元素的时间复杂度仅仅O(1)。
- a[i]的地址 = 数组元素的首地址+下标*sizeof(整型);
- 插入或者删除数据时效率会很低。
线性表一般可以用静态数组或者动态数组两种方式实现。
typedef struct
{
int m_data[10]; //静态数组来保存顺序表中的元素,一共10个位置(最多存入10个元素)
int m_length; //顺序表中当前实际长度(当前顺序表中已经存入了多少个元素)
}SeqList;
typedef struct
{
int* m_data; //顺序表中的元素保存再m_data所指向的动态数组内存中。
int m_length; //顺序表中当前实际长度
int m_maxsize; //动态数组最大容量,因为动态数组可以扩容,因此要记录该值。
}SeqList;
C++模板类实现顺序表及常用接口
#define InitSize 10 //动态数组的初始尺寸
#define IncSize 5 //当动态数组存满数据后每次扩容所能多保存的数据元素数量
template <typename T> //T代表数组中元素的类型
class SeqList
{
public:
SeqList(int length = InitSize); //构造函数,参数可以有默认值
~SeqList(); //析构函数
public:
bool ListInsert(int i, const T& e); //在第i个位置插入指定元素
bool ListDelete(int i); //删除第i个位置的元素
bool GetElem(int i, T& e); //获得第i个位置的元素值
int LocateElem(const T& e); //按元素值查找其在顺序表中第一次出现的位置
void DispList(); //输出顺序表中的所有元素
int ListLength(); //获取顺序表长度
void ReverseList(); //翻转顺序表
private:
void IncreaseSize(); //当顺序存满数据后可以调用此函数为顺序表扩容
private:
T* m_data; //存放顺序表中的元素
int m_length; //顺序表中当前实际长度(当前有几个元素)
int m_maxsize; //动态数组最大容量
};
顺序表的初始化和释放操作
//通过构造函数对顺序表进行初始化
template <typename T>
SeqList<T>::SeqList(int length)
{
m_data = new T[length]; //为一维数组动态分配内存
m_length = 0; //顺序表当前实际长度为0,表示还未向其中存入任何数据元素
m_maxsize = length; //顺序表最多可以存储m_maxsize个数据元素
}
//通过析构函数对顺序表进行资源释放
template <typename T>
SeqList<T>::~SeqList()
{
delete[] m_data;
m_data = nullptr;
m_length = 0; //非必须
}
顺序表的常用接口:插入、删除、翻转.......
//在第i个位置(位置编号从1开始)插入指定元素e,时间复杂度O(n),时间开销主要是缘于元素的移动。
template <typename T>
bool SeqList<T>::ListInsert(int i, const T& e)
{
//如果顺序表已经存满数据,则不允许再插入数据了
if (m_length >= m_maxsize)
{
cout << "顺序表已满,不能再进行插入操作了!" << endl;
return false;
}
//判断插入位置i是否合法,i的和合法值应该是从1到m_length+1之间
if (i < 1 || i >(m_length + 1))
{
cout << "元素" << e << "插入位置" << i << "不合法,合法的位置是1到" << m_length+1 << "之间!" << endl;
return false;
}
//从最后有一个元素 开始向前遍历到要插入新元素的第i个位置,分别将这些位置中原有的元素向后移动一个位置
//后移次数平均值=(1+2+3+...+n)/(n+1) = (n(n+1)/2)/(n+1),平均时间复杂度O(n)
for (int j = m_length; j >= i; --j) //时间复杂度最好是O(1),最坏是O(n)
{
m_data[j] = m_data[j - 1];
}
m_data[i - 1] = e; //在指定位置i处插入元素e,因为数组下标从0开始,所以这里用i-1表示插入位置所对应的数组下标。
cout << "成功在位置为" << i << "处插入元素" << m_data[i - 1] << "!" << endl;
m_length++; //表长度+1
return true;
}
//删除第i个位置的元素
template <typename T>
bool SeqList<T>::ListDelete(int i)
{
if (m_length < 1)
{
cout << "当前顺序表为空,不能删除任何数据!" << endl;
return false;
}
if(i < 1 || i > m_length)
{
cout << "删除的位置" << i << "不合法,合法的位置是1到" << m_length << "之间!" << endl;
return false;
}
cout << "成功删除位置为" << i << "的元素,该元素的值为" << m_data[i-1] << "!" << endl;
//从数组中第i+1个位置开始向后遍历所有元素,分别将这些位置中原有的元素向前移动一个位置
for (int j = i; j < m_length; ++j)
{
m_data[j - 1] = m_data[j];
}
m_length--; //实际表长-1
return true;
}
//获取第i个位置的元素值
template <typename T>
bool SeqList<T>::GetElem(int i, T& e) //参数e是引用类型参数,确保将该值带回调用者
{
if (m_length < 1)
{
cout << "当前顺序表为空,不能获取任何数据!" << endl;
return false;
}
if (i < 1 || i > m_length)
{
cout << "获取元素的位置" << i << "不合法,合法的位置是1到" << m_length << "之间!" << endl;
return false;
}
e = m_data[i - 1];
cout << "成功获取位置为" << i << "的元素,该元素的值为" << m_data[i - 1] << "!" << endl;
return true;
}
//按元素值查找其在顺序表中第一次出现的位置
template <typename T>
int SeqList<T>::LocateElem(const T& e)
{
for (int i = 0; i < m_length; ++i)
{
if (m_data[i] == e)
{
cout << "值为" << e << "的元素在顺序表中第一次出现的位置为" << i + 1 << "!" << endl;
return i + 1; //返回位置应该用数组下标值+1
}
}
cout << "值为" << e << "的元素在顺序表中没有找到!" << endl;
return -1; //返回-1表示查找失败
}
template <typename T>
void SeqList<T>::DispList()
{
for (int i = 0; i < m_length; ++i)
{
cout << m_data[i] << " "; //每个数据之间以空格分隔
}
cout << endl; //换行
}
//获取顺序表的长度
template <typename T>
int SeqList<T>::ListLength()
{
return m_length;
}
//翻转顺序表,时间复杂度为O(n)
template <typename T>
void SeqList<T>::ReverseList()
{
if (m_length <= 1)
{
//如果顺序表中没有元素或者只有一个元素,那么就不用做任何操作
return;
}
T temp;
for (int i = 0; i < m_length / 2; ++i)
{
temp = m_data[i];
m_data[i] = m_data[m_length - i - 1];
m_data[m_length - i - 1] = temp;
}
}
//当顺序表存满数据后可以调用此函数为顺序表扩容,时间复杂度O(n)
template <typename T>
void SeqList<T>::IncreaseSize()
{
T* p = m_data;
m_data = new T[m_maxsize + IncSize]; //重新为顺序表分配更大的内存空间
for (int i = 0; i < m_length; ++i)
{
m_data[i] = p[i]; //将数据复制到新区域
}
m_maxsize = m_maxsize + IncSize; //顺序表最大长度增加IncSize
delete[] p; //释放原来的内存空间
}
顺序表的特点
- 通过下标访问数据元素的时间复杂度仅为O(1);
- 存储的数据紧凑,无须为维持表中的元素之间的前后关系而增加额外的存储空间。
- 插入和删除操作可能会移动大量元素导致这两个动作效率不高。
- 需要大片连续的内存空间来存储数据。
- 扩容操作所扩展的空间大小不好确定。
STL中基于数组的容器——vector——reserve-capacity。预留空间(m_maxsize)