堆是什么?

堆是基于树抽象数据类型的一种特殊的数据结构,用于许多算法和数据结构中。一个常见的例子就是优先队列,还有排序算法之一的堆排序。这篇文章我们将讨论堆的属性、不同类型的堆以及
堆的常见操作。另外我们还将学习堆排序,并将使用SPL实现堆。

根据定义,堆是一个拥有堆特性的树形数据结构。如果父节点大于子节点,那么它被称为最大堆,如果父节点小于子节点,则称为最小堆。下图是最大堆的例子

我们看根节点,值100大于两个子节点19和36。对于19来说,该值大于17和3。其他节点也适用相同的规则。我们可以看到,这棵树没有完全排序。但重要的事实是我们总能找到树的最大值或最小值,在许多特殊的情况下这是非常有用的。

堆结构有很多种,如二叉堆、B堆、斐波那契堆、三元堆,树堆、弱堆等。二叉堆是堆实现中最流行的一种。二叉堆是一个完全二叉树(不了解二叉树的朋友可以看PHP实现二叉树),树的所有内部节点都被完全填充,最后一层可以完全填充的或部分填充。对于二叉堆,我们可以在对数时间复杂度内执行大部分操作。

堆的操作

堆是一个特殊的树数据结构。我们首先根据给定的数据构建堆。由于堆有严格的构建规则,所以我们每一步操作都必须满足这个规则。下面是堆的一些核心操作。

  • 创建堆
  • 插入新值
  • 从堆中提取最小值或最大值
  • 删除一个值
  • 交换

从给定的项或数字集合创建堆需要我们确保堆规则和二叉树属性得到满足。这意味着父节点必须大于或小于子节点。对于树中的所有节点,都需要遵守这个规则。同样,树必须是一个完全的二叉树。在创建堆时,我们从一个节点开始,并向堆中插入一个新节点。

当插入节点操作时,我们不能从任意节点开始。插入操作如下

  • 将新节点插入堆的底部
  • 检查新节点和父节点的大小顺序,如果它们是正确的顺序,停止。
  • 如果它们不是正确的顺序,交换它们然后继续前一步的检查。这一步骤与前一步一起被称为筛分或上升,等等。

提取操作(最小或最大)即从堆中取出根节点。在此之后,我们必须执行下列操作以确保剩余节点然仍符合堆的特点。

  • 从堆移动最后一个节点作为新根
  • 将新根节点与子节点进行比较,如果它们处于正确的顺序,则停止。
  • 如果不是,则将根节点与子节点交换(当是小根堆时为最小子节点,当大根堆时为最大子节点)并继续前面的步骤。这一步与前一个步骤一起被称为下堆。

在堆中,一个重要的操作是交换。现在我们将使用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);
}

}


<p>堆排序的时间复杂度为O(nlog n),空间复杂度为O(1)。对比归并排序,堆排序有更好的表现。</p>
<h3>PHP中的SplHeap、SplMinHeap和SplMaxHeap</h3>
<p>当然,方便的PHP内置的标准库已经帮助我实现了堆,你可以通过<code>SplHeap</code>、<code>SplMinHeap</code>、<code>SplMaxHeap</code>来使用它们。</p>
<h3>更多内容</h3>
<p>PHP基础数据结构专题系列目录: <a href="https://github.com/xx19941215/light-tips" rel="nofollow noreferrer">地址</a>。主要使用PHP语法总结基础的数据结构和算法。还有我们日常PHP开发中容易忽略的基础知识和现代PHP开发中关于规范、部署、优化的一些实战性建议,同时还有对Javascript语言特点的深入研究。</p> 原文地址:https://segmentfault.com/a/1190000016067129

