数据结构——顺序表的定义和实现

顺序表

顺序表是基于数组的顺序存储的一种线性结构,并且每一个表项的逻辑结构和物理存放顺序一致,其中包含了对表项(数据元素)进行的相关操作,例如增删改查等等操作。
那么顺序表有什么用呢?对于一般的数组而言(基本数据类型),我们可以利用下标进行元素的增加和修改,但却没有办法表示实际元素个数或者长度(如果你初始化数组的话当我没说),而对于一些自定义数据结构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);
};

  1. 溢出处理功能实现。
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成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值