C++ 中的 H 指数

2025年5月15日 | 阅读12分钟

C++ H指数简介

H指数是指一个量化学者科学产出的指标。它的定义是,一名研究者发表了'h'篇论文,且每篇论文的引用次数都至少为'h'次。这个指标整合了学者研究的数量和质量。

在C++中,H指数的计算涉及对研究者论文的引用次数进行排序,并利用C++库中已有的算法,确定最大的'h'值,使得拥有不低于'h'引用分数的论文数量恰好等于'h'。最简单的方法是将列表降序排序,然后检查每个列表项是否大于或等于当前项的排名。

方法 1:基本方法

程序

输出

 
The H-Index is: 3   

说明

  • 输入与准备
    程序以一个研究者论文的引用次数列表开始。这个列表存储在一个vector中,vector是C++中用于动态数组的高效容器。
  • 排序引用次数
    为了找到H指数,第一步是将引用次数按降序排序。这是通过使用std::sort函数并配合一个将元素按降序排序的比较器(std::greater<int>())来实现的。排序有助于轻松确定满足H指数引用标准的论文数量。
  • 遍历排序后的引用次数
    排序后,程序遍历排序后的列表。通过比较每个引用次数与其在排序列表中的排名,来跟踪可能的最高H指数。
    将排名 i+1(因为索引从0开始)与该位置的引用次数进行比较。如果一篇论文的引用次数至少等于其排名,它就有可能对H指数做出贡献。
  • 确定H指数
    对于每篇论文,如果引用次数大于或等于其排名,H指数就会被更新。这个过程会一直持续,直到找到一篇论文,其引用次数小于其排名,这表明H指数的标准在此之后不再满足。
  • 输出
    最后,在遍历过程中找到的最高有效H指数将作为结果打印出来。这个值代表了最大的'h'值,使得研究者至少有'h'篇论文,每篇论文的引用次数不少于'h'次。

复杂度分析

时间复杂度

排序步骤

主导时间复杂度的主要操作是排序引用数组。

std::sort函数通常的时间复杂度为O(nlogn),其中n是引用vector中的元素数量。

遍历排序数组

排序后,遍历排序数组以确定H指数的时间复杂度为O(n),其中n同样是引用vector中的元素数量。

在此遍历过程中,我们执行常数时间的操作进行比较和更新。

因此,H指数计算算法的整体时间复杂度由排序步骤主导,为O(nlogn)。

空间复杂度

附加空间

该算法主要使用额外的空间来存储引用vector。空间复杂度为O(n),其中n是提供引用的元素(或论文)的数量。

排序通常也需要额外的空间,但与实际场景中的输入大小相比,这通常可以忽略不计。

原地排序

如果使用原地排序算法(如std::sort,它通常使用内省排序或快速排序实现,并且是原地排序),则额外空间复杂度为O(1),但我们仍然考虑引用vector本身,其复杂度为O(n)。

方法二:使用计数排序和分桶

此方法利用计数排序和分桶,在不显式排序引用次数的情况下有效地确定H指数。

程序

输出

 
The H-Index is: 3   

说明

  • 初始化分桶
    创建一个名为bucket的数组,其中索引代表引用次数,数组中每个索引的值代表具有该引用次数的论文数量。bucket数组的大小为n+1,其中n是论文的总数(或提供的引用次数)。
  • 填充分桶
    遍历引用次数vector。对于遇到的每个引用次数,增加bucket数组中相应索引的值。如果引用次数超过n,则增加bucket数组的最后一个索引以处理这种情况。
  • 计算H指数
    填充完bucket数组后,从最高的可能引用次数(即n)开始。在从最高计数递减到0的过程中,维护一个论文总数的运行总和(total papers)。
    通过查找累积论文总数(total papers)大于或等于h的最高索引h来确定H指数。这个条件确保了至少有h篇论文的引用次数大于或等于h。
  • 返回结果
    一旦满足条件totalPapers >= h,h就成为H指数。

复杂度分析

时间复杂度

填充分桶

遍历引用次数vector以填充bucket数组需要O(n)时间,其中n是论文数量(或提供的引用次数)。这是因为每个引用次数都只处理一次以增加相应的bucket索引。

计算H指数

