顺序表和链表

目录

1. 线性表

2.顺序表

2.1 概念于结构

2.2 分类

2.2.1 静态顺序表

2.2.2 动态顺序表

2.3 动态顺序表的实现

2.3.1 动态顺序表的初始化

2.3.2 动态顺序表的销毁

2.3.2 动态顺序表的尾插

2.3.3 动态顺序表的打印

2.3.4 动态顺序表的头插

2.3.5 动态顺序表的尾删

​2.3.6 动态顺序表的头删

2.3.8 顺序表指定位置删除

2.4 顺序表算法题

2.4.1 移除元素

2.4.2 删除有序数组中的重复项

2.4.3 合并两个有序数组

2.5 顺序表问题与思考

2.6 顺序表的代码

2.7 顺序表参考博客 

3. 单链表

3.1 单链表博客以及代码

3.2 单链表算法题

3.2.1 移除链表元素

3.2.2 反转链表

3.2.3 链表的中间节点

3.2.4 合并两个有序链表

3.2.5 链表分割

3.2.6 链表的回文结构

3.2.7 相交链表

3.2.8 环形链表I

3.2.9 环形链表II

3.2.10 随机链表的复制

4.双向链表


1. 线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的
数据结构,常见的线性表:顺序表,链表,栈,队列,字符串...,线性表是一种具有相同特性的数据的集合。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

2.顺序表

2.1 概念于结构

概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

顺序表再逻辑结构上是线性的,再物理结构上也是线性的,因为它的底层是数组。

顺序表和数组的区别?

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

2.2 分类

顺序表是对数组进行封装,所以我们就要使用结构体。

2.2.1 静态顺序表

 静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费

2.2.2 动态顺序表

2.3 动态顺序表的实现

2.3.1 动态顺序表的初始化
//顺序表的初始化
void SLInit(SL* ps)
{
	//初始化顺序表的指针置为空
	ps->arr = NULL;
	//有效元素的个数和空间大小都为0
	ps->capacity = ps->size = 0;
}

问题1:

这张图片的问题就在于初始化的时候是传值调用,此时我们的s都没有初始化,因此传值调用的时候会报错,未初始化的变量s,其次传值调用形参的改变不会影响实参,所以要想影响我们的s,就必须传s的地址,这样初始化方法才有用,循序表的头文件中的函数定义也需要修改。

总结:函数传参,传值调用的时候,形参是实参的一份临时拷贝,指向的是两块不同的地址,但保存的诗句是一样的,改变形参并不影响实参,所以我们以后在函数传参的时候,要想形参影响实参的话,就必须传地址。 

2.3.2 动态顺序表的销毁

代码:

//顺序表的销毁
//因为顺序表是动态内存开辟的,所以我们代码结束的时候要销毁
void SLDestroy(SL* ps)
{
	//判断顺序表底层数数组是否为空
	if (ps->arr)//ps->arr != NULL
	{
		//不为空就将底层数组释放
		free(ps->arr);
		//将底层数组置为空,否则会变为野指针
		ps->arr = NULL;
	}
	//有效元素的个数和内存的大小都置为0
	ps->capacity = ps->size = 0;
}

动态顺序表是我们用free来申请扩容的空间,虽然我们不释放,代码结束操作系统也会释放,但是如果代码很多,这块空间不用了,但是代码又不结束,还是需要手动释放,否则长此以往,会浪费很多空间,直到空间耗尽。

