【从蛋壳到满天飞】JS 数据结构解析和算法实现-堆和优先队列(一)

思惟导图

前言

【从蛋壳到满天飞】JS 数据结构解析和算法实现,所有文章大概的内容以下: Arrays(数组)、Stacks(栈)、Queues(队列)、LinkedList(链表)、Recursion(递归思想)、BinarySearchTree(二分搜索树)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(优先队列)、SegmentTree(线段树)、Trie(字典树)、UnionFind(并查集)、AVLTree(AVL 平衡树)、RedBlackTree(红黑平衡树)、HashTable(哈希表)html

源代码有三个:ES6(单个单个的 class 类型的 js 文件) | JS + HTML(一个 js 配合一个 html)| JAVA (一个一个的工程)git

所有源代码已上传 github,点击我吧,光看文章可以掌握两成,动手敲代码、动脑思考、画图才能够掌握八成。github

本文章适合 对数据结构想了解而且感兴趣的人群,文章风格一如既往如此,就以为手机上看起来比较方便,这样显得比较有条理,整理这些笔记加源码,时间跨度也算将近半年时间了,但愿对想学习数据结构的人或者正在学习数据结构的人群有帮助。算法

堆和优先队列(HeapAndPriorityQueue)

  1. 使用了二分搜索树实现了集合和映射这两个相对来说更加高层的数据结构
    1. 树这种数据结构自己在计算机科学领域占有重要的地位,
    2. 树这种形状自己能够产生很是多的拓展,
    3. 在面对不一样的问题的时候能够稍微改变或者限制树这种数据结构的性质,
    4. 从而产生不一样的数据结构,高效的解决不一样的问题,
    5. 有四个不一样的例子,堆、线段树、字典树、并查集,
    6. 经过这些不一样的数据结构的学习能够体会到数据结构的灵活之处,
    7. 以及在设计数据结构的时候其中的一些思考很是重要,
    8. 由于这些思考会让你对数据结构这个领域有更加深入的认识。
  2. 堆是一个特殊的树结构

优先队列(PriorityQueue)

  1. 优先队列自己就是一种队列
  2. 普通队列:先进先出、后进后出
    1. 如排队买票吃饭同样。
  3. 优先队列:出队顺序和入队顺序无关,和优先级相关
    1. 如去医院看病,床位很是的紧张,
    2. 须要排队才能去作手术的话,
    3. 此时作手术的这个队伍的顺序就是一个优先队列,
    4. 由于医生是根据每个患者的病症的不一样以及须要这个手术的紧急程度的不一样
    5. 来去安排谁先作手术谁后作手术,
    6. 也能够认为每个患者是有一个优先级的,
    7. 是优先级高着先得,这样的一个队列就叫作优先队列,
    8. 它和普通队列最主要的区别是在于出队这个操做上,
    9. 入队很简单,可是优先级高的先出队。
  4. 优先队列的应用
    1. 在计算机的世界中优先队列的使用也是很是多的,
    2. 例如操做系统中进行任务的调度,
    3. 如今的操做系统中会同时执行多个任务,
    4. 操做系统就要为这多个任务分配计算资源,
    5. 包括去分配 cpu 的时间片,具体去分配这些资源的时候,
    6. 操做系统就要看各个任务的优先级,
    7. 而后动态的去选择优先级最高的任务来执行。
    8. 这个动态很重要,若是任务数量是固定的,
    9. 那么就不须要去制做新的数据结构来处理这个问题,
    10. 那么这个过程就只须要一个排序算法而并非一个优先队列,
    11. 一般实际状况不是这样的,
    12. 假设有一个任务处理中心,有三个任务请求过来,
    13. 那么这个时候任务处理中心就须要去找出优先级最高的那个请求,
    14. 而后对这个任务进行相应的处理,可是处理完这个任务的同时,
    15. 颇有可能就来了不少新的任务请求,这就是动态的意思,
    16. 并不可以在一开始就肯定任务中心一共须要处理多少个任务,
    17. 其实医生是不知道天天或者每月每一个季度每年要来多少患者,
    18. 它要随时根据新来的患者的状况来调整整个队列的优先级,
    19. 这就是动态的意思,而不是一开始任务中心就知道全部的任务是什么,
    20. 而后排排序就行了,就像医生也不能一开始就把今年要作的全部的手术都列出来,
    21. 而后排排序就行了,随着时间的推移会不停的有新的元素入队,
    22. 也就是队列中的元素是在不断的变化的,
    23. 因此必须使用优先队列这样的数据结构来解决这个问题,
    24. 而不只仅是按照优先级排序。
  5. 好比作一个简单的 AI
    1. 这个 AI 要自动的帮你打怪,其实 AI 也没有那么高级,
    2. 在不少游戏的底层自己就存在这样的 AI,
    3. 好比说在一个即时战略的游戏中,当你建立了己方军队的时候,
    4. 固然能够经过本身的操做来指挥己方的军队去攻击哪些敌人,
    5. 可是你不去指定,当敌方军队接近己方军队的时候,
    6. 己方军队也会自动的去攻击敌方军队,这也叫作一个 AI,
    7. 这种 AI 实际上是很是常见的,这种时候 AI 可能同时面对不一样的敌人,
    8. 那么它就须要选择优先去打那种敌人,在这种状况下就须要使用优先队列,
    9. 其实就是去打威胁程度最高的敌人,也就是去打优先级最高的那个敌人,
    10. 优先级的高低是能够定义的,多是最强悍的那个敌人、
    11. 也有多是最弱小的敌人、也有多是距离你最近的敌人等等,
    12. AI 自动打怪中的优先队列也是动态的处理敌人,
    13. 由于敌人是不断的在接近你,在每一时刻 AI 都须要考虑新的敌人的出现,
    14. 由于新的敌人多是优先级更高的须要被打击的目标,
    15. 这就是一个动态的过程,因此才须要使用优先队列。
  6. 实现优先级队列的时候是不用去管优先高低的
    1. 当用户具体去使用的时候,才会去定义优先级。

