时间复杂度:O(nlgn)
空间复杂度:O(1)
最关键的问题:创建大根堆
关键:创建大根堆
/*
说明:堆排序是完全二叉树顺序存储的应用,存储在数组中。
Q1:假设有N个结点进行堆排序,那么存储在数组中的下标是1~N,0下标不使用。
为什么用1~N,而不是用0~N-1下标呢?
A1:(1)由父子结点的关系决定的 (2)还有一个重点应用:A[0]在向下调整
函数AdjustDown函数中保存着i的父节点k的值。
Q2:AdjustDown(A, i, len)函数每执行一次,至多调整几个值?
A2:最初认为最多能调整一个值,其实不然,最多可以调整值的个数是(树高-父节点i所在高度+1)。
AdjustDown(A, i, len)函数在每次调整完一次父节点i的值后,要更新父节点i变成i的子节点,
然后在进行调整判断,...,如此循环往复,直到退出循环。
Q3:创建大根堆函数BuildMaxHeap中调用AdjustDown调整函数的调用方向-->是从
第一个父节点到最后一个父节点?还是从最后一个父节点到第一个父节点?
A3:根据堆排序创建大根堆过程,可得:调整方向是从最后一个父节点,到第一个父
节点-->因此循环是for(int i=len/2;i>=1;i--)
*/
#include<stdio.h>
/*
A[0]: 始终存放着每一次循环的父节点的值
k: 始终存放着每一次循环的父节点的下标位置
i: 始终存放着k父节点左右结点较大的值
for循环的范围是左孩子结点i的范围
for(int i=2*k;i<=len;i*=2)
for循环中,
更新父节点更新的是父节点的下标位置
*/
void AdjustDown(int A[], int k, int len) //k是要调节的父节点
{
A[0] = A[k]; //将k父节点的值赋值给A[0],A[0]始终保存着每个父节点的值
//调节是一个循环的过程,因此写个循环
for (int i = 2 * k; i <= len; i *= 2) //i节点初始为父节点k的左孩子
{
//在保证有右孩子的基础上,判断k的右孩子是否大于左孩子
if (i+1<=len && A[i] < A[i + 1]) //i+1<=len表示右孩子在数组中,右孩子存在
i++;
if (A[i] > A[0]) //如果孩子i大于父节点,进行处理
{
A[k] = A[i]; //将左孩子的值赋值给父节点
k = i; //更新左孩子i变成新的父节点,用于下一次循环
}
else //若孩子i不大于父节点,则证明下面已经全部符合大根堆要求,直接break出for循环
break;
}
//执行完所有的for循环调整后,不要忘记把A[0]中存储的最初的父节点A[k]赋值给当前父节点k
A[k] = A[0];
}
void BuildMaxHeap(int A[], int len)
{
for (int i = len / 2; i >= 1; i--)
{
AdjustDown(A, i, len);
}
}
int main()
{
int A[] = { 0,53,17,78,9,45,65,87,32 };
int length = sizeof(A) / sizeof(A[0]);
BuildMaxHeap(A, length);
for (int i = 1; i < length; i++)
printf("%5d", A[i]);
printf("\n");
}
堆排序
#include<stdio.h>
/*
A[0]: 始终存放着每一次循环的父节点的值
k: 始终存放着每一次循环的父节点的下标位置
i: 始终存放着k父节点左右结点较大的值
*/
void AdjustDown(int A[], int k, int len) //k是要调节的父节点
{
A[0] = A[k]; //将k父节点的值赋值给A[0],A[0]始终保存着每个父节点的值
//调节是一个循环的过程,因此写个循环
for (int i = 2 * k; i <= len; i *= 2) //i节点初始为父节点k的左孩子
{
//在保证有右孩子的基础上,判断k的右孩子是否大于左孩子
if (i+1<=len && A[i] < A[i + 1]) //i+1<=len表示右孩子在数组中,右孩子存在
i++;
if (A[i] > A[0]) //如果孩子i大于父节点,进行处理
{
A[k] = A[i]; //将左孩子的值赋值给父节点
k = i; //更新左孩子i变成新的父节点,用于下一次循环
}
else //若孩子i不大于父节点,则证明下面已经全部符合大根堆要求,直接break出for循环
break;
}
//执行完所有的for循环调整后,不要忘记把A[0]中存储的最初的父节点A[k]赋值给当前父节点k
A[k] = A[0];
}
void BuildMaxHeap(int A[], int len) //创建大根堆
{
for (int i = len / 2; i >= 1; i--)
{
AdjustDown(A, i, len);
}
}
void swap(int &a, int &b)
{
int t = a; a = b; b = t;
}
void HeapSort(int A[], int len) //堆排序
{
for (int i = 1; i <= len; i++)
{
BuildMaxHeap(A, len - i + 1);
swap(A[1], A[len - i + 1]);
}
}
int main()
{
int A[] = { 0,5,17,7,9,5,65,87,32 };
int length = sizeof(A) / sizeof(A[0]);
HeapSort(A, length-1);
for (int i = 1; i < length; i++)
printf("%5d", A[i]);
printf("\n");
}
堆排序的应用题:
#include<stdio.h>
void AdjustDown(int A[], int k, int len) //k是要调节的父节点
{
A[0] = A[k]; //将k父节点的值赋值给A[0],A[0]始终保存着每个父节点的值
//调节是一个循环的过程,因此写个循环
for (int i = 2 * k; i <= len; i *= 2) //i节点初始为父节点k的左孩子
{
//在保证有右孩子的基础上,判断k的右孩子是否大于左孩子
if (i + 1 <= len && A[i] > A[i + 1]) //i+1<=len表示右孩子在数组中,右孩子存在
i++;
if (A[i] < A[0]) //如果孩子i大于父节点,进行处理
{
A[k] = A[i]; //将左孩子的值赋值给父节点
k = i; //更新左孩子i变成新的父节点,用于下一次循环
}
else //若孩子i不大于父节点,则证明下面已经全部符合大根堆要求,直接break出for循环
break;
}
//执行完所有的for循环调整后,不要忘记把A[0]中存储的最初的父节点A[k]赋值给当前父节点k
A[k] = A[0];
}
//创建小根堆
void BuildMinHeap(int A[], int len)
{
for (int i = len / 2; i >= 1; i--)
{
AdjustDown(A, i, len);
}
}
void swap(int &a, int &b)
{
int t = a; a = b; b = t;
}
//堆排序
void HeapSort(int A[], int len)
{
for (int i = 1; i <= len; i++)
{
BuildMinHeap(A, len - i + 1);
//printf("%5d", A[1]);
swap(A[1], A[len - i + 1]);
}
}
int main()
{
int A[] = { 0,2, 1, 4, 3, 6, 5, 8, 7, 10, 9 };
int k = 2;
int kk = k;
int len = sizeof(A) / sizeof(A[0]) - 1;
int *B = new int[len + 1];
for (int i = k, j = 1; k <= len;) //排序1~N-K+1个
{
BuildMinHeap(A, 2);
B[j++] = A[1];
//printf("%5d", A[1]);
A[1] = A[++k];
}
//剩下的N-K+2~N个结点用堆排序
A[1] = A[kk];
HeapSort(A, kk-1);
for (int i = 1; i <= len; i++)
printf("%5d", B[i]);
printf("\n");
}