初识Go语言25-数据结构与算法【堆、Trie树、用go中的list与map实现LRU算法、用go语言中的map和堆实现超时缓存】

文章目录

    • Trie树
      • 练习-用go中的list与map实现LRU算法
      • 练习-用go语言中的map和堆实现超时缓存


  堆是一棵二叉树。大根堆即任意节点的值都大于等于其子节点。反之为小根堆。
  用数组来表示堆,下标为 i 的结点的父结点下标为(i-1)/2,其左右子结点分别为 (2i + 1)、(2i + 2)。

在这里插入图片描述

构建堆

在这里插入图片描述

package main

import "fmt"

//AdjustTraingle 如果只是修改slice里的元素,不需要传slice的指针;如果要往slice里append或让slice指向新的子切片,则需要传slice指针
func AdjustTraingle(arr []int, parent int) {
	left := 2*parent + 1
	if left >= len(arr) {
		return
	}

	right := 2*parent + 2
	minIndex := parent
	minValue := arr[minIndex]
	if arr[left] < minValue {
		minValue = arr[left]
		minIndex = left
	}
	if right < len(arr) {
		if arr[right] < minValue {
			minValue = arr[right]
			minIndex = right
		}
	}
	if minIndex != parent {
		arr[minIndex], arr[parent] = arr[parent], arr[minIndex]
		AdjustTraingle(arr, minIndex) //递归。每当有元素调整下来时,要对以它为父节点的三角形区域进行调整
	}
}

func ReverseAdjust(arr []int) {
	n := len(arr)
	if n <= 1 {
		return
	}
	lastIndex := n / 2 * 2
	fmt.Println(lastIndex)
	for i := lastIndex; i > 0; i -= 2 { //逆序检查每一个三角形区域
		right := i
		parent := (right - 1) / 2
		fmt.Println(parent)
		AdjustTraingle(arr, parent)
	}
}

func buildHeap() {
	arr := []int{62, 40, 20, 30, 15, 10, 49}
	ReverseAdjust(arr)
	fmt.Println(arr)
}

  每当有元素调整下来时,要对以它为父节点的三角形区域进行调整。

插入元素

在这里插入图片描述

删除堆顶

在这里插入图片描述

下面讲几个堆的应用。
堆排序

  1. 构建堆O(N)。
  2. 不断地删除堆顶O(NlogN)。

求集合中最大的K个元素

  1. 用集合的前K个元素构建小根堆。
  2. 逐一遍历集合的其他元素,如果比堆顶小直接丢弃;否则替换掉堆顶,然后向下调整堆。

把超时的元素从缓存中删除

  1. 按key的到期时间把key插入小根堆中。
  2. 周期扫描堆顶元素,如果它的到期时间早于当前时刻,则从堆和缓存中删除,然后向下调整堆。
      golang中的container/heap实现了小根堆,但需要自己定义一个类,实现以下接口:
  • Len() int
  • Less(i, j int) bool
  • Swap(i, j int)
  • Push(x interface{})
  • Pop() x interface{}
type Item struct {
	Value    string
	priority int //优先级,数字越大,优先级越高
}

type PriorityQueue []*Item

func (pq PriorityQueue) Len() int {
	return len(pq)
}

func (pq PriorityQueue) Less(i, j int) bool {
	return pq[i].priority > pq[j].priority //golang默认提供的是小根堆,而优先队列是大根堆,所以这里要反着定义Less。定义的是大根堆
}

func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
}

//往slice里append,需要传slice指针
func (pq *PriorityQueue) Push(x interface{}) {
	item := x.(*Item)
	*pq = append(*pq, item)
}

//让slice指向新的子切片,需要传slice指针
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]   //数组最后一个元素
	*pq = old[0 : n-1] //去掉最一个元素
	return item
}
func testPriorityQueue() {
	pq := make(PriorityQueue, 0, 10)
	pq.Push(&Item{"A", 3})
	pq.Push(&Item{"B", 2})
	pq.Push(&Item{"C", 4})
	heap.Init(&pq)
	heap.Push(&pq, &Item{"D", 6})
	for pq.Len() > 0 {
		fmt.Println(heap.Pop(&pq))
	}
}
//&{D 6}
//&{C 4}
//&{A 3}
//&{B 2}

