【堆排序算法详解】Java/Go/Python/JS/C不同语言实现

【堆排序算法详解】Java/Go/Python/JS/C不同语言实现

说明

堆排序(Heap Sort)算法,是将数据看成近似完全二叉树结构,并根据完全二叉树的特性来进行排序的一种算法。完全二叉树要求每个节点的值都大于等于其左右子节点的值,称为大顶堆;或者每个节点的值都小于或等于其左右子节点的值,称为小顶堆。先将父节点的最大数取出,并构建最大堆,再将堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。

堆排序本质也是一种选择排序,利用树形结构选择排序。只不过在直接选择排序中,为了从A[1…n]中选择最大记录,需比较n-1次,然后从A[1…n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分的比较结果,因此能减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

实现过程

  1. 首先数据按照二叉数来看待,将其下标与二叉树节点对应起来;
  2. 构建最大堆(Build-Max-Heap),将堆所有数据重新排序,使其成为最大堆,并且冒出最大数;
  3. 堆排序(Heap-Sort),从最后一个子节点开始遍历,并将根节点与其交换,也就是移除第一个数据的根节点,并做最大堆调整的递归调用,直到排序完成。

示意图

heap2.png
heap2.gif

性能分析

平均时间复杂度:O(nlogn)
最佳时间复杂度:O(nlogn)
最差时间复杂度:O(nlogn)
稳定性:不稳定

代码

Java

public class HeapSort {
  
  private static int getLeft(int i) {
    return i * 2 + 1;
  }

  private static int getRight(int i) {
    return i * 2 + 2;
  }

  static void swap(int arr[], int from, int to) {
    int temp = arr[from];
    arr[from] = arr[to];
    arr[to] = temp;
  }

  private static void maxHeapify(int arr[], int idx, int size) {
    int max = idx;
    int left = getLeft(idx);
    int right = getRight(idx);
    if (left < size && arr[left] > arr[max]) {
      max = left;
    }
    if (right < size && arr[right] > arr[max]) {
      max = right;
    }
    System.out.println("idx=" + idx + " left=" + left + " right=" + right + " max=" + max + " size=" + size);
    if (max != idx) {
      swap(arr, idx, max);
      maxHeapify(arr, max, size);
    }
  }

  static void heapSort(int arr[]) {
    int len = arr.length;
    int parent = (len - 1) / 2 - 1;
    int child = len - 1;

    while (parent >= 0) {
      maxHeapify(arr, parent, len);
      System.out.println("parent sort:" + parent + " " + len + " ");
      parent--;
    }

    System.out.println("child start: parent=" + parent + " child=" + child);

    while (child > 0) {
      swap(arr, 0, child);
      maxHeapify(arr, 0, child);
      System.out.println("child sort:" + 0 + " " + child + " ");
      child--;
    }
  }
}

Python

class HeapSort:

  def __init__(self, arr):
    self.arr = arr

  def get_parent(self, i):
    return int((i - 1) / 2)

  def get_left(self, i):
    return 2 * i + 1

  def get_right(self, i):
    return 2 * i + 2

  # 始终保持大顶堆特性
  def max_heapify(self, idx, size):
    arr = self.arr
    max = idx
    left = self.get_left(idx)
    right = self.get_right(idx)
    if (left < size and arr[left] > arr[max]):
      max = left
    
    if (right < size and arr[right] > arr[max]):
      max = right
    
    print('idx=', idx, 'left=', left, 'right=', right, 'max=', max, 'size=', size)

    if (max != idx):
      [arr[idx], arr[max]] = [arr[max], arr[idx]]
      self.max_heapify(max, size)
  
  def build_max_heap(self):
    arr = self.arr
    length = len(arr)
    last_parent = self.get_parent(length) - 1
    for parent in range(last_parent, -1, -1):
      self.max_heapify(parent, length)
      print('parent sort:', parent, arr)

  def sort_tree(self):
    arr = self.arr
    length = len(arr)
    child = length - 1
    while (child > 0):
      [arr[0], arr[child]] = [arr[child], arr[0]]
      self.max_heapify(0, child)
      print('child sort:', child, arr)
      child -= 1

  def start(self):
    self.build_max_heap()
    print('child start:')
    self.sort_tree()
    return arr

Go

/* 根据完全二叉树结构性质,父子节点与数组下标的关系,通过数组下标i得到节点位置 */

// Tree 定义堆排序过程中使用的堆结构
type Tree struct {
  arr  []int // 用来存储堆的数据
  size int   // 用来标识堆的大小
}

// 保持最大顶堆特性非递归版
func maxHeapify(tree *Tree, idx int) {
  arr := tree.arr
  var current = arr[idx]
  var child = 2*idx + 1
  // 从当前位置的左节点开始遍历
  for child < tree.size {
    fmt.Println("current=", current, " idx=", idx, " child=", child, " size=", tree.size)
    // 如果左节点小于右节点且小于总长度,则指向右节点
    if child+1 < tree.size && arr[child] < arr[child+1] {
      child++
    }
    if arr[child] > current {
      // 如果子节点大于父节点,将子节点的值赋给父节点
      arr[idx] = arr[child]
      // 当前节点指向该子节点,继续循环
      idx = child
    } else {
      // 子节点小于父节点则跳出循环
      break
    }
    // 遍历子树父节点
    child = 2*idx + 1
  }
  // 当前节点赋值为父节点的值
  arr[idx] = current
}

// 利用堆结构对数组进行排序
func heapSort(arr []int) []int {
  tree := &Tree{}
  tree.arr = arr
  tree.size = len(arr)

  // 构建大顶堆,把最大节点冒出到堆顶,从任意1个父节点开始均可
  var current = (tree.size - 1) / 2
  for ; current >= 0; current-- {
    maxHeapify(tree, current)
  }

  // 从最底层子节点开始排序,置换顶根节点与子节点,并继续构建大顶堆
  for tree.size > 0 {
    // 将最大的数值调整到堆的末尾
    tree.arr[0], tree.arr[tree.size-1] = tree.arr[tree.size-1], tree.arr[0]
    // 减少堆的长度
    tree.size--
    // 由于堆顶元素改变了,而且堆的大小改变了,需要重新调整堆,维持堆的性质
    maxHeapify(tree, 0)
  }
  return tree.arr
}

JS

  // 根据完全二叉树结构性质,父子节点与数组下标的关系,通过数组下标i得到节点位置
  const getParent = (i) => Math.floor((i - 1) / 2)
  const getLeft = (i) => 2 * i + 1
  const getRight = (i) => 2 * i + 2

  /**
   * @param {Array<number>} arr
   * @param {number} idx - the index of element
   * @param {number} size - the array's length
   * 始终保持大顶堆特性, 构建大顶堆的递归写法
   */
  function maxHeapify(arr, idx, size) {
    let max = idx
    const left = getLeft(idx)
    const right = getRight(idx)
    if (left < size && arr[left] > arr[max]) {
      max = left
    }
    if (right < size && arr[right] > arr[max]) {
      max = right
    }
    console.log('idx=', idx, 'left=', left, 'right=', right, 'max=', max, 'size=', size)
    if (max !== idx) {
      // 保持最大堆,如果当前父节点小于子节点,则进行交换
      ;
      [arr[idx], arr[max]] = [arr[max], arr[idx]]
      // 继续递归执行,直到符合最大堆特性
      maxHeapify(arr, max, size)
    }
  }

  // 构建最大堆的非递归写法,此处可以覆盖上面递归maxHeapify函数
  function maxHeapify(arr, idx, size) {
    const current = arr[idx]
    let child = getLeft(idx)
    // 从当前位置的左节点开始遍历
    for (; child < size;) {
      console.log('current=', current, ' idx=', idx, ' child=', child, ' size=', size)
      // 如果左节点小于右节点且小于总长度,则指向右节点
      if (child + 1 < size && arr[child] < arr[child + 1]) {
        child++
      }
      if (arr[child] > current) {
        // 如果子节点大于父节点,将子节点的值赋给父节点
        arr[idx] = arr[child]
        // 当前节点指向该子节点,继续循环
        idx = child
      } else {
        // 子节点小于父节点则跳出循环
        break
      }
      // 遍历子树父节点
      child = getLeft(idx)
    }
    // 赋值为父节点的值
    arr[idx] = current
  }

  function heapSort(arr) {
    const len = arr.length
    // 最底层的父节点
    let parent = getParent(len) - 1
    // 最底层的子节点
    let child = len - 1
    // 从最后的父节点开始遍历,构建大顶堆,并把最大数冒出到堆顶
    while (parent >= 0) {
      maxHeapify(arr, parent, len)
      console.warn('parent sort:', parent, arr)
      parent--
    }
    console.warn('child start:', 'parent=' + parent, ' child=' + child)
    // 自下向上逐个将子节点数与最顶端的数进行交换,并保持最大堆特性
    while (child > 0) {
      // 将顶端的父节点与当前子节点互换
      ;
      [arr[0], arr[child]] = [arr[child], arr[0]]
      // 自最底层往上遍历构建大顶堆,已经排好序的不再交换
      maxHeapify(arr, 0, child)
      console.warn('child sort:', child, arr)
      child--
    }

    return arr
  }

TS

class HeapSort {
  arr: number[]
  constructor(arr: Array<number>) {
    this.arr = arr
    this.heapSort()
  }
  getParent(i: number) {
    return Math.floor((i - 1) / 2)
  }
    
  getLeft(i: number) {
    return 2 * i + 1
  }

  getRight(i: number) {
    return 2 * i + 2
  }
  maxHeapify(idx: number, size: number) {
    let max = idx
    const arr = this.arr
    const left = this.getLeft(idx)
    const right = this.getRight(idx)
    if (left < size && arr[left] > arr[max]) {
      max = left
    }
    if (right < size &&  arr[right] > arr[max]) {
      max = right
    }
    console.log('idx=', idx, 'left=', left, 'right=', right, 'max=', max, 'size:', size)
    if (max !== idx) {
      [arr[idx], arr[max]] = [arr[max], arr[idx]]
      this.maxHeapify(max, size)
    }
  }

  heapSort() {
    const arr = this.arr
    const len = arr.length
    // 最底层的父节点
    let parent = this.getParent(len) - 1
    // 最底层的子节点
    let child = len - 1
    // 从最后的父节点开始遍历,把最大的那个父节点冒出到堆顶
    while (parent >= 0) {
      this.maxHeapify(parent, len)
      console.warn('parent sort:', parent, arr)
      parent--
    }
    console.warn('child start:', 'parent=' + parent, ' child=' + child)
    // 从子节点往上开始交换和保持大顶堆
    while (child > 0) {
      // 将顶端的父节点与当前子节点互换
      [arr[0], arr[child]] = [arr[child], arr[0]]
      // 自最底层往上遍历排序
      this.maxHeapify(0, child)
      console.warn('child sort:', child, arr);
      child--
    }
    return arr
  }

}

C

int get_left(int i)
{
  return i * 2 + 1;
}

int get_right(int i)
{
  return i * 2 + 2;
}

void swap(int arr[], int from, int to)
{
  int temp = arr[from];
  arr[from] = arr[to];
  arr[to] = temp;
}
/**
 * 始终保持大顶堆特性
 */
void max_heapify(int arr[], int idx, int size)
{
  int max = idx;
  int left = get_left(max);
  int right = get_right(max);
  if (left < size && arr[left] > arr[max])
  {
    max = left;
  }
  if (right < size && arr[right] > arr[max])
  {
    max = right;
  }

  printf("\r\nidx=%d, left=%d, right=%d, max=%d, size=%d", idx, left, right, max, size);

  if (max != idx)
  {
    // swap the current width max value
    swap(arr, idx, max);
    // make max tree recursive
    max_heapify(arr, max, size);
  }
}

void heap_sort(int arr[], int len)
{
  int parent = (len - 1) / 2 - 1;
  int child = len - 1;
  while (parent >= 0)
  {
    max_heapify(arr, parent, len);
    printf("\r\nparent=%d, len=%d", parent, len);
    parent--;
  }

  printf("child start: parent=%d child=%d", parent, child);

  while (child > 0)
  {
    swap(arr, 0, child);
    max_heapify(arr, 0, child);
    printf("\r\nchild=%d, parent=%d", 0, child);
    child--;
  }
}

链接

堆排序算法源码:https://github.com/microwind/algorithms/tree/master/sorts/heapsort

其他排序算法源码:https://github.com/microwind/algorithms

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容