堆排序以及归并排序的理解

一、堆排序

       堆排序的核心是把数组看作一个完全二叉树,通过入堆操作把整个二叉树整理一遍,使所有的父节点都比其子节点大,如此根节点a(即数组的第一位)就是最大的数。把根节点a与二叉树最后一位z调换,称为a的出堆,即a已经排序完成。再处理被换上去的z,由于所有的父节点都比子节点大,只要把根节点下面子节点较大的那一位换到根节点,而根节点上的z就重复的进行处理沉下去,直到沉到底。重复出堆的操作,就可以得到升序排列的数组。整个操作的时间复杂度为O(Nlog(N)),空间复杂度为O(1)。

1.入堆:

从根节点开始,判断其与父节点的大小关系,如果比父节点大,就与父节点交换,交换完成后继续与新的父节点作比较。而根节点没有父节点就不进行操作。

2.出堆:

将根节点与二叉树最后一片叶交换,并且二叉树的节点减一,即原先的根节点已经排序完成,退出比较。再把新的根节点通过与子节点比较交换一路沉下去。需要注意的是由于排好序的节点已经退出比较,所以要注意更新子节点是否存在,否则会把已经排序完成的数唤醒。

规律:画出一个完全二叉树并且标号(根节点标号为1)会发现任何一个父节点K的子节点为2k和2k+1,有这样的位置关系容易比较。

3.代码部分:

#include<stdio.h>
#include<stdlib.h>
#define N 21
void enheap(int *a,int n);
void outheap(int *a,int n,int count);
void heap_sort(int *a,int n)
{
	int i=0,temp=0,j=0;
	for(i=1;i<=n;i++)	//将数组内按照堆排序的入堆操作从前往后推,使所有的父节点都比其子节点大,如此则根节点最大。一次操作复杂度为O(log(n))
		enheap(a,i);
	for(i=n;i>=1;i--)
	{
		temp=a[1]; a[1]=a[i]; a[i]=temp;	//把根节点(即最大的数)和数组后面未排序的数交换
		outheap(a,1,i-1);	//交换完成后i节点已经完成排序,不再进入比较序列,传入参数变为i-1
	}
	return;
}
void enheap(int *a,int n)
{
	int temp=0,index=0;
	if(n/2<1 || a[n/2]>a[n])	//若该节点是根节点或者比父节点小则不作任何操作
		return;
	if(a[n]>a[n/2])		//该节点比父节点大,则与父节点交换并继续往上比较
	{
		temp=a[n]; a[n]=a[n/2]; a[n/2]=temp;
		enheap(a,n/2);
	}
	return;
}
void outheap(int *a,int n,int count)	//count数记录目前排序未完成的个数,若超出此数则说明触碰了已排序完成的数
{
	int temp,index=0;
	if(2*n>count)		//n节点下面的已经比较过或者超出了数组范围
		return;
	if(2*n+1>count || a[2*n]>=a[2*n+1])	//	n节点下只有左节点或左节点不比右节点小,则n节点和其左节点比较
		index=2*n;
	else	//n节点下右节点比左节点大
		index=2*n+1;
	if(a[n]<=a[index])	//若n节点不比子节点大,则交换后继续比较
	{
		temp=a[n]; a[n]=a[index]; a[index]=temp;
		outheap(a,index,count); //在这里count是不变的,因为一次比较还没有完成
	}
	return;
}

int main(void)
{
	int i=0;
	int a[N+1]={0};
	for(i=1;i<=N;i++)
		a[i]=rand()%N;
	/*for(i=1;i<N;i++)
		printf("%d\n",a[i]);*/
	heap_sort(a,N);
	for(i=1;i<=N;i++)
		printf("%d\n",a[i]);
	return 0;
}

二、归并排序:

归并排序和快速排序在将排序数组递归划分成两段上有一点点像,但快速排序是根据一个关键数将数据分成两段,递归过程中是先处理大的数据再划分处理小的数据量。而归并排序是将所有的数据先不断递归划分,直到一个数字单元,再处理该段数据,然后返回,处理的数据量是从小到大的。而且归并排序需要一个额外的空间。但是快速排序在最坏情况下时间复杂度会退化到O(n*n),归并不会存在这个问题。
1.原理:
将所有的数据递归划分成两段,直到单个数据不可分。然后再把两两相邻的数组合并。合并完之后得到的数组再次合并,直到整个数组合并。
2.代码部分:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 1000000

int temp[N];
void merge(int *a,int *b,int n,int m)	//将两个数组合并到temp数组中
{
	int i=0,j=0,count=0;
	while(i<n && j<m)		//两段数组都未比较完毕
	{
		if(a[i]<b[j])
			temp[count++]=a[i++];
		else
			temp[count++]=b[j++];
	}
	if(i>=n)		//如果a数组先被处理完就把b数组剩下的所有的数放入temp数组中
		while(j<m)
			temp[count++]=b[j++];
	else			//b数组先处理完
		while(i<n)
			temp[count++]=a[i++];
	return;
}
void merg_sort(int *a,int n)
{
	if(n<=1)		//若已经不可再分则不处理
		return;
	if(n>2)		//若还可再分,就先处理小的
	{
		merg_sort(a,n/2);
		merg_sort(a+n/2,n-n/2);
	}
	merge(a,a+n/2,n/2,n-n/2);		//此处第一次处理的话就是两个单个数合并
	memcpy(a,temp,n*sizeof(int));
	return;
}

int main(void)			//测试代码
{
	int a[N]={0};
	int i=0;
	for(i=0;i<N;i++)
	{
		a[i]=rand()%N;
		//printf("%d ",a[i]);
	}
	puts("");
	merg_sort(a,N);
	for(i=0;i<N;i++)
		printf("%d\n",a[i]);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值