【数据结构】第二章——线性表(3)

这篇具有很好参考价值的文章主要介绍了【数据结构】第二章——线性表(3)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享

导言

大家好,很高兴又和大家见面了!!!
在上一篇中,咱们介绍了顺序表的基本概念,以及通过C语言实现顺序表的创建和对表长的修改。今天咱们将详细介绍一下使用C语言实现顺序表的增删改查。接下来,跟我一起来看看今天的内容吧!!!

一、顺序表的创建

我们先来回顾一下上一篇的内容,在上一篇中,我们介绍了顺序表的两种创建方式——静态分配与动态分配。今天我们通过动态分配的方式来创建一个顺序表,如下所示:

//顺序表的创建——动态分配
#define InitSize 5//定义顺序表初始表长
typedef struct
{
	int* date;//定义指向顺序表的指针
	int MaxSize, length;//定义顺序表的最大表长以及当前表长
}Sqlist;//将顺序表重命名为Sqlist
int main()
{
	Sqlist L;//创建顺序表
	return 0;
}

在定义完顺序表的初始表长、指向顺序表的指针、顺序表的最大表长以及顺序表的当前表长后,我们通过重命名的顺序表名字定义了一个顺序表L,此时的顺序表在内存空间中还未申请空间,所以我们需要通过对顺序表进行初始化,来给顺序表申请一块空间,如下所示:

//顺序表的初始化
void InitList(Sqlist* L)
{
	//通过calloc在内存中申请长度为初始表长,大小为int的一块连续空间
	L->date = (int*)calloc(InitSize, sizeof(int));
	L->MaxSize = InitSize;//将此时的最大表长初始化为初始表长
	L->length = 0;//目前顺序表中并未有任何元素,所以给当前表长初始化为0
}
int main()
{
	Sqlist L;//创建顺序表
	InitList(&L);//通过传址传参对L进行初始化
	return 0;
}

现在我们已经完成了顺序表的创建与初始化,现在我们就可以对顺序表进行一些基本操作了;

二、插入元素

插入操作ListInsert(&L,I.e):在表中的第i个位置上插入指定元素e。

这里的i指的是元素的位序,那对应的数组下标就是i-1

现在我们可以想象一个情景:目前我们的顺序表中有了一系列的元素,我们想要在其中两个元素中间插入一个新的元素,如下所示:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
我们可以看到,如果想将这个新元素e插入到顺序表中,我们就需要将其余的元素往后移,将e要插入的位置空出来才行,如下所示:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
在移动完元素后,我们才能顺利的将新的元素插入进行,这时就会出现以下几种情况:

  1. 顺序表为空表;
  2. 顺序表当前表长小于顺序表最大表长;
  3. 顺序表当前表长等于顺序表最大表长;

接下来我们来一一分析这几种情况;

  1. 顺序表为空表

在顺序表为空表时,我们想要给顺序表插入元素,只能够从首元素开始插入,如果顺序表为空表,而我们想要从位序3的位置插入一个元素,这是不合理的,此时不能正常插入元素。

  1. 顺序表当前表长小于顺序表最大表长

在这个情况下,又会有几种插入方式:

  • 如果我们想要在已有元素的位序上进行插入操作,可以通过将该位序以及后面的元素往后移动,然后再插入元素;
  • 如果我们想要在顺序表的最后插入元素,此时我们可以直接将元素插入到顺序表中;
  • 如果我们想要插入的元素位序不符合当前表长,那我们也不能正常插入元素;
  1. 顺序表当前表长等于顺序表最大表长

在这个情况下,我们也不能正常插入元素;

综上所述,我们可以的出结论:

  1. 顺序表进行插入操作时,需要判断插入元素的位序的合理性以及当前表长的合理性
  2. 顺序表进行插入操作时,需要对当前位置后面的元素进行移动,最好的情况是为空表时直接在第一个位序上进行插入,以及在表的最后面的空位上进行插入
  3. 顺序表在完成插入操作后,需要给用户进行一个反馈,是插入成功还是插入失败

下面我们来看一下插入操作的基本格式;

2.1 插入操作的基本格式

