数据结构之顺序表

最近在学数据结构和C++,数据结构是跟着比特杭哥学的,想着复习一下这段时间的学习成果。

这次介绍顺序表,C++的知识仅仅用了 cout输出和cin输入,其它基本都是C的语法,之后的文章会去介绍链表。


1.什么是线性表

简单的说,线性表就是一个数组,不过实现的时候我们把这个数组放在一个结构体里面,再记录一下有效元素的个数,就可以了。

从数据结构的角度来看,数据元素之间的逻辑关系是线性的,存储结构是顺序存储。

我们来看实现,先看看两种结构体的描述:

第一种,静态的数组

typedef int SQdateType;//把int 重定义一下,方便以后存储数据类型发生改变时去修改
 typedef struct SeqList//创建一个结构体,当作顺序表来使用
{
	 SQdateType a[MAX];//创建一个整形数组
	 SQdateType size; //记录当前存放了几个数据

} SL;//给这个顺序表起一个别名,叫做SL,方便调用

 第二种,动态的数组

typedef int SQdateType;//把int 重定义一下,方便以后存储数据类型发生改变时去修改
//动态版顺序表的创建
typedef struct SeqList//创建一个结构体,当作顺序表来使用
{
	SQdateType* a;//定义一个指针变量来管理内存
	SQdateType capacity;//记录当前顺序表的容量
	SQdateType size; //记录当前存放了几个数据
} SL;//给这个顺序表起一个别名,叫做SL,方便调用

我们可以看到,第二种情况和第一个不太一样,为什么?

我们思考,直接用数组在栈区开辟空间,这个空间是死的,不能更改,如果给多了,是浪费,如果给少了无法增容,所以干脆我们直接自己开辟空间,要多少给多少。

所以就有了第二种结构体的定义方法,我们用一个指针来管理开辟的内存,size还是记录元素的个数,capacity就用来记录开辟的空间是否足够。

所以我们直接介绍第二种,它们功能的实现基本是一样的。

SQdateType就是 int 类型!!!

SL 是我们结构体的别名!!!

2.顺序表的功能


1. 初始化顺序表、打印顺序表、检查顺序表的容量
2.增加元素:1)头部增加  2)尾部增加  3)中间增加
3.删除元素:  1)头部删除     2)尾部删除   3)中间删除

4.查找元素:查找某个元素

5.修改元素:找到某个元素,把它修改掉        
6.销毁顺序表

初始化顺序表:


void InitSeqList(SL *ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

这个函数就是给我们结构体里面的元素给一个初始值。

检查顺序表的容量:

void CheckCapacity(SL *ps)
{
	if (ps->size >= ps->capacity)//如果容量满了,去增容
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		SQdateType *temp = (SQdateType *)realloc(ps->a, newcapacity * sizeof(SQdateType));
		if (temp == NULL)
		{
			cout << "realloc 开辟失败" << endl;
			exit(-1);
			return;
		}
		else
		{
			cout << "开辟成功!!" << endl;
			ps->a = temp;
			ps->capacity = newcapacity;
			return;
		}
	}
	else
		return;

}

检查容量的逻辑也很简单,就是在我们需要在顺序表中插入数据时,发现size和capacity是相等的,证明我们开辟的空间已经被占满了,size和capacity的单位都是“个”,就是说 几个int型空间。当满的时候,我们就用realloc函数对我们的容量进行扩容,一般是扩容到原来的两倍。扩容失败程序就结束了,扩容成功我们还让我们的a指针来指向这片连续的内存空间。

如果不熟悉malloc 和realloc的使用可以看看我之前写的一篇文章,介绍这两个函数如何用。

https://blog.csdn.net/aaa1336/article/details/125340749?spm=1001.2014.3001.5501

int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

讲一下这个代码是什么意思,这是个三目运算符,判断一开始我们的容量是否为零,如果是为零,我们就直接让newcapacity为4,在再下面用realloc开辟空间,如果不为零,那么就让容量变为原来的二倍。

为啥要这样写,我们看下面的增容函数:

void CheckCapacity(SL *ps)
{
	if (ps->size >= ps->capacity)//如果容量满了,去增容
	{
		

		SQdateType *temp = (SQdateType *)realloc(ps->a, 2*ps->capacity * sizeof(SQdateType));
		if (temp == NULL)
		{
			cout << "realloc 开辟失败" << endl;
			exit(-1);
			return;
		}
		else
		{
			cout << "开辟成功!!" << endl;
			ps->a = temp;
			ps->capacity *= 2;
			return;
		}
	}
	else
		return;

}

