【数据结构】顺序表和单链表

一、实验目的

1、熟练掌握线性表的结构特点,掌握顺序表和单链表的基本操作。

2、巩固 C++与面向对象的程序设计方法与技术。

二、实验要求

1、实现顺序表类,包括创建,插入(多个),删除(多个) ,析构,以及表里所有元素的显示;

2、实现单链表类,包括创建,插入(多个) ,删除(多个) ,析构,以及表里所有元素的显示;

3、demo程序的创建:

主页面:  1)表插入:从键盘输入任意个整数(<100,@表示结束),将元素插入顺序表、链表中。

2)元素删除。

3)退出系统。

补充需求:

1、删除操作的参数从下标变为元素值,并支持删除多个相同元素值的节点,顺序表和链表都需要实现。

2、插入操作从最后一个元素插入变为从特定元素值节点后插入,多个元素值仅考虑第一个。

3、重载多个删除、插入操作。单链表的插入和删除操作不要基于下标进行,对单链表来说,无下标一说(请勿使用Locate(i)函数)。

4、将单链表逆序并输出,且不借助任何辅助数组和链表。

5、顺序表和链表类从同一个基类以继承的方式构造。

三、概要设计

首先定义基类LinearList,单链表类List和顺序表类SqList均继承自LinearList。基类中有三个虚函数,分别是Insert(int i, T& x),用于在第i个元素后插入元素x;Remove(int i),用于删除第i个元素;output(),用于输出显示所有元素。根据C++的多态机制,通过不同类的对象调用名称相同的函数来实现不同的功能。

顺序表类SqList公有继承自基类LinearList,其数据成员和函数成员如下:

class SqList: public LinearList<T>
{
private:
	int maxSize;
	int last;
	T* data;
public:
	SqList(int sz);
	bool Insert(int i, T& x);
	bool Remove(int i);
	void delete_x(T& x);
	void output();
	void input();
};

单链表结构中,一个结点包含数据域和指针域两个部分,一个链表由若干个结点依次链接构成,其中第一个结点为头结点,最后一个结点的直接后继为NULL。在本次实验中直接用struct定义LinkNode类,因为struct的成员默认为公有数据成员,所以可直接访问。LinkNode定义如下:

template <class T> 
struct LinkNode { 
	T data; //数据域
	LinkNode<T>* link; //指针域
	LinkNode(LinkNode<T>* ptr = NULL)
	{
		link = ptr;
	} //仅初始化指针成员的构造函数
	LinkNode(const T& item, LinkNode<T>* ptr = NULL)
	{
		data = item; link = ptr;
	} //初始化数据与指针成员的构造函数
};

单链表类List公有继承自基类LinearList,其数据成员和函数成员如下,其中构造函数和析构函数在类中直接定义,为内联函数。

class List : public LinearList<T>
{
protected:
	LinkNode<T>* first; //表头指针
public:
	List() { first = new LinkNode<T>; }
	List(const T& x) {first = new LinkNode<T>(x);}
	~List()//析构
	{
		LinkNode<T>* q;
		while (first->link != NULL) //如果链不空,删除链中所有结点
		{
			q = first->link;
			first->link = q->link;//保存被删结点,从链上摘除此结点
			delete q;//删除,仅留一个表头结点
		}
	}
	bool Insert(int i, T&x);//插入
	bool Remove(int i);//删除
	void input();//输入
	void output();//输出
	LinkNode<T>* Locate(int i);//定位
	void delete_x(T& x);//删除相同值的元素
	void Reverse();//逆序排列
}

四、详细设计

1、SqList.h

数据成员T *data用于存放数组,maxSize用于表示最大可容纳表项的项数,last用于表示当前已存表项的最后位置。

下面对函数成员进行说明:

//构造函数
template <class T>
SqList<T>::SqList(int sz) {
	if (sz > 0) {
		maxSize = sz; last = -1;
		data = new T[maxSize];
	}
}

