链表

定义

链表通常跟数组对比来讲,最大不同从底层的存储结构上:数组需要连续的内存空间,对内存的要求比较高。而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来。

链表种类

单链表

头节点

尾节点,尾节点的next指针指向null。

双向链表

每个节点相比单链表都多存储了一个前驱指针,比较耗费内存。但是比单链表更便捷的插入和删除操作(单项链表插入和删除需要遍历两次链表,一次找到对应的值,第二次找到前驱指针,然后才能做插入或者删除操作。而双向链表,每个节点保持的前驱节点指针,直接就能找到前驱节点,更加高效),利用了空间换时间的设计思想

循环链表

尾节点的next指针指向头节点。

双向循环链表

涉及设计思想

空间换时间,如缓存设计

时间换空间

特点

  1. 链表跟数组一样,支持查找,插入和删除。但是由于其存储结构不像数组那样要求连续内存,本身就是零散存储,所以插入和删除不需要维护连续性,非常快速,时间复杂度为O(1)。
  2. 数组内存连续存储,借助CPU缓存机制,预读数组中的数据,所以访问更加高效。而链表分散存储,对cpu缓存机制并不友好。
  3. 数组动态扩容比较耗时,而链表本身没有大小限制,天然支持动态扩容。
  4. 数组仅存储数组区域,链表需要多存储指向指针,所以耗费的存储空间会翻倍。链表比数组更加多占用存储空间。对于空间很有要求的场景,适合选用数组。

缺点

不支持随机访问K个元素。因为地址不是连续存储,不像数组那样可以通过寻址公式(假设二维数组维度为m*n,则a[i][j]_adress = base_adress+(i*n+j)*type_size)直接定位到指定内存地址的值,需要通过首结点依次遍历,直到找到相应的节点。所以随机访问并不高效,时间复杂度O(n)。

时间复杂度

  链表 数组
插入,删除 O(1) O(n)
随机访问 O(n) O(1)

 

应用

LRU缓存淘汰策略

维护一个有序单链表,越靠近队尾位置,是越早被访问的数据。当有访问一个数据时,先遍历链表:

  1. 此链表存在链表中时,直接删除此节点,并把此节点移动到队首。
  2. 当此链表不存在此节点时:
  • 此链表缓存未满:直接将此数组插入到链表队首。
  • 此链表缓存已满:直接删除掉队尾结点,把新数据插入到队首结点。

单链表实现

package main

import (
	"fmt"
)

type LinkNode struct {
	next *LinkNode
	value interface{}
}

type LinkedList struct {
	head *LinkNode
	length uint
}

func NewLinkNode(v interface{}) *LinkNode {
	return &LinkNode{
		next:  nil,
		value: v,
	}
}

func (linkNode *LinkNode) GetNext() *LinkNode {
	return linkNode.next
}

func (linkNode *LinkNode) GetValue() interface{} {
	return linkNode.value
}

func NewLinkedList() *LinkedList {
	return &LinkedList{
		head:   NewLinkNode(0),
		length: 0,
	}
}

//在某个节点后面插入节点
func (this *LinkedList) InsertAfter(p *LinkNode, v interface{}) bool {
	if p == nil {
		return false
	}

	newNode 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值