PHP面试:说下什么是堆和堆排序?的更多相关文章

  1. python下实现二叉堆以及堆排序

    python下实现二叉堆以及堆排序 堆是一种特殊的树形结构, 堆中的数据存储满足一定的堆序.堆排序是一种选择排序, 其算法复杂度, 时间复杂度相对于其他的排序算法都有很大的优势. 堆分为大头堆和小头堆 ...

  2. (转)在.NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?

    转自:http://www.cnblogs.com/xiaoyao2011/archive/2011/09/09/2172427.html 在.NET程序运行过程中,什么是堆,什么是栈? 堆也就是托管 ...

  3. Python 堆与堆排序

    堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足 ...

  4. 堆与堆排序、Top k 问题

     堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满 ...

  5. .net下使用最小堆实现TopN算法

    测试代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespac ...

  6. [面经] 南京SAP面试(下)

    上一篇讲到了一面结束,这一篇说说剩下的事情. 周三上午一面完了之后回去上班,本以为要等几天才会二面,结果那个经理M下午就打电话给我,约了第二天(周四)下午过去面试,会有Boss从上海过来面,办事效率还 ...

  7. 堆与堆排序/Heap&Heap sort

    最近在自学算法导论,看到堆排序这一章,来做一下笔记.堆排序是一种时间复杂度为O(lgn)的原址排序算法.它使用了一种叫做堆的数据结构.堆排序具有空间原址性,即指任何时候都需要常数个额外的元素空间存储临 ...

  8. 基本数据结构 —— 堆以及堆排序(C++实现)

    目录 什么是堆 堆的存储 堆的操作 结构体定义 判断是否为空 往堆中插入元素 从堆中删除元素 取出堆中最大的元素 堆排序 测试代码 例题 参考资料 什么是堆 堆(英语:heap)是计算机科学中一类特殊 ...

  9. Java实现的二叉堆以及堆排序详解

    一.前言 二叉堆是一个特殊的堆,其本质是一棵完全二叉树,可用数组来存储数据,如果根节点在数组的下标位置为1,那么当前节点n的左子节点为2n,有子节点在数组中的下标位置为2n+1.二叉堆类型分为最大堆( ...

随机推荐

  1. 模块化开发(二)--- seaJs入门学习

    SeaJS是一个基于CMD模块定义规范实现一个模块系统加载器   [CMD规范](https://github.com/cmdjs/specification/blob/master/draft/mo ...

  2. Java原型模式之浅拷贝-深拷贝

    一.是什么? 浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量仅仅复制引用,不复制引用的对象 深拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制 内部机制: ...

  3. HDFS04

    ===================HDFS副本放置策略=================== 一个文件划分成多个block,每个 block存多份,如何为每个block选 择节点存储这几份数据? ...

  4. ios7--UIImageView

    // // ViewController.m // 03-UIImageView的使用 // #import "ViewController.h" @interface ViewC ...

  5. Silverlight 2学习笔记二:三个基本布局控件(Canvas、StackPanel、Grid )

    这篇文章主要是翻译了ScottGu博客的文章:Silverlight Tutorial Part 2: Using Layout Management.虽然是翻译,但通过笔记记录,我发现对这三个布局控 ...

  6. bzoj 3721 Final Bazarek

    题目大意: n个数 选k个使和为奇数且最大 思路: 可以先将这n个数排序 然后先去最大的k个数 若和为奇数则直接输出 为偶数可以用没选的最大的奇数替换选了的最小的偶数或用没选的最大的偶数替换选了的最小 ...

  7. createrepo -g /enp/comps.xml .

    cd /enp; createrepo -g /enp/comps.xml .

  8. Vue 页面回退参数被当作字符串处理

    当时情景是这样的,我从A页面跳到B页面时会传一个Boolean类型的参数,当B跳到C,再从C返回B的时候,控制台打印发现参数还在,可是判断怎么都不起作用,后来发现,当页面返回的时候,默认将参数变成了字 ...

  9. 利用Kibana来查看和管理Elasticsearch的索引(Kibana使用篇)

    经过前面几篇的学习,我们已经知道如何在Kibana里边对Elasticsearch进行操作了,那么如何查看已存在的索引呢,我们来看, 在Management里边可以看到我们刚刚创建的sdb索引.另外还 ...

  10. Kafka详解与总结(四)

    Kafka消息分发和消费者push.pull机制 1. 消息分发 Producer客户端负责消息的分发 kafka集群中的任何一个broker都可以向producer提供metadata信息,这些me ...