堆排序:
堆排序与归并排序的时间复杂度一样,同为O(nlogn),但与归并排序不同的是,堆排序具有空间原址性:任何时候都只需要常数个额外的空间存储临时数据。
堆的定义:
(二叉)堆是一个数组,可以被看成是一个近似的完全二叉树。树上的每个结点对应数组中的一个元素。除了最顶层外,树应该是充满的,且为从左向右填充。
二叉堆通常分为两种:最大堆和最小堆。
最大堆:堆中的最大元素存放在根节点中,并且在任何一个子树中,该子树所包含的所有结点的值都不大于该子树的根结点的值。最大堆性质如下:
A[i / 2] >= A[i]
最小堆的性质与最大堆正好相反:
A[i / 2] <= A[i]
要使用堆排序,就要熟悉堆的基本操作:维护堆的性质(最大堆为例)。
给性一个数组A和其一个下标i,在维护堆的性质时,i的左孩子或右孩子有可能大于i结点的值,这样就不符合堆的性质了。要维护最大堆的性质,就要让A[i]的值在最大堆中“逐级下降”,使得以下标为i为根结点的子树重新具有最大堆的性质。
下面是维护最大堆过程:
/* length为数组是长度,i为最后一个非叶子结点 */
void Max_Heapify(int A[], int length, int i)//维护最大堆
{
int left = 2 * i;
int right = 2 * i + 1;
int largest; //largest暂存值最大的节点的下标
if(left < length && A[left] > A[i])
largest = left;
eles
largest = i;
if(right < length && A[right] > A[largest])//选出结点值最大的下标
largest = right;
if(i != largest)//若最大值不为根结点,则把最大值交换至根节点处
swap(&A[largest], &A[i]);
Max_Heapify(A, length, largest);//继续维护以largest为根结点的子树
}
有了维护最大堆的过程,下面就可以由给出的数组来建一个最大堆了。字数组A([n / 2]…n)中的元素都是叶子结点,每个叶子结点可以看成只包含一个元素的堆。
void BuildHeap(int A[], int length)
{
int i;
for(i = length / 2; i >= 1; i--)//从最后一个非叶子结点开始建最大堆
Max_Heapify(A, length, i);
}
开始时,堆排序算法利用Max_Heapify将输入的数组A[]建成最大堆,由于数组中的最大元素总是存放在根结点A[1]中,通过让它与A[n]互换,可以将最大值放在正确的位置。接下来,将n结点去掉,n的孩子结点仍然满足最大堆的性质,而新的根结点不一定满足,所以就要调用Max_Heapify()来调整新的根节点。以使其满足最大堆性质,从而在A[1…n-1]上新建一个最大堆。重复这一过程,直至堆的大小从n - 1降到2.
代码如下:
void heap_sort(int A[], int length)
{
int size = length;
BuildHeap(A, size); //建立最大堆
while(size > 1)
{
swap(&A[size - 1], &A[1]); //每次将最大的值放到正确的位置
size--;
Max_Heapify(A, size, 1); //将剩下的元素重新建堆
}
}