堆以及堆排序
堆的定义:
1.是一颗完全二叉树(除最后一层叶子节点,其他层级节点不为空,并且叶子节点全部要偏向左边)
2.当前节点的值要大于等于(小于等于)左右子树,那么就会有大顶堆和小顶堆的区别,大顶堆意味着堆顶值最大,小顶堆意味着堆顶最小
如何实现堆
由于完全二叉树的定义,那么堆完全可以使用数组的形式进行实现,通过下标来确定每个节点的位置,这样就省去了左右节点指针的额外开销。
1.插入:
方式1:从下往上进行插入,每次都将值放在最末端,通过不断与其父节点比较并且置换。
方式2:将所有数据先放入到数组中,由于最后一排叶子节点不需要进行堆化,那么我们可以直接从n/2节点开始进行堆化,直到堆顶(后面代码有说明)
实现堆以及堆排序
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
#define MAX_CAPACITY 10
template<typename T>
class Heap
{
Heap():m_data(new T[MAX_CAPACITY+1])
{};
~Heap()
{
delete[] m_data;
};
//插入数据
void insertData(T data)
{
if (m_curCount >= MAX_CAPACITY)return;
m_curCount++;
m_data[m_curCount] = data;
int nSize = m_curCount;
while(nSize / 2 > 0 && m_data[nSize] >= m_data[nSize / 2])
{
std::swap(m_data,nSize,nSize/2);
nSize /= 2;
}
};
//删除操作
void deleteData()
{
if (m_curCount == 0)return;
std::swap(m_data,m_curCount,1);
m_curCount--;
int nMax = 1;
while(true)
{
int nMaxIndex = nMax;
if (2 * nMax <= m_curCount && m_data[2*nMax] >= m_data[nMax])
nMaxIndex = 2 * nMax;
if( 2 * nMax + 1 <= m_curCount && m_data[2 * nMax + 1] >= m_data[nMaxIndex])
nMaxIndex = 2 * nMax + 1;
if(nMax == nMaxIndex) break;
//每一次都更新下值的位置
std::swap(m_data,nMax,nMaxIndex);
nMax = nMaxIndex;
}
};
void buildHeap(T* data,int n)
{
for(int i = n / 2; i > 0; i--)
{
heapify(data,n,i);
}
};
void heapify(T* data,int n,int i)
{
while(true)
{
int nMaxIndex = i;
if(2*i <= n && m_data[2*i] >= m_data[i])
nMaxIndex = 2*i;
if (2*i+1 <= n && m_data[2*i+1] >= m_data[nMaxIndex])
nMaxIndex = 2*i+1;
if(nMaxIndex == i)break;
std::swap(m_data,nMaxIndex,i);
i = nMaxIndex;
}
};
void sort(T* data,int n)
{
//先建堆
buildHeap(data,n);
//删除堆顶数据
int size = n;
while(size > 1)
{
//每次弹出堆顶,然后进行处理
std::swap(data,size,1);
size--;
heapify(data,size,1);
}
}
private:
T* m_data;
int m_curCount;
};
上面代码中,从上往下进行堆化,只需要处理n/2个节点之前的数据就行了,因为叶子节点没有左右子节点,所以不需要进行堆化,而应该从倒数第二排进行堆化,假设一共有n层,那么第一层为1个节点,第二层为2个节点,依次类推,第n层为(2……n-1),第n-1层为(2……n-2),那么此时除开最后一层,前面的层数节点之和为:1+2+4+…(2……n-2)=(2……n-1)-1,比最后一层的数据少一个,那么n个数据,其中n/2+1到n的节点一定都是叶子节点
堆排序的性能分析
由于建堆的过程不需要涉及到所有节点,只需要涉及到n/2 个节点,因为堆化的开销与高度相关,所以令高度为h,那么总时间开销为:S1 = 1h+2(h-1)+…(2……n-1)*1,通过错位相乘再相减,2S1-S1 = (2……h-1)-h-2,因为h的大致时间为logn,所以时间复杂度近似为O(n),而排序就是一个删除过程,每个节点删除之后都需要重新建堆,时间复杂度为O(nlogn),所以总的时间复杂度为O(nlogn)