堆排序(一)——建小堆(含向下调整代码)

堆排序有两个侧重点,一个是 “堆”,一个是“排序”

1、“堆”——建小堆

2、“排序”——将堆顶元素和堆底的最后一个元素交换,除了堆底最后一个元素外,调整剩下的元素


目录

一、提出问题

二、思路分析

1、画二叉树

2、调整最底部的子树

3、调整前一个子树

三、代码实现

1、向下调整代码

2、建堆 代码实现

3、测试


一、提出问题

假设现在有一个无序的数组

int arr[] = { 70,56,30,24,33,10,75,68,90,21};

现在要在不额外开辟空间的情况下,将这个数组转化为堆

二、思路分析

1、画二叉树

堆在物理实现上使用的是顺序容器,但是在逻辑分析中使用的是二叉树

所以我们的第一步是将改数组 画 成二叉树的样子

2、调整最底部的子树

我们的目的是建小堆,父亲要比孩子节点小,否则,交换顺序,然后继续向下调整

 我们这里标注一下最底部子树的双亲节点的位置

第 i 个孩子节点的双亲节点位置

                                        parent = (i -1)/2

最后一个节点的位置 是数组的最后一个元素,即

                                         i = sizeof(arr)/sizeof(arr[0]) - 1

所以        

                                        parent = (sizeof(arr)/sizeof(arr[0]) - 1 -1)/2

3、调整前一个子树

由于是顺序存储,只需要parent-- 就可以向前移动一个位置

和上面一样的方法,向下调整

继续向前调整,这个时候要调整的是 56  下面的所有子树

但是 56 的子树都是我们已经调整过的小堆,受到影响的只是 56 的左右孩子中更小的那个(左图)

很显然,56的右孩子更小,那就要把 21 往上调(中图)

原本子树的顺序也被打乱了,要继续向下调整,33 和 56 交换顺序(右图)

 从上面可以看出

只要根的左右子树满足 小堆,受到影响的只是 孩子更小的那一条路径,另一条路径不受影响 

三、代码实现

1、向下调整代码

我们在处理堆底的子树时,最先要做的,就是向下调整,向下调整的本质就是通过父结点找到子结点,然后和更小的那一个子结点交换

向下调整的思路:

(1)求出左孩子的下标,child = parent*2 + 1(默认左孩子更小,因为右孩子不一定存在)

(2)在有右孩子的前提下,跟右孩子比较,如果右孩子更小,child++,因为child代表更小的那个孩子的下标;如果没有右孩子或者右孩子比左孩子大,那么child指向的依然是左孩子

(3)得到更小的那个孩子以后,用左右孩子中更小的节点和父亲节点比较,

         如果父亲节点更大,则交换顺序

         如果父亲节点更小,则说明无需交换顺序,直接跳出循环

向下调整的停止条件:

(1)父亲节点的数  <  左右孩子中更小的那个数(即无需调整)

(2)左孩子/右孩子 到达最后一个节点,即child > size

(这里的size表示树的节点总个数 或 数组元素的个数)

//交换位置
void Swap(int& x,int& y)
{
    int tmp = x;
    x = y;
    y= x;
}

//向下调整
void AdjustDown(int* a, int parent,int size)
{
    //计算左孩子的下标,则右孩子的下标为 child + 1
    int child = parent*2 + 1;

    //判断左孩子是否到达最后一个节点
    while(child < size)
    {
        //child表示更小的那个孩子,默认是左孩子更小
        //判断是否 有右孩子,如果右孩子大于左孩子,child需要指向右孩子,即child+1
        if(child + 1 < size && a[child+1] < a[child])
        {
            child++;
        }

        //父亲节点如果比 更小的那个孩子 还大,则交换位置
        if(a[parent] > a[child])
        {
             Swap(a[parent],a[child]);   
        }
        else
        {
            //如果父亲节点比 更小的那个孩子 还要小,那就无需调整
            break;
        }
    }
}

2、建堆 代码实现

现在对于每一个子树的向下调整,我们已经写好了

现在只需要移动 parent 的位置,AdjustDown函数会为我们调整子树

停止建堆条件:parent < 0,即parent = -1

void CreateHeap(int* a,int size)
{
    //最后一个节点的位置是size-1,则可以得到双亲节点的位置 
    int parent = (size-1-1)/2;
    while(parent >= 0)
    {
        //调整子树
        AdjustDown(a,parent,size);
        
        //调整完以后,parent向前移动
        parent--;
    }
}

3、测试

int main() {
	int arr[] = { 70,56,30,24,33,10,75,68,90,21};
	CreateHeap(arr, sizeof(arr) / sizeof(arr[0]));

	for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

测试结果:

以上就是本次的全部内容,如果对你有帮助的话,还请点个赞鼓励一下,如果有错,欢迎指正 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值