经典排序算法(三)堆排序

堆的概念

堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆

大根堆和小根堆

性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。下图为大根图的示意图:

大根堆
将完全二叉树的大根堆映射为一维数组(首位置0不用,置空),如下图所示:
在这里插入图片描述

还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么

  • 父结点: ( i n t ) i / 2 (int)i/2 (int)i/2
  • 左孩子: i ∗ 2 i*2 i2
  • 右孩子: i ∗ 2 + 1 i*2+1 i2+1

堆排序的基本步骤

算法思想

  • 构造堆:将待排序列生成一个大根堆。序列的最大元素也即大根堆的堆顶(根结点)
  • 交换堆顶堆尾:将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  • 再构造堆:将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2-1 构造堆

构造堆的算法有很多,本文采用的构造堆算法是基于《算法4》的sink(下沉)算法:
sink算法是从最后一个非叶子结点开始,依次将非叶子结点和其左右孩子的元素值相比较。如果父结点的元素是比两个孩子结点大,保持元素位置不变;如果父结点的元素值小于孩子结点的值,将父结点和最大的孩子结点元素进行位置交换。从最后一个非叶子结点开始,直到根结点结束。

算法过程:
在这里插入图片描述

视频来源:《算法4》配套视频

算法代码:

void ArrSwap(int* arr, int pos1, int pos2)
{
    
    
   int temp = arr[pos1];
   arr[pos1] = arr[pos2];
   arr[pos2] = temp;
}
void sink(int* arr, int length, int loc)
{
    
    
   while (2 * loc <= length)
   {
    
    
   	int lchild = 2 * loc;
   	if ((lchild + 1) < length && arr[lchild] < arr[lchild + 1])
   		++lchild;
   	if (arr[loc] > arr[lchild]) break;
   	//swap
   	ArrSwap(arr, loc, lchild);
   	loc = lchild;
   }
}
void CreateBHT(int* arr, int length)
{
    
    
   int first = length / 2;
   for (int i = first; i > 0; --i)
   {
    
    
   	sink(arr, length, i);
   }
}

2-2 交换堆顶堆尾+再构造堆

上一步我们将一个无序序列构造成了一个大根堆,按照堆排序的算法思想,还有两个步骤需要完成:

  1. 交换堆顶和堆尾元素
    一维数组交换堆顶堆尾元素,代码非常简单。大家可以想象一下,我们把堆尾的元素放在了二叉树的根结点位置处,此时该完全二叉树已经不满足大根堆的性质。因此,我们还需要重建该大根堆。
  2. 再构建堆
    交换首尾元素后的二叉树,重构建堆的思想非常简单。通过将位于堆顶的小元素不断sink(下沉),即可重新得到一个大根堆:

算法代码:

void reCreateBHT(int* arr, int length)
{
    
    
	for (int i = length; i > 1; --i)
	{
    
    
		//swap
		ArrSwap(arr, i, 1);
		sink(arr, i - 1, 1);
	}
}

堆排序的完整代码:

void HeapSort(int* arr, int length)
{
    
    
	//step 1: create BinaryHeap
	CreateBHT(arr, length);
	//step 2: sort
	reCreateBHT(arr, length);
}

总结

  • 算法时间复杂度: O ( n ∗ l o g n ) O(n*logn) O(nlogn)
  • 算法空间复杂度: O ( 1 ) O(1) O(1)
  • 算法是否稳定:不稳定
    算法稳定指的是序列通过算法排序后,比较值相同(key)的两个元素相对顺序不会发生改变
  • 算法适用范围:由于堆排序运算快,而且占用缓存资源极少,一般用于单片机等嵌入式芯片内部的算法。正所谓优点也是缺点,堆排序由于不能有效的利用计算机的缓存,在当代计算机缓存资源及其丰富的今天已经逐渐被淘汰。

Guess you like

Origin blog.csdn.net/qq_42518941/article/details/115217528