一、实验目的
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、链表删除相同元素
2、链表插入、删除元素
3、单链表插入、删除、逆序排列
七、源程序清单
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(); }
}
}
}