堆排序详细剖析(C++)

前面对选择排序,快速排序,希尔排序,归并排序,冒泡排序都进行了分析,最后一个是堆排序,刚开始实在不想写这个,感觉太麻烦了,无奈搜了一些面经,发现什么百度、腾讯、阿里等,他们都问到了堆排序,所以还是得认真聊一聊堆排序!

为什么要有堆排序?

堆非常典型的一个应用是优先队列,普通队列是先进先出,优先队列则出队和入队无关,和他们各自的优先级有关!算法可以动态的选择优先级最高的任务执行!而任务的排序使用之前静态的排序方法是做不到的!

还有一个好处是,在N个元素里面选出前M个元素,用前面静态的方法,先排序再提取它的时间复杂度是nlogn,而使用优先队列它可以达到nlogM,把效率提高十几倍。

优先队列正常的思维都是创建一个普通数组,入队时依次添加,出队时遍历一遍谁优先级最高,让它出队。
或者创建一个有序数组,入队的时候维护数组的有序性,这样出队就会按照优先级高低出队。

这两种方法都非常的好,但是都有其局限性,所以发明了第三种方法!(具体什么局限性俺也不知道,先继续往后看吧)
根据图片来看,堆虽然入队效率比普通数组差,出队效率比顺序数组差,但是平均下来效率却是三者中最好的!最差情况普通数组是O(n*n)级别,而堆是O(nlogn)级别。
在这里插入图片描述
(二叉堆是一颗)完全二叉树:除了最后一层外,其他层节点个数必须是最大值,并且叶子节点都要从最左边开始依次填满!(只有完全二叉树才能用数组表示)

最大堆:父节点都不小于其子节点。
最小堆:父节点都不大于其子节点。

入队出队

给这个二叉树从上到下,从左往右标记一下顺序,就会发现,左孩子序号都是父亲序号✖2,右孩子序号都是父亲序号✖2+1.这样的话,入队的值放在最后,怎样保证它能调整到应有最大堆的位置呢?先让他和父节点进行比较,比父节点大就交换,然后依次往上对比,最后交换到自己的位置。

出队怎么出呢?出队就删除现在的根节点,然后把数组最后一个节点交换到根节点的位置,然后对它进行 “下沉”操作,即把它与其较大的那个子节点比大小,如果偏小就和子节点交换。
在这里插入图片描述

小插曲:在用Microsoft visiual C++ 进行编译的时候,一直跳出来说 “.exe无法解析的外部符号 _mainCRTStartup”错误。后来发现出错原因是自己建立的文件,其中的main函数并没有被编译,系统根本没有找到你的main函数,可能的原因是在其他地方创建的文件,没有被包含到项目里,解决办法就是右击项目名称,重新创建一个你需要的文件。

下面代码是入队出队的代码,最后以树的形式打印出来,并且保证树是最大堆。树形打印代码是复制粘贴修改后的,不用去记。

#include<iostream>
using namespace std;//没有上面头文件std会报错!
#include<ctime>
#include<cassert>//等于#include<assert.h>

#include <algorithm>
#include <string>
#include <cmath>

