高级堆排序

    今天在一个OJ上做了一个叫“Advanced Heap Sort”的题,题的解决算法没什么难的,但是对时间复杂度有要求,用正常的算法实现,都会超时,所以我就把这个题拿过来分享一下。


问题:

题目内容:
有两个序列S1和S2,各有N个元素。当我们在S1,S2各取一个数字时,总共会有N*N这么多可能的”和”(sum)。请找出这N*N这么多和里最小的N个值,并将它们加总后输出。

输入格式:
只有一笔测资。
	测试资料第一行为一个正整数N。
	第二行有N个数字,以空白隔开,代表序列S1。
	第二行有N个数字,以空白隔开,代表序列S2。
 数字范围:
0 < N < 10000

输出格式:
输出一行,N个最小的可能的和的加总。

输入样例:
5
1 3 5 7 9
2 4 6 8 10


输出样例:
27

时间限制:100ms内存限制:128000kb


算法分析:

    这个题目既然是要求用堆排序,所以首先想到的时,建立一个N²的数组,通过两层循环,将所有的结果和填写到N²的大数组中,然后通过最小堆排序,将前N个进行和运算就可以了。但是这个题N的范围到10000,所以堆的大小是100000000,那么这个算法的时间复杂度就是O(n²)+ O(nlogN)+ O(nlogN) + O(n),可见时间复杂度和空间复杂度都很高,不能满足题目要求。

    所以,这里需要先将两个数列时行从小到大的排序,然后,建立一个N大的固定的堆,先填入N的数,并经进行最大堆排序,排序完成后,结果也就出来了。这里还有一个关键就是往堆里插入的值,原来是要插入N²个,因为现在将两个数列进行了从大到小的排序,所以满足插入的数列为:从第一个数组元素到(N/1) + (N/2) + (N/3) + .... + (N/N)  【因为A[1] + B[i], 0 <= i <= (N-1)/2都是答案的话(也就是前N小的),那么,A[0] + B[i]也是答案。因为A[0] + B[i] <= A[1] + B[i]】 ,通过这种方式进行时间复杂度的提升,再就是在进行两个数列的排序时,要使用归并法时行排序。

    这样的话,空间复杂度是N,时间复杂度是O(N log N) + O(log N) + O(NlogN * logN) + O(N log N),提升了不少,通过测试,完胜。

算法代码:

#include <stdio.h>

int arrRst[10000]; //堆 
int arrMer[10000]; //归并排序的临时数组 
int n = 0; // 堆游标 

//归并函数 
void merge(int *arr, int start, int mid, int end){
	int i, j, k;
	
	i = k = start;
	j = mid + 1;
	
	while((i <= mid) && (j <= end)){
		if(arr[i] <= arr[j]){
			arrMer[k++] = arr[i++];
		}else{
			arrMer[k++] = arr[j++];
		}
	}
	
	while(i <= mid){
		arrMer[k++] = arr[i++];
	}
	
	while(j <= end){
		arrMer[k++] = arr[j++];
	}
	
	for(i = start; i <= end; i++){
		arr[i] = arrMer[i];
	}
}

//归并函数 
void mergeSort(int *arr, int start, int end){
	int mid;
	
	if(start < end){
		mid = (start + end) / 2;
		mergeSort(arr, start, mid);
		mergeSort(arr, mid + 1, end);
		merge(arr, start, mid, end);
	}
}


void swap(x, y){
	int t;
	
	t = arrRst[x];
	arrRst[x] = arrRst[y];
	arrRst[y] = t;
}

void shiftDown(int x){
	int t, flag = 0;
	
	while(x * 2 <= n && flag == 0){
		//左孩子 
		if(arrRst[x * 2] > arrRst[x]){
			t = x * 2;
		}else{
			t = x;
		}
		
		//右孩子
		if(x * 2 + 1 <= n){
			if(arrRst[x * 2 + 1] > arrRst[t]){
				t = x * 2 + 1;
			}
		} 
		
		if(t == x){
			flag = 1;
		}else{
			swap(x, t);
			x = t;
		}
	}
}

void creatHeap(){
	int i;
	for(i = n / 2; i >= 1; i--){
		shiftDown(i);
	}
}

int main(){
	int arr[10000], arr2[10000];
	int N, sum = 0;
	int i, j;
	
	//输入 
	scanf("%d", &N);
	for(i = 1; i <= N; i++){
		scanf("%d", &arr[i]);
	}
	for(i = 1; i <= N; i++){
		scanf("%d", &arr2[i]);
	}
	
	//排序
	mergeSort(arr, 1, N);
	mergeSort(arr2, 1, N); 
	
	for(i = 1; i <= N; i++){
		for(j = 1; j <= N / i; j++){//要插入的数据 
			if(n <= N - 1){ //只存放前N个 
				arrRst[++ n] = arr[i] + arr2[j];
				if(n == N){
					creatHeap();//对前N个建立最大堆 
				}	
			}else{
				if(arr[i] + arr2[j] < arrRst[1]){
					arrRst[1] = arr[i] + arr2[j];
					shiftDown(1);
				}
			}			
		}
	}
	
	for(i = 1; i <= n; i ++){
		sum += arrRst[i];
	}
	
	printf("%d", sum);
	
	return 0;
} 

博客名称:王乐平博客
博客地址:http://blog.lepingde.com
优快云博客地址:http://blog.youkuaiyun.com/lecepin


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王乐平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值