2.3.2 动态顺序表的尾插
//扩容
void SLCheckCapacity(SL* ps)
{
	//如果有效元素个数和空间一样大,则说明需要申请空间
	if (ps->capacity == ps->size)
	{
		//这里就是需要增容多大的空间
		/*
		因为我们的ps->capacity初始化是0,所以不能直接传参ps->capacity*sizeof(SL)
		这样算出的结果还是0,所以我们要用三目表达式来判断一下ps->capacity是否为0
		如果为0newCapacity就是4,否则就是以2倍扩容
		*/
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		//需要增容,所以要用realloc
		/*
		这里需要创建临时变量来接收realloc返回的值,因为realloc申请失败会返回NULL
		一旦我们用ps->arr指针来接收,并且realloc返回空指针,那么原来的地址野找不到了
	  realloc给空地址扩容也是可以的,旧相当于申请空间,所以就算ps->arr是空指针,也是
	  可以进行扩容的 - 注意realloc的传参
		*/
		SLDataType* newArr = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		//判断realloc的返回值
		if (newArr == NULL)
		{
			//打印申请失败的原因
			perror("malloc fail!");
			//退出
			exit(1);
		}
		//将扩容后的地址赋值给顺序表
		ps->arr = newArr;
		//将空间大小赋值给顺序表的capacity成员
		ps->capacity = newCapacity;
	}
}


//尾部插入数据
void SLPushBack(SL* ps, SLDataType x)
{
	//不能传空指针 - 表达式为真继续往下执行,表达式为假,报错
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	//插入
	*(ps->arr + ps->size++) = x;
    //上面代码拆解得到下面代码
	/*
	*(ps->arr + ps->size) = x;
	ps->size++;
	*/
}

常见问题:

1.扩容问题

2.realloc的传参

realloc传参最后一个参事的单位是字节,所以我们确定好申请的空间个数还要乘数据类型的大小。

动态内存管理-CSDN博客文章浏览阅读672次,点赞7次,收藏27次。在学校计算机语言的时候是这样讨论的,学习操作系统的时候会变。C/C++程序内存分配的几个区域:1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限, 栈区主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等。《函数栈帧的创建和销毁》2. 堆区(heap):⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。https://blog.csdn.net/m0_74271757/article/details/140023654?spm=1001.2014.3001.5502详细可以看动态内存管理的3.2 realloc的使用方法。

3.对空指针解引用的问题

从调试可以看出代码如果传NULL,该函数会出问题,因为不能对空指针进行解引用操作。所以我们要对空指针做处理或者直接加上断言。 

调试结果:

我们的头插也是需要判断空间是否足够的,所以我们再尾插写的判断空间是否足够的代码可以封装成一个函数。 

2.3.3 动态顺序表的打印

代码:

//打印顺序表
//可以传地址也可以不传地址,因为只是遍历顺序表,不需要修改实参。
void SLPrint(SL* ps)
{
	//遍历顺序表
	for (int i = 0; i < ps->size; i++)
	{
		//打印
		printf("%d ", *(ps->arr+i));
	}
	//最后来个换行,好看
	printf("\n");
}

为了方便我们查看顺序表我们可以写一个顺序表的打印方法,这样就不通过调试看结果了。 

2.3.4 动态顺序表的头插

代码:

//头部插入数据
void SLPushFront(SL* ps, SLDataType x)
{
	//不能传空指针
	assert(ps);
	//判断空间是否足够
	SLCheckCapacity(ps);
	//插入 - 整体往后移动
	for (int i = ps->size; i > 0; i--)
	{
		*(ps->arr + i) = *(ps->arr + i - 1);//*(ps->arr+1) = *(ps->arr+0)
		//ps->arr[i] = ps->arr[i - 1];
	}
	//在第一个位置插入要插入的值
	*ps->arr = x;
	//有效元素个数自增
	ps->size++;
}

输出结果:

2.3.5 动态顺序表的尾删

代码:

//尾部删除数据
void SLPopBack(SL* ps)
{
	//传空指针以及顺序表为空是不能执行删除操作的
	assert(ps && ps->size);
	ps->size--;
}

 输出结果:

顺序表为空不能进行删除操作,因为代码中加了assert断言。 

2.3.6 动态顺序表的头删

代码:

//头部删除数据
void SLPopFront(SL* ps)
{
	//传空指针以及顺序表为空是不能执行删除操作的
	assert(ps && ps->size);
	//数据整体往前移动
	for (int i = 0; i < ps->size-1; i++)
	{
		*(ps->arr + i) = *(ps->arr + i + 1);
	}
    //有效元素个数自减
	ps->size--;
}

