堆排序以及时间复杂度分析

214 阅读3分钟

前言

本文主要介绍一种常见的排序算法——堆排序,以及堆排序的时间复杂度分析。

正文

关于堆的基本概念以及常见操作在之前的文章里已经介绍过了,这里不再赘述,有兴趣可以参考数据结构——堆以及常见操作介绍

下面就来介绍下如何利用大根堆来进行数组排序。

堆排序思想

拿到一个无序数组后,首先做的是把无序数组转成一个大根堆,转成大根堆的过程如下:

  • 0 ~ 1位置上的元素转成大根堆
  • 0 ~ 2位置上的元素转成大根堆
  • 0 ~ 3位置上的元素转成大根堆
  • 0 ~ N位置上的元素转成大根堆

我们知道大根堆的最大元素是下标为0的那个元素,所以把一个无序数组转成大根堆后,排序思路如下:

  • 0位置的元素和N-1位置的元素进行交换,此时N-1位置的元素为最大值
  • 0 ~ N-2位置的元素转成大根堆,并将0位置的元素和N-2位置的元素进行交换
  • 0 ~ N-3位置的元素转成大根堆,并将0位置的元素和N-3位置的元素进行交换
  • 0 ~ 1位置的元素转成大根堆,并将0位置的元素和1位置的元素进行交换

经过上述步骤之后,整个无序数组就变成了有序数组。

代码实现

public void heapSort(int[] arr) {
   if (arr == null || arr.length < 2) {
      return;
   }
   // 时间复杂度O(N*logN), for循环为O(N),heapInsert为 O(logN)
   for (int i = 0; i < arr.length; i++) {
      heapInsert(arr, i); 
   }
   int heapSize = arr.length;
   swap(arr, 0, --heapSize);
   // 时间复杂度为 O(N*logN)
   while (heapSize > 0) {
      heapify(arr, 0, heapSize);
      swap(arr, 0, --heapSize);
   }
}
public void heapInsert(int[] arr, int index) {
   while (arr[index] > arr[(index - 1) / 2]) {
      swap(arr, index, (index - 1) / 2);
      index = (index - 1) / 2;
   }
}
public void heapify(int[] arr, int index, int heapSize) {
	// 左孩子的下标
  int left = index * 2 + 1; 
  // 下方还有孩子的时候
  while (left < heapSize) { 
    // 两个孩子中,谁的值大,把下标给largest
    // 1)只有左孩子,left -> largest
    // 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
    // 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
    int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
    // 父和较大的孩子之间,谁的值大,把下标给largest
    largest = arr[largest] > arr[index] ? largest : index;
    if (largest == index) {
      break;
    }
    swap(arr, largest, index);
    index = largest;
    left = index * 2 + 1;
  }
}
public void swap(int[] arr, int i, int j) {
  int tmp = arr[i];
  arr[i] = arr[j];
  arr[j] = tmp;
}

时间复杂度分析

代码中,第一个for循环调用heapInsert函数,就是让数组中的元素一步步从00~10~20~N-1变成大根堆的过程,for循环执行了N次,时间复杂度为O(N)。在构建大根堆的时候,新加入的元素只需要和他的父节点一直到根节点比较,也就是比较了树的高度次,所以时间复杂度为O(logN)。那么在第一个for循环这个过程时间复杂度为O(N*logN)

将无序数组转成大根堆后,进行元素的交换,一共交换了heapSize次,也就是数组的长度,所以交换的时间复杂度为O(N)heapify元素交换的时间复杂度和元素插入类似,时间复杂度也是O(logN),所以元素整体交换的时间复杂度为O(N*logN)

所以,两个时间复杂度相加,不过常数项是不影响整体的时间复杂度的,因此该算法的整体时间复杂度为O(N*logN)

总结

本文主要介绍一种常见的排序算法——堆排序以及堆排序的时间复杂度分析,文中介绍了堆排序算法的思想以及时间复杂度的分析方法。