template<typename Item>
class MaxHeap
{
   
private:
	Item *data;//数组指针
	int count;//数组元素个数
	int capacity;
	void shiftup(int k){
   
		//如果该该节点值比父节点大,就把他俩交换一下,注意k>1!!
		//!注意是while不是if!
		while(k>1 && data[k/2]<data[k]){
   
			swap(data[k/2],data[k]);
			k/=2;
		}
	}
	void shiftdown(int k){
   
		//下沉的前提条件是必须要有孩子
		while(2*k<=count){
   
		int j=2*k;
		//先比较两个子节点大小
		if(data[j]<data[j+1] && j+1<=count){
   
			j= j+1;
		}
		//遇到while循环就要先找到停止条件
		if(data[k]>=data[j])//!if里的条件不是<!
			break;

		swap(data[k],data[j]);
		k=j;//!不是k*2!

		//下面这样写是错误的,如果父节点不小于子节点的情况下, 就不会跳出了,陷入死循环
		//if(data[k]<data[j])//!if里的条件不是<!
		//{
   
		//	swap(data[k],data[j]);
		//	k=j;//!不是k*2!
		//}				
		}

		
		//再跟父节点进行比较
		
	}
public:
	//构造函数
	MaxHeap(int capacity){
   
	//开辟一个临时数组
		data=new Item[capacity+1];//!注意中括号前面的写法!
		count = 0;//初始化元素个数
		this->capacity = capacity;
	}
	~MaxHeap()
	{
   
		delete[] data;
	}
	int size()//获取元素个数
	{
   
		return count;
	}
	bool IsEmpty()
	{
   
		return count==0;
	}

	void Insert(Item item)//入队插入函数
	{
   
		assert(count+1<=capacity);//判断要放置的位置有没有越界
		data[count+1] = item;//把新元素插入数组中
		count++;//元素个数加1
		shiftup(count);//把元素上移到对应位置,注意该函数放在私有位置,不对用户开放
	}

	int ExtractMax(){
   
		assert(count>0);//取最大值的时候保证不是第0个
		Item ret = data[1];
		swap(data[1],data[count]);
		count--;//取出来之后要记得减去
		//对变化后的最大堆下沉之后,又会变成新的最大堆
		shiftdown(1);
		return ret;
	}

public:
    // 以树状打印整个堆结构
    void testPrint(){
   

        // 我们的testPrint只能打印100个元素以内的堆的树状信息
        if( size() >= 100 ){
   
            cout<<"This print function can only work for less than 100 int";
            return;
        }

        // 我们的testPrint只能处理整数信息
        if( typeid(Item) != typeid(int) ){
   
            cout <<"This print function can only work for int item";
            return;
        }

        cout<<"The max heap size is: "<<size()<<endl;
        cout<<"Data in the max heap: ";
        for( int i = 1 ; i <= size() ; i ++ ){
   
            // 我们的testPrint要求堆中的所有整数在[0, 100)的范围内
            assert( data[i] >= 0 && data[i] < 100 );
            cout<<data[i]<<" ";
        }
        cout<<endl;
        cout<<endl;

        int n = size();
        int max_level = 0;
        int number_per_level = 1;
        while( n > 0 ) {
   
            max_level += 1;
            n -= number_per_level;
            number_per_level *= 2;
        }

        int max_level_number = int(pow(2.0, max_level-1));
        int cur_tree_max_level_number = max_level_number;
        int index = 1;
        for( int level = 0 ; level < max_level ; level ++ ){
   
            string line1 = string(max_level_number*3-1, ' ');

            int cur_level_number = min(count-int(pow(2.0,level))+1,int(pow(2.0,level)));
            bool isLeft = true;
            for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index ++ , index_cur_level ++ ){
   
                putNumberInLine( data[index] , line1 , index_cur_level , cur_tree_max_level_number*3-1 , isLeft );
                isLeft = !isLeft;
            }
            cout<<line1<<endl;

            if( level == max_level - 1 )
                break;

            string line2 = string(max_level_number*3-1, ' ');
            for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index_cur_level ++ )
                putBranchInLine( line2 , index_cur_level , cur_tree_max_level_number*3-1 );
            cout<<line2<<endl;

            cur_tree_max_level_number /= 2;
        }
    }

private:
    void putNumberInLine( int num, string &line, int index_cur_level, int cur_tree_width, bool isLeft){
   

        int sub_tree_width = (cur_tree_width - 1) / 2;
        int offset = index_cur_level * (cur_tree_width+1) + sub_tree_width;
        assert(offset + 1 < (int)line.size());
        if( num >= 10 ) {
   
            line[offset + 0] = '0' + num / 10;
            line[offset + 1] = '0' + num 
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值