输出结果:

2.3.7 顺序表指定位置插入 

代码:

//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	//不能传空指针以及pos的有效范围
	assert(ps && pos >= 0 && pos <= ps->size);
	//判断空间是否足够
	SLCheckCapacity(ps);
	//pos以及pos之后的位置整体往后挪动
	for (int i = ps->size; i > pos; i--)
	{
		*(ps->arr + i) = *(ps->arr + i - 1);
	}
	//顺序表的pos位置赋值为要插入的值
	*(ps->arr + pos) = x;
	//元素的有效个数自增
	ps->size++;
}

输出结果:

 

2.3.8 顺序表指定位置删除

代码:

//指定位置删除数据
void SLErase(SL* ps, int pos)
{
	//不能传空指针以及删除的范围必须有效
	assert(ps && pos >= 0 && pos < ps->size);
	//pos以后的数据整体往前挪动一位
	for (int i = pos; i < ps->size-1; i++)
	{
		*(ps->arr + i) = *(ps->arr + i + 1);//*(ps->arr + pos) 
	}
	//有效元素个数自减
	ps->size--;
}

思路: 

输出结果:

 2.3.9 顺序表的查找

代码:

//查找顺序表中的数据
int SLFind(SL* ps, SLDataType x)
{
	//不能传空指针以及顺序表为空不能查找
	assert(ps && ps->size);
	//遍历顺序表
	for (int i = 0; i < ps->size; i++)
	{
		if (*(ps->arr + i) == x)
		{
			return i;
		}
	}
	return -1;
}

思路很简单,遍历顺序表,如果有要找的值就返回下标,否则返回-1,因为下标没有负数。

输出结果:

2.4 顺序表算法题

2.4.1 移除元素

顺序表算法 - 移除元素-CSDN博客文章浏览阅读28次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。从上面的代码我们可以看出不管nums[src]是否等于val,dest都要++,那么我们的代码就可以优化一下。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140413018?spm=1001.2014.3001.5502

2.4.2 删除有序数组中的重复项

顺序表算法 - 删除有序数组重复项-CSDN博客文章浏览阅读117次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。上面代码我们可以看到不管dest位置的数据和src位置的数据是否一样,dest都需要++,所以我们还可以优化代码。题目中的非严格递归意思就是这个数到下个数不是递归就是相等,也就是说重复的数组是连许的,中间不可能有其它数字。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140415421?spm=1001.2014.3001.5501

2.4.3 合并两个有序数组

顺序表算法 - 合并两个有序数组-CSDN博客文章浏览阅读25次。88. 合并两个有序数组 - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140420591?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22140420591%22%2C%22source%22%3A%22m0_74271757%22%7D

2.5 顺序表问题与思考

顺序表我们已经实现完成了,最后我们来看看顺序表有哪些问题呢?

1.中间/头部的插入删除,时间复杂度为O(N),这个复杂度相较于尾插的O(1)来说要差一些。

2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3.增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入5个数据,后面就没有数据插入了,那么就浪费了95个数据空间。

那么如何解决上面的问题呢?

让头部/中间位置的插入和删除,时间复杂度降到O(1),减少或者避免增容带来的性能的消耗,以及避免空间浪费,链表就可以解决这类问题。

2.6 顺序表的代码

Elementary data structure: Data structure code and blackboard writing - Gitee.comicon-default.png?t=N7T8https://gitee.com/Axurea/elementary-data-structure/tree/master/SeqListTest

2.7 顺序表参考博客 