//插入操作的基本格式
bool ListInsert(Sqlist* L, int i, ElemType e)
{
	//判断插入位序的合理性
	if (i<1 || i>L->length + 1)
		return false;
	//判断表长的合理性
	if (L->length >= L->MaxSize)
		return false;
	//进行元素移动
	for (int j = L->length; j >= i; j--)
		L->date[j] = L->date[j - 1];
	//插入元素
	L->date[i - 1] = e;
	//更新当前表长
	L->length++;
	return true;
}
  • 在判断位序的合理性时,之所以是i<1,这是因为顺序表中的位序是从1开始,对应的数组下标是0,当小于1时,对应的数组下标就为负数,此时无法放入数组中;
  • i=L->length+1,表示的是在顺序表的表尾进行插入,如果i>L->length+1,表示的是此时的元素是插入到其他位置,这个位置与表中的最后一个元素没有做到物理位置上相邻这个要求,所以也是不能正常插入顺序表的;
  • L->length==L->MaxSize,表示的是此时顺序表中元素已经放满了,无法继续插入新的元素;
  • 我们在移动元素时,之所以是j>=i,这是因为,我们需要从顺序表的最后一个元素进行移动,直到将需要插入的位序上的元素全部移动完,才能正常插入。所以对于位序为i上的元素也要进行移动,位序i对应的下标i-1这个位置就空出来了,此时我们再进行插入的话就是插入到下标为i-1的位置,对应的位序则是i

2.2 插入操作的实现

接下来我们来尝试着插入相应的元素,并将插入完的顺序表打印出来,如下所示:

//插入操作
bool ListInsert(Sqlist* L, int i, int e)
{
	//判断插入位序的合理性
	if (i<1 || i>L->length + 1)
		return false;
	//判断表长的合理性
	if (L->length >= L->MaxSize)
		return false;
	//进行元素移动
	for (int j = L->length; j >= i; j--)
		L->date[j] = L->date[j - 1];
	//插入元素
	L->date[i - 1] = e;
	//更新当前表长
	L->length++;
	return true;
}
//打印顺序表
void PrintList(Sqlist L)
{
	printf("打印顺序表L:>");
	for (int i = 0; i < L.length; i++)
		printf("%d ", L.date[i]);
	printf("\n");
}
int main()
{
	Sqlist L;//创建顺序表
	InitList(&L);//通过传址传参对L进行初始化
	if (ListInsert(&L, 1, 1))
		PrintList(L);
	else
		printf("插入失败\n");
	if (ListInsert(&L, 2, 2))
		PrintList(L);
	else
		printf("插入失败\n");
	if (ListInsert(&L, 4, 4))
		PrintList(L);
	else
		printf("插入失败\n");
	if (ListInsert(&L, 3, 4))
		PrintList(L);
	else
		printf("插入失败\n");
	if (ListInsert(&L, 4, 5))
		PrintList(L);
	else
		printf("插入失败\n");
	if (ListInsert(&L, 3, 3))
		PrintList(L);
	else
		printf("插入失败\n");
	return 0;
}

我们在进行插入操作后,对返回值通过if语句来判断是否插入成功,成功,则打印插入后的顺序表,不成功则提示插入失败,测试结果如下所示:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
可以看到,当我们第三次想要在位序为4的位置插入元素4时,结果是插入失败;当我们将元素4和5都放入顺序表后,再插入元素3,此时元素4和5有往后进行移动。现在我们就很好的实现了插入操作。

2.3 插入操作的时间复杂度

对于插入操作来说,我们需要关注的是插入操作中最深层的代码的时间复杂度,即

	for (int j = L->length; j >= i; j--)
		L->date[j] = L->date[j - 1];
  • 这里最好的情况就是没有移动任何元素,也就是此时的代码是顺序执行,时间复杂度为O(1)
  • 最坏的情况是从表头插入元素,此时所有的元素都需要进行移动,对应的时间复杂度为O(n);
  • 正常情况下,假设有一个表长为n的顺序表,我们可以在表中插入的位置是从1~n+1,即从表头到表尾的位置进行插入,那每个位置插入的概率都为 p = 1 / ( n + 1 ) p=1/(n+1) p=1/(n+1)
    当我从表头插入的话,那此时的循环次数为n次,从第二个位置插入则是n-1次,以此类推,当在表尾插入时,循环指向次数为0次,所以我们很容易的得到平均时间复杂度为:
    Σ i = 1 n + 1 p i ( n − i + 1 ) = n p + ( n − 1 ) p + … … + p \Sigma_{i=1}^{n+1}p_i(n-i+1)=np+(n-1)p+……+p Σi=1n+1pi(ni+1)=np+(n1)p+……+p
    Σ i = 1 n + 1 p i ( n − i + 1 ) = [ ( n + 1 ) n / 2 ] ∗ [ 1 / ( n + 1 ) ] = n / 2 \Sigma_{i=1}^{n+1}p_i(n-i+1)=[(n+1)n/2]*[1/(n+1)]=n/2 Σi=1n+1pi(ni+1)=[(n+1)n/2][1/(n+1)]=n/2
    即平均时间复杂度为O(n)