填充完bucket数组后,计算H指数需要从最高引用次数遍历到0,这同样需要O(n)时间,因为需要遍历大小为n+1的bucket数组。

总时间复杂度

因此,使用分桶的计数排序方法的整体时间复杂度为O(n),其中n是论文数量。这种线性时间复杂度对于在不排序整个引用次数数组的情况下计算H指数来说是高效且最优的。

空间复杂度

分桶数组

使用分桶的计数排序方法所使用的额外空间是分桶数组,其大小为n+1(因为它包含从0到n的索引)。因此,空间复杂度为O(n)。

输入空间

除了分桶数组之外,空间复杂度还包括输入引用次数的vector,它也占用O(n)的空间。

方法三:使用排序数组上的二分搜索

与其对引用次数进行排序然后使用线性搜索查找H指数,不如使用此方法在引用次数vector上进行二分搜索来高效地查找H指数。

程序

输出

 
The H-Index is: 3   

说明

  • 排序
    首先,将引用次数按降序排序。排序确保我们可以有效地确定最大的h值,使得至少有h篇论文的引用次数大于或等于h。
  • 二分搜索
    初始化二分搜索参数:left从0开始,right从n-1开始,其中n是论文数量。
  • 执行二分搜索
    计算中间索引mid。
    检查mid位置的引用次数是否大于或等于mid + 1。
    如果为真,则意味着至少有mid + 1篇论文的引用次数大于或等于mid + 1。将搜索区域移至右半部分(left = mid + 1)。
    如果为假,则将搜索区域移至左半部分(right = mid - 1)。
    继续进行,直到left大于right。此时,left将是H指数。
  • 返回结果
    二分搜索完成后left的值就是H指数。

复杂度分析

时间复杂度

排序步骤

使用快速排序或归并排序等高效排序算法对引用次数进行初始排序需要O(nlogn)时间,其中n是论文数量(或提供的引用次数)。

二分搜索步骤

在排序后的引用次数数组上进行二分搜索需要O(logn)时间。这是因为在二分搜索的每次迭代中,搜索空间都会减半,直到确定H指数。

总时间复杂度

因此,此方法的整体时间复杂度由排序步骤主导,即O(nlogn)。一旦排序完成,使用二分搜索查找H指数会增加O(logn)的复杂度,但会被排序所掩盖。

空间复杂度

排序开销

排序通常需要O(n)的额外空间,特别是如果使用归并排序或堆排序等算法,它们可能需要与输入大小相等的辅助数组。

二分搜索

二分搜索本身在原地进行,除了输入数组外,额外空间复杂度为O(1)。

方法四:使用最大堆

用于计算H指数的最大堆方法利用优先队列来管理和访问引用次数。此方法在处理大型数据集时特别有用,在这种数据集中,需要动态且高效地跟踪最大引用次数。

程序

输出

 
The H-Index is: 3   

说明