顺序表专题-CSDN博客文章浏览阅读1k次,点赞30次,收藏29次。数据结构是由“数据”和“结构”两词组合而来。什么是数据?常见的数值1、2、3、4.....、网页肉眼眼可以看到的信息(文字、图片、视频等等),这些都是数据。什么是结构?当我们想要使用大量使用同⼀类型的数据时,通过手动定义⼤量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在⼀起,结构也可以理解为组织数据的方式。概念:数据结构是计算机存储,组织数据的方式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。https://blog.csdn.net/m0_74271757/article/details/139769856?spm=1001.2014.3001.5502顺序表的应用-CSDN博客文章浏览阅读743次,点赞17次,收藏6次。3. 增容⼀般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。通讯录的实现是基于顺序表的,所有通讯录的底层还是顺序表,只不过顺序表的存储类型是int整型类型,而现在我要存储结构体类型。针对顺序表:中间/头部插入效率dixia,增容降低代码运行效率,增容造成空间浪费,我们可以通过另一种数据结构:链表来解决。2. 增容需要申请新空间,拷贝数据,释放旧空间。1.至少能够存储100个⼈的通讯信息。https://blog.csdn.net/m0_74271757/article/details/139843485?spm=1001.2014.3001.5502

3. 单链表

3.1 单链表博客以及代码

单链表专题-CSDN博客文章浏览阅读783次,点赞13次,收藏21次。从图我们可以看出,每个节点由两部分组成, 第一个节点的第一部分存储的是整型1,第二部分存储的是一个地址,这个地址就是第二个节点的地址,那第二个节点的第一部分存储的是整型2,也就是数据,第二个部分也是存储的第三个节点的地址,以此类推,所以节点的组成是由数据和指向下一个节点的指针两部分组成。该函数的第二个需要的参数是链表节点的地址,而我们的查找函数刚好返回的就是查到的某个节点的地址,所以就可以用查找函数的返回值作为第二个参数的实参。火车是由一个一个的车厢组成,链表是由一个一个的节点组成。https://blog.csdn.net/m0_74271757/article/details/140235441?spm=1001.2014.3001.5502

3.2 单链表算法题

3.2.1 移除链表元素

单链表算法 - 移除链表元素-CSDN博客文章浏览阅读2次。从代码调试可以看到,5这个节点的next指针一直指向6这个节点,就算函数执行完成后也并没有把5这这个节点的next指针置为NULL,所以在打印的时候还是会打印出6。. - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140422976?spm=1001.2014.3001.5502

3.2.2 反转链表

单链表算法 - 反转链表-CSDN博客文章浏览阅读98次。206. 反转链表 - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140439678?spm=1001.2014.3001.5501

3.2.3 链表的中间节点

单链表算法 - 链表的中间节点-CSDN博客文章浏览阅读281次,点赞7次,收藏3次。这里输出3,4,5是因为这是链表,返回的时候返回的是3这个节点的地址,3节点的next指针指向的是4节点,以此类推直到某个节点的next指针指向空的时候就不打印。. - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。当我们把while循环中的两条表达式交换一下顺序,此时代码会有问题。建议不要修改while循环的顺序,这么小的细节找起来太难了。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140446236?spm=1001.2014.3001.5501

3.2.4 合并两个有序链表

单链表算法 - 合并两个有序链表-CSDN博客文章浏览阅读129次。当一个指针已经指向空的时候,我们只需要将另一个指针指向的节点插入到新的链表中就可以了,那么因为另一个指针指向的节点的next指针指向下一个节点,下一个节点的next指针指向下下一个节点,一次类推,直到空,所有我们只需将一个节点插入到新的链表中,其它一连串都会过来。我们可以看到,当我们一个指针走向空的时候我们要将另一个指针指向的剩下的所有数据插入到新链表中,如果剩下的数据多,那么就应该使用循环啊,为什么这里是if语句呢?. - 备战技术面试?. - 力扣(LeetCode)这里有效的减少了代码的冗余。https://blog.csdn.net/m0_74271757/article/details/140447842?spm=1001.2014.3001.5501

3.2.5 链表分割

单链表算法 - 链表分割-CSDN博客文章浏览阅读2次。现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的。题目来自【牛客题霸】当我们提交代码之后代码有问题,那么代码到底哪里的逻辑不合适呢?链表分割_牛客题霸_牛客网。https://blog.csdn.net/m0_74271757/article/details/140463290?spm=1001.2014.3001.5501

3.2.6 链表的回文结构