构造函数的参数sz表示要创建的顺序表的最大长度,把sz的值赋给maxSize,用new运算符开辟maxSize个T类型的内存空间。

//将新元素x插入到表中第i (0≤i≤last+1) 个表项之后
template <class T>
bool SqList<T>::Insert(int i, T& x) {
	if (last == maxSize - 1) return false; //表满
	if (i < 0 || i > last + 1) return false; //参数i不合理
	for (int j = last; j >= i; j--) //依次后移
		data[j + 1] = data[j];
	data[i] = x; //插入(第 i 表项在data[i-1]处)
	last++;
	return true;
}

Insert函数的第一个参数表示插入位置,第二个参数表示插入元素的值。如果表满或参数i不合理则返回false,否则将第i个及之后的元素依次后移一位,空出的位置插入x,同时last自增,返回true。

//删除第i(0≤i≤last+1)个表项
template <class T>
bool SqList<T> ::Remove(int i) {
	if (last == -1) return false; //表空,不能删除
	if (i<1 || i>last + 1) return false; //参数i不合法
	for (int j = i; j <= last; j++)
		data[j - 1] = data[j]; //依次前移
	last--; //最后位置减1 
	return true;
}

Remove函数的参数表示要删除的项数。如果表空或参数i不合理则返回false,否则将第i个及之后的元素依次前移一位,同时last自减,返回true。

//删除所有值为x的元素
template <class T>
void SqList<T>::delete_x(T& x)
{
	for (int k = 0; k <= last; k++)
	{
		if (data[k] == x)
		{
			for (int j = k; j <=last; j++)
				data[j] = data[j + 1];
			last--;
			k--; 
		}
	}
}

delete_x函数用于删除所有值为x的元素。定义变量k进行循环,如果data[k]与x相等,则k及之后的数前移一位,同时last自减,k自减。

//将顺序表所有元素输出到屏幕上
template<class T>
void SqList<T>::output() {
	cout << "顺序表当前元素最后位置为:" << last << endl;
	for (int i = 0; i < last; i++)
		cout << " " << i + 1 << ":" << data[i] << endl;
}

该函数用于输出显示。使用一个for循环遍历顺序表并输出每个元素。

//从键盘逐个输入数据,建立顺序表
template<class T>
void SqList<T>::input() {
	cout << "开始建立顺序表,请输入表中元素的个数:";
	while (1) {
		cin >> last;
		if (last <= maxSize - 1)break;
		cout << "表元素个数输入有误,范围不能超过" << maxSize - 1;
	}
	for (int i = 0; i < last; i++)
	{
		cout << "第" << i + 1 << "个数:" << endl;
		cin >> data[i];

	}
}

input函数首先提示用户输入元素个数,再让用户从键盘逐个输入数据,最后将数据一一显示。

2、List.h

template <class T>
LinkNode<T>* List<T>::Locate(int i) 
{
	if (i < 0) return NULL; //i不合理
	LinkNode<T>* current = first; int k = 0;
	while (current != NULL && k < i)
	{
		current = current->link; k++;
	}
	return current; //返回第 i 号结点地址或NULL
}

Locate函数的作用为定位,返回表中第 i 个元素的地址,若i<0或i超出表中结点个数,则返回NULL。

