C++用类实现线性表(保姆级教你从0到封装一个SeqList类,附源码)

[着急用代码的直接拿]

目录

1.什么是线性表

2.构造线性表类(SeqList)

线性表的构造

判断线性表是否为空&私有属性的获取

获取数据信息(即查找功能)

展示线性表(输出线性表的全部内容)

插入(按位插入与按值插入)

最后来个老师交代的任务——合并线性表并保持其递变性

测试函数


 

(线性表(非链表)源码网盘链接:https://pan.baidu.com/s/18NTKsZmmFiPYcfaaghLu6w 
提取码:s1am 
--来自百度网盘超级会员V4的分享

    链表源码网盘链接:https://pan.baidu.com/s/1wSwVcBdUl3IndvLWRd-Ihg 
提取码:nevg )

1.什么是线性表

        何谓线性表呢?先看一手百度的解释:

                        

        扯白了线性表就是一种序列并且其长度是作为它本身的一种固有属性的。而提到序列最先想到的数据类型自然是数组啦!那么下面便以构造一个储存数据类型为整形数组的线性表类。

2.构造线性表类(SeqList)

        要构造一个类就先得想明白需要哪些私有属性,也就是这个类固有的一些组成部分。从百度的解释可以知道两个必需元素——1.序列2.线性表的长度

        这里我们将序列定义成整型数组——int a[];长度则定义为int length;

        而线性表是有长度限制的,那么我们对其储存的序列最好也有一个限度的控制,于是如下定义数组最大长度:const int MaxSize = 100;

        这么一来一个类的雏形就有了:

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;
const int MaxSize = 100;

class SeqList
{

private:
    int data[MaxSize];
    int length;

};


int main()
{


}

      接下来就应该着手思考要一个线性表要实现哪些功能

线性表的构造

      首先,肯定得实现能构造一个线性表,而构造一个线性表需要它的序列和长度,也就是需要带参构造。换句话说,不带参构造时得到的自然是空表。那办事先办简单再办难,先来写一个不带参构造:

public:
    //不带参构造,得到空表(即长度为0的表)
    SeqList()
    {
        length = 0;
    };

        接着再来实现一手带参数的线性表构造,我们需要两个参数——1.序列2.线性表的长度,有了这两个参数之后,将后者直接赋给length,前者进行排序后赋给data[]。你可能会奇怪干嘛要排序直接给它不就得了,这一手操作实际上大有来头,且先埋下伏笔!

        那既然要排序,就不得不面临两个亘古难题——1.排序方式是?2.排序算法是?

        对于第二个问题,推荐直接用algorithm库中的快排sort,第一个问题则需要与人交互才能得出答案,那么我们就由此得到了SeqList的一个船新属性——排序方式,定义一个bool型变量,若其为假则从小到大,反之从大到小。并且我们人为定义一手默认排序方式是从小到大,这个时候我们的类就初具其形了:

#include <iostream>
#include <algorithm>
#include <string>

using namespace std;
const int MaxSize = 100;

class SeqList
{
public:
	//无参构造,即构造空线性表
	SeqList()
	{
		length = 0;
		rev = false;//默认是从小打大
	};

	//带参构造,reverse代表反转排序顺序,默认从小到大
	SeqList(int a[], int n, bool reverse = false)
	{
		rev = reverse;
		length = n;
		//对给的数组进行快排
		sort(a, a + n);
		if (!reverse)
		{
			for (int i = 0; i < n; i++)
				data[i] = a[i];
		}
		else//倒序赋给data
		{
			for (int i = 0; i < n; i++)
			{
				data[i] = a[n - 1 - i];
			}
		}
	};

private:
	int length;  //线性表长度
	int data[MaxSize]; //数据储存于数组之中
	bool rev; //判断是从小到大还是从大到小
};