三、修改表长

现在我们的顺序表已经放满了,此时如果我们想修改表长的话,上一篇也介绍过,可以通过malloccalloc进行申请新的空间,也可以通过realloc直接对顺序表进行修改。

这里我们先看看通过realloc如何进行修改,如下所示:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
这里初始化空间是为了让大家看的更清楚,实际操作的过程中是不需要的,我们如果想插入新的元素可以直接在表尾进行插入即可,下面我们来看一下通过malloccalloc应该如何进行修改:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
可以看到,此时能够达到同样的效果,但是要注意的是:

  1. malloc申请的空间,不会主动初始化为0,而calloc申请的空间会主动将空间内的元素初始化为0;
  2. 由realloc增加的新空间,不会主动初始化为0,但是它会在申请空间时将原空间的数据主动复制到新的空间中去;

所以,大家在修改表长时对函数的选择可以根据自己的喜好来决定,对应的代码如下:

//修改表长
void IncreaseSize(Sqlist* L, int len)
{
	//通过realloc进行修改
	L->date = (int*)realloc(L->date, (L->MaxSize + len) * sizeof(int));
	//L-date——修改空间的起始点,必须是通过malloc或者calloc申请的空间
	//(L->MaxSize + len) * sizeof(int)——修改后的空间大小
	L->MaxSize += len;//修改顺序表的最大表长
------------------------------------------------------------------------------------------
	//通过calloc进行修改
	int* p = L->date;//通过临时指针指向原先的空间
	L->date = (int*)calloc(L->MaxSize + len, sizeof(int));//申请新的空间
	//复制元素到新的空间
	for (int i = 0; i < L->length; i++)
		L->date[i] = p[i];
	L->MaxSize += len;//修改顺序表的最大表长
	free(p);//释放原先的空间
	p = NULL;//将临时指针p变成空指针可以不需要
}

四、删除元素

删除操作ListDelete(&L,i,&e):删除表L中第i个位置的元素,并用e返回删除元素的值;

删除元素与插入元素原理相同的,当我需要删除一个元素时,我只需要将这个元素后面的元素往前移动就可以完成删除操作了,如下所示:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
在删除时,我们同样需要对删除对象的合理性进行判断,接下来我们就来看看删除的格式;

2.1 删除操作的基本格式

//删除操作的基本格式
bool ListDelete(Sqlist* L, int i, ElemType* e)
{
	//判断删除对象位序的合理性
	if (i<1 || i>L->length)
		return false;
	//将删除的对象赋值给e
	*e = L->date[i - 1];//通过同类型的变量e来记录被删除的元素
	//移动元素
	for (int j = i; j < L->length; j++)
		L->date[j - 1] = L->date[j];
	//修改表长
	L->length--;
	return true;
}
  • 我们需要删除的元素肯定是在顺序表中已经存放的元素,也就是当前表长中的全部元素。当我们需要删除的元素不在这个范围内时,我们是无法进行删除操作的;
  • 我们在通过变量e记录被删除的元素后,此时就位序为i的位置就空出来了,我们就需要将此位置后面的元素依次前移,移动完所有元素后,此时的表长需要减1;

2.2 删除操作的实现

接下来我们来尝试着删除两个元素:

//删除操作
bool ListDelete(Sqlist* L, int i, int* e)
{
	//判断删除对象位序的合理性
	if (i<1 || i>L->length)
		return false;
	//将删除的对象赋值给e
	*e = L->date[i - 1];//通过同类型的变量e来记录被删除的元素
	//移动元素
	for (int j = i; j < L->length; j++)
		L->date[j - 1] = L->date[j];
	//修改表长
	L->length--;
	return true;
}
int main()
{
	Sqlist L;//创建顺序表
	InitList(&L);//通过传址传参对L进行初始化
	//插入元素
	……
	//修改表长
	IncreaseSize(&L, 5);//将表长增加5
	L.length = L.MaxSize;
	//打印顺序表
	PrintList(L);
	int e = -1;//定义同类型的变量e来记录删除的元素
	if (ListDelete(&L, 3, &e))
	{
		printf("位序%d上的元素%d已经成功删除\n", 3, e);
		PrintList(L);
	}
	else
	{
		printf("要删除的位序不合理,删除失败\n");
	}
	if (ListDelete(&L, 11, &e))
	{
		printf("位序%d上的元素%d已经成功删除\n", 3, e);
		PrintList(L);
	}
	else
	{
		printf("要删除的位序不合理,删除失败\n");
	}
	return 0;
}