template <class T>
bool List<T>::Insert(int i, T& x) {
	LinkNode<T>* current = Locate(i);
	if (current == NULL) return false; //无插入位置
	LinkNode<T>* newNode = new LinkNode<T>(x); //创建新结点
	newNode->link = current->link; //链接在current之后
	current->link = newNode;
	return true; //插入成功

Insert函数用于将新元素 x 插入在链表中第 i 个结点之后,语句LinkNode<T>* current = Locate(i);将current指针定位到i,如果current为空则说明无插入位置,返回false;否则创建新结点并链接在current之后,返回true。

template <class T>
bool List<T>::Remove(int i)
{
	LinkNode<T>* current = Locate(i - 1);//将current定位在i-1
	if (current == NULL || current->link == NULL)
		return false; //删除不成功
	LinkNode<T>* del = current->link;
	current->link = del->link;//将被删结点从链表中摘取
	delete del;//释放del
	return true;
}

Remove函数用于删除单链表中第i个元素。首先将current定位在i-1,如果当前指针为空或下一个指针为空则无法删除,返回false;否则定义LinkNode<T>*类型的del,并把current->link赋给del,再把del->link赋给current->link,这样就将被删结点从链表中摘取,之后释放del,返回true。

template<class T>
void List<T>::output()
{
	LinkNode<T>* current = first->link;
	while(current!=NULL)
	{
		cout << current->data << endl;
		current = current->link;
	}
}

output函数用于输出显示,只要current指针不为空,就依次显示data所存的数据。

template<class T>
void List<T>::delete_x(T& x)
{
	LinkNode<T>* first;
	LinkNode<T>* p = first->link, * q, *pre = first;
	while (p != NULL)
	{
		if (p->data == x)
		{
			q = p;
			p = p->link;
			pre->link = q;//摘除*q结点
			delete q;
		}
		else { pre = p; p = p->link; }
	}
}

delete_x函数用于删除链表中所有值为x的元素。其基本思路是依次扫描每个结点,如果结点数据域元素与x相同则摘除该结点并释放内存,否则再继续扫描直到结束。

template <class T>
void List<T>::Reverse()
{
	LinkNode<T>* p, * r;
	p = first->link;
	first->link = NULL;
	while (p)
	{
		r = p->link;
		p->link = first->link;
		first->link = p;
		p = r;
	}
};

Reverse函数用于将链表元素逆序排列。定义了两个指针p和r,p指针为当前的工作指针,r指针为指向p下一个结点的指针,并把first->link置空。下面简述工作过程:p指向结点1,r指向结点2,语句p->link = first->link让p指向NULL,语句first->link = p让first的下一个指向p,即指向结点1,p=r语句即让r的下一个指向p,如此循环,实现元素的逆置。

五、调试分析

在完成编写代码后调试的过程中出现错误,后经检查发现基类Remove函数参数列表与子类中的不完全一样。需要注意的是C++中的虚函数的作用主要是实现了多态的机制,基类定义虚函数,子类可以重写该函数;在派生类中对基类的虚函数进行重写时,需要在派生类中声明该方法为虚方法。当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数,且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。而函数的重载可以认为是多态,只不过是静态的。注意,非虚函数静态联编,效率要比虚函数高,但是不具备动态联编能力。如果使用了virtual关键字,程序将根据引用或指针指向的对象类型来选择方法,否则使用引用类型或指针类型来选择方法。

六、测试结果

1、链表删除相同元素

749d789963954f928a26b0a527da2964.png

 2、链表插入、删除元素

88e3d67cc68c45c1a6fd1f790f8e9d49.png

 3、单链表插入、删除、逆序排列

46381c13c3774328b33d073ed46feb3c.png

七、源程序清单

LinearList.h

#ifndef LINEARLIST_H_
#define LINEARLIST_H_
#include <iostream>
#include <stdlib.h>
using namespace std;
template <class T>
class LinearList
{
public:
	virtual bool Insert(int i, T& x)=0;
	virtual bool Remove(int i)=0;
	virtual void output()=0;
};
#endif

SqList.h

#include "LinearList.h"
#ifndef SQLIST_H_
#define SQLIST_H_
template <class T>
class SqList: public LinearList<T>
{
private:
	int maxSize;
	int last;
	T* data;
public:
	SqList(int sz);
	bool Insert(int i, T& x);
	bool Remove(int i);
	void delete_x(T& x);
	void output();
	void input();
};

//构造函数
template <class T>
SqList<T>::SqList(int sz) {
	if (sz > 0) {
		maxSize = sz; last = -1;
		data = new T[maxSize];
	}
}

//将新元素x插入到表中第i (0≤i≤last+1) 个表项之后
template <class T>
bool SqList<T>::Insert(int i, T& x) {
	if (last == maxSize - 1) return false; //表满
	if (i < 0 || i > last + 1) return false; //参数i不合理
	for (int j = last; j >= i; j--) //依次后移
		data[j + 1] = data[j];
	data[i] = x; //插入(第 i 表项在data[i-1]处)
	last++;
	return true;
}

//删除第i(0≤i≤last+1)个表项
template <class T>
bool SqList<T> ::Remove(int i) {
	if (last == -1) return false; //表空,不能删除
	if (i<1 || i>last + 1) return false; //参数i不合法
	for (int j = i; j <= last; j++)
		data[j - 1] = data[j]; //依次前移
	last--; //最后位置减1 
	return true;
}


//删除所有值为x的元素
template <class T>
void SqList<T>::delete_x(T& x)
{
	for (int k = 0; k <= last; k++)
	{
		if (data[k] == x)
		{
			for (int j = k; j <=last; j++)
				data[j] = data[j + 1];
			last--;
			k--;//如果遇见要删除的数,那么这个数之后整体前移了1位
		}
	}
}

//将顺序表所有元素输出到屏幕上
template<class T>
void SqList<T>::output() {
	cout << "顺序表当前元素最后位置为:" << last << endl;
	for (int i = 0; i < last; i++)
		cout << " " << i + 1 << ":" << data[i] << endl;
}

//从键盘逐个输入数据,建立顺序表
template<class T>
void SqList<T>::input() {
	cout << "开始建立顺序表,请输入表中元素的个数:";
	while (1) {
		cin >> last;
		if (last <= maxSize - 1)break;
		cout << "表元素个数输入有误,范围不能超过" << maxSize - 1;
	}
	for (int i = 0; i < last; i++)
	{
		cout << "第" << i + 1 << "个数:" << endl;
		cin >> data[i];

	}
}
#endif

List.h

#include "LinearList.h"
#ifndef LIST_H_
#define LIST_H_

template <class T> 
struct LinkNode { 
	T data; //数据域
	LinkNode<T>* link; //指针域
	LinkNode(LinkNode<T>* ptr = NULL)
	{
		link = ptr;
	} //仅初始化指针成员的构造函数
	LinkNode(const T& item, LinkNode<T>* ptr = NULL)
	{
		data = item; link = ptr;
	} //初始化数据与指针成员的构造函数
};

template <class T>
class List : public LinearList<T>
{
protected:
	LinkNode<T>* first; //表头指针
public:
	List() { first = new LinkNode<T>; }
	List(const T& x) {first = new LinkNode<T>(x);}
	~List()//析构
	{
		LinkNode<T>* q;
		while (first->link != NULL) //如果链不空,删除链中所有结点
		{
			q = first->link;
			first->link = q->link;//保存被删结点,从链上摘除此结点
			delete q;//删除,仅留一个表头结点
		}
	}
	bool Insert(int i, T&x);//插入
	bool Remove(int i);//删除
	void input();//输入
	void output();//输出
	LinkNode<T>* Locate(int i);//定位
	void delete_x(T& x);//删除相同值的元素
	void Reverse();//逆序排列
};

//定位,返回表中第 i 个元素的地址,若i<0或i超出表中结点个数,则返回NULL。
template <class T>
LinkNode<T>* List<T>::Locate(int i) 
{
	if (i < 0) return NULL; //i不合理
	LinkNode<T>* current = first; int k = 0;
	while (current != NULL && k < i)
	{
		current = current->link; k++;
	}
	return current; //返回第 i 号结点地址或NULL
}

//将新元素 x 插入在链表中第 i 个结点之后
template <class T>
bool List<T>::Insert(int i, T& x) {
	LinkNode<T>* current = Locate(i);//将current定位到i
	if (current == NULL) return false; //无插入位置
	LinkNode<T>* newNode = new LinkNode<T>(x); //创建新结点
	newNode->link = current->link; //链接在current之后
	current->link = newNode;
	return true; //插入成功
}

//删除链表第i个元素
template <class T>
bool List<T>::Remove(int i)
{
	LinkNode<T>* current = Locate(i - 1);//将current定位在i-1
	if (current == NULL || current->link == NULL)
		return false; //删除不成功
	LinkNode<T>* del = current->link;
	current->link = del->link;//将被删结点从链表中摘取
	delete del;//释放del
	return true;
}

//输出,将单链表中所有数据按逻辑顺序输出显示
template<class T>
void List<T>::output()
{
	LinkNode<T>* current = first->link;
	while(current!=NULL)
	{
		cout << current->data << endl;
		current = current->link;
	}
}

//删除所有数据为x的结点
template<class T>
void List<T>::delete_x(T& x)
{
	LinkNode<T>* first;
	LinkNode<T>* p = first->link, * q, *pre = first;
	while (p != NULL)
	{
		if (p->data == x)
		{
			q = p;
			p = p->link;
			pre->link = q;//摘除*q结点
			delete q;
		}
		else { pre = p; p = p->link; }
	}
}

//逆序
template <class T>
void List<T>::Reverse()
{
	LinkNode<T>* p, * r;
	p = first->link;
	first->link = NULL;
	while (p)
	{
		r = p->link;
		p->link = first->link;
		first->link = p;
		p = r;
	}
};
#endif

main.cpp

#include "LinearList.h"
#include "SqList.h"
#include"List.h"
int main()
{
	int a, b;
	cout << "请输入要创建顺序表还是链表,1表示顺序表,2表示链表:" << endl;
	cin >> a;
	if (a == 1)
	{
		cout << "请输入要创建的顺序表的大小:" << endl;
		cin >> b; SqList<int>Sq(b);
		Sq.input();
		cout << "1.删除第i个元素  2.插入元素  3.删除所有的x  " << endl;
		int c; 
		while (cin >> c) {
			if (c == 1)
			{
				cout << "删除第几个元素?" << endl;
				int d; cin >> d; Sq.Remove(d); Sq.output();
			}
			if (c == 2)
			{
				cout << "在第几项后插入?" << endl;
				int e; cin >> e;
				cout << "请输入插入的数" << endl;
				int f; cin >> f;
				Sq.Insert(e, f);
				Sq.output();
			}
			if (c == 3)
			{
				cout << "请输入要删除的数" << endl;
				int f; cin >> f;
				Sq.delete_x(f);
				Sq.output();
			}
		}
	}
	
		if (a == 2)
		{
			List<int> li;
			while (1)
			{
				cout << "1.输入元素 2.删除第i个元素 3.在第i个元素之后插入元素 4.逆序 5.展示所有元素" << endl;
				int i;
				cin >> i;
				if (i == 1)
				{
					cout << "输入你一次想输入的个数" << endl;
					int u;
					int q = 0;
					int k = 0;
					cin >> u;
					cout << "输入你的元素:" << endl;
					while (1)
					{
						int a;
						cin >> a;
						li.Insert(q, a);
						q++;
						k++;
						if (k == u)
							break;
					}
				}
				if (i == 5)
					li.output();
				if (i == 2)
				{
					int f;
					cout << "输入想删除的位置i:" << endl;
					cin >> f;
					li.Remove(f);
				}
				if (i == 3)
				{
					cout << "在第几个元素后插入?" << endl;
					int m; cin >> m;
					cout << "请输入插入的数" << endl;
					int cr; cin >> cr;
					li.Insert(m, cr);
				}
				
				if (i == 4) { li.Reverse(); }
			}
		}

}

猜你喜欢

转载自blog.csdn.net/m0_53700832/article/details/128915626