单链表算法 - 链表的回文结构-CSDN博客文章浏览阅读2次。除此之外,该思路只能在牛客上通过,若换成力扣,通过不了,因为如果明确规定空间复杂度为O(1)的话就不能额外的去申请空间,更别说申请900个整型了,牛客平台对时间和空间复杂度没有力扣那么严格。在代码中我们申请了900个整型的数组,因为我们知道链表最大是900个节点,这里的空间复杂度也是O(1),复合要求,那如果这里没有对链表节点个数进行限制,这种思路肯定是不行的。对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为。题目来自【牛客题霸】链表的回文结构_牛客题霸_牛客网。https://blog.csdn.net/m0_74271757/article/details/140467383?spm=1001.2014.3001.5501

3.2.7 相交链表

单链表算法 - 相交链表-CSDN博客文章浏览阅读2次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140473835?spm=1001.2014.3001.5501

3.2.8 环形链表I

单链表算法 - 环形链表I-CSDN博客慢指针每次走一步,快指针每次走三步,快指针和慢指针一定会相遇,快指针一次走4,5....步最终也会相遇。力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。既然快指针不管走几步和慢指针都会相遇,那么我们慢指针每也可以在代码中修改为次走一步,快指针每次走三步。虽然已经证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候。会有额外的步骤引入,涉及到快慢指针的算法题中通常习惯使用慢指针走一步快指针走两步。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140478031?spm=1001.2014.3001.5502

3.2.9 环形链表II

单链表算法 - 环形链表II-CSDN博客文章浏览阅读389次,点赞13次,收藏2次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。那么为什么相遇点和头节点到入环节点的距离是相等的。. - 力扣(LeetCode)https://blog.csdn.net/m0_74271757/article/details/140480989?spm=1001.2014.3001.5501

3.2.10 随机链表的复制

单链表算法 - 随机链表的复制-CSDN博客文章浏览阅读169次,点赞2次,收藏3次。- 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。. - 力扣(LeetCode)2.修改random指针的指向。1.创建新节点,插入到原链表中。3.复制链表和原链表断开。https://blog.csdn.net/m0_74271757/article/details/140496213?spm=1001.2014.3001.5501

4.双向链表

双向链表的介绍以及代码:

双向链表专题-CSDN博客文章浏览阅读105次,点赞2次,收藏5次。当跳出循环我们还可以看到plist指针还是指向那块空间,可是那块空间以及在销毁函数中被free掉了,被操作系统回收掉了,这是因为我们是值传递,形参的修改并不会影响实参,所以即使phead在函数中被置空,plist也不会被置空,此时plist变成了野指针,所以我们要在函数结束之后手动置空。当代码free完哨兵位之后,我们的链表中的节点以及被操作系统回收,指针也置为空,哨兵位也被回收,只不过还没置空,我们还可以看到phead和plist指向的是同一块地址空间。断点打在销毁函数之前,链表还是完整的。https://blog.csdn.net/m0_74271757/article/details/140509099?spm=1001.2014.3001.5501

相关推荐

  1. 顺序

    2024-07-19 05:34:05       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 05:34:05       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 05:34:05       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 05:34:05       62 阅读
  4. Python语言-面向对象

    2024-07-19 05:34:05       72 阅读

热门阅读

  1. junit mockito service

    2024-07-19 05:34:05       21 阅读
  2. MySQL为什么使用B+树而不是跳表?

    2024-07-19 05:34:05       20 阅读
  3. 前端代码审查大纲

    2024-07-19 05:34:05       20 阅读
  4. 解决xshell连接不上ubuntu首次安装的虚拟机问题

    2024-07-19 05:34:05       18 阅读
  5. 【Redis】基础用法

    2024-07-19 05:34:05       19 阅读
  6. 7.18文章分享

    2024-07-19 05:34:05       23 阅读
  7. 交易积累-OSC

    2024-07-19 05:34:05       21 阅读
  8. 【16】时间单位换算

    2024-07-19 05:34:05       19 阅读