与插入操作相同,我们也是通过if语句来进行判断是否成功删除元素,下面我们来看一下测试结果:

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
可以看到位序3上的元素3已经成功删除,但是位序11上的元素并未被删除,因为我们此时的表长时10,没有11这个位序,所以系统提示删除失败。现在我们就很好的实现了删除操作。

2.3 删除操作的时间复杂度

对于删除操作而言,我们可以看到它其实就是插入操作的反操作,插入操作是在位序i上插入一个新元素,需要将该位序即后面的元素往后移动,而删除操作则是将位序i后面的元素往前移动,因此他们的时间复杂度相同:

  • 最好时间复杂度为O(1);
  • 最坏时间复杂度为O(n);
  • 平均时间复杂度为O(n);

五、查找元素

我们查找顺序表中的元素有两种方式:按值查找与按位查找。

  • 按值查找操作LocateElem(L,e):在表L中查找具有给定关键字值的元素;
  • 按位查找操作GetElem(L,i):获取表L中第i个位置的元素的值;

简单的理解就是一个通过给定值,来查找顺序表中有没有该元素,如果有返回元素的下标;而按位查找则是通过位序i找到表中对应位序的元素的值。下面我们就来一一介绍一下这两种查找方式;

5.1 按位查找

按位查找的话我们需要在查找前先判断一下该位序是否合法,如果合法我们就可以将该位序上的元素的值返回给函数,按位查找的基本格式如下所示:

//按位查找的基本格式
ElemType GetElem(Sqlist L, int i)
{
	//判断位序的合理性
	if (i<1 || i>L.length)
		return -1;//不合理则返回-1
	else
		return L.date[i - 1];//合理则返回对应位序上的元素的值
}

大家在定义按位查找时一定要注意函数的返回类型与元素的类型是一致的。

5.2 按值查找

按值查找的话就是通过在顺序表中查找有无对应元素的值,如果找到了就将元素的位序返回给函数,按值查找的基本格式如下所示:

//按值查找的基本格式
int LocateElem(Sqlist L, ElemType e)
{
	//进行顺序查找
	for (int i = 0; i < L.length; i++)
	{
		//判断对应的值是否是我们需要查找的值
		if (L.date[i] == e)
			return i + 1;//返回对应的位序
	}
	return 0;//没有找到则返回0
}

按值查找的查找方式这里我是一顺序查找举例,在顺序表中,因为元素在逻辑上也是相邻的,也就是说,顺序表的逻辑是有序的,我们在查找时也可以通过折半查找法进行查找,当然后面会陆续介绍更多的查找方法。

5.3 查找操作的时间复杂度

如果我们是通过按位查找的话,时间复杂度就是O(1);
当我们通过按值查找的话,根据查找方式的不同,它会有不同的时间复杂度,就拿这里的顺序查找来说,只要是从头开始往后找,那它就会有三种情况:

  1. 最好情况:查找的元素为首元素,此时只需要查找一次就行,对应的时间复杂度为O(1)
  2. 最坏情况:查找的元素在表尾,此时需要查找n次,对应的时间复杂度为O(n)
  3. 平均情况:因为查找各元素的概率都相同,此时就好比插入元素一样,在插入之前我们也是需要进行查找,所以此时的时间复杂度为O(n)

六、修改元素

我们在查找完元素后,可以通过返回值对该元素进行修改,如下所示:
【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
如果我们是通过按值查找来查找对应元素的话,那我们在修改元素时就是通过元素的位序进行修改;

【数据结构】第二章——线性表(3),数据结构,保姆级教学,数据结构,算法,c语言,改行学it,学习,经验分享
如果我们通过按位查找来查找对应元素的话,那我们在修改元素时,就可以通过查找到的值来进行修改。

当然对于修改元素来说,不管是通过值来修改,还是通过位序来修改,它都需要在修改前找到对应的元素才行。对于这些函数的定义,可以根据自己的需求来进行,但是前提是定义的函数需要有健壮性。

结语

到这里,咱们对顺序表的介绍就全部完成了,希望大家看完这篇内容,能够掌握对顺序表的基本操作。接下来我们将开始介绍线性表的第二种表示方式——链式表示,大家记得关注哦!

最后感谢各位的翻阅,咱们下一篇再见!!!文章来源地址https://www.toymoban.com/news/detail-764072.html