P.S.:打消一个偷鸡耍滑的主意:

        在带参构造时,你可能会想干嘛还得输入个n来表示长度捏?我明明已经知道了数据的数组,那我直接计算数组长度不就得了吗,既然我们知道一个整形占用的空间是4字节,那么整型数组a[]的长度不就是sizeof(a)/4吗?

        这样不就得到了简单些的带参构造嘛:

pubilc:
    //带参构造,reverse代表反转排序顺序,默认从小到大
	SeqList(int a[], bool reverse = false)
	{
		rev = reverse;
		length = sizeof(a)/4;
		//对给的数组进行快排
		sort(a, a + length);
		if (!reverse)
		{
			for (int i = 0; i < length; i++)
				data[i] = a[i];
		}
		else//倒序赋给data
		{
			for (int i = 0; i < length; i++)
			{
				data[i] = a[length - 1 - i];
			}
		}
	};

        这样你会发现这里的length永远是1,这是我一开始特别疑惑的一点,后来查找资料才知道,在函数里作为参数的数组datatype a[]会退化成一个指针,也就是说此时sizeof(a)得到的字节数实际上只是一个datatype类型的数据所占的字节数! 

        也就是说上述函数里sizeof(a)等同于在外部的sizeof(a[0])=4。所以会使length永远是1。这也就是为啥带参构造时要有一个int n的参数在。

判断线性表是否为空&私有属性的获取

        在构造完线性表后,还能实现哪些好玩的功能捏?你可能想的是输出、按位插入、按值插入啊什么的。在这之前我们得干两件基本的事——1.判断线性表是否为空2.私有属性的获取

        正所谓万丈高楼平地起,地基便是我们上面所写的两种构造方式以及三个私有属性。接下来我们要获取一下盖房子的原材料——水泥、石砖等等。也就是线性表是否为空和外部获取私有属性的值。

        先来看是否为空,回顾一手空线性表的定义——长度为0的线性表,那我们只需要判断length是不是0就好了。

bool empty()
	{
		return (length == 0) ? true : false;
	}

        ok,再随手写下获取数据序列data[]、长度length和排序方式的函数就好啦!


//获取所储存的数据序列
void get_data(int a[])
{
	for (int i = 0; i < length; i++)
		a[i] = data[i];
}
//获取长度
int get_length()
{
	return length;
}
//获取排序方式,假为从小到大
bool get_rev()
{
	return rev;
}

获取数据信息(即查找功能)

        接下来,让我们来实现一手获取信息的功能叭!说到获取数据信息,作为小白,我首先想到的是通过下标index获取一个数据,也就是通过给定i来获得data[i]的值。

        那么反过来呢?如果我想找找这个线性表里有没有一个元素x,是不是就是通过给定data[i]来确定i的值呢。没错,这就是所谓按值查找,而上一种即是所谓的按位查找。

        那我们先构思一手这个俩函数怎么写,首先否定掉重载,因为遑论下标index还是数据data[i]的数据类型都是int,重载的话就寄了,所以只好老老实实的写俩。

        先做简单再办难,显然,按位查找最简单(可能因为它最符合自然规律罢哈哈),直接return data[index]就成了。

        但是要注意一下异常处理:因为线性表是有限的(长度为length),那自然不能放任index随意取值,所以当index不属于[0, length)这个区间时就无法返回数据,应该抛出异常。

//按位获取
int get_index(int index)
{
	if (index<0 || index>length - 1)
	{
		cout << "所给下标溢出范围!" << endl;
		throw 1;
	}
	else
	{
		return data[index];
	}
}

        然后来实现一手按值查找,也就是返回x值在线性表中的下标index。那么先考虑一下异常情况——data中没有x。因为学C艹的同时摸了摸py,所以马上想到py里处理这一情况的操作,返回一个-1代表爷找不到。

        那我一开始就给要返回的index定义成-1,如果在for循环中遍历整个data都没找到x我就return index(也就是return -1),如果运气好,遍历着遍历着,欸嘿,找到了,那就急流勇退,直接return i就完了。

