堆排序—大根堆,小根堆

1.小根堆
若根节点存在左子女则根节点的值小于左子女的值;若根节点存在右子女则根节点的值小于右子女的值。
2.大根堆
若根节点存在左子女则根节点的值大于左子女的值;若根节点存在右子女则根节点的值大于右子女的值。
3.结论
(1)堆是一棵完全二叉树(如果公有h层,那么1~h-1层均满,在h层连续缺失若干个右叶子)。
(2)小根堆的根节点的值是最小值,大根堆的根节点的值是最大值。
(3)堆适合于采用顺序存储。
4.堆的插入算法
将一个数据元素插入到堆中,使之依然成为一个堆。
算法描述:先将结点插入到堆的尾部,再将该结点逐层向上调整,直到依然构成一个堆,调整方法是看每个子树是否符合大(小)根堆的特点,不符合的话则调整叶子和根的位置。
5.堆的删除算法
堆在删除元素时,只可以删除根节点。
算法描述:将根节点删除后用堆尾结点进行填补,调整二叉树,使之依然成为一个堆。
6.堆排序(大根堆,小根堆类似)
其基本思想为(大根堆):
    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区,构建的过程是每个非叶子结点都经过一次调整,调整顺序为从底层至顶层(调整过程中含有递归),这样调整下来这个二叉树整体上就是一个大根堆(或小根堆)了;
    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlog2(n)。堆排序为不稳定排序,不适合记录较少的排序。
关于log2(n)的理解:根据堆排序的过程,每次将大根堆根节点的值跟最后一个叶子的值进行交换,那如果最后的叶子结点正好是最小的数,那么这个叶子结点就会一层层的被放到子树最终放到叶子结点的位子(不是前面的叶子结点的位置了),这样的话这个叶子结点经过的层数就刚好为log2(n)。然而其他没有交换的二叉树的分支,因为以前都是大根堆,所以大根堆的性质还是没有变化,这一点对理解程序至关重要。


在创建的过程:

先是一个无序的数组,然后从非叶子节点开始进行调整,比较他的左右孩子,如果有比较大的替换为根节点,然后再向下比较替换了后的孩子节点,

等到完成到root节点后创建完成

输出的过程:

将顶点元素输出,一般将其和最后一个节点交换位置(注意这里堆的长度应该-1),因为别的非叶子节点仍然是满足要求的,所以只需要检查root节点是否满足要求,并进行相应的更新操作

构建大根堆,同时进行输出排序

#include <string>
#include <map>
#include <iostream>
#include <sstream>
using namespace std;

void PercDown(int A[],int i,int N)   //<i:每个非叶子节点,N数组长度
{  
	int child = 0;
	int Tmp; //<用于保存当前根节点
	for (Tmp = A[i]; (i*2+1)<N; i = child) //<一个循环是因为更新完当前节点后还要看之后的节点是否满足要求
	{
		child = i*2+1;
		if (child != N-1 && A[child+1]>A[child])
		{
			child++; //<选择出子节点中最大的
		}
		if (Tmp < A[child])
		{
			A[i] = A[child];  //<将当前节点由比较大的子节点进行替换
		}
		else
			break;
	}
	A[i] = Tmp; //<i是对应的最后的孩子节点的位置
}  
void main()
{
	int A[9] = {3,8,2,7,9,26,28,54,20};
	int N = 9;
	int i;
	for (i = N/2;i >=0; i--) //<每个非叶子节点,只需要验证非叶子节点
	{
		PercDown(A,i,N); //<i 表示当前需要验证的根节点,N表示数组长度
	}
	for (i = N-1;i > 0; i--) //<这里的i表示数组的长度,因为每一次输出一个数据到vector最后面,树就减少1
	{
		swap(A[0],A[i]);
		PercDown(A,0,i); //<因为每次从堆顶端进行输出,所以每一次验证需要从0开始
	}
	i = 0;
}


类似于大根堆,完成小根堆的创建和输出

#include <string>
#include <map>
#include <iostream>
#include <sstream>
using namespace std;

void PercDown(int A[],int i,int N)   //<i:每个非叶子节点,N数组长度
{  
	int child = 0;
	int tmp= A[i];
	for (; (i*2+1) < N; i = child)
	{
		child = 2*i+1;
		if ((child < (N-1)) && (A[child] > A[child+1])) //<N-1是因为还有有节点
		{
			child++;
		}
		if (tmp > A[child])
		{
			A[i] = A[child];
		}
		else
		{
			break;
		}
	}
	A[i] = tmp;
}  
void main()
{
	int A[9] = {3,8,2,7,9,26,28,54,20};
	int N = 9;
	int i;
	for (i = N/2;i >=0; i--) //<每个非叶子节点,只需要验证非叶子节点
	{
		PercDown(A,i,N); //<i 表示当前需要验证的根节点,N表示数组长度
	}
	for (i = N-1;i > 0; i--) //<这里的i表示数组的长度,因为每一次输出一个数据到vector最后面,树就减少1
	{
		swap(A[0],A[i]); //<注意传入的是i是数组的长度,所以后面的判断是N-1
		PercDown(A,0,i); //<因为每次从堆顶端进行输出,所以每一次验证需要从0开始
	}
	i = 0;
}


还有网上一些别的解法,大同小异:

/*堆排序(大根堆)*/ 
#include <stdio.h>

/*注意:这个函数只会在调整被交换的位置为大根堆,未交换的分支不会处理,
所以不能将一个非大根堆二叉树的根结点传递过来让这个函数将其处理为大根堆*/
void heap_ajust(int *a, int i, int size)  /*a为堆存储数组,size为堆的大小*/
{
    int lchild = 2*i+1;       //i的左孩子节点序号 
    int rchild = 2*i +2;     //i的右孩子节点序号 
    int max = i; /*存放三个顶点中最大的数的下标*/
	int temp;
    if(i <= (size-1)/2)          //如果i是叶节点就不用进行调整 
    {
        if(lchild<size && a[lchild]>a[max])
        {
            max = lchild;
        }    
        if(rchild<size && a[rchild]>a[max])
        {
            max = rchild;
        }
        if(max != i)
        {
            temp = a[i]; /*交换a[i]和a[max]的值*/
			a[i] = a[max];
			a[max] = temp;
            heap_ajust(a, max, size); /*被交换的位置以前是大根堆,现在可能不是大根堆
			                            所以需要重新调整使其成为大根堆结构*/ 
        }
    }        
}

void build_bheap(int *a, int size) /*建立大根堆*/ 
{
    int i;
    for(i=(size-1)/2; i >= 0; i--) /*非叶节点最大序号值为size/2*/
    {
        heap_ajust(a, i, size); /*每个非叶子结点都需要调用这个函数*/   
    }    
} 

void heap_sort(int *a, int size) /*堆排序*/ 
{
    int i;
	int temp;

    build_bheap(a, size); //<创建大根堆
    for(i=size; i >= 1; i--) //<以数组长度为单位
    {
        temp = a[0];
		a[0] = a[i-1];
		a[i-1] = temp; /*交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面*/ 
        heap_ajust(a, 0, i-1); /*重新调整堆顶节点成为大顶堆,只有被交换的分支才有可能不是大根堆*/
    }
} 

int main(int argc, char *argv[])
{
    int a[]={0,16,20,3,11,17,8};
    int size = sizeof(a)/sizeof(int);
	int i;

	printf("size = %d\n", size);
    heap_sort(a, size);
	printf("Sort over:"); 
    for(i=0;i < size; i++)
        printf("%d ", a[i]);
    printf("\n");

    return 0;
}


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值