想给老衲说:数组和链表放在一起研究,能对比两种数据结构的优点与不足!
数组不陌生吧!数组一般来说都是内存里连续的一段存储区域。
最左边这幅图,0~8表示的是数组的下标,咱们可以通过数组下标随机访问这个数组中任意一个元素!右边这里面8位的一串数字这里是内存地址(这里只是示例,真正的可能还会涉及到虚拟内存,寻址算法)!
右边这幅图指的意思是,Memory Controller(内存管理器)可以通过内存管理器实现对任何下标位置的数组元素进行访问。因此访问任何一个数组元素的时间复杂度是O(1),因为他通过内存管理器通过一次操作就可以拿到元素的值。
数组是内存里一段连续的存储区域!
如下图,要把D插入到下标为3的位置处,需要分别挪动E,F,G元素,然后把D插入进去!也就是时间复杂度的话并不是O(1)的。是挪动要插入位置后面的全部元素,因此是O(n)的时间复杂度。
这里在深入一下
1.最好插入情况下,刚好插在最屁股后面,时间复杂度是O(1)!
2.最坏插入情况下,刚好插在最前面,就需要挪动数组里面所有的元素,时间复杂度是O(n)
那么平均下来,插在最中间,时间复杂度为O(n/2),时间复杂度为O(n)的!(声明一下,虽然时间复杂度是O(N/2),在表示法里,一般n旁边的乘积是阿拉伯值的话就可以忽略的!
数组的删除跟上面的插入,是一样的情况,删除同样也需要挪动元素,以保证他们的顺序存储! 平均值也是O(N/2) ,最后时间复杂度也是O(N)
一般来说用于两种使用场景:
1.改善插入和删除操作,你想改善插入和删除,并且这两种操作用的较频繁,不想浪费时间
因为跟数组对比的话:链表的插入和删除都是O(1)的,数组的话是O(N).这也就是链表的优势!
2. 不知道有多少个元素存在 ,来一个元素咱就往后面插一个
相比较于数组,数组刚开始就得知道元素的大小,针对于这种情况下不适用!
单链表:从图中可以看出是由一个一个的结点通过next指针串起来,形成一个表,这就是单链表!
链表单个结点一般结构为:数据域和指针域
数据域:图中用value来表示,可以存放任何类型的数据(线程池、内存池结构实现方式)
指针域:不知道老衲对指针是否了解,如果不了解在数据结构中寸步难行的!
简单介绍一下指针:
老衲你知道变量是什么吗?数据是什么?看最下面那张图
其实在数据是什么那篇博客中提到了,变量是存储数据的容器。但是没说仔细了。
int a = 5; int b = 2; int c = 0;
变量就是图中a,b,c,它存储的数据就分别是5,2,0。变量是用来存放东西的,那么肯定能存放地址,专门存放两一个变量的地址,这个变量就叫指针变量!
struct List{ int data; //数据域 List* next; //指针域 }Node;
那么,上图中指针域,List* 表示变量的类型,是链表结点的类型,因此啊,next就是一个用来存放链表结点的指针变量。怎么存?
现在有结点 a,b a.next = &b; //这就表示结点a有个指针变量存放了一个结点的地址,这个节点地址是b
要插入新的结点,需要两步:因为是2步,所以时间复杂度是O(1),如果是100步他也是O(1),因为1,2,3,4~100这是常数,O(1)表示常数时间复杂度
删除操作的话,也是分为两步:
注意的是,释放后的target node结点要释放
双链表只是比单链表再多了一个指针域
单个结点的表现形式为:
struct List{ int data; //数据域 List* next; //下一个节点的指针域 List* prev; //前一个节点的指针域 };
数组和链表的话各有优缺点,可以因为具体使用到的场景,来挑选不同的数据结构!
数组:查找O(1)比链表快,但是插入和删除都是O(N)的,就比链表慢很多了
链表:查找O(n)他需要从头挨个遍历,才能找到,不能通过下标来查询。
双向链表的改善了单链表的查找效率,因为他它具有遍历前面结点和后面结点的能力,可以使用二分查找等方式!
结束语:老衲,你理解了吗?注意多联系链表的插入节点和删除操作,就2步不可逆的!