这个代码会出现一种比较尴尬的情况,因为在我们的初始化函数里面,我们一开始capacity给的是零,这个时候你调用realloc,你会申请零个字节的空间,而且我们的capacity*2还是零,所以为了避免这种情况,我们要做这样的处理。

其实一开始我们在初始化里面就为顺序表malloc一块空间也能解决这个问题。

打印顺序表

就是正常打印一个数组里面的元素,直接看代码就可以了。

void PrintSeqList(SL *ps)
{

	int i = 0;
	for (i = 0; i < ps->size; ++i)
	{
		cout << ps->a[i] << "  " ;
	}
	cout << endl;
}

这些基本的功能介绍完成之后,我们看重要的功能。


增加元素

在顺序表中的某个位置插入元素,根据位置的不同可分为三种情况,头部插入,尾部插入,中间插入。

头插

 顾名思义,就是在顺序表一个位置插入元素。

插入元素我们需要考虑:找到插入的位置,线性表的容量是否足够,如何插入。

头插,最常用的方法就是,从最后一个元素开始,依次向后移动,最后再把插入的元素放在头部的位置即可。

上代码

void PushFrontSeqList(SL *ps, SQdateType x)//在头部插入数据
//从后边往前边移动
{
	CheckCapacity(ps);//先检查容量,防止移动时越界
	//用循环进行移动
	int end = ps->size;//end表示下表
	while (end > 0)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}
	ps->a[0] = x;
	ps->size++;

}

CheckCapacity用来检查容量,end用来表示下标,while循环负责移动移动元素,最后再在第一位置插入元素x,增加了一个元素,不要忘了让size增加。

这个时候若顺序表为空,则end=0,循环不会进行,直接在第一个位置插入元素。


尾插

在顺序表尾部插入元素 

尾插的方法就是找到尾部,插入数据即可,我们的size记录的是有效元素的个数,但在大小上,它其实就是最后一个元素下一个位置的下标,所以我们直接在这个位置插入元素即可。

上代码:(不要忘记检查容量和让size++)

void PushBackSeqList(SL *ps, SQdateType x)//在尾部插入数据
{
	
	CheckCapacity(ps);//检查容量
	ps->a[ps->size] = x;//size代表有效元素下一个位置的下表
	ps->size++;
}

任意位置插入元素


在顺序表中,任意位置插入元素,只要保证插入位置是合法的,插入即可。

在顺序表的中间插入元素(某个元素的前边),我们让从这个位置开始向后的所有元素向后边移动,再将插入的元素插入即可。

在顺序表中,任意位置插入元素,只要保证插入位置是合法的,插入即可。

在顺序表的中间插入元素(某个元素的前边),我们让从这个位置开始向后的所有元素向后边移动,再将插入的元素插入即可。


void InsertSeqList(SL *ps, int pos, SQdateType x)
//在顺序表某个 位置pos处插入元素x
//假设,这个pos是物理意义上的位置,不是数组下表,我们要将它转化成下表
//假设我们的这个插入,是把目标位置的元素统一向后移动,而不是直接覆盖
{
	if (pos == 1)//这其实就是头插
	{
		PushFrontSeqList(ps, x);
	}
	else if (pos>1&&pos<ps->size)
	{
		pos -= 1;//转化为数组下表
		int end=ps->size;//end记录下标
		CheckCapacity(ps);//检查容量
		while (end > pos)
		{
			ps->a[end] = ps->a[end - 1];
			--end;
		} 
		ps->a[pos] = x;
		ps->size++;

	}
	else
	{
		cout << "插入位置不合法,无法进行数据插入" << endl;
	}

}

删除元素 

和插入元素类似,我们也从 头删,尾删,中间删三个方面。 

头删

void PopFrontSeqList(SL *ps)//在头部删除数据
//覆盖移动
{
	if (ps->size == 0)//顺序表为空,不删除。
	{
		cout << "顺序表为空,无法删除" << endl; 
			return;
	}
	else
	{
		int start = 1;
		while (start < ps->size)//覆盖删除
		{
			ps->a[start - 1] = ps->a[start];
			++start;
		}
		ps->size--;//最后size--
	}
}		

 先判断顺序表是否为空,如果为空直接退出并提示,如果不为空,那么就从第二个元素开始,依次向前移动,最后size减一。 

