数据结构-练习 12 堆以及最优队列的实现

堆排序是常见的排序算法,基于完全二叉树的特性,分为最大堆和最小堆。本文将探讨堆的插入、删除操作,并详细解析堆排序的过程,最后介绍如何实现堆化的优先队列。堆排序的时间复杂度为O(NlgN),优于冒泡和插入排序。插入时在数组末尾进行,然后自底向上调整堆;删除时移除顶部元素,由底部元素顶替并自顶向下调整堆。
摘要由CSDN通过智能技术生成

堆排序是最重要的排序算法之一,在平时的开发以及面试中经常会用到。堆的特点是:

        1,有一颗完全二叉树构成,如图1;

         2,可分为最大堆和最小堆。最大堆的意思就是:任何根节点的数据不小于左右孩子节点的数据;反之,最小堆的意思就是任何节点的数据不大于左右孩子节点的数据;

         3,堆排序的算法复杂度为O(NlgN),比冒泡和插入快,究其原因在于堆只维护局部最大或最小。

         4,堆的存储用数组实现,按层存储,如图1;

                                          

                                                       存储在数组里:

                                                   

                                                         图 1

  下面分四部分讲解堆,分别为:堆的插入,删除,以及堆排序,最后是堆化的优先队列。

  一,堆的插入

  堆的插入只能在最后一个位置插入,如图 2。只能在黑色圈的地方插入。插入以后,必须进行更新,以保持堆的性质,即根数据最大或是最小。更新的思路:从插入节点到根节点,依次比较更新,直到满足条件(本列子就是:根数据小于左右孩子)。

                                                                大致原理如图解:



关键代码如下:

            注意的是:左孩子和右孩子的顺序分别为2i+1,2i+2。结合上面的流程图,插入的过程为:从下往上依次比较,重新调整堆的结构。

void MinHeapFixup(int* a, int i)  
{  
    int j, temp;       
    temp = a[i];  
    j = (i - 1) / 2;      //父结点  
    while (j >= 0 && i != 0)  
    {  
        if (a[j] <= temp)  
            break;  
          
        a[i] = a[j];     //把较大的子结点往下移动,替换它的子结点  
        i = j;  
        j = (i - 1) / 2;  
    }  
    a[i] = temp;  
}  


void MinHeapAddNumber(int* a, int n, int nNum)  
{  
    a[n] = nNum;  
    MinHeapFixup(a, n);  
}  
二,堆的删除

记住只能删除最顶部的元素,再把最低端的元素放到最顶端,从上往下依次更新整个堆。示意图如下:





void MinHeapFixdown(int a[], int i, int n)  
{  
    int j, temp;  
  
    temp = a[i];  
    j = 2 * i + 1;  
    while (j < n)  
    {  
        if (j + 1 < n && a[j + 1] < a[j]) //如果存在有孩子,比较左右孩子的大小
            j++;  
  
        if (a[j] >= temp)  
            break;  
  
        a[i] = a[j];     
        i = j;  
        j = 2 * i + 1;  
    }  
    a[i] = temp;  
}  

void MinHeapDeleteNumber(int a[], int n)  
{  
	std::swap(a[0], a[n - 1]);  
    MinHeapFixdown(a, 0, n - 1);  
}  

总结:删除,只能删除头结点,然后从上往下依次调整堆;插入,只能在末端插入,然后从下往上依次调整堆。
三,堆化数组,采用插入的思想从下往上依次调整堆即可。此时调整每一个非叶子节点即可。
关键代码如下:
void MakeMinHeap(int* a, int n)  
{  
    for (int i = n / 2 - 1; i >= 0; i--)  
    MinHeapFixdown(a, i, n);  
}  
测试:
结合图形中的数据,我们演示一遍:

int main()
{
	int a[8]={9,7,5,6,8,4,10};
	cout<<"原始数组里的数据为:";
	for(int i=0;i<7;++i)
		cout<<a[i]<<" ";
	cout<<endl<<"堆化后的数组为:";
    MakeMinHeap(a, 7);
	for(int i=0;i<7;++i)
		cout<<a[i]<<" ";
    cout<<endl<<"增加一个数据3后的数组为:";
    MinHeapAddNumber(a,7,3);
    for(int i=0;i<8;++i)
		cout<<a[i]<<" ";
	
    MinHeapDeleteNumber(a,8);
   cout<<endl<<"删除顶点后的数据为:";

   for(int i=0;i<7;++i)
		cout<<a[i]<<" ";
    return 0;
}




我们自然要问:维护一个堆结构的意义何在?
至少有两个目的:堆排序和优先队列。
四 ,堆排序。思想:既然我们可以每次取出顶点的数据,取到的数据又是最小数据,根据此思想,我们可以把数组里的数据全部取完后得到的数据就是有顺序的了。不过跟归并排序一样,空间复杂度增加了,必须有一个数组去接受。
测试:
int b[8];
    for(int i=0;i<8;++i)
	{
		b[i]=a[0];
	    MinHeapDeleteNumber(a,8-i);
	}
	for(int i=0;i<8;++i)
		cout<<b[i]<<" ";


我们知道,任何算法都得用时间复杂度计算一下,否则,算法没什么意义。由于按树堆化,树高lgN,最坏情况下,每层计算N次,所以复杂度为:O(NlgN),跟归并一样。
最优队列:最优队列是每次都弹出数据的最大或是最小值。每次进队列后,都要一次更新操作。每次出队列都要一次删除操作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值