当前位置: 首页 > news >正文

一种基于堆的链式优先队列实现(使用golang)

前言

传统的堆使用的是数组实现方式。众所周知数组是定长的,因此给堆的实际使用带来了限制与不便。本文使用基于链式的实现方式。

原理

堆逻辑上是一棵完全二叉树。关于堆的知识不再赘述,可叁考其它博主的博文。
既然是树,使用 链式的实现方式不仅更加直观,而且支持任意长度的节点个树。
本文的树结点定义如下:

type _QueueNode[T interface{}] struct {
	parent *_QueueNode[T] // 父结点
	left   *_QueueNode[T] // 左孩子
	right  *_QueueNode[T] // 右孩子
	last   *_QueueNode[T] // 链表上一个结点
	next   *_QueueNode[T] // 链表下一个结点
	data   T              // 结点数据
}

结合上述定义和下图,可以看出,结点兼顾了两重身份:

  • 树结点 (如图中蓝线)
  • 链表结点(如图中红线)

之所以引入链表,是因为存粹的树结点不能找到与它同一层的其它结点,而堆的基本操作需要用到它些结点。例如:

  1. 在堆中加入一个结点时,需要为这个节点寻找一个父结点
  2. 在堆中删除堆首数据时,会与堆尾元素交换并把堆尾的结点删除,这时就需要能够改变 tail结点 为它的上一个结点
    在这里插入图片描述

代码

完整代码如下。本代码以优先队列为例进行实现:

package structure

/**
优先队列数据结构
最小堆要求,对于任意一个父结点来说,其子结点的值都大于这个父节点
这里实现链式堆
@author cloudea
@date 2022/9/10
*/

type _QueueNode[T interface{}] struct {
	parent *_QueueNode[T] // 父结点
	left   *_QueueNode[T] // 左孩子
	right  *_QueueNode[T] // 右孩子
	last   *_QueueNode[T] // 链表上一个结点
	next   *_QueueNode[T] // 链表下一个结点
	data   T              // 结点数据
}

type PriorityQueue[T interface{}] struct {
	head     *_QueueNode[T]              // 头节点
	tail     *_QueueNode[T]              // 尾结点
	length   int                         // 堆的长度
	lessThan func(data1 T, data2 T) bool // 比较函数
}

func NewPriorityQueue[T interface{}](lessThan func(data1 T, data2 T) bool) *PriorityQueue[T] {
	if lessThan == nil {
		panic("lessThan fun can not be nil!")
	}
	node := &_QueueNode[T]{}
	return &PriorityQueue[T]{
		head:     node,
		tail:     node,
		length:   0,
		lessThan: lessThan,
	}
}

func (this *PriorityQueue[T]) Empty() bool {
	return this.Size() == 0
}

func (this *PriorityQueue[T]) Size() int {
	return this.length
}

func (this *PriorityQueue[T]) Front() T {
	if this.Empty() {
		panic("heap is empty!")
	}
	return this.head.next.data
}

func (this *PriorityQueue[T]) Back() T {
	if this.Empty() {
		panic("heap is empty!")
	}
	return this.tail.data
}

func (this *PriorityQueue[T]) Enqueue(data T) {
	if this.Empty() {
		node := &_QueueNode[T]{
			parent: nil,
			data:   data,
		}
		this.head.next = node
		this.tail = node
	} else {
		// 把节点加入树中
		var parent *_QueueNode[T] = nil
		if this.tail.parent == nil {
			parent = this.tail
		} else {
			if this.tail.parent.right == nil {
				parent = this.tail.parent
			} else {
				parent = this.tail.parent.next
			}
		}

		node := &_QueueNode[T]{
			parent: parent,
			left:   nil,
			right:  nil,
			last:   this.tail,
			next:   nil,
			data:   data,
		}
		if parent.left == nil {
			parent.left = node
		} else {
			parent.right = node
		}
		this.tail.next = node
		this.tail = this.tail.next

		// 循环上浮
		for parent != nil && this.lessThan(node.data, parent.data) {
			node.data, parent.data = parent.data, node.data
			node = parent
			parent = parent.parent
		}
	}
	this.length++
}

