堆是基于树抽象数据类型的一种特殊的数据结构,用于许多算法和数据结构中。一个常见的例子就是优先队列,还有排序算法之一的堆排序。这篇文章咱们将讨论堆的属性、不一样类型的堆以及 堆的常见操做。另外咱们还将学习堆排序,并将使用SPL实现堆。php
根据定义,堆是一个拥有堆特性的树形数据结构。若是父节点大于子节点,那么它被称为最大堆,若是父节点小于子节点,则称为最小堆。下图是最大堆的例子git
咱们看根节点,值100大于两个子节点19和36。对于19来讲,该值大于17和3。其余节点也适用相同的规则。咱们能够看到,这棵树没有彻底排序。但重要的事实是咱们总能找到树的最大值或最小值,在许多特殊的状况下这是很是有用的。github
堆结构有不少种,如二叉堆、B堆、斐波那契堆、三元堆,树堆、弱堆等。二叉堆是堆实现中最流行的一种。二叉堆是一个彻底二叉树(不了解二叉树的朋友能够看PHP实现二叉树),树的全部内部节点都被彻底填充,最后一层能够彻底填充的或部分填充。对于二叉堆,咱们能够在对数时间复杂度内执行大部分操做。算法
堆是一个特殊的树数据结构。咱们首先根据给定的数据构建堆。因为堆有严格的构建规则,因此咱们每一步操做都必须知足这个规则。下面是堆的一些核心操做。api
建立堆bash
插入新值数据结构
从堆中提取最小值或最大值数据结构和算法
删除一个值post
交换学习
从给定的项或数字集合建立堆须要咱们确保堆规则和二叉树属性获得知足。这意味着父节点必须大于或小于子节点。对于树中的全部节点,都须要遵照这个规则。一样,树必须是一个彻底的二叉树。在建立堆时,咱们从一个节点开始,并向堆中插入一个新节点。
当插入节点操做时,咱们不能从任意节点开始。插入操做以下
将新节点插入堆的底部
检查新节点和父节点的大小顺序,若是它们是正确的顺序,中止。
若是它们不是正确的顺序,交换它们而后继续前一步的检查。这一步骤与前一步一块儿被称为筛分或上升,等等。
提取操做(最小或最大)即从堆中取出根节点。在此以后,咱们必须执行下列操做以确保剩余节点然仍符合堆的特色。
在堆中,一个重要的操做是交换。如今咱们将使用PHP7来实现二叉堆。
namespace DataStructure\Heap;
class MaxHeap {
public $heap;
public $count;
public function __construct(int $size) {
//初始化堆
$this->heap = array_fill(0, $size, 0);
$this->count = 0;
}
public function create(array $arr = []) {
array_map(function($item){
$this->insert($item);
}, $arr);
}
public function insert(int $data) {
//插入数据操做
if ($this->count == 0) {
//插入第一条数据
$this->heap[0] = $data;
$this->count = 1;
} else {
//新插入的数据放到堆的最后面
$this->heap[$this->count++] = $data;
//上浮到合适位置
$this->siftUp();
}
}
public function display() {
return implode(" ", array_slice($this->heap, 0));
}
public function siftUp() {
//待上浮元素的临时位置
$tempPos = $this->count - 1;
//根据彻底二叉树性质找到父节点的位置
$parentPos = intval($tempPos / 2);
while ($tempPos > 0 && $this->heap[$parentPos] < $this->heap[$tempPos]) {
//当不是根节点而且父节点的值小于临时节点的值,就交换两个节点的值
$this->swap($parentPos, $tempPos);
//重置上浮元素的位置
$tempPos = $parentPos;
//重置父节点的位置
$parentPos = intval($tempPos / 2);
}
}
public function swap(int $a, int $b) {
$temp = $this->heap[$a];
$this->heap[$a] = $this->heap[$b];
$this->heap[$b] = $temp;
}
public function extractMax() {
//最大值就是大跟堆的第一个值
$max = $this->heap[0];
//把堆的最后一个元素做为临时的根节点
$this->heap[0] = $this->heap[$this->count - 1];
//把最后一个节点重置为0
$this->heap[--$this->count] = 0;
//下沉根节点到合适的位置
$this->siftDown(0);
return $max;
}
public function siftDown(int $k) {
//最大值的位置
$largest = $k;
//左孩子的位置
$left = 2 * $k + 1;
//右孩子的位置
$right = 2 * $k + 2;
if ($left < $this->count && $this->heap[$largest] < $this->heap[$left]) {
//若是左孩子大于最大值,重置最大值的位置为左孩子
$largest = $left;
}
if ($right < $this->count && $this->heap[$largest] < $this->heap[$right]) {
//若是右孩子大于最大值,重置最大值的位置为右孩子
$largest = $right;
}
//若是最大值的位置发生改变
if ($largest != $k) {
//交换位置
$this->swap($largest, $k);
//继续下沉直到初始位置不发生改变
$this->siftDown($largest);
}
}
}
复制代码
由于不一样种类的堆有不一样的实现,因此各类堆实现也有不一样的复杂度。可是有一个堆的操做在各种实现中都是O(1)的复杂度,就是获取最大值或者最小值。我看来看下二分堆的复杂度分析。
操做 | 平均复杂度 | 最坏复杂度 |
---|---|---|
Search | O(n) | O(n) |
Insert | O(1) | O(log n) |
Delete | O(log n) | O(log n) |
Extract | O(1) | O(1) |
由于二叉堆不是彻底排序的,因此搜索操做会比二叉搜索树花更多的时间。
一个最经常使用的操做就是将堆看成优先队列来使用。在PHP实现栈和PHP实现队列中,咱们已经了解到优先队列是一种根据元素权重而不是入队顺序来进行出队操做的结构。咱们已经用链表实现优先队列和Spl实现优先队列,如今咱们使用堆来实现优先队列。
namespace DataStructure\Heap;
class PriorityQueue extends MaxHeap {
public function __construct(int $size) {
parent::__construct($size);
}
public function enqueue(int $val) {
parent::insert($val);
}
public function dequeue() {
return parent::extractMax();
}
}
复制代码
在堆排序中,咱们须要用给定的值构建一个一个堆。而后连续的检查堆的值以确保任什么时候候整个堆都是排序的。在正常的堆结构中,咱们每当插入一个新的值到合适位置以后就中止检查,可是在堆排序中,只要有下一个值,咱们就不断的去检查构建堆。伪代码以下:
HeapSort(A)
BuildHeap(A)
for i = n-1 to 0
swap(A[0],A[i])
n = n - 1
Heapify(A, 0)
BuildHeap(A)
n = elemens_in(A)
for i = floor(n / 2) to 0
Heapify(A, i)
Heapify(A, i)
left = 2i+1;
right = 2i + 2;
max = i
if (left < n and A[left] > A[i])
max = left
if (right < n and A[right] > A[max])
max = right
if (max != i)
swap(A[i], A[max])
Heapify(A, max)
复制代码
从上面的伪代码能够看到,堆排序的第一步就是构建一个堆。每次咱们向堆中添加新的元素,咱们都调用heapify来知足堆的特性。一旦堆构建好以后,咱们对全部的元素都进行检查,下面使用PHP的实现堆排序。完整的代码能够点这里查看。
function heapSort(&$arr) {
$length = count($arr);
buildHeap($arr);
$heapSize = $length - 1;
for ($i = $heapSize; $i >= 0; $i--) {
list($arr[0], $arr[$heapSize]) = [$arr[$heapSize], $arr[0]];
$heapSize--;
heapify(0, $heapSize, $arr);
}
}
function buildHeap(&$arr) {
$length = count($arr);
$heapSize = $length - 1;
for ($i = ($length / 2); $i >= 0; $i--) {
heapify($i, $heapSize, $arr);
}
}
function heapify(int $k, int $heapSize, array &$arr) {
$largest = $k;
$left = 2 * $k + 1;
$right = 2 * $k + 2;
if ($left <= $heapSize && $arr[$k] < $arr[$left]) {
$largest = $left;
}
if ($right <= $heapSize && $arr[$largest] < $arr[$right]) {
$largest = $right;
}
if ($largest != $k) {
list($arr[$largest], $arr[$k]) = [$arr[$k], $arr[$largest]];
heapify($largest, $heapSize, $arr);
}
}
复制代码
堆排序的时间复杂度为O(nlog n),空间复杂度为O(1)。对比归并排序,堆排序有更好的表现。
固然,方便的PHP内置的标准库已经帮助我实现了堆,你能够经过SplHeap
、SplMinHeap
、SplMaxHeap
来使用它们。
PHP基础数据结构专题系列目录: 地址。主要使用PHP语法总结基础的数据结构和算法。还有咱们平常PHP开发中容易忽略的基础知识和现代PHP开发中关于规范、部署、优化的一些实战性建议,同时还有对Javascript语言特色的深刻研究。