线性表
一、逻辑结构及基本运算
1.线性表的概念
线性表是N个具有相同特征的节点 A 0 , A 1 , ⋯ , A N − 1 A_0,A_1, \cdots ,A_{N-1} A0,A1,⋯,AN−1构成的集合。在这个集合中,除了 A 0 A_0 A0和 A N − 1 A_{N-1} AN−1外,每个元素都有直接唯一的前驱和后继。
2. 表的基本操作:
- 构造类:构造出表结构
- 属性类:只观察不修改
- 求线性表的长度
- 搜索某个元素是否在线性表中出现
- 访问线性表的第i个元素
- 数据操纵类:对数据进行改变
- 在第i个位置插入一个元素
- 删除第i个位置的元素
- 清除一个线性表
- 遍历类:对每个元素进行访问且只访问一次
3.线性表的抽象类
由上述分析可以得到线性表的类模板定义即父类,如下。
template<class elemType>
class list {
public:
//对于属性类的函数,因其并不改变原数组or链表的值,在后面加const
//可以达到保护隐含this指针以及供常对象使用的目的
virtual int length() const = 0;
virtual int search(const elemType& x) const = 0;
//const &结构:在数据类型未知or比较复杂时,可以提高运行效率,同时节省空间
virtual elemType visit(int i) const = 0;
//数据操纵类函数
virtual void insert(int i, const elemType& x) = 0;
virtual void remove(int i) = 0;
virtual void clear() = 0;
//遍历操作
virtual void traverse()const = 0;
//析构函数,防止内存泄露
virtual ~list() {};
};
二、物理结构
物理结构即指数据结构的存储实现,包括以下两个部分:
- 数据元素的存储
- 数据元素之间的关系的存储
存储方式有两种:
- 顺序储存:存放于连续空间,主要用数组实现
- 链接存储:用指针显式指出元素之间的关系
线性表的顺序存储结构
采用动态数组实现,保存一个动态数组,需要三个变量:
- 指向数组的指针
- 数组规模(容量)
- 数组中实际元素的个数(表长)
存储映像图如下(重要!!):
顺序表类的定义(子类):
template<class elemType>
class seqList :public list<elemType>
{
private:
elemType* data;//存储数组元素
int currentLength;
int maxSize;
void doubleSpace();//扩大数组空间
public:
seqList(int initSize = 10);//构造函数
~seqList() { delete[]data; }//析构函数
int length()const;
int search(const elemType& x)const;
elemType visit(int i)const;
void insert(int i, const elemType& x);
void remove(int i);
void clear();
void traverse()const;
};
三、 基本操作实现
- 构造类:
- 构造出表结构
//构造函数的实现
//注意在这里参数不能写成initSize=10(只需在声明时说明)
template<class elemType>
seqList<elemType>::seqList(int initSize) {
data = new elemType[initSize];
if (!data)throw illegalSize();//异常处理
maxSize = initSize;
currentLength = 0;
}
- 当空间不足时,扩大容量
template<class elemType>
void seqList<elemType>::doubleSpace() {
elemType* tmp = data;
maxSize *= 2;
data = new elemType[maxSize];
for (int i = 0; i < maxSize; ++i) {
data[i] = tmp[i];
}
delete[]tmp;
}
- 属性类:只观察不修改
- 求线性表的长度
//数组长度
//注意声明时+const,定义时也要加
template<class elemType>
int seqList<elemType>::length()const {
return currentLength;
}
- 搜索某个元素是否在线性表中出现
//查找某个元素是否在表内并返回编号
template<class elemType>
int seqList<elemType>::search(const elemType& x)const {
int i;
for (i = 0; i < currentLength; ++i) {
if (data[i] == x) break;
}
if (i == currentLength) return -1;//已经找遍了整个序列仍没有找到
else return i;
}
- 访问线性表的第i个元素
//访问线性表中下标为i的元素
template<class elemType>
elemType seqList<elemType>::visit(int i)const {
return data[i];
}
- 数据操纵类:对数据进行改变
- 在第i个位置插入一个元素
//插入一个元素,使其下标为i
template<class elemType>
void seqList<elemType>::insert(int i, const elemType& x) {
if (currentLength == maxSize) doubleSpace();
for (int j = currentLength ; j > i; --j) {
data[j] = data[j - 1];
}
data[i] = x;
++currentLength;
}
- 删除第i个位置的元素
//移除下标为i的元素
template<class elemType>
void seqList<elemType>::remove(int i) {
for (int k = i; k < currentLength - 1; ++k) {
data[k] = data[k + 1];
}
--currentLength;
}
- 清除一个线性表
//清除列表中所有元素
template<class elemType>
void seqList<elemType>::clear() {
//delete[]data;
currentLength = 0;
}
- 遍历类:对每个元素进行访问且只访问一次
//遍历所有元素
template<class elemType>
void seqList<elemType>::traverse() const {
for (int i = 0; i < currentLength; ++i) {
cout << data[i] << " ";
}
}
四、顺序表实现的总结
- 顺序表在插入删除时需要移动大量的数据,性能并不理想
- 事先需要估计空间,如果数据很大,则需要反复doubleSpace
- 逻辑次序和物理次序的一致性使其定位访问性能很好
- 较为适合静态的、经常做定位访问的线性表
五、seqList代码汇总
注: 有些编译器在处理类模板时,并不支持将函数的声明和定义分别写在头文件和.cpp文件中,会报错LNK2019,如下图:
解决方法可以将函数的声明和定义都写在头文件中,用main函数调用。
seqList列表实现代码清单如下:
- 头文件 seqList.h
//文件名:seqList.h
#include<iostream>
using namespace std;
//父类:指定子类要实现什么功能
template<class elemType>
class list {
public:
//对于属性类的函数,因其并不改变原数组or链表的值,在后面加const
//可以达到保护隐含this指针以及供常对象使用的目的
virtual int length() const = 0;
virtual int search(const elemType& x) const = 0;
//const &结构:在数据类型未知or比较复杂时,可以提高运行效率,同时节省空间
virtual elemType visit(int i) const = 0;
//数据操纵类函数
virtual void insert(int i, const elemType& x) = 0;
virtual void remove(int i) = 0;
virtual void clear() = 0;
//遍历操作
virtual void traverse()const = 0;
//析构函数,防止内存泄露
virtual ~list() {};
};
//子类函数
template<class elemType>
class seqList :public list<elemType>
{
private:
elemType* data;//存储数组元素
int currentLength;
int maxSize;
void doubleSpace();//扩大数组空间
public:
seqList(int initSize = 10);//构造函数
~seqList() { delete[]data; }//析构函数
int length()const;
int search(const elemType& x)const;
elemType visit(int i)const;
void insert(int i, const elemType& x);
void remove(int i);
void clear();
void traverse()const;
};
class illegalSize {};//异常处理
//扩大数组空间的实现
//申请一块更大的空间,将原来的数据复制进去,并释放原数组空间
template<class elemType>
void seqList<elemType>::doubleSpace() {
elemType* tmp = data;
maxSize *= 2;
data = new elemType[maxSize];
for (int i = 0; i < maxSize; ++i) {
data[i] = tmp[i];
}
delete[]tmp;
}
//构造函数的实现
//注意在这里参数不能写成initSize=10(只需在声明时说明)
template<class elemType>
seqList<elemType>::seqList(int initSize) {
data = new elemType[initSize];
if (!data)throw illegalSize();//异常处理
maxSize = initSize;
currentLength = 0;
}
//数组长度
//注意声明时+const,定义时也要加
template<class elemType>
int seqList<elemType>::length()const {
return currentLength;
}
//查找某个元素是否在表内并返回编号
template<class elemType>
int seqList<elemType>::search(const elemType& x)const {
int i;
for (i = 0; i < currentLength; ++i) {
if (data[i] == x) break;
}
if (i == currentLength) return -1;//已经找遍了整个序列仍没有找到
else return i;
}
//访问线性表中下标为i的元素
template<class elemType>
elemType seqList<elemType>::visit(int i)const {
return data[i];
}
//插入一个元素,使其下标为i
template<class elemType>
void seqList<elemType>::insert(int i, const elemType& x) {
if (currentLength == maxSize) doubleSpace();
for (int j = currentLength ; j > i; --j) {
data[j] = data[j - 1];
}
data[i] = x;
++currentLength;
}
//移除下标为i的元素
template<class elemType>
void seqList<elemType>::remove(int i) {
for (int k = i; k < currentLength - 1; ++k) {
data[k] = data[k + 1];
}
--currentLength;
}
//清除列表中所有元素
template<class elemType>
void seqList<elemType>::clear() {
//delete[]data;
currentLength = 0;
}
//遍历所有元素
template<class elemType>
void seqList<elemType>::traverse() const {
for (int i = 0; i < currentLength; ++i) {
cout << data[i] << " ";
}
}
- 调用文件(测试集) main函数
#include<iostream>
#include"seqList.h"
using namespace std;
int main() {
//构造列表
seqList<int> array(15);
//插入数据,列表为0-14的顺序排列
for (int i = 0; i < 15; ++i)
array.insert(i, i);
//遍历数据
cout << "此时线性表中元素为:" << endl;
array.traverse();
cout << endl;
cout << "列表长度为:" << array.length() << endl;
//访问数据
cout << "下标为1的数据为" << array.visit(1) << endl;
//查找数据
cout << "2在列表中的下标为" << array.search(2) << endl;
cout << "15在列表中的下标为" << array.search(15) << endl;
//移除数据
array.remove(3);
cout << "移除data[3]后线性表中元素为:" << endl;
array.traverse();
cout << endl;
cout << "此时列表长度为:" << array.length() << endl;
array.clear();
}
运行结果如下:
(实现了所有功能)
六、 顺序表的应用
例:求集合的交集
#include<iostream>
#include"seqlist.h"
using namespace std;
int main() {
seqList<int> list1(10), list2(10), list3(10);
int i = 0, j, x;
int len1;
//输入第一个集合中的元素
cout << "请输入第一个集合中元素:(以0作为结束标志)";
cin >> x;
while (x != 0)
{
list1.insert(i, x);
i++;
cin >> x;
}
//输入第二个集合的元素
i = 0;
cout << "请输入第二个集合中元素:(以0作为结束标志)";
cin >> x;
while (x != 0)
{
list2.insert(i, x);
i++;
cin >> x;
}
//查找list1中的元素是否在list2中出现
//如果出现则将其存入list3中
len1 = list1.length();
int num = 0;
for (j = 0; j < len1; ++j) {
int y = list1.visit(j);
if (list2.search(y) != -1) {
list3.insert(num, y);
num++;
}
}
//输出list3
list3.traverse();
}
运行结果:
下期预告:线性表的链式存储结构