问题描述
•给定一个数组,找出其中出现次数最多的那个元素(即众数)。
核心思想
•普遍的解决思路。
•如果我们将所有元素的出现次数进行统计,并从中找出次数中的最大值,那么,这个最大值对应的元素就是众数。
•从这一思想出发,我总结出以下两种算法:
–算法1:利用排序算法统计
–算法2:利用数组或散列表统计
算法1
•算法思路:首先将数组元素按照大小排序,然后按顺序扫描一遍数组,扫描的同时进行统计。这样,通过一次排序和一遍扫描,我们就能够找到众数。
•关键步骤:排序,扫描统计
算法代价分析
1、时间代价
•由于扫描的时间代价是Θ(n)的,所以算法的总时间代价主要依赖于排序算法的代价。
•如果我们选取Θ(nlgn)的排序算法,最终的时间代价是Θ(nlgn)+Θ(n)= Θ(nlgn)
•Θ(nlgn)是基于比较的排序不可逾越的时间下界,也是该算法能够达到的最低代价。
2、空间代价
•扫描统计时,我们只需要三个辅助变量:一个用于计数,一个用于记录当前出现次数最多的元素的出现次数,一个用于记录当前出现次数最多的元素。
•所以,如果排序算法的辅助空间是O(1)的,那么整个算法的辅助空间代价就是O(1)的。
改进想法
•思想:改进排序算法,使得排序算法在排序的同时就统计出现次数,并记录次数最多的元素,以及删除所有重复的元素。这样,不仅可以减小排序的规模,并且省去了最后扫描的步骤,在一定程度上优化了该算法。
•然而,虽然改进后的算法比原始算法效率高,但是由于其本质仍然是基于比较的排序算法,所以时间代价还是Θ(nlgn)的,并没有在数量级上取得突破。
算法2
•算法思路:直接统计各元素出现的次数,用某一种线性数据结构存储统计结果。
•朴素的实现方法:用一个辅助数组存储统计结果,统计时用数组下标对应相应元素。
算法代价分析
1、时间代价
•用下标对应元素,访问时间O(1)。顺序扫描一遍原数组,就可以得到统计结果。
•总的时间代价Θ(n)。
算法代价分析
2、空间代价
•依赖于原数组中元素的范围。
•假设我们知道元素集中分布在宽度为m的区间内,那么我们就可以开辟大小为m的数组用于统计。这时,最后使用的辅助空间便是O(m)的。
•然而我们一般并不能确切地知道数组中数的范围,或者数组中元素的分布非常稀疏(即数组中有相当比例的空间存储的统计数为0)。这时,下标连续分布的、上下界明确的数组就难以承担其职责了。
•因此,我们必须改进数据结构,以更加广泛地适应算法的需求。
改进想法
•核心思想:采用散列表。
•散列表的优点:存储灵活,检索效率高。
•因此,使用散列表能够有效地替代数组,实现算法的功能。
•散列表的缺点:空间利用率低。
•据实验,当散列表的负载因子小于0.5时,散列表在大部分情况下的检索长度小于2。但是,如果超过0.5,散列表的性能就会急剧下降。
•因此,如果原数组元素分布稠密,使用散列表的空间效率就要低于使用数组。
改进后算法的代价分析
•前提假设:散列表的性能良好(负载因子小于0.5)
1、时间代价
•同样是扫描一遍,然而每一次的平均探查长度大于1。因此,时间效率要比数组低。
•但是,由于散列表性能良好,平均探查长度小于2,所以时间代价依然是O(n)的。
改进后算法的代价分析
2、空间代价
•如果原数组中共有m个不同的元素,那么,由于负载因子小于0.5,因此散列表的大小至少是2m。空间效率低于元素稠密时的数组,但是可能会远高于数据稀疏时的数组。
//众数问题
/*
问题描述:
给定含有n个元素的多重集合S,每个元素在S中出现的次数成为该元素的重数。多重
集S中重数最大的元素称为众数。
例如, S = {1,2,2,2,3,5}。
多重集s的众数是2,其重数为3。
编程任务:
对于给定的由n个自然数组成的多重集S,编程计算S的众数及其重数。
*/
#include
#include
#include
#include