数据结构 101:如何在 JavaScript 中构建最小堆和最大堆

简介: 数据结构 101:如何在 JavaScript 中构建最小堆和最大堆

什么是堆?

堆是一种高级的基于树的数据结构, 主要用于排序和实现优先级[队列]它们是具有以下特征的完全二叉树:

  • 除了叶节点(没有子节点的节点称为叶)之外,每个级别都被填充。
  • 每个节点最多有 2 个孩子。
  • 所有节点都尽可能靠左。这意味着每个孩子都在他父母的左边。

网络异常,图片无法展示
|

堆使用完全二叉树来避免数组中出现空洞。完全二叉树是每个结点至多有两个孩子的树,所有层的结点都是满的,除了叶子结点可以为空。堆是基于堆属性构建的,堆属性将父节点键与其子节点键进行比较。

重要的是要注意堆并不总是排序的。它们遵循的关键条件是最大或最小元素放置在根节点(顶部)上,具体取决于它是最大堆还是最小堆。堆数据结构与堆内存不同。

堆的优点和缺点

优点:

  • 垃圾收集在堆内存上运行以释放对象使用的内存。
  • 堆是灵活的,因为可以按任何顺序分配或删除内存。
  • 变量可以全局访问。
  • 它有助于找到最小和最大的数字。

缺点:

  • 与栈相比,堆需要更多的时间来执行。
  • 内存管理在堆内存中更为复杂,因为它是全局使用的。
  • 堆通常需要更多的时间来计算。

堆数据结构的应用

堆对于查找数组中的最小或最大元素非常有效,并且在顺序统计和选择算法中很有用。从堆中获取最小值/最大值的时间复杂度为O(1) ,(恒定时间复杂度)。

优先级队列是基于堆结构设计的。有效地插入 ( ) 和删除 ( ) 优先级队列中的每个元素需要O(log(n))时间。insert()``delete()

堆实现的优先级队列用于流行的算法,例如:

  • 普里姆算法
  • Dijkstra 算法
  • 堆排序算法

堆中的基本操作

以下是您在实现堆数据结构时可能使用的基本操作:

  • heapify 重新排列堆中的元素以保持堆属性。
  • insert 将一个项目添加到堆中,同时保持其堆属性。
  • delete 删除堆中的项目。
  • extract 返回一个项目的值,然后从堆中删除它。
  • isEmpty:  boolean,如果 boolean 为空则返回 true,如果有节点则返回 false。
  • size: 返回堆的大小。
  • getMax() 返回堆中的最大值

如何构建最大堆

最大堆中的元素遵循最大堆属性。这意味着父节点的键总是大于两个子节点的键。要构建最大堆:

  • 在堆的开头(根)创建一个新节点。
  • 给它赋值。
  • 将子节点的值与父节点进行比较。
  • 如果父节点的值小于任一子节点(向左或向右)的值,则交换节点。
  • 重复直到最大元素位于根父节点(然后你可以说堆属性成立)。

将新元素插入堆时也可以遵循这些步骤。这里的关键是无论在最大堆上执行什么操作,堆属性都必须保持。

移除/删除最大堆中的根节点:

  • 删除根节点。
  • 将最后一层的最后一个子节点移动到根。
  • 将父节点与其子节点进行比较。
  • 如果父节点的值小于子节点,则交换它们,并重复直到满足堆属性。

让我们看一下这在代码中是什么样子的。在下一节中,我们将使用[JavaScript] 实现最大堆。

在 JavaScript 中实现最大堆

在我们开始构建最大堆之前,先看看我们将要实现的一些方法以及它们的作用:

  • _percolateUp() 将堆属性从子节点恢复到根节点。
  • _maxHeapify() 将堆属性从特定节点向下恢复到叶节点。
  • insert() 将给定值附加到堆数组并根据元素的堆属性重新排列元素。在每次插入时,堆均匀增长,大小增加一。
  • getMax() 返回堆(根节点)中的最大值,不修改堆。注意这里的时间复杂度是常数时间O(1)
  • removeMax() 返回并删除堆中的最大值(想想pop())。该函数的时间复杂度为O(log(n))

如果堆大小大于一,它将最大值存储到一个变量中,将该值与最后一个叶子交换,然后从堆中删除最大值。

如果堆只有一个元素,则删除并返回该元素的值,最后一个条件是如果堆为空,则返回null。

__percolateUp()方法在每个父节点上递归[调用] ,直到到达根节点。对于要定位在 max-heap 属性之后的每个节点,我们在该数组的每个索引处调用该__maxHeapify()方法,从堆的底部开始。

