sort散列法排序

题目描述

给你n个整数,请按从大到小的顺序输出其中前m大的数。

输入

每组测试数据有两行,第一行有两个数n,m(0<n,m<1000000),第二行包含n个各不相同,且都处于区间[-500000,500000]的整数。

输出

对每组测试数据按从大到小的顺序输出前m大的数。

样例输入

5 3
3 -35 92 213 -644

样例输出

213 92 3

这是算法课的第一道OJ题,我的第一想法是利用冒泡排序把n个整数由大到小排序,再利用for循环输出前m个数字,虽然能实现题目要求,但是时间复杂度达到了O(n^{2}),效率太低

#include <iostream>
using namespace std;
void sort(int n,int m)
{
	int a[1000] = { 0 };
	for (int k = 0; k < n; k++)
	{
		cin >> a[k];
	}
	for (int i = 0; i < n - 1; i++)  //冒泡排序
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] < a[j + 1])
			{
				swap(a[j], a[j + 1]);   //大一的时候都用的中间变量temp来交换,这个swap()就很方便
			}
		}
	}
	for (int z = 0; z < m; z++)
	{
		cout << a[z] << ' ';  //输出排序后的前m个数即可
	}
}
int main()
{
	int n, m;
	cin >> n >> m;
	sort(n, m);
	return 0;
}

改良算法:散列表(Hash)

这种数据结构只是在纸上手写模拟过,这是第一次用代码实现。

思路:

        因为n个数都是区间[-500000,500000]的整数(用i表示其中某一个数)。我们把i+500000,则得到了一个区间在[0,1000000]的整数,正好符合数组下标是大于等于0的整数的规则,我们建立一个数组a[1000000],遍历这n个数所在的数组,每遍历到一个i,则使数组a的a[i+500000]这个元素变成1(其余元素都是0)。然后从后往前(a[999999]到a[0])遍历数组a(即这个只有0和1的数组),当访问到1,输出这个元素的(下标-500000),即为i的值,当访问了m个1的元素,则也输出了前m个最大的元素了。

注意:

        本题说明了n个数字各不相同,所以可以这样,否则如果n个数字中有两个相同,a[i+500000]这个元素已经变成1了,再遍历到第二个这个相同数值的i时,a[i+500000]又赋值为1,这样是感知不到有两个相同数值的i的。若题目说n个数中的数字可以有重复,则要对思路进行一些小改动,后面会介绍。

这种方法时间复杂度达到了O(n),比冒泡排序快很多

 

#include <iostream>
using namespace std;
int a[1000000] = { 0 };  //一定要放在外面
void sort1(int n, int m)
{
	int i;
	for (int k = 0; k < n; k++)
	{
		cin >> i;   //输入n个数
        a[i + 500000] = 1;   //散列表a中,以输入的数+500000为下标的元素赋值为1
	}
	for (int j = 999999; j > 0; j--)  //从后往前遍历散列表,实现从大到小输出
	{
		while (m > 0 && a[j] == 1)   
		{
			m--; //有一个数满足条件,则m减1,减到零则输出了m个数了
			cout << j - 500000 << ' '; 
			a[j] = 0;   //输出后记得把a[j]赋值回0,否则同一个数会一直输出
		}
	}
}
int main()
{
	int n, m;
	cin >> n >> m;
	sort1(n, m);
	return 0;
}

若加上n个数中可能会有数字重复的限定条件?

这时只需要把把散列表元素赋值为1这一操作变成对相应散列表元素值进行自增(若一个数字出现了两次,散列表对应元素的值就是2),然后从后往前遍历的时候的判定条件就是a[j]!=0,每一次满足条件就a[j]--,而不是直接把a[j]赋值回0

#include <iostream>
using namespace std;
int a[1000000] = { 0 };  //一定要放在外面
void sort1(int n, int m)
{
	int i;
	for (int k = 0; k < n; k++)
	{
		cin >> i;   //输入n个数
        a[i + 500000]++;   //散列表a中,以输入的数+500000为下标的元素赋值为1
	}
	for (int j = 999999; j > 0; j--)  //从后往前遍历散列表,实现从大到小输出
	{
		while (m > 0 && a[j]!=0)   
		{
			m--; //有一个数满足条件,则m减1,减到零则输出了m个数了
			cout << j - 500000 << ' '; 
			a[j]--;   //输出后把a[j]的值减去1,若此时a[j]没到0,则会继续输出这个数字
		}
	}
}
int main()
{
	int n, m;
	cin >> n >> m;
	sort1(n, m);
	return 0;
}

这段代码和上一题的改变就在a[i + 500000] = 1 变为了   a[i + 500000]++a[j]==1变为了a[j]!=0a[j] = 0变为了a[j]--。 三处

这段代码是兼容上一题的,而且考虑了重复的情况,更加成熟。

样例输入:

5 3

3 -35 92 213 92

样例输出:

213 92 92

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值