链表
链表与数组的对比
- 存储
- 数组需要一块连续的内存空间来存储
- 链表不需要一块连续的内存空间,而是通过“指针”将一组零散的内存块串联起来
链表结构
单链表
- 结点
- 链表的每个结点,除了存储数据,还需要记录链上下一个结点的地址
- 后继指针 next
- 记录下一个结点地址的指针叫做后继指针
- 头节点
- 记录链表的基地址,可以通过基地址遍历得到整条链表
- 尾结点
- 尾结点的指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链上的最后一个
链表的操作
- 支持数据的查找、插入和删除操作
- 在链表中插入和删除一个数据的时间复杂度是O(1)
- 链表查找需要O(n)的时间复杂度
双向链表
双向链表是一种特殊的单链表。跟单链表的区别是尾结点。
单链表的尾结点指向空地址,表示为最后的节点。
循环链表的尾结点指针指向链表的头节点。
循环链表
单链表只有一个方向,节点只有一个后继指针指向后面的节点。
双向链表有两个方向,每个结点除了一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的节点。
应用场景
LRU缓存淘汰算法
-
链表的一个经典应用场景是LRU缓存淘汰算法
缓存淘汰算法用来解决在有限大小的缓存内,当缓存用满时,数据的清理策略。常见的缓存淘汰策略有三种:- 先进先出策略FIFO(First In, First Out)
- 最少使用策略LFU(Least Frequently Used)
- 最近最少使用策略LRU(Least Recently Used)
-
我们通过力扣上的一道题来看如何应用
Leetcode LRU缓存机制 -
GO实现
package main
import "fmt"
//LRU HashLink
type LRUCache struct {
hashmap map[int]*LinkNode
LinkList LinkList
}
//链表
type LinkList struct {
head *LinkNode//指向第一个节点,通过遍历节点next到所有节点
tail *LinkNode//指向最后一个节点,通过遍历prev到所有节点
size int
capacity int
}
//链表节点
type LinkNode struct {
key int//对应map中的key
value int
prev *LinkNode
next *LinkNode
}
func Constructor(capacity int) LRUCache {
var LRUCache LRUCache
LRUCache.InitLink(capacity)
return LRUCache
}
func (this *LRUCache) Get(key int) int {
if(this.hashmap[key]==nil) {
return -1
}
//放到最新
this.MakeNodeRecenty(key)
return this.hashmap[key].value
}
func (this *LRUCache) Put(key int, value int) {
if(this.hashmap[key]!=nil) {
this.RemoveLink(key)
}
this.AddLink(key,value)
}
//初始化链表
func (l *LRUCache) InitLink(capacity int) {
l.hashmap = make(map[int]*LinkNode)
head := &LinkNode{}
tail := &LinkNode{}
l.LinkList.head = head
l.LinkList.tail = tail
l.LinkList.head.next = tail
l.LinkList.tail.prev = head
l.LinkList.size = 0
l.LinkList.capacity = capacity
}
//添加链表节点
//添加到链表尾部
//尾部节点即是最新的节点
func (l *LRUCache) AddLink(key int, value int) {
//存储已满,删除旧的一个节点
if(l.LinkList.size>=l.LinkList.capacity) {
l.RemoveLastLink()
}
newNode := &LinkNode{}
newNode.key = key
newNode.value = value
l.LinkList.size++
l.hashmap[key] = newNode
}
//将节点放到最新位置
func (l *LRUCache) MakeNodeRecenty(key int) {
node := l.hashmap[key]
value := node.value
l.RemoveLink(key)
l.AddLink(key, value)
}
//删除指定key
func (l *LRUCache) RemoveLink(key int) {
node := l.hashmap[key]
node.prev.next = node.next
node.next.prev = node.prev
l.hashmap[key] = nil
l.LinkList.size--
}
//删除最旧的节点
//最旧的节点即是头部节点
func (l *LRUCache) RemoveLastLink() {
node := l.LinkList.head.next
key := node.key
node.next.prev = node.prev
node.prev.next = node.next
l.hashmap[key] = nil
l.LinkList.size--
}
func main() {
obj := Constructor(1)
obj.Put(2,1)
param_1 := obj.Get(2)
obj.Put(3,2)
param_2 := obj.Get(2)
param_3 := obj.Get(3)
fmt.Printf("%v", param_1)
fmt.Printf("%v", param_2)
fmt.Printf("%v", param_3)
}
链表反转
- 同样我们通过力扣上的一道题来看看
链表反转 - GO实现
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
if(head == nil || head.Next==nil) {
return head
}
current := head.Next
prev := head
prev.Next = nil
for current!=nil {
tmpnode := current.Next
current.Next = prev
prev = current
current = tmpnode
}
head = prev
return head
}