前言
链表指多个不连续的最小存储单元所构成的集合。链表会记录起始最小存储单元的下标(start)与结束最小存储单元的下标(end)。链表解决了数组的增删缺陷,同时能提高总空间利用率,但同时也带来了查询开销的增大的问题。
下图中w,q,e为数组、e为链表。
原理
链表实际上是通过记录元素之间的关系来完成整个数据结构的搭建。
伪码
List 为双向链表数据结构,ListData为数据“链”,其中有操作函数removeListData删除一个“链”,insertListData在一个“链”后插入一个“链”,findIndexListData根据下标查找“链”,push往后面增加一个元素。
ListData
{
data;
next;
previous;
}
List
{
start;
end;
removeListData(listData)
{
listData.previous.next= listData.next;
listData.next.previous = listData.previous;
}
insertListData(listData,data)
{
newData = new listData();
newData.data= data;
newData.previous = listData;
newData.next = listData.next;
newData.previous.next = newData;
newData.next.previous = newData;
}
findIndexListData(index)
{
forData= start;
add=0;
while(add!=index)
{
add++;
forData = forData.next;
}
return forData;
}
push(data)
{
insertListData(end,data);
}
}
数据基本操作
设:“链”的下一个“链”为next,上一个“链”为previous。
增
例:
如上图,要在“链”8的位置插入一个“链”,只需要改变“链”8的next, 和“链”10的previous,与“链”9的next,previous 即可完成插入。
即已知插入位置“链”I,与插入“链”K,那么I.next.previous=K; K.next = I.next; I.next = K ; K.previous = I, 即可完成新增操作,所以链表在任意位置增加插入一个“链”,开销为O(1)。
删
例:
如上图,要删除“链”8的,只需要改变“链”6的next, 和“链”10的previous 即可完成删除。
已知删除位置“链” I,那么I.next.previous = I.previous ; I.previous.next =I.next 即可完成删除操作,所以链表删除任意位置的“链”,开销为O(1)。
查
例子:
如上图,已知第一个“链”、start的下标为12,第三个“链”的下标,无法直接计算得到,需要遍历前两个“链”
在链表中,已知start“链”的下标,无法直接计算得到第N个“链”的下标,需要遍历1 - N之间的所有“链”,所以查询第N个“链”需要进行N次循环,则开销为O(N)。
改
改变一个“链”时需要先查询,修改的成本为O(1),则总成本为O(1)+O(查) ,所以修改一个“链”的开销和查找性能相当。
后语
1.链表本质和数组的区别,一个是连续,一个是不连续的最小存储单元集合,在数组上通过连续这个信息,来规定了最小存储单元之间的关系,可以直接通过偏移值算出存储位置,同时因为要保持连续,所以数组的增删开销较大。链表使用next和previous记录了最小存储单元之间的关系,可以直接删除或新增,同时因为这种是记录信息,需要处理这个信息,所以链表的查开销较大。
2.数组这种数据结构的信息,实际上是已知获取,每个最小存储单元之间的关系都是确定的,且可直接计算的(运算过程无需别的最小存储单元参与)。
3.链表这种数据结构的信息,实际上是 计算获取,每个最小存储单元之间的关系,必须通过循环计算才能得到(运算过程中需要别的最小存储单元参与)。
4.在性能总结中,总会说道一个概念,即空间换时间,为什么链表使用了更多的空间,却换来了更差的性能。因为链表中记录的信息它是一个中间信息,并不是一个结果信息(无法直接推测其余“链”与当前“链”的关系),需要结果时,还需要进行关联计算,且这个关联的信息究竟有多少也是未知的(在链表中已知一个“链”要获取相对于这个“链”的第三个“链”,需要遍历前两个“链”才能拿到第三个“链”的地址),且链表的建立是在破坏数组的已知条件的基础上进行的。