优先队列的实现思路

  1. MyQueue
    1. void enqueue(e)
    2. E dequeue()
    3. E getFront()
    4. int getSize()
    5. boolean isEmpty()
  2. MyPriorityQueue
    1. 实现的时候与普通队列的实现会有些区别,
    2. 主要区别在于出队操做和获取队首元素操做上,
    3. 由于出队元素是优先级最高的元素,
    4. 队首的元素也是优先级最高的元素,
    5. 并非普通队列那样的选择最先进入队列的元素,
    6. 对于优先队列这样的数据结构,
    7. 也是可使用不一样的底层实现的,
    8. 可使用最基础的普通线性数据结构和顺序线性结构来实现,
    9. 也可使用来进行实现。

两种基础实现优先队列思路

  1. 使用普通的线性结构
    1. 入队为O(1)级别的操做
    2. 出队须要扫描一遍线性结构中全部的元素,
    3. 从而找出其优先级最高的那个元素,
    4. 而后把它拿出队列,
    5. 因此为O(n)级别的操做,
    6. 若是某一操做为O(n)级别的操做,
    7. 那么就会大大的下降整个数据结构的效率。
    8. 因此普通线性结构的实现方式性能方面会很很差。
  2. 顺序线性结构
    1. 整个线性结构维持全部元素的顺序,
    2. 整个线性数据结构都是从小到大或者从大到小排列的,
    3. 在这种状况下出队将变得很是的容易,
    4. 出队会是O(1)级别的操做,
    5. 由于只须要拿出当前这个数据结构队首
    6. 或者队尾的那个元素就行了,
    7. 那么入队的时候就会是一个O(n)级别的操做了,
    8. 在入队的时候须要找到这个元素在线性结构中应该插入的位置,
    9. 这个找到合适插入位置的操做须要O(n)的复杂度,
    10. 在最差的状况就须要将整个线性数据结构都扫描一遍,
    11. 因此顺序线性结构在出队的操做上是O(1)的复杂度,
    12. 可是在入队的操做上是O(n)的复杂度。
    13. 因此不管是普通的线性结构仍是顺序的线性结构,
    14. 它们都有必定的劣势,都会存在一个操做是O(n)级别的操做,
    15. 它们都不够好。
  3. 可使用动态数组、链表这样的底层实现来进行优先队列的实现
    1. 虽然这样实现出来的优先队列,可能实际不会去应用,
    2. 可是也是一个很好的练习,能够深刻的理解队列这样抽象的数据结构,
    3. 在这个基础上限制它的性质,就建立出了优先队列这个概念,
    4. 具体在实现优先队列这个概念的时候,还可使用不一样的底层实现,
    5. 这在数据结构领域是很是重要的思想。