Trie树

  trie树又叫字典权。
  现有term集合:{分散,分散精力,分散投资,分布式,工程,工程师},把它们放到Trie树里如下图:

在这里插入图片描述

  Trie树的根节点是总入口,不存储字符。对于英文,第个节点有26个子节点,子节点可以存到数组里;中文由于汉字很多,用数组存子节点太浪费内存,可以用map存子节点。从根节点到叶节点的完整路径是一个term。从根节点到某个中间节点也可能是一个term,即一个term可能是另一个term的前缀。上图中红圈表示从根节点到本节点是一个完整的term。

package main

import "fmt"

type TrieNode struct {
	Word     rune               //当前节点存储的字符。byte只能表示英文字符,rune可以表示任意字符
	Children map[rune]*TrieNode //孩子节点,用一个map存储
	Term     string
}

type TrieTree struct {
	root *TrieNode
}

//add 把words[beginIndex:]插入到Trie树中
func (node *TrieNode) add(words []rune, term string, beginIndex int) {
	if beginIndex >= len(words) { //words已经遍历完了
		node.Term = term
		return
	}

	if node.Children == nil {
		node.Children = make(map[rune]*TrieNode)
	}

	word := words[beginIndex] //把这个word放到node的子节点中
	if child, exists := node.Children[word]; !exists {
		newNode := &TrieNode{Word: word}
		node.Children[word] = newNode
		newNode.add(words, term, beginIndex+1) //递归
	} else {
		child.add(words, term, beginIndex+1) //递归
	}
}

//walk words[0]就是当前节点上存储的字符,按照words的指引顺着树往下走,最终返回words最后一个字符对应的节点
func (node *TrieNode) walk(words []rune, beginIndex int) *TrieNode {
	if beginIndex == len(words)-1 {
		return node
	}
	beginIndex += 1
	word := words[beginIndex]
	if child, exists := node.Children[word]; exists {
		return child.walk(words, beginIndex)
	} else {
		return nil
	}
}

//traverseTerms 遍历一个node下面所有的term。注意要传数组的指针,才能真正修改这个数组
func (node *TrieNode) traverseTerms(terms *[]string) {
	if len(node.Term) > 0 {
		*terms = append(*terms, node.Term)
	}
	for _, child := range node.Children {
		child.traverseTerms(terms)
	}
}

func (tree *TrieTree) AddTerm(term string) {
	if len(term) <= 1 {
		return
	}
	words := []rune(term)

	if tree.root == nil {
		tree.root = new(TrieNode)
	}

	tree.root.add(words, term, 0)
}

func (tree *TrieTree) Retrieve(prefix string) []string {
	if tree.root == nil || len(tree.root.Children) == 0 {
		return nil
	}
	words := []rune(prefix)
	firstWord := words[0]
	if child, exists := tree.root.Children[firstWord]; exists {
		end := child.walk(words, 0)
		if end == nil {
			return nil
		} else {
			terms := make([]string, 0, 100)
			end.traverseTerms(&terms)
			return terms
		}
	} else {
		return nil
	}
}

func main() {
	tree := new(TrieTree)
	tree.AddTerm("分散")
	tree.AddTerm("分散精力")
	tree.AddTerm("分散投资")
	tree.AddTerm("分布式")
	tree.AddTerm("工程")
	tree.AddTerm("工程师")

	terms := tree.Retrieve("分散")
	fmt.Println(terms)
	terms = tree.Retrieve("人工")
	fmt.Println(terms)
}

练习-用go中的list与map实现LRU算法

type LRUCache struct {
	cache map[int]int
	lst   list.List
	Cap   int // 缓存容量上限
}

func NewLRUCache(cap int) *LRUCache {
	lru := new(LRUCache)
	lru.Cap = cap
	lru.cache = make(map[int]int, cap)
	lru.lst = list.List{}
	return lru
}

