堆排序是最重要的排序算法之一,在平时的开发以及面试中经常会用到。堆的特点是:
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]<<" ";