使用堆来实现优先队列思路

  1. 使用堆这种数据结构
    1. 入队操做和出队操做都是O(logn)这种级别的操做,
    2. 并且它和二分搜索树不一样,
    3. 二分搜索树是在平均状况下是O(logn)的时间复杂度,
    4. 而堆是在最差的状况下是O(logn)的时间复杂度,
    5. 这也使得堆这种数据结构是至关高效的。
  2. 它和前面两种基础的实现方式有着天壤之别。

堆(Heap)

  1. 在计算机科学的领域一般你见到了O(logn)这样的时间复杂度
    1. 那么近乎必定就和树这样的数据结构有关,
    2. 并不必定是显示的构造出一棵树,
    3. 不管是排序算法中的归并排序、快速排序都是O(nlog(n))这个级别的,
    4. 在排序的过程当中没有使用树这种数据结构,
    5. 可是这个递归的过程当中其实造成了一棵隐形的递归树。

堆的基本结构

  1. 堆这种结构自己也是一棵树数组

    1. 其实堆也有不少种,
    2. 堆最为主流的一种实现方式是使用二叉树来表示一个堆,
    3. 也叫二叉堆(BinaryHeap),
    4. 说白了二叉堆就是知足一些特殊性质的二叉树。
  2. 满二叉树与彻底二叉树数据结构

    1. 满二叉树,就是除了叶子节点以外,
    2. 全部的节点的左右孩子都不为空。
    3. 彻底二叉树不必定是一个满的二叉树,可是它不满的那部分,
    4. 也就是在缺失节点的那部分必定是在整颗树的右下侧,
    5. 也就是说把元素按照一层一层的顺序排列成
    6. 一棵二叉树的形状的时候,获得的这棵树就是彻底的二叉树。
    7. 一个三层的满二叉树能装 7 个节点,一个四层的满二叉树能装 15 个节点,
    8. 10 个节点对于一棵彻底二叉树来讲,前七节点装满前三层,
    9. 对于第四层则是从左到右把剩下的三个节点放进去,
    10. 这就是彻底二叉树的定义。
  3. 教材中对于彻底二叉树的定义很是的拗口架构

    1. 其实彻底二叉树很是的简单的,
    2. 就是把元素一层一层的放置,直到放不下了为止,
    3. 全部整棵树的右下角的这部分多是空的,由于缺乏一些元素。
  4. 二叉堆知足的性质dom

    1. 首先它是一棵彻底二叉树,
    2. 除此以外它还有一个很是重要的性质,
    3. 在堆(树)中的某个节点的值或者任意一个节点的值
    4. 老是不大于其父节点的值,
    5. 也就是说全部节点的值必定大于或者等于它的孩子节点的值,
    6. 因此根节点的元素必定是最大的元素,它大于它全部左右节点的值,
    7. 同时它的左右子树也是一个堆,对于树中任意节点都会知足这个性质,
    8. 这样获得的堆一般叫作最大堆,由于根节点是最大的一个元素,
    9. 相应的也能够定义出最小堆,这个定义方式和最大堆同样,
    10. 只不过每个节点的值都要小于等于它的孩子节点的值,
    11. 这样获得的堆叫作最小堆,
    12. 最大堆和最小堆在某种程度上是能够统一的,
    13. 由于什么叫大什么叫小能够本身来定义。
    14. 虽然在堆中每个节点的值都大于等于它的左右孩子节点的值
    15. 可是在堆这种数据结构上,
    16. 层次比较低的节点的值不必定大于层次比较高的节点的值,
    17. 由于二叉堆只保证每个节点的父节点比本身大,
    18. 可是节点的大小和节点所在的层次其实没有必然的联系。
  5. 实现二叉堆必须知足的要求,ide

    1. 首先是彻底二叉树,
    2. 其次对于堆中每个节点相应的元素值都是大于等于它的孩子节点的,
    3. 这样获得的就是最大堆,最大堆是一个彻底二叉树,因此在具体实现上,
    4. 有一个很巧妙的手段,可使用二分搜索树的方式,
    5. 先定义一个节点而后定义它的左右孩子就能实现这个堆了,
    6. 可是彻底二叉树的特色其实就是一个一个的节点
    7. 按照顺序一层一层的码放出来。
    8. 可使用数组的方式表现出一颗彻底二叉树,
    9. 对于数组的表示方式来讲,要解决的问题是,
    10. 在数组中的每个节点应该怎么找到它的左右孩子,
    11. 由于设置一个节点的话直接使用这个节点左右的指针
    12. 去指向左右孩子所在的节点,可是用数组去存储的话,
    13. 其实存在一些规律。
  6. 以数组的方式表现一棵彻底二叉树的规律函数

    1. parent(i) = i / 2
    2. 这个节点的父节点所在位置的索引就是当前节点二分之一(忽略小数或向下取整),
    3. left child (i) = 2 * i
    4. 这个节点的左孩子所在位置的索引就是当前节点索引的 2 倍,
    5. right child (i) = 2 * i + 1
    6. 这个节点的右孩子所在位置的索引就是当前节点索引的 2 倍+1,
    7. 这样一来不只能够很是方便的去索引这个节点的左右孩子,
    8. 还能够很是方便的去索引到当前节点的父节点,
    9. 这就是以数组的方式去表现一棵彻底二叉树的规律和性质。
  7. 彻底二叉树的好处

    1. 它自己就是那些元素按照顺序的一层一层的在这棵树中排列,
    2. 因此将它标上索引以后,
    3. 每个节点和它的左右孩子节点以及它的父节点在索引之间
    4. 就存在了一种很明显的逻辑关系,
    5. 这个逻辑关系对于彻底二叉树来讲是成立的。
  8. 在不少教科书中实现堆的时候,

    1. 用数组来存储,索引都是从 1 开始标,
    2. 这是由于相对来讲,计算孩子节点和父亲节点会比较方便,
    3. 这样一来就会出现一个小问题,也就是将 0 这个位置空出来了,
    4. 可是空出来并无什么影响,例如在循环队列中、虚拟头节点链表中,
    5. 也都诚心的空出这么一个位置来方便逻辑的编写,
    6. 不过对于堆来讲,就算不空出这个位置,逻辑同样是很是简单的,
    7. 区别在于计算父节点和左右孩子节点索引时相应的公式发生了一点点的改变,
    8. 也就是相应的 i 进行了偏移
    // 原来是这样的 空了数组中索引为0的位置
       parent(i) = i / 2
       left child (i) = 2 * i
       right child (i) = 2 * i + 1
    
       // 偏移以后是这样的 没空数组中索引为0的位置
       parent(i) = (i - 1) / 2
       left child (i) = 2 * i + 1
       right child (i) = 2 * i + 2
    复制代码

