算法 - 堆排序
不上图,简单的文字描述
堆其实就是利用完全二叉树的结构来维护的一维数组,他比二叉树有内存上的优势,毕竟只是以数组的形式存在。
大顶堆:可以理解为堆顶元素最大,同时还要满足每个节点的值都比其子节点的值要大 nums[i] >= nums[2 * i + 1] && nums[i] >= nums[2 * i + 2]
小顶堆:与大顶堆相反。可以理解为堆顶元素最小,同时还要满足每个节点的值都比其子节点的值要小,nums[i] <= nums[2 * i + 1] && nums[i] <= nums[2 * i + 2]
升序使用大顶堆,降序使用小顶堆
堆的操作(这里有大堆来说明)
创建最大堆(Build Max Heap):将堆中的所有数据重新排序
最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算,,,, 可以通过下沉方式,比如Swap当前根节点和数组尾部,下次排序时候不考虑尾部(减少未排序数组长度)
下面直接是代码,有详细注释
/// <summary>
/// 升序使用大顶堆
/// 降序使用小顶堆
///
/// 要熟悉 建立堆,调整堆,和所谓的删除堆(数据下沉之类的)
/// 时间复杂度O(nlogn),建立堆是O(n) 调整堆的时间O(nlogn)
/// 空间复杂度O(logn),即递归使用栈的空间
/// </summary>
/// <param name="nums"></param>
public static void HeapSort(int[] nums)
{
// 安全判断
if (nums == null || nums.Length == 0) return;
// 构建大顶堆,构建完毕后,最大的值已经在根节点
BuildHeap(nums);
// 每一次重构顶堆,我们都会把当前的最大值给“沉”到堆尾,即排序完一个当前最大值,,,那么这种操作就是升序拉
int len = nums.Length - 1; // 确定每次没有排序的长度
for (int i = nums.Length - 1; i > 0; i--) // 这里总计调整 nums.Length - 1次,因为构建堆的时候调整过一次了!!!
{
// 交换堆顶与堆尾,把当前最大值“沉”到最下面
Swap(nums, 0, i);
// 所以每重构一次,要 len--,防止最大值又被重构到最顶层
Heapify(nums, 0, --len);
}
}
private static void BuildHeap(int[] nums)
{
// 从最后一个 非叶子节点开始向前遍历,,也就是最后一个父节点开始调整
int start = nums.Length / 2 - 1;
for (int i = start; i >= 0; i--)
{
Heapify(nums, i, nums.Length);
}
}
/// <summary>
/// 递归方式
/// </summary>
/// <param name="nums"></param>
/// <param name="i"></param>
/// <param name="len"></param>
private static void Heapify(int[] nums, int i, int len)
{
// 计算当前非叶子节点的左右节点索引
int left = i * 2 + 1;
int right = i * 2 + 2;
// 默认当前节点是最大值
int largestIndex = i;
// 如果有左节点,且左节点比当前节点大,则更新最大值的索引
if (left < len && nums[left] > nums[largestIndex]) largestIndex = left;
// 如果有右节点,且右节点更大,则更新最大值的索引
if (right < len && nums[right] > nums[largestIndex]) largestIndex = right;
// 这里取得largestIndex可能是i本身,或者是两个子节点的较大值
if (largestIndex != i)
{
// 如果当前节点比它的子节点小,则交换位置
Swap(nums, largestIndex, i);
// 交换位置后子节点的值就变化了(子节点值变成了原来当前节点值)
// 所以要判断子节点(当前节点的子节点也是非叶子节点的情况)
Heapify(nums, largestIndex, len);
}
}
/// <summary>
/// 大顶堆的迭代方式
/// </summary>
/// <param name="nums"></param>
/// <param name="i"></param>
/// <param name="heapSize"></param>
public void MaxHeapify(int[] nums, int i, int heapSize)
{
// 确定左右节点的索引位置
int left = i * 2 + 1;
int right = i * 2 + 2;
// 假定最大索引值是i
int largest = i;
// 尝试使用迭代的方式来 实现
while (left <= heapSize - 1)
{
if (left < heapSize && nums[left] > nums[largest])
{
largest = left;
}
if (right < heapSize && nums[right] > nums[largest])
{
largest = right;
}
// 继续处理
if (largest != i)
{
Swap(nums, i, largest);
i = largest;
left = i * 2 + 1;
right = i * 2 + 2;
}
else
{
break;
}
}
}