//按值获取
int get_num(int x)
{
	int index = -1;//没找到就返回-1
	for (int i = 0; i < length; i++)
	{
		if (data[i] == x)
		    return i;
	}
	return index;
}

展示线性表(输出线性表的全部内容)

        这个没啥好说的,直接上代码,就是直接输出表中内容。

//展示线性表
void show()
{

	cout << "该线性表长度为" << length << endl;
	string sort_func = (rev) ? "从大到小" : "从小到大";
	cout << "该线性表排列方式为" << sort_func << endl;
	for (int i = 0; i < length; i++)
	{
		cout << "第" << i + 1 << "个元素为:" << data[i] << endl;
	}

}

删除元素

        同查找一样,删除元素也有两种方式——1.按位删除(删除data[i])2.按值删除(删除第一个或所有值为x的数据)

        下面我们来一一实现,从上面括号的思考可以看出1更简单,那就先敲按位删除:

        既然是按位,那就要处理一手位置溢出的异常(详见按位查找),那当index合法时怎么删除它捏?想想删除后和删除前的变化——index前的所有元素统统没有变化,其后的所有元素往前跑了一位,那我们就来模拟其后的元素往前跑的过程便完成了删除:

        还有一件事!!长度是线性表固有属性,在删除一个元素之后线性表的长度会-1,所以记得写上length--!!!

//按位删除元素
void del_index(int index)
{

    if (index<0 || index>length - 1)
	{
		cout << "下标溢出,无法删除!" << endl;
		throw 1;
	}

	for (int i = index; i < length - 1; i++)
	{
		data[i] = data[i + 1];
	}
	length--;//长度-1

}

        接下来来写较为麻烦的按值删除:

        按值删除可以建立在按位删除的基础之上,我们将按值删除分为3步:

        1.找到它(找到所有符合条件的下标)

        2.处理异常(找不到它时抛错)

        3.杀掉他(删除掉所有要删的下标对应的元素)

public:
    //按值删除元素
	void del_num(int num, bool all = false)
	{

		if (!all)
		{
			int index = get_num(num);

			if (index == -1)
			{
				cout << "删除失败!线性表中不存在元素" + to_string(num) << endl;
				throw 1;
			}
			
			del_index(index);

		}
		
		else
		{
			int index = get_num(num);

			if (index == -1)
			{
				cout << "删除失败!线性表中不存在元素" + to_string(num) << endl;
				throw 1;
			}

			//找到了再来全删除
			int ind[MaxSize];//逆天了也就全是一个数
			int count = 0;
			for (int i = index; i < length; i++)
			{
				if (data[i] == num)
				{
					ind[count] = i;
				}
			}
			//进行删除
			for (int i = 0; i <= count; i++)
			{
				del_index(ind[i]-i);
			}

		}

	}

        这里处理全删时也可以套用上面的三段式,只不过需要注意一点——全删时由于待删下标往往不止一个,所以每删除一个下标之后此后所有的待删下标都会前移一格!

        第一个元素不会偏移,在待删下标数组中对应的下标是0;第二个元素前移1格,在待删下标数组中对应的下标是1;第三个元素前移2格,在待删下标数组中对应的下标是2......

        得到规律!所以将第三步的操作写为del_index(ind[i]-i)就好啦!

插入(按位插入与按值插入)

        好!接下来就是类里面我能想到的最后一个功能了,那就是插♂入

        自古以来,插入一直是人们乐此不疲的话题(狗头保命),而插入也是有着按位和按值两种插法的,先来康康按位,一般来说按位都是最简单的哈哈哈!

        要实现按位插入我们先假想一下在i处插入了值x,即data[i]=x,那么此后线性表中会有何变化呢?显然,易得:

1.0~i-1的数据没动

2.i~length-1的数据统统后移一位

