排序算法之堆排序(C#)

堆排序

所谓堆排序,就是将数组元素组成一个包含左右子节点的树,与之前使用的结构(如下所示相同)

	public class TreeNode
    {
        public int val;
        public TreeNode left;
        public TreeNode right;
        public TreeNode(int x) { val = x; }
    }

但我们并不是生成一个实体的TreeNode,而是根据数组中元素的位置去判断该元素是叶子节点,还是非叶子节点。
假如数组为 [4,1,2,6,5,8,7] 对应数组下标分别为**[0,1,2,3,4,5,6]**
我们先来找规律,假设我们的任意位置i,那么如何找到i元素对应的左右节点的数组对应下标位置呢?
这里注意,组成的树我们是按照广度遍历去组成树的。可以组成如下图所示的树形结构。
在这里插入图片描述
参考途中所示,你会发现一个规律,所有的一横排的子树数量,比他所有的父节点的数量和都多一。
第一排为1个,第二排为2个。 2-1
第二排为2个+第一排为1个,第三排为4个。4-3
以此类推…
由上面的规律,我们可以算出,最大的非叶子节点为arr.length/2-1;
还有一个规律,就是一个父节点下标为i,那么该元素左子节点为arr[2i+1],右子节点为 arr[2i+2]
之所以在这里写下这两个规律,是因为我们的堆排序中需要使用这两个规律。为了避免看到代码不理解,先将规律写在上方。

接下里就是代码,我怕各位不好直接理解,代码我会从最后被调用的代码开始写。

首先交换数组中的两个元素的代码
这个应该很好理解,交换数组中的两个节点值。

		/// <summary>
        /// 交换数组中的两个元素
        /// </summary>
        /// <param name="nums"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public void Swap(ref int a, ref int b)
        {
            int temp = a;
            a = b;
            b = temp;
        }

大顶推的调整
不要考虑的太复杂,比方一个树形结构,组成数组为[2,3,1]。父节点为2,左子节点为3,右子节点为1。我们需要做的就是找到这三个元素组成结构中最大的位置。显然数组中的第二个元素最大。我们交换用前面的Swap方法去交换父节点和左节点值为3的元素。就构成了一次大顶堆的调整。
这里还要考虑一下,我们是从最后一个父节点向前调整的,当交换数组结构后,有可能会对我们之前已经调整的结构打乱。因此这里加一个递归,判断被移动到下面的节点和所有的子节点大小。
代码如下

		/// <summary>
        /// 大顶推的调整 针对根节点以及根节点下所有被改变的节点
        /// </summary>
        /// <param name="nums">数组</param>
        /// <param name="currentIndex">要调整的根节点位置</param>
        /// <param name="Numslength">要调整的数组的长度</param>
        public void heapify(int[] nums,int currentIndex,int Numslength)
        {
            //左节点位置
            int left = currentIndex * 2 + 1;
            //右节点位置
            int right = currentIndex * 2 + 2;
            //记录父节点,左子节点,右子节点中最大的节点位置 我们默认先给父节点
            int largePosition = currentIndex;
            //判断左节点和根节点
            if (left<Numslength&&nums[left]>nums[largePosition])
            {
                largePosition = left;
            }
            //判断右节点和根节点
            if (right<Numslength&& nums[right] > nums[largePosition])
            {
                largePosition = right;
            }
            if (largePosition!=currentIndex)
            {
                Swap(ref nums[currentIndex],ref nums[largePosition]);
                //我们是从最后一个父节点向前调整的,当交换数组结构后,有可能会对我们之前已经
                //调整的结构打乱。因此这里加一个递归,此时largePosition位置为被移动的父节点。
                //判断父节点和所有的子节点大小
                heapify(nums, largePosition, Numslength);
            }
        }

循环所有的非叶子节点 构建一个大顶堆
如何将一整棵树形结构都构建成大顶堆呢,当然是遍历所有的非叶子节点,非叶子节点的数量我们已经介绍完了nums.Length / 2 - 1。代码如下

		/// <summary>
        /// 循环所有的非叶子节点 构建一个大顶堆
        /// </summary>
        /// <param name="nums"></param>
        public void buildMaxHeap(int[] nums)
        {
            //最大的非叶子节点为nums.Length / 2 - 1,我们从最后一个向前遍历
            for (int i = nums.Length / 2 - 1; i >= 0; i--)
            {
                heapify(nums,i,nums.Length); //调整大顶堆
            }
        }

堆排序
到了对获取排序的时候了,当我们构建了大顶堆的时候,顶层元素nums[0]一定为最大的,我们将最大元素与数组末尾依次交换,再调用heapify,此时我们需要排序的根节点是nums[0]的位置,因为我们将子节点上移至最上面,但我们要调整的数组的长度,为我们将数组长度减去最大元素移动末尾的次数。

		/// <summary>
        /// 堆排序
        /// </summary>
        /// <param name="nums"></param>
        public void HeapSort(int[] nums)
        {
            //我们先创建一个大顶堆
            buildMaxHeap(nums);
            //堆顶元素nums[0]为最大元素 ,所以我们将堆顶的元素与数组最后的元素交换
            //调用heapify,对交换后的最顶层元素进行重新的排序
            //但此时我们传入的要调整的数组的长度就减去一位。
            for (int i = nums.Length-1; i > 0; i--)
            {
                Swap(ref nums[0],ref nums[i]);
                heapify(nums, 0, i);
            }
        }

堆排序整体代码如下

		/// <summary>
        /// 堆排序
        /// </summary>
        /// <param name="nums"></param>
        public void HeapSort(int[] nums)
        {
            //我们先创建一个大顶堆
            buildMaxHeap(nums);
            //堆顶元素nums[0]为最大元素 ,所以我们将堆顶的元素与数组最后的元素交换
            //调用heapify,对交换后的最顶层元素进行重新的排序
            //但此时我们传入的要调整的数组的长度就减去一位。
            for (int i = nums.Length-1; i > 0; i--)
            {
                Swap(ref nums[0],ref nums[i]);
                heapify(nums, 0, i);
            }
        }

        /// <summary>
        /// 循环所有的非叶子节点 构建一个大顶堆
        /// </summary>
        /// <param name="nums"></param>
        public void buildMaxHeap(int[] nums)
        {
            //最大的非叶子节点为nums.Length / 2 - 1,我们从最后一个向前遍历
            for (int i = nums.Length / 2 - 1; i >= 0; i--)
            {
                heapify(nums,i,nums.Length); //调整大顶堆
            }
        }

        /// <summary>
        /// 大顶推的调整 针对根节点以及根节点下所有被改变的节点
        /// </summary>
        /// <param name="nums">数组</param>
        /// <param name="currentIndex">要调整的根节点位置</param>
        /// <param name="Numslength">要调整的数组的长度</param>
        public void heapify(int[] nums,int currentIndex,int Numslength)
        {
            //左节点位置
            int left = currentIndex * 2 + 1;
            //右节点位置
            int right = currentIndex * 2 + 2;
            //记录父节点,左子节点,右子节点中最大的节点位置 我们默认先给父节点
            int largePosition = currentIndex;
            //判断左节点和根节点
            if (left<Numslength&&nums[left]>nums[largePosition])
            {
                largePosition = left;
            }
            //判断右节点和根节点
            if (right<Numslength&& nums[right] > nums[largePosition])
            {
                largePosition = right;
            }
            if (largePosition!=currentIndex)
            {
                Swap(ref nums[currentIndex],ref nums[largePosition]);
                //我们是从最后一个父节点向前调整的,当交换数组结构后,有可能会对我们之前已经
                //调整的结构打乱。因此这里加一个递归,此时largePosition位置为被移动的父节点。
                //判断父节点和所有的子节点大小
                heapify(nums, largePosition, Numslength);
            }
        }

        /// <summary>
        /// 交换数组中的两个元素
        /// </summary>
        /// <param name="nums"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public void Swap(ref int a, ref int b)
        {
            int temp = a;
            a = b;
            b = temp;
        }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值