尾删

尾部删除最直接,就是让size--,不显示它就可以了。

void PopBackSeqList(SL *ps)//在尾部删除数据
//最直接的办法就是不显示它,达到删除的效果
{
	if (ps->size > 0)
	{
		ps->size--;
	}
	else{
		cout << "顺序表没有元素,无法删除" << endl;
	}
}

任意位置删除元素

删除还是覆盖删除,从该位置后一个元素,依次向前移动,达到删除的效果。

根据输入的位置不同,也可以直接调用头删和尾删函数。

void DeleteSeqList(SL *ps, int pos)//将顺序表的某给位置pos处的元素给删除掉
{
	if (pos == 1)//头删
	{
		PopFrontSeqList(ps);
	}
	else if (pos > 1 && pos < ps->size - 1)
	{
//覆盖删除
		int start = pos;//pos刚好是要删除位置下一个元素的下标
//所以这个pos不用再转化成下标了。
		while (start < ps->size)
		{
			ps->a[start - 1] = ps->a[start];
			++start;
		}
		ps->size--;
	}
	else//尾删
	{
		PopBackSeqList(ps);
	}
}

查找元素 

查找很简单,遍历这个数组,找到与相同的元素,返回它的下标(这里我们先不考虑重复的情况,只要第一个元素)。

int FindSeqList(SL *ps, SQdateType x)//查找某个元素,返回它的下标
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

找到后返回下标,没找到就返回-1.

修改元素

输入要修改元素的位置,如果输入位置合法,就把它修改掉。

既然我们的查找函数可以找到下标,那么我们就把它们结合起来,利用查找函数的返回值,只要不为-1 我们就调用修改函数。

void ModifySeqList(SL *ps, SQdateType i, SQdateType x)
{
	ps->a[i] = x;
	return;
}


测试 

我们在主函数里面测试一下我们的顺序表的功能是否正确。

int main()
{
	SL plist;//创建一个顺序表
	InitSeqList(&plist);//初始化顺序表
	int i = 0;
	for (i = 0; i < 10; i++)//用循环给顺序表赋一些值
	{
		CheckCapacity(&plist);//检查容量
		plist.a[i] = i;//赋值
		++plist.size;//有效元素个数增加
	}
	cout << "顺序表初始数据:" << endl;
	PrintSeqList(&plist);//打印一下 
	cout << endl;
    //结果 0 1 2 3 4 5 6 7 8 9
	PushFrontSeqList(&plist, 30);//头插一个30
	cout << "头插后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果30 0 1 2 3 4 5 6 7 8 9
	PushBackSeqList(&plist, 500);//尾插一个500
	cout << "尾插后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果30 0 1 2 3 4 5 6 7 8 9 500
	InsertSeqList(&plist, 7, 45);//第七个位置前插入一个45
	//即在5的前面插入45
	cout << "第七个位置插入后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果30 0 1 2 3 4 45 5 6 7 8 9 500
	PopFrontSeqList(&plist);//头删,即把30删掉
	cout << "头删后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果 0 1 2 3 4 45 5 6 7 8 9 500
	PopBackSeqList(&plist);//尾删,即把500删掉
	cout << "尾删后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果 0 1 2 3 4 45 5 6 7 8 9 
	DeleteSeqList(&plist, 5);//删掉第五个位置的元素,即把4删掉
	cout << "第五个位置删除后:" << endl;
	PrintSeqList(&plist);//打印一下
	cout << endl;
	//结果 0 1 2 3 45 5 6 7 8 9 
	int temp = FindSeqList(&plist, 7);//找到元素为7的下标
	if (temp != -1)
	{
		ModifySeqList(&plist, temp, 80);//将这个位置的元素修改为 80
		cout << "7修改为80后" << endl;
		PrintSeqList(&plist);//打印一下
		cout << endl;
		//结果 0 1 2 3 45 5 6 80 8 9 
	}
	else{
		cout << "未找到该元素" << endl;
	}
	system("pause");
	return 0;
}

以下是运行结果:

综上,就是我们循序表的功能实现啦。

整体来说,顺序表的功能相对简单,最重要的是理解原理!!!

分享到这里啦~~

下期再见~~~ 

需要源代码的家人们,可以去我的Gitee仓库直接下载哦~

链接:https://gitee.com/qiqi-loves-to-knock-code/qiqis-daily-code

文件名是顺序表哦~~

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
 

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值