3.插完后length+1

        好!那我们就模仿2、3两步即可!

        这里需要注意的是第二步中如果要插在表尾,即index=length-1,此时其后没有数据了,没法后移,所以这个情况单独拎出来处理!

public:
    //按位插入
	void insert(int num, int index)
	{

		if (index<0 || index>length)
		{
			cout << "所给下标溢出范围!" << endl;
			throw 1;
		}

		else
		{
			if (index != length)
			{
				for (int j = length; j >= index + 1; j--)
					data[j] = data[j - 1];
				data[index] = num;
				length++; //插入后长度+1
			}
            //插在表末,特殊情况
			else
			{
				data[length] = num;
				length++;
			}
			
		}

	}

        有了按位插入的经验之后,按值插入的三段式就呼之欲出了:

        1.找到合适的位置

        2.找不到(抛出异常)

        3.插入!

        回收伏笔:之所以构造线性表时对他进行一手排序就是为了这里的按值查找,因为只有在有序线性表中才能找到一个值合适的插入位置!

        这里需要注意的是第一步:

        以递增序列为例,如果data[i] <= num <= data[i+1],那么此时num应该插在哪呢?没错,应该插在i+1。那这么一来显然就漏了两种情况——头和尾。

        这个问题很容易解决,只需要比较一头一尾起即可。

        以递增为例,若待插的数比第一个还小,即num<=data[0],此时显然要插在0处,即index=0

        若待插的数比最后一个还大,即num>=data[length-1],此时显然要插在末尾处,即index=length

        这么一来就考虑全面了,最后再分从小到大和从大到小两种情况(先写个从大到小的,从小到大的只需把他copy一下再所有比较符全取反就好了)

public:
    //按值插入
	void insert(int num)
	{

		if (!rev)//从小到大
		{

			int index;

			//初始化插入下标
			if (num <= data[0])
			{
				index = 0;
			}

			else if (num >= data[length - 1])
			{
				index = length;
			}

			else
			{
				//寻找插入位置
				for (int i = 0; i < length - 1; i++)
				{
					if (data[i] <= num && num <= data[i + 1])
					{
						index = i+1;
						break;
					}
				}
			}

			//插入
			insert(num, index);
			

		}

		else//从大到小
		{

			//初始化插入下标
			int index;
			if (num >= data[0])
			{
				index = 0;
			}
			
			else if (num <= data[length - 1])
			{
				index = length;
			}

			else
			{
				//寻找插入位置
				for (int i = 0; i < length - 1; i++)
				{
					if (data[i] >= num && num >= data[i + 1])
					{
						index = i+1;
						break;
					}
				}
			}

			//插入
			insert(num, index);

		}

	}

这么一来,类SeqList就完成了

整个类的代码如下:

class SeqList
{
	
public:
	//无参构造,即构造空线性表
	SeqList()
	{
		length = 0;
		rev = false;//默认是从小打大
	};

	//带参构造,reverse代表反转排序顺序,默认从小到大
	SeqList(int a[], int n, bool reverse = false)
	{
		rev = reverse;
		length = n;
		//对给的数组进行快排
		sort(a, a + n);
		if (!reverse)
		{
			for (int i = 0; i < n; i++)
				data[i] = a[i];
		}
		else//倒序赋给data
		{
			for (int i = 0; i < n; i++)
			{
				data[i] = a[n - 1 - i];
			}
		}
	};
	
	//判断是否为空表
	bool empty()
	{
		return (length == 0) ? true : false;
	}

	//获取所储存的数据序列
	void get_data(int a[])
	{
		for (int i = 0; i < length; i++)
			a[i] = data[i];
	}

	//获取长度
	int get_length()
	{
		return length;
	}

	//按位删除元素
	void del_index(int index)
	{

		if (index<0 || index>length - 1)
		{
			cout << "下标溢出,无法删除!" << endl;
			throw 1;
		}

		for (int i = index; i < length - 1; i++)
		{
			data[i] = data[i + 1];
		}
		length--;//长度-1

	}

