百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

阿里朋友的忠告:大厂里算法很重要,先来了解一下堆排序

toyiye 2024-07-08 00:30 13 浏览 0 评论

前言

前几天和阿里的朋友聊天,得知大厂里经常考算法,还要考算法等级,分高级、中级、初级三个大等级,每个阶段的程序员需要考对应等级的算法,在大厂确实不易。

但是,算法算是研发人员的需要掌握的基础底层知识,所以想成为一名优秀的程序员还是有必要掌握的一些算法的,下面我们就一起来学习一下“堆排序”。

一、堆是什么?

堆是一种特殊的完全二叉树

1.1、二叉树

定义:二叉树是每个节点最多有两个子节点的树结构。

特点:

  • 每个节点最多有两个子节点,即二叉树不存在大于2的节点;
  • 二叉树的子树有左右之分,其子树的次序不能颠倒;

1.2、满二叉树

定义:除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉,一颗深度为k且有2^k-1个节点的树成为满二叉树

例如下图深度k=3,节点数N=7=2^3-1

特点:

  • 每层都是满的
  • 叶子节点(度为0的节点)全部在最底层

1.3、完全二叉树

定义:

深度为k的具有n个节点的二叉树,当且仅当其每一个节点都与深度为k满二叉树编号为1~N的节点一一对应时,称之为完全二叉树

特点:

1.4、堆的特点:

父节点的值 大于等于或小于等于 子节点的值,根据大于和小于也分成了二种

  • 完全二叉树
  • 父节点的值 大于等于 子节点的值的为大顶堆。
  • 父节点的值 小于等于 子节点的值的为小顶堆。

举例:

二、如何通过堆来排序?

给定一个待排序的数组tree = [a1,a2,a3,...an] 进行如下操作:

1. 建堆:将数组 tree 建成一个堆

2. 输出:删除根节点,将其输出,然后将最后一个叶子节点移动到根节点

3. 调整:移动之后的树不满足堆的性质,此时需要进行一轮调整,使其成为一个新堆

4. 不断进行步骤2、3 得到的输出序列就是排好的序列

三、如何构建堆?

根据建堆操作的不同,可分为筛选法建堆的堆排序和插入法建堆的堆排序

3.1、 筛选法

(1) 将数组按顺序生成一个完全二叉树。

(2) 按照从下到上,从右到左的原则扫描父节点,对每一个父节点比较其左右子节点,若子节点大于父节点,则交换父子节点,并向下继续检查有没有破坏堆结构。

(3) 反复执行步骤(2)直至根节点。

//建立大顶堆
function buildMaxHeap(tree, n) {
    //从最后一个节点开始
    let last_node = n - 1
    //当前节点的父节点
    let parent = (last_node - 1) / 2
    for (let i = parent; i >= 0; i--) {
        heapify(tree, n, i)
    }
}
/**
 * 
 * @param {*} tree 所有节点数组
 * @param {*} n    数组长度
 * @param {*} i    当前需heapify处理的节点
 * @returns 
 */
function heapify(tree, n, i) {
    if (i >= n) {
        return
    }
    let c1 = 2 * i + 1,//左节点
        c2 = 2 * i + 2//右节点
    let max = i //最大值
    if (c1 < n && tree[c1] > tree[max]) {
        max = c1
    }
    if (c2 < n && tree[c2] > tree[max]) {
        max = c2
    }
    if (max != i) {
        swap(tree, max, i)
        heapify(tree, n, max)
    }
}

//交换节点
function swap(arr, i, j) {
    let temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
}


let tree = [2, 5, 3, 1, 10, 4]
let n = tree.length
console.log('创建堆前结果', tree);// [ 2, 5, 3, 1, 10, 4 ]

buildMaxHeap(tree, n)
console.log('创建堆结果', tree);//[ 10, 5, 4, 1, 2, 

3.2、 插入法

(1) 生成一颗空的完全二叉树,并将第一个数据元素作为根节点,

(2) i从第二个数据元素开始,将第i个数据元素插入二叉排序数的第i个位置

(3) 将被插入的元素与其父节点进行比较,若大于父节点,则与父节点交换,交换后继续向上检查,直到根节点

(4) 反复执行(2)(3)直到所有元素插入到完全二叉树中并调整完成,得到一个大顶堆

