堆的实现以及堆排序(最大堆,最小堆)

一.堆的概念

堆是一种特殊的数据结构,相当于数组实现的一颗二叉树,但是和一般的二叉搜索树不同

1.堆分为最大堆(父结点的值必大于其孩子结点的值)和最小堆(父结点的值必小于其孩子结点的值),
而二叉搜索树中左孩子结点的值小于父节点的值,右孩子结点的值大于父节点的值。

2.堆采用数组形式存储,相较于二叉树的链表结构减少内存消耗

3.堆排序的时间复杂度可以稳定在O(nlogn),而当二叉树为非平衡状态时,相应操作的时间复杂度不能稳定

二.堆的应用

1.优先队列

优先队列的使用场景十分之广,比如此时此刻你的计算机操作系统正在处理很多的进程,进程与进程之间存在着优先执行关系,对应的每个进程都有一个优先级。

举个更简单的例子:云顶之弈中,很多英雄的技能会优先攻击血量最少的敌方英雄。LoL中也存在着类似的技能机制。实质上就是一个动态的优先队列。

2.索引堆

堆中的内容经过heapify后交换了位置,从而形成最大堆或者最小堆。对简单的数据内容进行交换空间、时间消耗还不大,但是若对于上述提到的进程、英雄单位这些对象,操作起来则会比较麻烦并且很多情况下我们并不想改变元素的位置。

索引堆就是为了解决这个问题,对于原数组中的元素不进行交换,而是额外创造一个对应于原数组的索引数组(int[]),索引数组中每个元素记录原数组中每个元素的索引,在索引数组中进行heapify交换索引位置,让索引生成最大堆或者是最小堆,从而对于原数组没有改变。

索引堆运用在优先队列、图的最小生成树等算法当中。

3.堆排序

根据最大堆、最小堆堆顶元素的性质,从而可以实现堆排序,具体实现如下。

三.cpp实现

1.最大堆

根结点以1开始

template<typename T>
class MaxHeap{
private:
	T *arr;
	int count;
	int capacity;

	
	//核心代码:shiftUp()、shiftDown()
	void shiftUp(int count){ //迭代条件:该节点的值大于父结点的值 且该节点不为根结点
		while(count>1 && arr[count/2]<arr[count]){
			swap(arr[count/2],arr[count]);
			count / =2;
		}
	}


	void shiftDown(int index){
		while(index*2<=count){ //迭代条件:某个结点存在左孩子结点
			int i = index*2;
			if(i+1<=count && arr[i]<arr[i+1])//如果右孩子存在  选择左右孩子结点中值最大的结点
				i++;
			if(arr[i]>arr[index])
				break;
			swap(arr[i],arr[index]);
			index = i;
		}
	}
public:
	MaxHeap(int n){
		arr = new int[n+1];
		count = 0;
		capacity = n;
	}

	MaxHeap(int n,int *arr){  //param:  a Random Array(int)、array.length  
		arr = new int[n+1];
		for (int i = 0; i < n; ++i)
		{
			arr[i+1] = arr[i];
		}
		capacity=n;
		count = n;

		for (int i = n/2; i >0; --i) //n/2表示第一个非叶子结点的结点
		{							 //从n/2这个结点开始做shiftDown()操作 直至达到根结点
			shiftDown(i);
		}
	}

	void insert(T x){
		assert(count+1<capacity);
		arr[++count] = x;
		shiftUp(count);
	}

	T extractMax(){
		assert(count>=1);
		T max = arr[1];
		swap(arr[1],arr[count--])shiftDown(1);
		return max;
	}
};

2.最小堆

根结点从0开始:

#include"iostream"
#include"assert.h"
using namespace std;


template<typename Item>
class MinHeap
{
private:
    /* data */
    int count;
    int capacity;
    Item *data;

    void shiftUp(int k){
        while(k-1>0 && data[(k-1-1)/2]>data[k-1] ){
            swap(data[k-1],data[(k-2)/2]);
            k = k/2;
        }
    }

    void shiftDown(int k){  //时间复杂度 logn
        while(2*k+1<count){
            int i = 2*k+1;
            if(2*k+2<count && data[i]>data[i+1])
                i++;
            if(data[i]>=data[k])
                break;
            swap(data[i],data[k]);
            k = i;
        }
    }
public:
    MinHeap(int n){
        data = new Item[n];
        count = 0;
        capacity = n;
    }

    void add(Item x){
        assert(count+1<=capacity);
        data[count++] = x;
        shiftUp(count); 
    }

    Item extractMin(){
        assert(count>0);
        Item k = data[0];
        swap(data[0],data[--count]);
        shiftDown(0);
        return k;
    }

    bool empty(){
        return count == 0;
    }
    
    ~MinHeap(){
        delete []data;
    }
};

四.堆排序及性能测试

1.最大堆排序

template<typename T>
void shiftDown(T arr[],int n,int index){
    //heapify条件  该节点至少存在左子树
    while(2*index+1<n){
        int i=2*index+1;
        if(i+1<n && arr[i]<arr[i+1])
            i++;
        if(arr[index]>arr[i])
            break;
        swap(arr[index],arr[i]);
        index = i;
    }
}



template<typename T>
void heapSort(T arr[],int n){
    //heapify
    for(int i=(n-1-1)/2;i>=0;i--)
        shiftDown(arr,n,i);

    //将最大堆的堆顶交换至最后,再对剩余元素进行heapify
    for(int i=n-1;i>0;i--){
        swap(arr[0],arr[i]);
        shiftDown(arr,i,0);
    }
}

2.性能测试

n=50000 测试范围0~50000
测试随机生成以及完全有序两种情况
在这里插入图片描述

发布了16 篇原创文章 · 获赞 1 · 访问量 1725

猜你喜欢

转载自blog.csdn.net/qq_42584241/article/details/105257130