calculateHIndex 函数

  • 优先队列初始化
    priority_queue<int> maxHeap(citations.begin(), citations.end());: 这一行使用接受迭代器范围(citations.begin() 到 citations.end())的构造函数初始化一个最大堆(C++中的priority_queue)。这将用 citations vector 中的所有引用次数来初始化堆。
  • 初始化
    int hIndex = 0;: 初始化hIndex为0。此变量将存储计算出的H指数。
    int count = 0;: 初始化count为0。此计数器将跟踪考虑过的论文数量(从0开始)。
  • 从最大堆中提取
    While (!maxHeap.empty()) {: 开始一个循环,只要最大堆中还有元素就继续执行。
    int citation = maxHeap.top();: 使用top()检索最大堆中的最高引用次数,top()会访问但不移除元素。
    maxHeap.pop();: 从最大堆中移除最高元素(堆顶元素)。
  • 递增计数
    count++;: 递增count变量,表示正在考虑另一篇论文(具有当前的引用次数)。
  • 确定H指数
    if (citation >= count) { hIndex = count; }: 检查当前的引用次数是否大于或等于count。如果为真,则将hIndex更新为count。此条件确保hIndex表示最大的h值,使得至少有h篇论文的引用次数大于或等于h。
    else { break; }: 如果条件不满足(引用次数小于count),则退出循环,因为我们已经找到了最大的hIndex。
  • 返回结果
    return hIndex;: 循环结束后,hIndex包含根据从最大堆处理的引用次数计算出的H指数。
    main 函数
  • 向量初始化
    vector<int> citations = {6, 5, 3, 1, 0};: 初始化一个名为citations的vector,其中包含示例引用次数。
  • 计算并输出H指数
    cout << "The H-Index is: " << calculateHIndex(citations) << endl;: 调用calculateHIndex函数,并将citations vector作为参数传递,以计算H指数并打印结果。

复杂度分析

时间复杂度

堆初始化

从引用次数vector构建最大堆需要O(n)时间,其中n是论文数量(或提供的引用次数)。这是因为每次插入堆需要O(logn)时间,而总共有n次插入。

提取元素

从最大堆中进行每次提取操作(top()和pop())也需要O(logn)时间。循环一直持续到堆为空,导致提取过程的时间复杂度为O(nlogn)。

总时间复杂度

因此,此方法的整体时间复杂度为O(nlogn)。此复杂度源于构建最大堆和提取元素以确定H指数。

空间复杂度

堆存储

存储最大堆所需的空间为O(n),其中n是论文数量。此空间复杂度源于优先队列(最大堆)需要存储所有n个引用次数。

其他空间使用

除了堆之外,对于index、count以及函数中使用的临时变量等变量,额外空间复杂度为O(1)。

方法五:使用计数数组实现

H指数的计数数组实现是一种战略性且有效的方法,用于基于引用分数来估计研究者的影响力。计数数组实现方法不逐一计数,而是独立测量引用频率。

程序

输出

 
The H-Index is: 3   

说明

calculateHIndex 函数

  • 初始化
    int n = citations.size();: 确定citations vector中提供的论文数量(或引用次数)。
  • 计数数组初始化
    vector<int> count(n + 1, 0);: 初始化一个大小为n+1的计数数组count,所有元素初始化为0。此数组将存储引用次数的频率。
  • 填充计数数组
    for (int citation : citations) { ... }: 遍历citations vector中的每个引用次数。
    如果citation大于或等于n(count中的最大可能索引),则增加count[n]。这处理了引用次数超过n的情况。
    否则,增加count[citation]。这记录了具有每个特定引用次数的论文数量。
  • 计算累积论文数
    int totalPapers = 0;: 初始化变量totalPapers为0。此变量在遍历count数组时跟踪考虑过的论文总数。
  • 查找H指数
    for (int i = n; i >= 0; --i) { ... }: 从最高可能的引用次数开始向下迭代到0。
    在每次迭代中将count[i]加到totalPapers。此步骤累积了具有i次或更多引用次数的论文总数。
    检查totalPapers是否大于或等于i。如果为真,则i是H指数,因为至少有i篇论文的引用次数大于或等于i。
  • 返回结果
    return i;: 一旦满足条件totalPapers >= i,则返回i作为H指数。
    main 函数
  • 向量初始化
    vector<int> citations = {6, 5, 3, 1, 0};: 初始化一个名为citations的vector,其中包含示例引用次数。
  • 计算并输出H指数
    cout << "The H-Index is: " << calculateHIndex(citations) << endl;: 调用calculateHIndex函数,并将citations vector作为参数传递,以计算H指数,并打印结果。

复杂度分析

时间复杂度

计数数组初始化

创建大小为n+1的计数数组count需要O(n)时间,其中n是论文数量(或提供的引用次数)。

填充计数数组

遍历citations vector以用引用频率填充count数组需要O(n)时间,其中n是论文数量。每个引用次数都在常数时间内处理。

计算H指数

主循环遍历count数组,该数组的大小为n+1。此操作也需要O(n)时间,因为它会处理每个索引一次。

总时间复杂度

因此,此方法的整体时间复杂度为O(n)。这种线性时间复杂度源于初始化计数数组、用引用频率填充计数数组以及通过遍历数组计算H指数。

空间复杂度

计数数组

大小为n+1的计数数组count需要O(n)空间,其中n是论文数量。此数组存储引用次数的频率。

其他空间使用

额外的空间使用量最少,可以认为是O(1),因为它只包含calculateHIndex函数中用于迭代和计算的几个变量。