	//按值删除元素
	void del_num(int num, bool all = false)
	{

		if (!all)
		{
			int index = get_num(num);

			if (index == -1)
			{
				cout << "删除失败!线性表中不存在元素" + to_string(num) << endl;
				throw 1;
			}
			
			del_index(index);

		}
		
		else
		{
			int index = get_num(num);

			if (index == -1)
			{
				cout << "删除失败!线性表中不存在元素" + to_string(num) << endl;
				throw 1;
			}

			//找到了再来全删除
			int ind[MaxSize];//逆天了也就全是一个数
			int count = 0;
			for (int i = index; i < length; i++)
			{
				if (data[i] == num)
				{
					ind[count] = i;
				}
			}
			//进行删除
			for (int i = 0; i <= count; i++)
			{
				del_index(ind[i]-i);
			}

		}

	}

	//按位获取
	int get_index(int index)
	{
		if (index<0 || index>length - 1)
		{
			cout << "所给下标溢出范围!" << endl;
			throw 1;
		}
		else
		{
			return data[index];
		}
	}

	//按值获取
	int get_num(int x)
	{
		int index = -1;//没找到就返回-1
		for (int i = 0; i < length; i++)
		{
			if (data[i] == x)
				return i;
		}
		return index;
	}

	//按位插入
	void insert(int num, int index)
	{

		if (index<0 || index>length)
		{
			cout << "所给下标溢出范围!" << endl;
			throw 1;
		}

		else
		{
			if (index != length)
			{
				for (int j = length; j >= index + 1; j--)
					data[j] = data[j - 1];
				data[index] = num;
				length++; //插入后长度+1
			}
			else
			{
				data[length] = num;
				length++;
			}
			
		}

	}

	//按值插入(任务1)
	void insert(int num)
	{

		if (!rev)//从小到大
		{

			int index;

			//初始化插入下标
			if (num <= data[0])
			{
				index = 0;
			}

			else if (num >= data[length - 1])
			{
				index = length;
			}

			else
			{
				//寻找插入位置
				for (int i = 0; i < length - 1; i++)
				{
					if (data[i] <= num && num <= data[i + 1])
					{
						index = i+1;
						break;
					}
				}
			}

			//插入
			insert(num, index);
			

		}

		else//从大到小
		{

			//初始化插入下标
			int index;
			if (num >= data[0])
			{
				index = 0;
			}
			
			else if (num <= data[length - 1])
			{
				index = length;
			}

			else
			{
				//寻找插入位置
				for (int i = 0; i < length - 1; i++)
				{
					if (data[i] >= num && num >= data[i + 1])
					{
						index = i+1;
						break;
					}
				}
			}

			//插入
			insert(num, index);

		}

	}

	//展示线性表
	void show()
	{

		cout << "该线性表长度为" << length << endl;
		string sort_func = (rev) ? "从大到小" : "从小到大";
		cout << "该线性表排列方式为" << sort_func << endl;
		for (int i = 0; i < length; i++)
		{
			cout << "第" << i + 1 << "个元素为:" << data[i] << endl;
		}

	}

	bool get_rev()
	{
		return rev;
	}

private:
	int length;  //线性表长度
	int data[MaxSize]; //数据储存于数组之中
	bool rev; //判断是从小到大还是从大到小
};

最后来个老师交代的任务——合并线性表并保持其递变性

        这个功能我分成了去重和不去重两种情况,异常情况是待合并的两表排序方式不同,此时无法合并。

        先说合并——合并实际上就是把一个表中的每一个元素挨个按值插入另一个中

        再说去重——对每一个重复元素del_num(num, true)就好了(这也是为啥要按值删除时还分个全删和删头了)

        代码如下:

//合并两线性表
SeqList combine(SeqList s1, SeqList s2, bool sift = false)//默认不去重
{

	//排序方式不同则无法合并
	if (s1.get_rev() != s2.get_rev())
	{
		cout << "两线性表排序方式不同,无法合并!" << endl;
		throw 1;
	}

	if (!sift)//不去重
	{
		cout << "两线性表不去重合并的结果为:" << endl;
		//将较短的一个个插进较长的
		if (s2.get_length() < s1.get_length())
		{

			int end = (s2.get_length() % 2 == 0) ? s2.get_length() / 2 : s2.get_length() / 2 + 1;
			for (int i = 0; i < end; i++)
			{
				s1.insert(s2.get_index(i));
				if (!((s2.get_length() % 2 != 0)&&(i!= s2.get_length()/2)))
					s1.insert(s2.get_index(s2.get_length() - 1 - i));
			}

			return s1;

		}

		else
		{

			int end = (s1.get_length() % 2 == 0) ? s1.get_length() / 2 : s1.get_length() / 2 + 1;
			for (int i = 0; i < end; i++)
			{
				s2.insert(s1.get_index(i));
				if (s1.get_length() - 1 - i != i)
					s2.insert(s1.get_index(s1.get_length() - 1 - i));
			}

			return s2;

		}

	}
	else//去重
	{
		cout << "两线性表去重合并的结果为:" << endl;
		//将较短的一个个插进较长的
		if (s2.get_length() < s1.get_length())
		{

			//去重
			for (int i = 0; i < s2.get_length(); i++)
			{
				//如果重复则插入数组中删去之后再插入被插入数组
				if (s1.get_num(s2.get_index(i)) != -1)
				{
					s2.del_num(s2.get_index(i), true);
				}
			}

			int end = (s2.get_length() % 2 == 0) ? s2.get_length() / 2 : s2.get_length() / 2 + 1;
			for (int i = 0; i < end; i++)
			{
				s1.insert(s2.get_index(i));
				if (s2.get_length() - 1 - i != i)
					s1.insert(s2.get_index(s2.get_length() - 1 - i));
			}

			return s1;

		}

		else
		{

			//去重
			for (int i = 0; i < s1.get_length(); i++)
			{
				//如果重复则插入数组中删去之后再插入被插入数组
				if (s2.get_num(s1.get_index(i)) != -1)
				{
					s1.del_index(i);
				}
			}

			int end = (s1.get_length() % 2 == 0) ? s1.get_length() / 2 : s1.get_length() / 2 + 1;
			for (int i = 0; i < end; i++)
			{
				s2.insert(s1.get_index(i));
				if (s1.get_length() - 1 - i != i)
					s2.insert(s1.get_index(s1.get_length() - 1 - i));
			}

			return s2;

		}

	}

}

测试函数

        放到main函数里调用就可以看到我们的成果啦!

void test()
{

	int a[] = { 1, 3, 4, 2, 6, 5 };
	int b[] = { 8, 9, 12, 10, 7, 11 };

	SeqList s1(a, sizeof(a) / 4, true), s2(b, sizeof(b) / 4, true), * ptr;

	ptr = &s1;
	int temp[MaxSize];
	ptr->get_data(temp);
	cout << "该线性表存储的序列数据如下:" << endl;
	for (int i = 0; i < s1.get_length(); i++)
		cout << "第" << i + 1 << "个元素是:" << temp[i] << endl;
	ptr->insert(20);
	ptr->insert(0);
	ptr->insert(15);
	ptr->show();
	ptr = &s2;
	ptr->insert(30);
	ptr->insert(0);
	ptr->insert(15);
	ptr->show();
	SeqList combined = combine(s1, s2);
	ptr = &combined;
	ptr->show();
	combined = combine(s1, s2, true);
	ptr->show();

	//异常测试(两表排列方式不同时无法进行合并)
	int c[] = {1,2,5,9,7,46,84};
	SeqList s3(c, sizeof(c)/4);
	ptr = &s3;
	ptr->show();
	combine(s1, s3);

}

运行结果

 完结撒花!

  • 6
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小狗Codog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值