到了这里,关于【数据结构】第二章——线性表(3)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构基础内容-----第二章算法

    算法 是指,解决问题或执行任务的一系列步骤、规则或指令的有序集合。它可以用来解决各种不同的问题,例如搜索、排序、优化、图像和语音识别等。在计算机科学中,算法通常用于编写程序以实现特定任务。算法可以被用于各种不同的领域,如人工智能、机器学习、数据

    2024年02月06日
    浏览(50)
  • 数据结构英文习题解析-第二章 链表List

    前言:最近快到FDS考试了,po重刷了一下学校的题目,自己整理了一些解析orz 因为po在自己找解析和学习的过程中非常痛苦,所以在此共享一下我的题目和自己写的解题思路,欢迎各位指出错误~全章节预计会陆续更新,可在专栏查看~ HW2 1. For a sequentially stored linear list of leng

    2024年04月11日
    浏览(53)
  • C++算法之旅、05 基础篇 | 第二章 数据结构

    常用代码模板2——数据结构 - AcWing 使用结构体指针,new Node() 非常慢,创建10万个节点就超时了,做笔试题不会用这种方式(优化是提前初始化好数组,但这样跟数组模拟没区别了,而且代码量很长) 使用两个数组,e存储val,ne存储next。空节点next用-1表示 826. 单链表 - AcWi

    2024年02月10日
    浏览(57)
  • 【AcWing算法基础课】第二章 数据结构(部分待更)

    本专栏文章为本人AcWing算法基础课的学习笔记,课程地址在这。如有侵权,立即删除。 邻接表 :存储图和树 e数组存储每个结点的值,ne数组存储每个结点的指向的下一个结点。 数组模拟链表比较快,指针模拟会涉及到new操作,比较慢。 题目链接 :826. 单链表 1.1题目描述

    2024年02月13日
    浏览(50)
  • 从零开始学数据分析之——《线性代数》第二章 矩阵

    元素全为实数的矩阵称为实矩阵  元素全为负数的矩阵称为复矩阵 只有一行(列)的矩阵称为行(列)矩阵 元素全为零的矩阵称为零矩阵 行数和列数都等于n的矩阵称为n阶矩阵或n阶方阵 主对角线元素全为1,其余元素全为0的矩阵称为单位矩阵,记作E或I 两个矩阵行数和列数

    2023年04月23日
    浏览(49)
  • 线性代数 第二章 矩阵

    一、概念 个数排成的m行n列的表格 二、运算法则 三、初等变换 (1)用非零常数k乘矩阵的某一行(列); (2)互换矩阵某两行(列)的位置; (3)把某行(列)的k倍加至另一行(列)。 称为矩阵的 初等行(列)变换 ,统称 初等变换 。矩阵经初等行变换后秩不变。 初等

    2024年02月08日
    浏览(47)
  • 高等数学:线性代数-第二章

    n bm{n} n 元线性方程组 设有 n 个未知数 m 个方程的线性方程组 { a 11 x 1 + a 12 x 2 + ⋯ + a 1 n x n = b 1 a 21 x 1 + a 22 x 2 + ⋯ + a 2 n x n = b 2 ⋯ ⋯ ⋯ ⋯ a m 1 x 1 + a m 2 x 2 + ⋯ + a m n x n = b m begin{cases} a_{11}x_{1} + a_{12}x_{2} + cdots + a_{1n}x_{n} = b_{1} \\\\ a_{21}x_{1} + a_{22}x_{2} + cdots + a_{2n}x_{n} = b

    2024年02月11日
    浏览(42)
  • 线性代数第二章矩阵及其运算详解

    一.线性方程组和矩阵 1.概念 如图所示,该矩阵称为 m行n列矩阵 若行数和列数都等于n,则该矩阵称为 n阶方阵 两个矩阵的行数相等,列数也相等,就称它们为 同型矩阵 若A=(aij)和B=(bij)是同型矩阵,且aij=bij(i=1,2,...,m;j=1,2,...,n),则称 矩阵A与矩阵B相等 ,记作 A=B 2.特殊

    2024年01月25日
    浏览(52)
  • 线性代数(魏福义)——第二章:矩阵及其向量特征

    矩阵 是一个 矩形数表 ,它是研究线性方程组、向量及其变换的重要工具 在数学中,矩阵是一个按照长方形排列的复数或实数集合,它是将一组 有序的数据 视为“ 整体量 ”进行 表述 和 运算 ,从而使问题的表述更加简洁。 2.1.1 矩阵 由 m × n 个数aij排成的 m行n列 的 数表

    2024年02月04日
    浏览(49)
  • <<数值分析>>第二章线性方程组的直接解法

              解线性方程组是工程数学中最常见的模型之一。所说的“最常见”有两方面的含义: 1)一部分工程问题的本身建立的就是线性方程组模型; 2)较多工程问题建立的非线性方程组模型需要转化为线性方程组的求解。           线性方程组为Ax=b,以下介绍

    2024年02月08日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包