You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于循环双向链表的自组织数字序列算法优化求助

优化循环双向链表的自组织序列操作性能

看起来你的核心问题在于每次操作后的节点移动是线性时间复杂度——哪怕你优化了移动方向,每次移动k步都需要循环k次,当处理大规模输入(比如34MB的测试用例)时,大量这样的操作会导致总时间复杂度飙升到O(N*K),直接超时。下面我们一步步分析瓶颈,给出针对性的优化方案:

一、核心瓶颈分析

你的代码中,每次执行插入/删除操作后,都需要通过for循环一步步移动指针:

for(int i = 0; i < moveNext; i++){
 list = list->next;
}

假设每次平均移动n/2步,操作次数是M,总时间复杂度就是O(M*n),当M和n都很大时(比如1e5级别),这个开销完全无法承受。你的方向优化只是减少了一半的循环次数,但本质还是线性遍历,无法解决根本问题。

二、针对性优化方案

1. 替换数据结构:使用有序统计树(Order Statistic Tree)

要实现O(logn)时间的插入、删除和第k个元素访问,最适合的是有序统计树——每个节点维护子树的大小,这样可以快速计算出第k个元素的位置,不需要一步步遍历。

在C++中,你可以利用GNU扩展的policy-based data structures,它提供了支持顺序统计的树结构:

#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

// 定义有序统计树,支持重复元素的存储
template<typename T>
using ordered_set = tree<T, null_type, less_equal<T>, rb_tree_tag, tree_order_statistics_node_update>;

使用时,你可以:

  • find_by_order(k):获取第k个元素的迭代器(O(logn))
  • order_of_key(x):获取小于x的元素个数(O(logn))
  • 插入/删除元素都是O(logn)时间

这样,你不需要维护链表指针,直接通过统计树来完成所有操作,移动k步的操作就变成了找到当前位置+k后的元素,时间复杂度从O(k)降到O(logn)。

2. 块状链表优化(如果坚持用链表)

如果不想换数据结构,可以把链表分成若干块,每块维护当前块的大小。移动k步时,先跳过整个块(块大小已知),再在块内移动,这样时间复杂度降到O(sqrt(n)),比线性遍历快很多:

  • 每个块存储一个节点列表,以及块的大小
  • 移动时,先计算需要跳过多少个完整块,再在目标块内移动到具体节点
  • 插入/删除时,如果块大小超过阈值(比如sqrt(n)),就拆分块;如果太小,就合并相邻块

3. 代码细节优化(立即可用的小改进)

除了核心数据结构,这些细节也能提升性能:

  • 不要混用new和free:C++中new分配的内存必须用delete释放,free是C的函数,混用会导致未定义行为,甚至内存泄漏。把free(tmp)改成delete tmp;。
  • 批量输出减少IO开销:你的输出是逐个fprintf,IO操作是非常慢的。可以把结果先写入一个string或字符数组,最后一次性输出:
    string output;
    for(int i = 0; i < numbersInSeq; i++){
        output += to_string(list->value);
        if(i != numbersInSeq-1) output += ' ';
        list = list->next;
    }
    fprintf(stdout, "%s\n", output.c_str());
    
  • 修复输入处理的feof问题:feof(stdin)的判断逻辑有问题,它会在读取到EOF后才返回true,可能导致多读一次。可以修改输入循环为:
    int number;
    while((number = getNumber()) != -1){ // 需要修改getNumber(),当遇到EOF时返回-1
        insert(&list, number);
        numbersInSeq++;
    }
    
    对应的getNumber()调整:
    int getNumber(){
        int c = getchar_unlocked();
        // 检查是否到达EOF
        if(c == EOF) return -1;
        int value = 0;
        for(; (c < 48 || c > 57); c = getchar_unlocked()){
            if(c == EOF) return -1;
        }
        for(; c > 47 && c < 58 ; c = getchar_unlocked()){
            value = 10*value+c-'0';
            allCharCounter++;
            if(c == EOF) break;
        }
        return value;
    }
    

三、优先级建议

  1. 优先尝试有序统计树,它能从根本上解决线性移动的问题,代码改动也相对可控。
  2. 如果不能使用GNU扩展,再考虑块状链表,但实现起来稍复杂。
  3. 同时应用所有细节优化,尤其是IO和内存管理的问题,这些小改进也能带来明显的性能提升。

内容的提问来源于stack exchange,提问作者dominosam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:45:04