class maxHeap {
    constructor() {
        this.heap = [];
        this.elements = 0;
    };
    insert(val) {
        if (this.elements >= this.heap.length) {
            this.elements = this.elements + 1;
            this.heap.push(val);
            this.__percolateUp(this.heap.length - 1);
        }
        else {
            this.heap[this.elements] = val;
            this.elements = this.elements + 1;
            this.__percolateUp(this.elements - 1);
        }
    };
    getMax() {
        if (this.elements !== 0)
            return this.heap[0];
        return null;
    };
    removeMax() {
        let max = this.heap[0];
        if (this.elements > 1) {
            this.heap[0] = this.heap[this.elements - 1];
            this.elements = this.elements - 1;
            this.__maxHeapify(0);
            return max
        } else if (this.elements === 1) {
            this.elements = this.elements - 1;
            return max;
        } else {
            return null;
        }
    };
    __percolateUp(index) {
        const parent = Math.floor((index - 1) / 2);
        if (index <= 0)
            return
        else if (this.heap[parent] < this.heap[index]) {
            let tmp = this.heap[parent];
            this.heap[parent] = this.heap[index];
            this.heap[index] = tmp;
            this.__percolateUp(parent);
        }
    };
    __maxHeapify(index) {
        let left = (index * 2) + 1;
        let right = (index * 2) + 2;
        let largest = index;
        if ((this.elements > left) && (this.heap[largest] < this.heap[left])) {
            largest = left
        }
        else if ((this.elements > right) && (this.heap[largest] < this.heap[right]))
            largest = right
        else if (largest !== index) {
            const tmp = this.heap[largest];
            this.heap[largest] = this.heap[index];
            this.heap[index] = tmp;
            this.__maxHeapify(largest);
        }
    };
    buildHeap(arr) {
        this.heap = arr;
        this.elements = this.heap.length;
        for (let i = this.heap.length - 1; i >= 0; i--) {
            this.__maxHeapify(i);
        }
    };
};
let heap = new maxHeap();
复制代码

如何构建最小堆

直观上,我们可以说最小堆中的元素遵循最小堆属性,因为这与最大堆相反。父节点的键总是小于两个子节点的键。要构建最小堆:

  • 在堆的末尾(最后一层)创建一个新的子节点。
  • 将新键添加到该节点(将其附加到数组)。
  • 向上移动子节点,直到到达根节点并且堆属性得到满足。

删除/删除最小堆中的根节点:

  • 删除根节点。
  • 将最后一个孩子的钥匙移到根。
  • 将父节点与其子节点进行比较。
  • 如果父节点的值大于子节点,则交换它们,并重复直到满足堆属性。

在 JavaScript 中实现最小堆

在我们开始构建最小堆之前,请注意它的实现类似于最大堆。minHeapify()恢复堆属性。getMin()返回堆(根节点)中的最小值而不修改堆。并removeMin()删除最小值并返回它。

class minHeap {
    constructor() {
        this.heap = []
        this.elements = 0;
    };
    insert(val) {
        if (this.elements >== this.heap.length) {
            this.elements = this.elements + 1
            this.heap.push(val);
            this.__percolateUp(this.heap.length - 1);
        }
        else {
            this.heap[this.elements] = val;
            this.elements = this.elements + 1;
            this.__percolateUp(this.elements - 1);
        }
    };
    getMin() {
        if (this.heap.length !== 0)
            return this.heap[0];
        return null;
    }
    removeMin() {
        const min = this.heap[0];
        if (this.elements > 1) {            
            this.heap[0] = this.heap[this.elements - 1];
            this.elements = this.elements - 1;
            this.__minHeapify(0);
            return min;
        } else if (this.elements == 1) {
            this.elements = this.elements - 1;
            return min;
        } else {
            return null;
        }
    };
    __percolateUp(index) {
        let parent = Math.floor((index - 1) / 2);
        if (index <= 0)
            return
        else if (this.heap[parent] > this.heap[index]) {
            let tmp = this.heap[parent];
            this.heap[parent] = this.heap[index];
            this.heap[index] = tmp;
            this.__percolateUp(parent);
        }
    };
    __minHeapify(index) {
        let left = (index * 2) + 1;
        let right = (index * 2) + 2;
        let smallest = index;
        if ((this.elements > left) && (this.heap[smallest] > this.heap[left])) {
            smallest = left;
        }
        if ((this.elements > right) && (this.heap[smallest] > this.heap[right]))
            smallest = right;
        if (smallest !== index) {
            let tmp = this.heap[smallest];
            this.heap[smallest] = this.heap[index];
            this.heap[index] = tmp;
            this.__minHeapify(smallest);
        }
    }
    buildHeap(arr) {
        this.heap = arr;
        this.elements = this.heap.length;
        for (let i = this.heap.length - 1; i >= 0; i--) {
            this.__minHeapify(i)
        }
    }
};
let heap = new minHeap();
heap.insert(12);
heap.insert(10);
heap.insert(-10);
heap.insert(100);
console.log(heap.getMin()); //you should get -10
let newheap = new minHeap();
let arr = [12, 6, 8, 3, 16, 4, 27];
newheap.buildHeap(arr) //builds this new heap with elements from the array
console.log(newheap.getMin()) //this logs 3
newheap.removeMin();
console.log(newheap.getMin())
复制代码