func (this *PriorityQueue[T]) Dequeue() T {
	if this.Empty() {
		panic("heap is empty!")
	}
	if this.Size() == 1 {
		popedNode := this.head.next
		this.head.next = nil
		this.tail = this.head
		this.length = 0
		return popedNode.data
	}

	// 把最后一个结点与第一个结点的值交换
	this.head.next.data, this.tail.data = this.tail.data, this.head.next.data
	// 删除最后一个结点
	if this.tail.parent.left == this.tail {
		this.tail.parent.left = nil
	} else {
		this.tail.parent.right = nil
	}
	popedNode := this.tail
	this.tail.last.next = nil
	this.tail = this.tail.last
	// 循环下沉
	parent := this.head.next
	for parent.left != nil || parent.right != nil {
		var minChild *_QueueNode[T] = nil
		if parent.right == nil || this.lessThan(parent.left.data, parent.right.data) {
			minChild = parent.left
		} else {
			minChild = parent.right
		}
		if this.lessThan(minChild.data, parent.data) {
			minChild.data, parent.data = parent.data, minChild.data
			parent = minChild
			continue
		}
		break
	}
	this.length--
	return popedNode.data
}

测试

下面代码及运行结果测试了该实现的正确性:
在这里插入图片描述

func TestPriorityValue(t *testing.T) {
	fmt.Println("测试")
	list1 := structure.NewList[int]()
	list2 := structure.NewList[int]()
	cmp := func(data1 int, data2 int) bool { return data1 < data2 }
	queue := structure.NewPriorityQueue[int](cmp)
	// 随机生成100个数
	rand.Seed(time.Now().UnixMicro() % 36)
	for i := 0; i < 100; i++ {
		number := rand.Intn(100)
		list1.Enqueue(number)
		queue.Enqueue(number)
	}
	
	// 把堆的数据取出放到list2
	for i := 0; i < 100; i++ {
		list2.Enqueue(queue.Dequeue())
	}
	
	// 把list1排序后与list2比较,看是否一致
	list1.Sort(cmp)
	if fmt.Sprint(list1) != fmt.Sprint(list2) {
		panic("not equal")
	}
	
	fmt.Println(list2)
}


相关文章:

  • 【笔记】文献阅读[YOLOV2]-YOLO9000: Better, Faster, Stronger
  • 【JVM基础】方法区
  • Delphi的函数指针传递和调用
  • Java实现简单图书操作系统思路讲解
  • SpringBoot MVC使用Gson,序列化LocalDate,LocalDateTime
  • 戴尔G3-3579改固态散热
  • C3P0和Druid数据库连接池的使用
  • 2022中国消费者智能网联汽车数据安全和个人隐私意识与顾虑调查报告
  • Java 大文件分片上传
  • Redis未授权访问漏洞
  • Java 修饰符 private、default、protected、public 的应用实例 (方法)
  • Java 多线程:锁(一)
  • VMware 搭建linux操作系统,入门必看
  • 2022牛客多校(三)
  • 阿里云服务器和腾讯云服务器哪个更好?多维度对比得出了结论
  • [nginx文档翻译系列] 控制nginx
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  •  D - 粉碎叛乱F - 其他起义
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Node 版本管理
  • vue2.0项目引入element-ui
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 机器学习学习笔记一
  • 京东美团研发面经
  • 如何使用 JavaScript 解析 URL
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • nb
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • ​LeetCode解法汇总518. 零钱兑换 II
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (三)docker:Dockerfile构建容器运行jar包
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET Framework杂记
  • .NET6实现破解Modbus poll点表配置文件
  • .NET单元测试
  • @SuppressWarnings(unchecked)代码的作用
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • []使用 Tortoise SVN 创建 Externals 外部引用目录
  • [20170705]diff比较执行结果的内容.txt
  • [20180224]expdp query 写法问题.txt
  • [APIO2015]巴厘岛的雕塑
  • [ASP]青辰网络考试管理系统NES X3.5
  • [AutoSar]BSW_Memory_Stack_004 创建一个简单NV block并调试
  • [BZOJ1178][Apio2009]CONVENTION会议中心
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [C++]:for循环for(int num : nums)
  • [CF543A]/[CF544C]Writing Code
  • [C语言]——C语言常见概念(1)
  • [Django开源学习 1]django-vue-admin
  • [echarts] y轴不显示0