实现最大堆基本架构 代码示例

  1. (class: Myarray, class: MaxHeap)

  2. Myarray

    // 自定义类
    class MyArray {
       // 构造函数,传入数组的容量capacity构造Array 默认数组的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 获取数组中的元素实际个数
       getSize() {
          return this.size;
       }
    
       // 获取数组的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判断数组是否为空
       isEmpty() {
          return this.size === 0;
       }
    
       // 给数组扩容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引处插入元素
       insert(index, element) {
          // 先判断数组是否已满
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而后判断索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最后 将指定索引处腾出来
          // 从指定索引处开始,全部数组元素所有日后移动一位
          // 从后往前移动
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引处插入元素
          this.data[index] = element;
          // 维护一下size
          this.size++;
       }
    
       // 扩展 在数组最前面插入一个元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 扩展 在数组最后面插入一个元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其实在数组中添加元素 就至关于在数组最后面插入一个元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其实指向的是 当前数组最后一个元素的 后一个位置的索引。
          this.data[this.size] = element;
          // 维护size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能访问没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 扩展: 获取数组中第一个元素
       getFirst() {
          return this.get(0);
       }
    
       // 扩展: 获取数组中最后一个元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一个自定义数组来存取这些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回这个自定义数组
          return myarray;
       }
    
       // 删除指定索引处的元素
       remove(index) {
          // 索引合法性验证
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暂存即将要被删除的元素
          let element = this.data[index];
    
          // 后面的元素覆盖前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 为容量的四分之一时 就能够缩容了
          // 防止复杂度震荡
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 缩容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 扩展:删除数组中第一个元素
       shift() {
          return this.remove(0);
       }
    
       // 扩展: 删除数组中最后一个元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 扩展: 根据元素来进行删除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 扩展: 根据元素来删除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每删除一个元素 原数组中就少一个元素,
          // // 索引数组中的索引值是按照大小顺序排列的,
          // // 因此 这个cur记录的是 原数组元素索引的偏移量
          // // 只有这样才可以正确的删除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在页面上展现
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    复制代码
  3. MaxHeap

    // 自定义二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其父节点的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引为0是根节点,根节点没有父亲节点,小于0就更加不能够了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其左孩子节点的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其右孩子节点的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 获取堆中实际的元素个数
       getSize() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否为空的判断值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    复制代码

向堆中添加元素 和 Sift Up

  1. 最大堆是一个彻底二叉树
    1. 因此能够很是方便使用数组的方式来表示它。
  2. 向堆中添加元素
    1. 从用户的角度上来看是添加元素,
    2. 可是从堆的角度上来看,
    3. 会涉及到堆的一个很是基础的内部操做,
    4. 也就是 Sift Up(堆中元素上浮的过程),
    5. 添加操做必定是往堆的最底层进行添加,
    6. 也就是向数组的尾部进行添加,
    7. 新添加的元素会与其父祖节点进行比较,
    8. 若是新添加的元素比父祖辈元素大,
    9. 则会不断的交换元素,直到知足彻底二叉树的性质,
    10. 也就是每个节点都比其孩子节点大,也比其父节点小,
    11. 这个不断交换元素,就是元素在堆中慢慢从底层
    12. 上浮到合适层的过程就叫 Sift UP,
    13. 计算父祖辈元素的索引能够经过新增长的元素的索引来进行计算,
    14. 也就是数组中实际个数减去一得到。
  3. SiftUp 操做不管是递归写法仍是非递归写法,他们都一个共同的特色
    1. 元素上浮后结束的条件 当前节点元素值 小于其父节点元素值
    2. 索引越界的终止条件 要上浮的元素索引 小于等于 0
    3. 有了这两个条件以后,实现上浮操做很简单。

代码示例

  1. (class: Myarray, class: MaxHeap)

  2. Myarray

    // 自定义类
    class MyArray {
       // 构造函数,传入数组的容量capacity构造Array 默认数组的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 获取数组中的元素实际个数
       getSize() {
          return this.size;
       }
    
       // 获取数组的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判断数组是否为空
       isEmpty() {
          return this.size === 0;
       }
    
       // 给数组扩容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引处插入元素
       insert(index, element) {
          // 先判断数组是否已满
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而后判断索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最后 将指定索引处腾出来
          // 从指定索引处开始,全部数组元素所有日后移动一位
          // 从后往前移动
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引处插入元素
          this.data[index] = element;
          // 维护一下size
          this.size++;
       }
    
       // 扩展 在数组最前面插入一个元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 扩展 在数组最后面插入一个元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其实在数组中添加元素 就至关于在数组最后面插入一个元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其实指向的是 当前数组最后一个元素的 后一个位置的索引。
          this.data[this.size] = element;
          // 维护size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能访问没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 扩展: 获取数组中第一个元素
       getFirst() {
          return this.get(0);
       }
    
       // 扩展: 获取数组中最后一个元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一个自定义数组来存取这些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回这个自定义数组
          return myarray;
       }
    
       // 删除指定索引处的元素
       remove(index) {
          // 索引合法性验证
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暂存即将要被删除的元素
          let element = this.data[index];
    
          // 后面的元素覆盖前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 为容量的四分之一时 就能够缩容了
          // 防止复杂度震荡
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 缩容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 扩展:删除数组中第一个元素
       shift() {
          return this.remove(0);
       }
    
       // 扩展: 删除数组中最后一个元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 扩展: 根据元素来进行删除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 扩展: 根据元素来删除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每删除一个元素 原数组中就少一个元素,
          // // 索引数组中的索引值是按照大小顺序排列的,
          // // 因此 这个cur记录的是 原数组元素索引的偏移量
          // // 只有这样才可以正确的删除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // 新增: 交换两个索引位置的变量 2018-11-6
       swap(indexA, indexB) {
          if (
             indexA < 0 ||
             indexA >= this.size ||
             indexB < 0 ||
             indexB >= this.size
          )
             throw new Error('Index is Illegal.'); // 索引越界异常
    
          let temp = this.data[indexA];
          this.data[indexA] = this.data[indexB];
          this.data[indexB] = temp;
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在页面上展现
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    复制代码
  3. MaxHeap

    // 自定义二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 添加操做
       add(element) {
          // 追加元素
          this.myArray.push(element);
    
          // 将追加的元素上浮到堆中合适的位置
          this.siftUp(this.myArray.getSize() - 1);
       }
    
       // 堆的上浮操做 -
       siftUp(index) {
          // this.nonRecursiveSiftUp(index);
          this.recursiveSiftUp(index);
    
          // 不管是递归仍是非递归都有一个
          // 元素上浮后结束的条件 当前节点元素值 小于其父节点元素值
          // 和
          // 索引即将越界的终止条件 要上浮的元素索引 小于等于0
       }
    
       // 堆的上浮操做 递归算法 -
       recursiveSiftUp(index) {
          // 解决最基本的问题, 递归终止条件
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          // 递归写法
          if (this.compare(currentValue, parentValue) > 0) {
             this.swap(index, parentIndex);
             this.recursiveSiftUp(parentIndex);
          }
       }
    
       // 堆的上浮操做 非递归算法 -
       nonRecursiveSiftUp(index) {
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          while (this.compare(currentValue, parentValue) > 0) {
             // 交换堆中两个元素位置的值
             this.swap(index, parentIndex);
    
             // 交换了位置以后,元素上浮后的索引变量也要进行相应的变动
             index = parentIndex;
             // 若是索引小于等于0了 那就结束循环
             if (index <= 0) break;
             currentValue = this.myArray.get(index);
             parentIndex = this.calcParentIndex(index);
             parentValue = this.myArray.get(parentIndex);
          }
       }
    
       // 堆中两个元素的位置进行交换
       swap(indexA, indexB) {
          this.myArray.swap(indexA, indexB);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其父节点的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引为0是根节点,根节点没有父亲节点,小于0就更加不能够了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其左孩子节点的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其右孩子节点的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 比较的功能 -
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is error. element can't compare.");
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 获取堆中实际的元素个数
       getSize() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否为空的判断值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    复制代码

取出堆中的最大元素和 Sift Down

  1. 从堆中取出元素也就是从堆的顶部取出元素
    1. 在最大堆中堆顶的元素就是最大元素,
    2. 也就是堆中根节点的元素,这个过程叫作 Extract Max,
    3. 也就是提取出堆中最大的元素
  2. 从堆中取出元素
    1. 取出了堆顶的元素后,
    2. 堆中就有两棵子树了,就不符合一棵彻底二叉树的性质了,
    3. 这时候就让最低层的最后一个元素放到最上层的根节点,
    4. 那么又开始符合一棵彻底二叉树的性质了,
    5. 只不过根节点的元素不必定符合父节点的值大于全部子节点的值的性质,
    6. 也就是不符合堆的性质了,由于每个节点要大于等于其孩子节点的值,
    7. 那这个根节点就要进行下沉操做了,
    8. 也就是每次下沉的时候都要去和它的两个孩子节点作比较,
    9. 选择它的两个孩子中最大的那个元素,
    10. 若是这两个孩子元素最大的那个元素比它本身还要大的话,
    11. 那么它本身就和两个孩子中最大的那个元素交换一下位置,
    12. 交换过位置以后若是仍是不符合堆的性质,
    13. 那么就继续下沉,继续交换,直到符合堆的性质为止,
    14. 由于那样就不须要再下沉了,这时候你须要手动的终止,
    15. 若是你不手动的终止,虽然整个操做到最后也会结束,
    16. 可是自动终止,时间复杂度一直都是最坏的状况O(logn)
    17. 其实手动终止,最好的状况是小于O(logn)的,
    18. 因此若是能够的话尽可能手动终止一下。
  3. 堆排序的基本原理
    1. 大概就是 Extract 这个过程,
    2. 可是真正的堆排序仍是有优化的空间的,
    3. 如今的方式是将数据扔进一个堆,
    4. 而后再从堆中一个一个取出来放入一个数组内,
    5. 还使用了额外的空间,
    6. 可是使用堆这种组织元素的思想彻底能够将数据进行原地的排序。
  4. 在一个彻底二叉树中,若是一个节点没有左孩子节点必然就没有右孩子节点。

代码示例

  1. (class: Myarray, class: MaxHeap, class: Main)

  2. Myarray

    // 自定义类
    class MyArray {
       // 构造函数,传入数组的容量capacity构造Array 默认数组的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 获取数组中的元素实际个数
       getSize() {
          return this.size;
       }
    
       // 获取数组的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判断数组是否为空
       isEmpty() {
          return this.size === 0;
       }
    
       // 给数组扩容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引处插入元素
       insert(index, element) {
          // 先判断数组是否已满
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而后判断索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最后 将指定索引处腾出来
          // 从指定索引处开始,全部数组元素所有日后移动一位
          // 从后往前移动
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引处插入元素
          this.data[index] = element;
          // 维护一下size
          this.size++;
       }
    
       // 扩展 在数组最前面插入一个元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 扩展 在数组最后面插入一个元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其实在数组中添加元素 就至关于在数组最后面插入一个元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其实指向的是 当前数组最后一个元素的 后一个位置的索引。
          this.data[this.size] = element;
          // 维护size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能访问没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 扩展: 获取数组中第一个元素
       getFirst() {
          return this.get(0);
       }
    
       // 扩展: 获取数组中最后一个元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改没有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一个自定义数组来存取这些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回这个自定义数组
          return myarray;
       }
    
       // 删除指定索引处的元素
       remove(index) {
          // 索引合法性验证
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暂存即将要被删除的元素
          let element = this.data[index];
    
          // 后面的元素覆盖前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 为容量的四分之一时 就能够缩容了
          // 防止复杂度震荡
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 缩容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 扩展:删除数组中第一个元素
       shift() {
          return this.remove(0);
       }
    
       // 扩展: 删除数组中最后一个元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 扩展: 根据元素来进行删除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 扩展: 根据元素来删除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每删除一个元素 原数组中就少一个元素,
          // // 索引数组中的索引值是按照大小顺序排列的,
          // // 因此 这个cur记录的是 原数组元素索引的偏移量
          // // 只有这样才可以正确的删除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // 新增: 交换两个索引位置的变量 2018-11-6
       swap(indexA, indexB) {
          if (
             indexA < 0 ||
             indexA >= this.size ||
             indexB < 0 ||
             indexB >= this.size
          )
             throw new Error('Index is Illegal.'); // 索引越界异常
    
          let temp = this.data[indexA];
          this.data[indexA] = this.data[indexB];
          this.data[indexB] = temp;
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在页面上展现
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    复制代码
  3. MaxHeap

    // 自定义二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 添加操做
       add(element) {
          // 追加元素
          this.myArray.push(element);
    
          // 将追加的元素上浮到堆中合适的位置
          this.siftUp(this.myArray.getSize() - 1);
       }
    
       // 堆的上浮操做 -
       siftUp(index) {
          // this.nonRecursiveSiftUp(index);
          this.recursiveSiftUp(index);
    
          // 不管是递归仍是非递归都有一个
          // 元素上浮后结束的条件 当前节点元素值 小于其父节点元素值
          // 和
          // 索引即将越界的终止条件 要上浮的元素索引 小于等于0
       }
    
       // 堆的上浮操做 递归算法 -
       recursiveSiftUp(index) {
          // 解决最基本的问题, 递归终止条件
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          // 递归写法
          if (this.compare(currentValue, parentValue) > 0) {
             this.swap(index, parentIndex);
             this.recursiveSiftUp(parentIndex);
          }
       }
    
       // 堆的上浮操做 非递归算法 -
       nonRecursiveSiftUp(index) {
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          while (this.compare(currentValue, parentValue) > 0) {
             // 交换堆中两个元素位置的值
             this.swap(index, parentIndex);
    
             // 交换了位置以后,元素上浮后的索引变量也要进行相应的变动
             index = parentIndex;
             // 若是索引小于等于0了 那就结束循环
             if (index <= 0) break;
             currentValue = this.myArray.get(index);
             parentIndex = this.calcParentIndex(index);
             parentValue = this.myArray.get(parentIndex);
          }
       }
    
       // 找到优先级最大的元素 (查找元素)操做
       findMax() {
          if (this.myArray.isEmpty())
             throw new Error('can not findMax when heap is empty.');
          return this.myArray.getFirst();
       }
    
       // 提取优先级最大的元素(删除元素)操做
       extractMax() {
          // 获取堆顶的元素
          let maxElement = this.findMax();
    
          // 获取堆底的元素
          let element = this.myArray.getLast();
    
          // 让堆底的元素替换掉堆顶的元素
          this.myArray.set(0, element);
    
          // 移除堆底的元素
          this.myArray.pop();
    
          // 让堆顶的元素开始下沉,从而可以正常知足堆的性质
          this.siftDown(0);
    
          // 返回堆顶的元素
          return maxElement;
       }
    
       // 堆的下沉操做 -
       siftDown(index) {
          // this.nonRecursiveSiftDown(index);
          this.recursiveSiftDown(index);
       }
    
       // 堆的下沉操做 递归算法 -
       recursiveSiftDown(index) {
          // 递归终止条件
          // 若是当前索引位置的元素没有左孩子就说也没有右孩子,
          // 那么能够直接终止,由于没法下沉
          if (this.calcLeftChildIndex(index) >= this.myArray.getSize()) return;
    
          const leftChildIndex = this.calcLeftChildIndex(index);
          const leftChildValue = this.myArray.get(leftChildIndex);
          const rightChildIndex = this.calcRightChildIndex(index);
          let rightChildValue = null;
    
          // let maxIndex = 0;
          // if (rightChildIndex >= this.myArray.getSize())
          // maxIndex = leftChildIndex;
          // else {
          // rightChildValue = this.myArray.get(rightChildIndex);
          // if (this.compare(leftChildValue, rightChildValue) > 0)
          // maxIndex = leftChildIndex;
          // else
          // maxIndex = rightChildIndex;
          // }
    
          // 这段代码是上面注释代码的优化
          let maxIndex = leftChildIndex;
          if (rightChildIndex < this.myArray.getSize()) {
             rightChildValue = this.myArray.get(rightChildIndex);
             if (this.compare(leftChildValue, rightChildValue) < 0)
                maxIndex = rightChildIndex;
          }
    
          let maxValue = this.myArray.get(maxIndex);
          let currentValue = this.myArray.get(index);
    
          if (this.compare(maxValue, currentValue) > 0) {
             // 交换位置
             this.swap(maxIndex, index);
             // 继续下沉
             this.recursiveSiftDown(maxIndex);
          }
       }
    
       // 堆的下沉操做 非递归算法 -
       nonRecursiveSiftDown(index) {
          // 该索引位置的元素有左右孩子节点才能够下沉,
          // 在彻底二叉树中 若是一个节点没有左孩子必然没有右孩子
          while (this.calcLeftChildIndex(index) < this.myArray.getSize()) {
             let leftChildIndex = this.calcLeftChildIndex(index);
             let leftChildValue = this.myArray.get(leftChildIndex);
             let rightChildIndex = this.calcRightChildIndex(index);
             let rightChildValue = null;
             let maxIndex = leftChildIndex;
    
             if (rightChildIndex < this.myArray.getSize()) {
                rightChildValue = this.myArray.get(rightChildIndex);
                if (this.compare(leftChildValue, rightChildValue) <= 0)
                   maxIndex = rightChildIndex;
             }
    
             let maxValue = this.myArray.get(maxIndex);
             let currentValue = this.myArray.get(index);
    
             if (this.compare(maxValue, currentValue) > 0) {
                this.swap(maxIndex, index);
                index = maxIndex;
                continue;
             } else break;
          }
       }
    
       // 堆中两个元素的位置进行交换
       swap(indexA, indexB) {
          this.myArray.swap(indexA, indexB);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其父节点的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引为0是根节点,根节点没有父亲节点,小于0就更加不能够了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其左孩子节点的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 辅助函数 计算出堆中指定索引位置的元素其右孩子节点的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 比较的功能 -
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is error. element can't compare.");
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 获取堆中实际的元素个数
       size() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否为空的判断值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    复制代码
  4. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('MyMaxHeap Area');
          const n = 100;
    
          const maxHeap = new MyMaxHeap();
          const random = Math.random;
    
          // 循环添加随机数的值
          for (let i = 0; i < n; i++) maxHeap.add(random() * n);
    
          console.log('MaxHeap maxHeap size:' + maxHeap.size());
          this.show('MaxHeap maxHeap size:' + maxHeap.size());
    
          // 使用数组取值
          let arr = [];
          for (let i = 0; i < n; i++) arr[i] = maxHeap.extractMax();
    
          console.log(
             'Array arr size:' +
                arr.length +
                ',MaxHeap maxHeap size:' +
                maxHeap.size()
          );
          this.show(
             'Array arr size:' +
                arr.length +
                ',MaxHeap maxHeap size:' +
                maxHeap.size()
          );
          console.log(arr, maxHeap);
          // 检验一下是否符合要求
          for (let i = 1; i < n; i++)
             if (arr[i - 1] < arr[i]) throw new Error('error.');
    
          console.log('test maxHeap completed.');
          this.show('test maxHeap completed.');
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

堆的时间复杂度分析

  1. 堆中的时间复杂度都是O(logn)级别的
    1. 其实仍是二叉树的高度这个级别的,
    2. 对于堆来讲它是一棵彻底二叉树,
    3. 因此它永远不会退化成一个链表,
    4. 一棵彻底二叉树它的高度和节点的数量之间的关系必定是logn这个级别的关系,
    5. 这使得堆中相应的 add、extractMax 操做是很是的高效的。
  2. add O(logn)
  3. extractMax O(logn)
  4. 能够给堆再添加两个操做,从而对这个堆再进行优化。
相关文章
相关标签/搜索