最近在学习a星算法的时候遇到这样一个问题,算法有一个操作:每次都要从一个不规则的数组中找出最小值(做法自然是遍历整个数组,找出最小值)。当然算法在遇到数据量较小时处理起来自然轻松,但假如处理海量的数据呢,每次都要遍历整个数组,那将导致整个算法相当低效。那我们就自然而然的想,有没有这样一种数组,它的最小值(或者最大值)永远都是在数组头部呢,这样,我们每次取最小值(或者最大值)的时候,直接从头部取就好了。嗯,这样的数组是有的。可以用二叉堆算法来实现这样的数组,那什么是二叉堆呢,也许你听过二叉树,是的,二叉堆具有二叉树的性质。我们先来看看数据使用二叉堆结构是如何存储的,
如图
根据上图左侧的二叉堆和右侧的数组,有发现什么规则吗。假如根节点在数组中的索引是1,那么存储在第n个位置的父节点它的子节点在数组中的存储位置为2n与2n+1。从上面的数组可以发现,用二叉堆数据结构实现的数组,最小值都是存储在第一个位置,这样的二叉堆我们称为最小堆,那么最大堆自然是最大值存储在数组第一个位置。
利用这样的数组我们就可以很轻易的拿到最小值(或最大值),因为我们不需要再遍历整个数组了,是不是很方便,其实二叉堆就是专门为取出最大或最小节点而设计的数据结构。好了,那么问题来了,怎么实现呢?下面我用一个例子来说明
以上就是我们对数组所做的向上整形,目的是每次都把最小值放到数组首位。聪明的你应该会想到,既然有向上整形,那是不是也有向下整形呢。哈哈,对的,那什么是向下整形呢。有没有想过,假如我们把数组的首位拿出来,即删除首位,那这个时候该数组的最小值应该是哪个呢。是的向下整形就是解决这个问题的。
下面我还是用例子解释
以上就是向下的整形过程。
通过上面的内容,我们已经大概知道二叉堆的结构以及如何构建一个二叉堆,下面我们通过代码再加深巩固一下,别怕哈,代码不长
--[[
二叉堆
by fly
]]
local BinaryHeap = class("BinaryHeap")
function BinaryHeap:ctor()
self.m_array = {} --数组
self.m_lastIndex = 0 --当前数组末尾的位置
end
--插入数据
function BinaryHeap:insert(data)
table.insert(self.m_array,data)
self.m_lastIndex = self.m_lastIndex + 1
if self.m_lastIndex > 1 then
self:swimUp(self.m_lastIndex)
end
end
--将插入的数据上移到合适的位置(向上整形)
function BinaryHeap:swimUp(index)
local pindex = math.floor(index/2) --取出父节点的位置
--父节点还在数组里并且当前节点的值小于父节点的值,于是交换
while pindex > 0 and self:compare(index,pindex) do
self:change(index,pindex)
index = pindex
pindex = math.floor(pindex/2) --寻找父节点的父节点
end
end
--比较数据的大小
function BinaryHeap:compare(n,m)
local a = self.m_array[n]
local b = self.m_array[m]
if a and b then
return a < b
end
end
--交换两个数据
function BinaryHeap:change(n,m)
local temp = self.m_array[n]
self.m_array[n] = self.m_array[m]
self.m_array[m] = temp
end
--获取最小值
function BinaryHeap:getMin()
return self.m_array[1]
end
--删除数据(相当于删除最后一个节点的数据)
function BinaryHeap:popMin()
self.m_array[1] = self.m_array[self.m_lastIndex]
table.remove(self.m_array,self.m_lastIndex)
self.m_lastIndex = self.m_lastIndex - 1
self:sinkDown(1)
end
--将根节点的数据下移到合适的位置(向下整形)
function BinaryHeap:sinkDown(index)
local cindex = index * 2 --当前节点的子节点
while cindex <= self.m_lastIndex do
--找出两个子节点中值最小的子节点
if cindex+1 <= self.m_lastIndex and self:compare(cindex+1,cindex) then
cindex = cindex + 1
end
if self:compare(cindex,index) then
self:change(cindex,index)
index = cindex
cindex = cindex*2 --寻找子节点的子节点
else
break --不满足条件,退出
end
end
end
--打印
function BinaryHeap:printArr()
dump(self.m_array,"array",10)
end
return BinaryHeap
看完代码,我们已经基本了解二叉堆是一个什么东东了。一个算法的优劣,可以通过计算它的时间复杂度来衡量,这里简单的给出答案:二叉堆的操作如:删除,插入节点,都是O(log2n),也就是对一个有n个数据的二叉堆,最糟糕情况下需要执行log2n次操作才完成。
二叉堆有很多应用,比如用在排序和某些算法里面,这里就不详细说了,感兴趣的可以看看堆排序和a*算法哦。