//创建大顶堆
function buildMaxHeap(tree, n) {
    for (var i = 0; i < n - 1; i++) {
        for (var j = i + 1; j > 0; j--) {
            let parent = Math.floor((j - 1) / 2);
            if (tree[j] > tree[parent]) {
                swap(tree, j, parent);
            }
        }
    }
}
//交换节点
function swap(arr, i, j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

let tree = [2, 5, 3, 1, 10, 4];
let n = tree.length;
console.log("创建堆前结果", tree); //[ 2, 5, 3, 1, 10, 4 ]

buildMaxHeap(tree, n);
console.log("创建堆后结果", tree);//[ 10, 5, 4, 1, 2, 3 ]

四、堆排序

删除根节点,将其输出,然后将最后一个叶子节点移动到根节点

移动之后的树不满足堆的性质,此时需要进行一轮调整,使其成为一个新堆

//筛选建立大顶堆
function buildMaxHeap(tree, n) {
  let last_node = n - 1;
  let parent = (last_node - 1) / 2;
  for (let i = parent; i >= 0; i--) {
    heapify(tree, n, i);
  }
}
//插入创建大顶堆
function buildMaxHeap2(tree, n) {
  for (var i = 0; i < n - 1; i++) {
    for (var j = i + 1; j > 0; j--) {
      let parent = Math.floor((j - 1) / 2);
      if (tree[j] > tree[parent]) {
        swap(tree, j, parent);
      }
    }
  }
}
/**
 * 堆调整
 * 找出左右子节点最大值,并与父节点交换
 * @param {*} tree 所有节点数组
 * @param {*} n    数组长度
 * @param {*} i    当前需heapify处理的节点
 * @returns
 */
function heapify(tree, n, i) {
  if (i >= n) {
    return;
  }
  let c1 = 2 * i + 1, //左节点
    c2 = 2 * i + 2; //右节点
  let max = i; //最大值
  if (c1 < n && tree[c1] > tree[max]) {
    max = c1;
  }
  if (c2 < n && tree[c2] > tree[max]) {
    max = c2;
  }
  if (max != i) {
    swap(tree, max, i);
    heapify(tree, n, max);
  }
}

//交换节点
function swap(arr, i, j) {
  let temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
//筛选堆排序
function heap_sort(tree, n) {
  buildMaxHeap(tree, n, 0);
  console.log("创建堆后结果", tree);//[ 10, 5, 4, 1, 2, 3 ]
  for (let i = n - 1; i >= 0; i--) {
    //交换根节点和最后一个节点
    swap(tree, i, 0); //砍断最后一个节点,即最后一个节点是最大值
    heapify(tree, i, 0);
  }
}
//插入堆排序
function heap_sort2(tree, n) {
  buildMaxHeap2(tree, n, 0);
  console.log("创建堆后结果2", tree);//[ 10, 5, 4, 1, 2, 3 ]
  for (let i = n - 1; i >= 0; i--) {
    //交换根节点和最后一个节点
    swap(tree, i, 0); //砍断最后一个节点,即最后一个节点是最大值
    buildMaxHeap2(tree, i, 0);
  }
}

let tree = [2, 5, 3, 1, 10, 4];
let n = tree.length;
console.log("创建堆前结果", tree); //[ 2, 5, 3, 1, 10, 4 ]

heap_sort(tree, n);
console.log("堆排序后结果", tree); //[ 1, 2, 3, 4, 5, 10 ]

console.log('=========================');
let tree2 = [2, 5, 3, 1, 10, 4];
let n2 = tree.length;
console.log("创建堆前结果2", tree2); //[ 2, 5, 3, 1, 10, 4 ]

heap_sort2(tree2, n2);
console.log("堆排序后结果2", tree2); //[ 1, 2, 3, 4, 5, 10 ]

五、时间复杂度分析

5.1、 筛选法

(1) 将数组按顺序生成一个完全二叉树。

(2) 按照从下到上,从右到左的原则扫描父节点,对每一个父节点比较其左右子节点,若子节点大于父节点,则交换父子节点,并向下继续检查有没有破坏堆结构。

(3) 反复执行步骤(2)直至根节点。

建堆最好时间复杂度 O(n)

当按顺序生成的完全二叉树就满足堆的要求的时候,那么步骤(2) 中每个节点只需要进行两次比较:

(1)左右孩子比较

(2)与最大的孩子节点进行比较

建堆最坏时间复杂度O(n):

当每次检查都需要交换并且一直检查到叶子节点时:第 k层节点最多需要向下移动 h-k次,h为堆高。

由二叉树的性质可知,第 k层最多有 2^(k-1)个节点

5.2、 插入法

(1) 生成一颗空的完全二叉树,并将第一个数据元素作为根节点,

(2) i从第二个数据元素开始,将第i个数据元素插入二叉排序数的第i个位置

(3) 将被插入的元素与其父节点进行比较,若大于父节点,则与父节点叫喊,交换后继续向上检查,直到根节点

(4) 反复执行(2)(3)直到所有元素插入到完全二叉树中并调整完成,得到一个大顶堆

建堆最好时间复杂度 O(n):

每次插入只需要比较一次,故最好时间复杂度为 O(n)

建堆最坏时间复杂度 :

每次插入后都发生交换,并一直检查到根节点;第 k层节点最多需要向上移动 k-1 次,由二叉树的性质可知,第k层最多有

2的(k-1)次个节点, h为堆高

5.3、堆排序时间复杂度

六、空间复杂度

使用自身存储,所以是O(1)

七、总结

以下均为个人总结,如有问题欢迎指点:

堆排序的步骤:建堆 -> 首尾交换,断尾重构 -> 重复第二步,直到断掉所有尾巴

稳定性:堆排序存在大量的筛选和交换过程,可能造成不相邻的两个等值节点之间的顺序会发生层级变化,最终导致这两个等值节点在序列中的相对位置发生变化。属于不稳定的排序算法。

使用场景:堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如TOP-K一类问题时,几乎是首选算法。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码