堆挑战:将最大堆转换为最小堆

让我们通过动手挑战来进一步学习。我们的目标是将最大堆转换为最小堆。跟随我们的代码解决方案,看看它是如何完成的。

问题陈述: 实现一个函数convertMax(maxHeap),将二进制最大堆转换为二进制最小堆,其中maxHeap是格式中的数组maxHeap(即父项大于其子项)。您的输出应该是一个转换后的数组。

示例输入:

maxHeap = [9,4,7,1,-2,6,5]
复制代码

示例输出:

result = [-2,1,5,9,4,6,7]
复制代码

网络异常,图片无法展示
|

问题:

function convertMax(maxHeap) {
    return maxHeap
}
复制代码

解决方案:

function minHeapify(heap, index) {
    var left = index * 2;
    var right = (index * 2) + 1;
    var smallest = index;
    if ((heap.length > left) && (heap[smallest] > heap[left])) {
        smallest = left
    }
    if ((heap.length > right) && (heap[smallest] > heap[right]))
        smallest = right
    if (smallest != index) {
        var tmp = heap[smallest]
        heap[smallest] = heap[index]
        heap[index] = tmp
        minHeapify(heap, smallest)
    }
    return heap;
}
function convertMax(maxHeap) {
    for (var i = Math.floor((maxHeap.length) / 2); i > -1; i--)
        maxHeap = minHeapify(maxHeap, i)
    return maxHeap
}
复制代码

****可以运行上面的代码解决方案。 我们可以将 given 视为maxHeap一个规则的元素数组并对其重新排序,以便它准确地表示一个最小堆。该函数通过在每个节点上convertMax()调用函数从最低父节点恢复所有节点上的堆属性。minHeapify()

构建堆的时间复杂度是O(n) 。这个问题也是如此。

function minHeapify(heap, index) {
    var left = index * 2;
    var right = (index * 2) + 1;
    var smallest = index;
    if ((heap.length > left) && (heap[smallest] > heap[left])) {
        smallest = left
    }
    if ((heap.length > right) && (heap[smallest] > heap[right]))
        smallest = right
    if (smallest != index) {
        var tmp = heap[smallest]
        heap[smallest] = heap[index]
        heap[index] = tmp
        minHeapify(heap, smallest)
    }
    return heap;
}
function convertMax(maxHeap) {
    for (var i = Math.floor((maxHeap.length) / 2); i > -1; i--)
        maxHeap = minHeapify(maxHeap, i)
    return maxHeap
}
var maxHeap = [9,4,7,1,-2,6,5]
console.log(convertMax(maxHeap))
-->
[ -2, 1, 4, 5, 7, 6, 9 ]


相关文章
|
1月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
39 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
1月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js:从零开始构建后端服务
【10月更文挑战第42天】在数字时代的浪潮中,掌握一门后端技术对于开发者来说至关重要。Node.js,作为一种基于Chrome V8引擎的JavaScript运行环境,允许开发者使用JavaScript编写服务器端代码,极大地拓宽了前端开发者的技能边界。本文将从Node.js的基础概念讲起,逐步引导读者理解其事件驱动、非阻塞I/O模型的核心原理,并指导如何在实战中应用这些知识构建高效、可扩展的后端服务。通过深入浅出的方式,我们将一起探索Node.js的魅力和潜力,解锁更多可能。
|
2月前
|
存储 JavaScript 前端开发
使用JavaScript构建动态交互式网页:从基础到实践
【10月更文挑战第12天】使用JavaScript构建动态交互式网页:从基础到实践
140 1
|
27天前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
20天前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
36 12
|
24天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
26天前
|
JavaScript NoSQL API
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发如同一座灯塔,指引着数据的海洋。本文将带你航行在Node.js的海域,探索如何从一张白纸到完成一个功能完备的RESTful API。我们将一起学习如何搭建开发环境、设计API结构、处理数据请求与响应,以及实现数据库交互。准备好了吗?启航吧!
|
29天前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
42 5
|
28天前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
78 16