顺序表
顺序表是基于数组的顺序存储的一种线性结构,并且每一个表项的逻辑结构和物理存放顺序一致,其中包含了对表项(数据元素)进行的相关操作,例如增删改查等等操作。
那么顺序表有什么用呢?对于一般的数组而言(基本数据类型),我们可以利用下标进行元素的增加和修改,但却没有办法表示实际元素个数或者长度(如果你初始化数组的话当我没说),而对于一些自定义数据结构class、struct等数据类型,进行相关操作就显得不是很方便。
顺序表的关键包括数组指针、当前标项位置、最大可容纳表项个数。
静态存储和动态存储的区别:其实顺序表开始时给数组分配的大小是一样的,只不过静态存储不能够当存满的时候实现自动扩充而导致存储失败,动态存储多了一个函数功能去实现溢出处理。
顺序表的类定义
template <class T>
class SeqList {
protected:
T* data{};//存放数组
int maxSize{};//最大可容纳表项的个数
int last{};//当前已存表项的最后位置
void reSize(int newSize);//改变data数组空间大小
public:
SeqList(int sz = defaulatSize);//构造函数
SeqList(SeqList<T>& L);//复制构造函数
~SeqList() { delete[]data; }//析构函数
bool copySeqList(SeqList<T>& L);
int Size()const { return maxSize; }//计算表最大可容纳表项的个数
int Length()const { return last + 1; }//计算最新表内表项的长度
int Search(T& x)const;//搜索X在表中的位置,函数返回表项的序号
int Locate(int i)const;//定位第I个表项,函数返回表项序号
bool getData(int i, T& x)const//获取第I个表项的值
{
if (i > 0 && i <= last + 1)
{
x = data[i - 1];
return true;
}
else
return false;
}
void setData(int i, T& x)//用X修改第I个表项的值
{
if (i > 0 && i <= last + 1)
data[i - 1] = x;
}
bool Insert(int i, T& x);
bool Remove(int i, T& x);
bool IsEmpty()
{
return (last == -1) ? true : false;
}
bool IsFull()
{
return (last == maxSize - 1) ? true : false;
}
void input();
void output();
bool setOne(T& x);
};
- 溢出处理功能实现。
void SeqList<T>::reSize(int newSize)//默认是对表的扩展大小为newSize
{
if (newSize <= 0)
{
cerr << "数组大小不能为负值" << endl; return;
}
T* newarray = new T[newSize+maxSize];
if (newarray == NULL)
{
cerr << "存储分配错误" << endl; exit(1);
}
int n = last + 1;
T* srcptr = data;//再次开辟一个大一点的连续空间
T* destptr = newarray;
for (int i = 0; i < n; i++)
*destptr++ = *srcptr++;
delete[]data;//销毁原来的
data = newarray; //数据指针指向新的数组
maxSize = maxSize+newSize;//新数组最大容量发生改变
}
};
构造函数:
//默认的构造函数,无用户设定时,sz=defaultSize
template<class T>
SeqList<T>::SeqList(int sz)
{
if (sz > 0)
{
maxSize = sz;
last = -1;
data = new T[maxSize];
}
else{
cerr<<"输入参数有误,请检查后再次操作!"<<endl;
}
};
//复制构造函数,注意浅拷贝和深拷贝的区别,前者共用,后者独立
template<class T>
bool SeqList<T>::copySeqList(SeqList<T>& L)
{
last = L.Length() - 1;//类似于指针的东西,一直都是处于最新表项所在的表的编号位置
T value;
for (int i = 1; i <= last + 1; i++)
{
L.getData(i, value); //这里比较容易理解错误,i的值是逻辑上的第一个
data[i - 1] = value;//
}
};
需要注意的是我们认知的个数、位置和存储是差1的,比如,第一个表项在数组里是a[0],表项个数最大值为maxSize-1……
插入和删除操作,指定位置插入表项和指定位置删除表项:
template<class T>
bool SeqList<T>::Insert(int i, T& x)
{
if (last == maxSize - 1) return false;//已经存满,返回失败
if (i<0 || i>last + 1)//插入只能插入在已经存在的,不能再空的空间插入
return false;
/*插入表项存储位置为I,实际为第I+1个位置,所以是尾插在第I个后面,
有点难解释,自己试一下就知道了*/
for (int j = last; j >= i; j--)
data[j + 1] = data[j];
data[i] = x;
last++;
return true;
};
template<class T>
bool SeqList<T>::Remove(int i, T& x)//逻辑位置第I个
{
if (i<0 || i>last + 1) return false;//删除的表项位置不合理
x = data[i - 1];//记录删除表项的值,传给参数X
for (int j = i; j < last; j++)
data[j - 1] = data[j];//后面的向前面覆盖
last--;
return true;
};
文件操作进行存储和读取顺序表数据:
template<class T>
void load(SeqList<T>& p)//传入函数参数
{
ifstream f;//定义文件流对象,注意ifstream为读
f.open("Date_a.dat", ios::binary);//“文件名”,
//iOS::binary表示二进制读取,如果文件不存在则自动创建
if (!f)//判断文件是否为空
{
return;
}
T m;
//Address对象,方便使用标准模板库函数p.push_back添加元素
while (f.peek() != EOF)//循环条件
{
f.read((char*)&m, sizeof(T));
p.setOne(m);
}
f.close();//注意文件流的打开和关闭成对出现
}
template<class T>
void save(SeqList<T>& p)
{
ofstream f;//
f.open("Date_a.dat", ios::binary);
if (!f)
{
return;
}
int num = p.Length();//容器中实际元素的个数
T tem{};
for (int i = 1; i <= num; i++)
{
p.getData(i, tem);
f.write((char*)&tem ,sizeof(T));
}
f.close();
}
完整代码:
#include<iostream>
#include<stdlib.h>
#include<fstream>
using namespace std;
const int defaulatSize = 100;
template <class T>
class SeqList {
protected:
T* data{};//存放数组
int maxSize{};//最大可容纳表项的个数
int last{};//当前已存表项的最后位置
void reSize(int newSize);//改变data数组空间大小
public:
SeqList(int sz = defaulatSize);//构造函数
SeqList(SeqList<T>& L);//复制构造函数
~SeqList() { delete[]data; }//析构函数
bool copySeqList(SeqList<T>& L);
int Size()const { return maxSize; }//计算表最大可容纳表项的个数
int Length()const { return last + 1; }//计算最新表内表项的长度
int Search(T& x)const;//搜索X在表中的位置,函数返回表项的序号
int Locate(int i)const;//定位第I个表项,函数返回表项序号
bool getData(int i, T& x)const//获取第I个表项的值
{
if (i > 0 && i <= last + 1)
{
x = data[i - 1];
return true;
}
else
return false;
}
void setData(int i, T& x)//用X修改第I个表项的值
{
if (i > 0 && i <= last + 1)
data[i - 1] = x;
}
bool Insert(int i, T& x);
bool Remove(int i, T& x);
bool IsEmpty()
{
return (last == -1) ? true : false;
}
bool IsFull()
{
return (last == maxSize - 1) ? true : false;
}
void input();
void output();
bool setOne(T& x);
};
template<class T>
SeqList<T>::SeqList(int sz)
{
if (sz > 0)
{
maxSize = sz;
last = -1;
data = new T[maxSize];
}
};
template<class T>
bool SeqList<T>::copySeqList(SeqList<T>& L)
{
last = L.Length() - 1;//类似于指针的东西,一直都是处于最新表项所在的表的编号位置
T value;
for (int i = 1; i <= last + 1; i++)
{
L.getData(i, value); //这里比较容易理解错误,i的值是逻辑上的第一个
data[i - 1] = value;//
}
};
template<class T>
SeqList<T>::SeqList(SeqList<T>& L)
{
maxSize = L.Size(); //新建立的表的最大容纳量要一样
last = L.Length() - 1;//类似于指针的东西,一直都是处于最新表项所在的表的编号位置
T value;
data = new T[maxSize];
if (data == NULL)
{
cerr << "存储分配错误!" << endl; exit(1);
}
for (int i = 1; i <= last + 1; i++)
{
L.getData(i, value); //这里比较容易理解错误,i的值是逻辑上的第一个
data[i - 1] = value;//
}
};
template<class T>
void SeqList<T>::reSize(int newSize)//默认是对表的扩展
{
if (newSize <= 0)
{
cerr << "数组大小不能为负值" << endl; return;
}
if (newSize != maxSize)
{
T* newarray = new T[newSize];
if (newarray == NULL)
{
cerr << "存储分配错误" << endl; exit(1);
}
int n = last + 1;
T* srcptr = data;//再次开辟一个大一点的连续空间
T* destptr = newarray;
for (int i = 0; i < n; i++)
*destptr++ = *srcptr++;
delete[]data;
data = newarray; maxSize = newSize;
}
};
template<class T>
int SeqList<T>::Search(T& x)const//返回的是逻辑位置,是从第一个开始的【1-last+1】
{
for (int i = 0; i <= last; i++)
if (data[i] == x)
return i + 1;
return 0;
};
template<class T>
int SeqList<T>::Locate(int i)const
{
if (i >= 1 && i <= last + 1)
return i;//返回的还是逻辑值位置,需要观看着自己理解(自动减去一),这么做是为了当输入i值比较超越的时候返回0
else
return 0;
};
template<class T>
bool SeqList<T>::Insert(int i, T& x)
{
if (last == maxSize - 1) return false;
if (i<0 || i>last + 1)//插入只能插入已经存在的,不能再空的空间插入
return false;
for (int j = last; j >= i; j--)
data[j + 1] = data[j];
data[i] = x;
last++;
return true;
};
template<class T>
bool SeqList<T>::setOne(T& x)
{
if (last == maxSize - 1) return false;
data[last + 1] = x;
last++;
return true;
};
template<class T>
bool SeqList<T>::Remove(int i, T& x)
{
if (i<0 || i>last + 1) return false;
x = data[i - 1];
for (int j = i; j < last; j++)
data[j - 1] = data[j];
last--;
return true;
};
template<class T>
void SeqList<T>::input()
{
cout << "开始建立顺序表,请输入顺序表最后元素当前位置:";
cout << "有效值不超过" << maxSize - 1 << ":";
cin >> last;
for (int i = 0; i <= last; i++)
{
cin >> data[i];
}
cout << "元素添加完成!" << endl;
};
template<class T>
void SeqList<T>::output() {
cout << "顺序表当前元素最后位置为:" << last << endl;
for (int i = 0; i <= last; i++)
cout << "#" << i + 1 << ":" << data[i] << endl;
};
void menu()
{
cout << "1、计算顺序表最大容纳量" << endl;
cout << "2、计算表的长度" << endl;
cout << "3、初始化顺序表" << endl;
cout << "4、输出顺序表内容" << endl;
cout << "5、判断顺序表是否为空" << endl;
cout << "6、判断顺序表是否满" << endl;
cout << "7、搜索元素" << endl;
cout << "8、获取第I个表项值" << endl;
cout << "9、修改第I个表项值" << endl;
cout << "10、指定位置插入元素" << endl;
cout << "11、顺序添加元素"<<endl;
cout << "12、删除指定位置元素" << endl;
cout << "0、结束程序!" << endl;
}
template<class T>
void load(SeqList<T>& p)//传入函数参数
{
ifstream f;//定义文件流对象,注意ifstream为读
f.open("Date_a.dat", ios::binary);//“文件名”,
//iOS::binary表示二进制读取,如果文件不存在则自动创建
if (!f)//判断文件是否为空
{
return;
}
T m;
//Address对象,方便使用标准模板库函数p.push_back添加元素
while (f.peek() != EOF)//循环条件
{
f.read((char*)&m, sizeof(T));
p.setOne(m);
}
f.close();//注意文件流的打开和关闭成对出现
}
template<class T>
void save(SeqList<T>& p)
{
ofstream f;//
f.open("Date_a.dat", ios::binary);
if (!f)
{
return;
}
int num = p.Length();//容器中实际元素的个数
T tem{};
for (int i = 1; i <= num; i++)
{
p.getData(i, tem);
f.write((char*)&tem ,sizeof(T));
}
f.close();
}
int main()
{
int n; int x; int ii;
SeqList<int> List (100);
load(List);
while (true)
{
menu();
cout << "请输入你的操作:";
cin >> n;
if (n == 0)
break;
switch (n)
{
case 1:cout << List.Size() << endl;; break;
case 2:cout << List.Length()<<endl; break;
case 3:List.input(); break;
case 4:List.output(); break;
case 5:cout << List.IsEmpty()<<endl; break;
case 6:cout << List.IsFull()<<endl; break;
case 7:cout << "请输入元素值:"; cin >> x; cout <<"元素所在序号为:"<< List.Search(x) << endl; break;
case 8:cout << "请输入要查元素序号值:"; cin >> ii; List.getData(ii, x); cout << "元素值为:"<< x<< endl; break;
case 9:cout << "请输入要修改的元素表项值和改后值:"; cin >> ii >> x; List.setData(ii,x); break;
case 10:cout << "请输入要插入的位置和元素:"; cin >> ii >> x; List.Insert(ii, x); break;
case 11:cout << "请输入要添加的元素值:"; cin >> x; List.setOne(x); break;
case 12:cout << "请输入要删除元素的位置:"; cin >> ii; List.Remove(ii, x); cout << "成功删除元素"+x << endl; break;
default:cout << "请输入有效操作!" << endl;
}
}
//cout<<List.IsEmpty()<<endl;
//cout << List.IsFull()<<endl;
//List.input();
//List.output();
save(List);
return 0;
}
总结一下:
1、顺序表中的表项序号是从1开始,第一个表项存放于data[0],第二个表项存放于data[1]……第n个表项存放于data[n-1]。由此可知,表项序号与它再数组中的实际存放位置差1。
2、由于函数的参数传递是单向值传递,若不用&例如在主函数中调用 List_Sq(SqList L)创建一个顺序表L,如下面代码,把实参myL的值传给形参L,在函数中初始化L(赋值),当函数返回时,myL并没有被初始化,因为形参L不能反向传给实参。而加上引用&(在C语言中叫带地址传递)就不一样了,形参L变成实参myL的别名,实参和形参实际是同一段内存空间,当然修改形参就是修改实参。
3、const的作用。例如在函数int Length()const的作用,将关键字const放在函数参数表后面、函数体的前面起一种保护作用,表明它不能改变操作它的对象的数据成员的值,除此之外,const成员函数执行时不能调用非const成员。