func (lru *LRUCache) Add(key, value int) {
	if len(lru.cache) < lru.Cap { //还未达到缓存的上限
		// 直接把key value放到缓存中
		lru.cache[key] = value
		lru.lst.PushFront(key)
	} else { // 缓存已满
		// 先从缓存中淘汰一个元素
		back := lru.lst.Back()
		delete(lru.cache, back.Value.(int))
		lru.lst.Remove(back)
		// 把key value放到缓存中
		lru.cache[key] = value
		lru.lst.PushFront(key)
	}
}

func (lru *LRUCache) find(key int) *list.Element {
	if lru.lst.Len() == 0 {
		return nil
	}
	head := lru.lst.Front()
	for {
		if head == nil {
			break
		}
		if head.Value.(int) == key {
			return head
		} else {
			head = head.Next()
		}
	}
	return nil
}

func (lru *LRUCache) Get(key int) (int, bool) {
	value, exists := lru.cache[key]
	ele := lru.find(key)
	if ele != nil {
		lru.lst.MoveToFront(ele)
	}
	return value, exists
}

func testLRU() {
	lru := NewLRUCache(10)
	for i := 0; i < 10; i++ {
		lru.Add(i, i) // 9 8 7 6 5 4 3 2 1 0
	}

	for i := 0; i < 10; i += 2 {
		lru.Get(i) // 8 6 4 2 0 9 7 5 3 1
	}

	for i := 10; i < 15; i++ {
		lru.Add(i, i) //14 13 12 11 10 8 6 4 2 0
	}

	for i := 0; i < 15; i += 3 {
		_, exists := lru.Get(i)
		fmt.Printf("key %d exists %t\n", i, exists)
	}
}

练习-用go语言中的map和堆实现超时缓存

type HeapNode struct {
	value    int //对应到map里的key
	deadline int //到期时间戳,精确到秒
}

type Heap []*HeapNode

func (heap Heap) Len() int {
	return len(heap)
}
func (heap Heap) Less(i, j int) bool {
	return heap[i].deadline < heap[j].deadline
}
func (heap Heap) Swap(i, j int) {
	heap[i], heap[j] = heap[j], heap[i]
}
func (heap *Heap) Push(x interface{}) {
	node := x.(*HeapNode)
	*heap = append(*heap, node)
}
func (heap *Heap) Pop() (x interface{}) {
	n := len(*heap)
	last := (*heap)[n-1]
	//删除最后一个元素
	*heap = (*heap)[0 : n-1]
	return last //返回最后一个元素
}

type TimeoutCache struct {
	cache map[int]interface{}
	hp    Heap
}

func NewTimeoutCache(cap int) *TimeoutCache {
	tc := new(TimeoutCache)
	tc.cache = make(map[int]interface{}, cap)
	tc.hp = make(Heap, 0, 10)
	heap.Init(&tc.hp) //包装升级,从一个常规的slice升级为堆
	return tc
}

func (tc *TimeoutCache) Add(key int, value interface{}, life int) {
	//直接把key value放入map
	tc.cache[key] = value
	//计算出deadline,然后把key和deadline放入堆
	deadline := int(time.Now().Unix()) + life
	node := &HeapNode{value: key, deadline: deadline}
	heap.Push(&tc.hp, node)
}

func (tc TimeoutCache) Get(key int) (interface{}, bool) {
	value, exists := tc.cache[key]
	return value, exists
}

func (tc *TimeoutCache) taotai() {
	for {
		if tc.hp.Len() == 0 {
			time.Sleep(100 * time.Millisecond)
			continue
		}
		now := int(time.Now().Unix())
		top := tc.hp[0]
		if top.deadline < now {
			heap.Pop(&tc.hp)
			delete(tc.cache, top.value)
		} else { //堆顶还没有到期
			time.Sleep(100 * time.Millisecond)
		}
	}
}

