堆排序
1. 堆:
1. 一种完全二叉树。
2. 每个结点的值都大于或等于其左右子结点的值,大顶堆。
3. 小顶堆同理。
2. 是简单选择排序的一种改进:把每次比较的结果用堆来保存起来。
3. 堆排序(大顶堆):
1. 将待排序列构造成一个大顶堆。
2. 将堆顶和待排序列最后一个元素交换,也就是保存起来。
3. 将剩余的序列(去除最后一个元素)重新构造成一个堆。
4. 重复23 。
4. 待排序列构造初始大顶堆:
1. 设序列长度length,已经构造好最初的完全二叉树,无序。
2. 从最下层最右边的非叶子结点开始向左向上。
3. 二叉树的性质:根节点从序号1开始,设某个结点的序号为k,则其左子树的序号是2k,右子树的序号是2k+1。最下层最右边的非叶子结点就是length/2取整。
4. 从第【length/2取整】个结点开始,向根节点(序号为1)开始 逐个调整每个子树的三个结点的顺序,让其成为大顶堆。
5. 交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整
6. 最后调整到根节点时候,整个树就是一个大顶堆。
复杂度
1. 构建堆:O(n)
2. 重建堆:O(nlogn),调整的是根节点的左子树或者右子树,而调整的次数也就是该二叉树目前的深度,也就是【log2n】+1,每次都要调整,一共调整n-1次,所以复杂度就是O(nlogn)。
3. 总复杂度:O(nlogn)
4. 对原始记录的排序状态不敏感,反正都要构造初始堆。
5. 不稳定。
6. 构建堆需要多次比较,待排序列个数较少的情况不适合。
图示
代码实现
//构建大顶堆的调整算法
void sift(int r[],int k,int m)
{
int i = k;
int j = 2*k;
while (j<=m)
{
if (j<m && r[j]<r[j+1])
{
//j指向较大的孩子结点
j = j + 1;
}
if (r[i]<=r[j])
{
int temp = r[i];
r[i] = r[j];
r[j] = temp;
//根节点的调整会影响孩子结点,所以一直调整到m
i = j;
j = 2*i;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int r[ ], int n)
{
int i;
for (i=n/2; i>=1; i--) //初始建堆,从最后一个非终端结点至根结点
{
sift(r, i, n) ;
}
for (i=1; i<n; i++) //重复执行移走堆顶及重建堆的操作
{
int temp = r[1];
r[1] = r[n-i+1];
r[n-i+1] = temp;
sift(r, 1, n-i);
}
}
用例:
int a[] = {0,36,30,18,40,32,45,22,50};
int i ;
for (i = 1;i<9;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
HeapSort(a,8);
for (i = 1;i<9;i++)
{
cout<<a[i]<<" ";
}
时间: 2024-11-07 03:49:24