func testTimeoutCache() {
	tc := NewTimeoutCache(10)
	go tc.taotai() //在子协程里面去执行,不影响主协程继续往后走

	tc.Add(1, "1", 1)
	tc.Add(2, "2", 3)
	tc.Add(3, "3", 4)

	time.Sleep(2 * time.Second)

	for _, key := range []int{1, 2, 3} {
		_, exists := tc.Get(key)
		fmt.Printf("key %d exists %t\n", key, exists) //1不存在,2 3还存在
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/34990.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

性能优化 :删除项目中没有引用关系的文件 useless-files-webpack-plugin

一般此类包不需要安装到项目中&#xff0c;减少node_modules体积&#xff08;以项目实际情况决定-S/-D&#xff09; npm i useless-files-webpack-plugin -S然后再vue.config.js中配置 const UselessFile require(useless-files-webpack-plugin) chainWebpack: config > …

【NLP模型】文本建模(2)TF-IDF关键词提取原理

一、说明 tf-idf是个可以提取文章关键词的模型;他是基于词频,以及词的权重综合因素考虑的词价值刻度模型。一般地开发NLP将包含三个层次单元:最大数据单元是语料库、语料库中有若干文章、文章中有若干词语。这样从词频上说,就有词在文章的频率,词在预料库的频率,文章在预…

SpringBoot3.0整合RocketMQ时出现未能加载bean文件

SpringBoot3.0整合RocketMQ时出现未能加载bean文件 问题 APPLICATION FAILED TO START Description: Field rocketMQTemplate in com.spt.message.service.MqProducerService required a bean of type ‘org.apache.rocketmq.spring.core.RocketMQTemplate’ that could not …

window debug ios webview

业务需求 在window上想要debug在ios的应用中的webview页面&#xff0c;毕竟页面是在安卓端和ios端都有webview的。安卓的页面使用edge的edge://inspect/#devices&#xff0c;手机开启调试模式就可以了。对于ios就没有办法&#xff0c;页面中已经使用了vconsole可以看到部分的信…

火山引擎 Iceberg 数据湖的应用与实践

在云原生计算时代&#xff0c;云存储使得海量数据能以低成本进行存储&#xff0c;但是这也给如何访问、管理和使用这些云上的数据提出了挑战。而 Iceberg 作为一种云原生的表格式&#xff0c;可以很好地应对这些挑战。本文将介绍火山引擎在云原生计算产品上使用 Iceberg 的实践…

XXX汽车ERP系统供应商索赔业务上线,助力业财数据快速闭环(投稿数据化月报四)

供应商三包索赔款项源起QMS质量系统&#xff0c;联动金税系统完成发票开具&#xff0c;最终在SAP系统中创建完成财务凭证。该流程上线前为手工操作&#xff0c;费时费力且效率低下容易出错。针对该业务现状&#xff0c;SAP与QMS业务顾问及开发团队组成开发小组&#xff0c;导入…

使用matplotlib制作动态图

使用matplotlib制作动态图 一、简介二、模块简介1. **FuncAnimation**类介绍2. 定义动画更新函数 三、使用matplotlib制作动画1.一步法制作动态图片2. 两步法制作动态图片 一、简介 matplotlib(https://matplotlib.org/)是一个著名的python绘图库&#xff0c;由于其灵活强大的…

计算机视觉 + Self-Supervised Learning 五种算法原理解析

计算机视觉领域下自监督学习方法原理 导语为什么在计算机视觉领域中进行自我监督学习&#xff1f; 自监督学习方法Generative methodsBEiT 架构 Predictive methodsContrastive methodsBootstraping methodsSimply Extra Regularization methods 导语 自监督学习是一种机器学习…

SQL Server SQL语句

在很多情况下&#xff0c;可以用CREATE TABLE语句创建数据表、使用ALTER TABLE语句修改表结构、使用DROP TABLE语句删除表&#xff1b; 可以使用CREATE DATABASE创建数据库、ALTER DATABASE修改文件或文件组、DROP DATABASE语句删除数据库&#xff1b; 1、数据定义语句&#x…

【MySQL】MySQL基本语句大全

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️MySQL】 文章目录 前言结构化查询语句分类MySQL语句大全&#x1f4da;DDL&#xff08;对数据库和表的操作&#xff09;&#x1f916;DQL&#xff08;查询语句&#xff09;&#x1f4bb;关键字&#x…

AI最新开源:LMSYS Org开源LongChat、法律大语言模型ChatLaw、中文医疗对话模型扁鹊

一周SOTA&#xff1a;LMSYS Org开源LongChat、法律大语言模型ChatLaw、中文医疗对话模型扁鹊 文章目录 1. LMSYS Org发布LongChat&#xff0c;上下文碾压64K开源模型2. 北大团队发布法律大模型 ChatLaw3. 扁鹊&#xff1a;指令与多轮问询对话联合微调的医疗对话大模型 1. LMSY…

目标检测的评估指标

Precision(精确率/查准率)&#xff1a;是指在所有被预测为正的样本中&#xff0c;确实是正样本的占比。当Precision越大时&#xff0c;FP越小&#xff0c;此时将其他类别预测为本类别的个数也就越少&#xff0c;可以理解为预测出的正例纯度越高。Precision越高&#xff0c;误检…

使用 Jackson 库对日期时间的动态序列化反序列化操作

0.背景 因某项目中的数据报表功能在创建年报 和月报时需要生成不同的日期格式&#xff0c;但数据结构未变&#xff0c;为避免类的冗余定义&#xff0c;故使用如下方式来动态设置日期格式&#xff0c;在不同报表是使用不同格式的时间格式来保存数据。 1.代码介绍 PS:此介绍有Cha…

Quiz 12: Regular Expressions | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Regular Expressions单选题&#xff08;1-8&#xff09;操作题Regular Expressions Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;Python 入门&#xff09; This course aims to teach everyone the …

OpenCV——分水岭算法

目录 一、分水岭算法1、概述2、图像分割概念3、分水岭算法原理 二、主要函数三、C代码四、结果展示1、原始图像2、分割结果 五、参考链接 一、分水岭算法 1、概述 分水岭算法是一种图像分割常用的算法&#xff0c;可以有效地将图像中的目标从背景中分离出来。本文以OpenCV库中…

神坑:ElasticSearch8集群启动报错“Device or resource busy”(Docker方式)

昨天在Docker中配置ElasticSearcch8集群模式时&#xff0c;先初步配置了master主节点。然后主节点启动就报错&#xff0c;看日志&#xff0c;提示“Device or resource busy”。异常第一句大概这个样子&#xff1a; Exception in thread "main" java.nio.file.FileS…

【ARIMA-WOA-CNN-LSTM】合差分自回归移动平均方法-鲸鱼优化-卷积神经网络-长短期记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis优化

目录 一、Redis高可用 二、Redis持久化 1.RDB持久化 1.1触发条件 1.1.1手动触发 1.1.2自动触发 1.2其他自动触发机制 1.3执行流程 1.4启动时加载 2.AOF 持久化 2.1开启AOF 2.2执行流程 2.2.1命令追加(append) 2.2.2文件写入(write)和文件同步(sync) 2.2.3文件重…

docker-compose实现微服务jar+mysql的容器服务发布(经典版)

一 安装mysql服务 1.1 拉取镜像 1.拉取&#xff1a; docker pull mysql:5.7.29 2.查看镜像&#xff1a; docker images 1.2 在宿主机创建文件存储mysql 1.创建映射目录&#xff1a;mysql-c5 在/root/export/dockertest 目录下&#xff0c;mkdir -p mysql-c5 &#…

SpringBoot实战(十九)集成Ribbon

目录 一、负载均衡的分类1.服务端负载均衡2.客户端负载均衡 二、定义和依赖1.Ribbon2.Spring Cloud Ribbon3.Spring Cloud Loadbalancer 三、搭建测试项目1.Maven依赖2.yaml配置3.配置类4.启动类5.接口类 四、测试五、补充&#xff1a;认识 Ribbon 的组件 一、负载均衡的分类 …