【算法】常用数学计算算法总结(C++代码)

 

1、求最大公因数和最小公倍数

  利用辗转相除法,我们可以很方便地求得两个数的最大公因数(greatest common divisorgcd);
  将两个数相乘再除以最大公因数即可得到最小公倍数(least common multiple, lcm)。

int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a% b);
}
int lcm(int a, int b) {
return a * b / gcd(a, b);
}

2、判定质数

复制代码
bool is_prime2(unsigned long long n) { //middle
    long long stop = sqrt(n) + 1;
    if (n == 2) {
        return 1;
    }
    if (n % 2 == 0) {
        return 0;
    }
    for (int i = 3; i <= stop; i += 2) {
        if (n % i == 0) {
            return 0;
        }
    }
    return 1;
}
复制代码

  求所有<n的质数

204. 计数质数 - 力扣(LeetCode) (leetcode-cn.com)

复制代码
int countPrimes(int n) {
        if(n<=2) return 0; 
        vector<unsigned char> N(n,1);
        int ans=0;
        for(int i=2;i<n;++i)
        { 
            if(N[i]==1) 
            {
                ans++; 
                for(int j=2*i; j<n; j+=i) N[j]=0; 
            }
        } 
        return ans;
    }
复制代码

 3.进制转换

  处理符号-每次取余放到前面-原数除以基数

  10转7

复制代码
string convertToBase7(int num) {
    if (num == 0) return "0";
    bool is_negative = num < 0;
    if (is_negative) num = -num;
    string ans;
    while (num) {
        ans = to_string(num % 7) + ans;
num = num/7; } return is_negative ? "-" + ans : ans; }
复制代码

递归写法

string convertToBase7(int num) {
    if (num < 0) return "-" + convertToBase7(-1 * num);
    if (num < 7) return to_string(num);
    return convertToBase7(num / 7) + to_string(num % 7);
}

 4.打乱与抽奖

4.1打乱

  打乱一个数组的顺序,如果采用真.随机洗牌,对于n张牌组成的有序排列,经过了n次随机选择,漏掉1只牌从未选过的概率不等于0,而且,随着牌的张数数量增加,这个概率非常可观。

如果想要“彻底地”洗到每一张牌,可以采用经典的 Fisher-Yates洗牌算法原理是通过随机交换位置来实现随机打乱,有正向和反向的写法。(就默认反向吧)

例如随机1,2,3,4,5,6,7,8这个数组,反向遍历顺序如下:

 

 

 

 

 

 

 

 

 

 

 

  它利用了抽卡本身的顺序,【保证照顾】到了每一张原本序列中的卡,而简单粗暴随机抽取存在出现重复位置的可能性,就等于浪费了一次排序的机会,换句话说,其等效抽卡次数因为出现了过去相同的洗法,有效洗牌次数下降,样本空间缩小,无法充满整个n!空间,所以有效性会下降。而Fisher–Yates算法在原理上保证了不会出现浪费次数,重复选择的情况,导致样本空间一直保持n!,没有坍缩,这就是其在数学意义上优秀的原因。

洗牌算法 详解 - 打乱数组 - 力扣(LeetCode) (leetcode-cn.com)

  C++代码如下

复制代码
vector<int> shuffle() {
    if (origin.empty()) return {};
    vector<int> shuffled(origin);
    int n = origin.size();
    // 可以使用反向或者正向洗牌, 效果相同。
    // 反向洗牌:
    for (int i = n - 1; i >= 0; --i) {
        swap(shuffled[i], shuffled[rand() % (i + 1)]);
    }
    // 正向洗牌:
    // for (int i = 0; i < n; ++i) {
    // int pos = rand() % (n - i);
    // swap(shuffled[i], shuffled[i+pos]);
    // }
    return shuffled;
}
复制代码

4.2.1权重抽奖

C++ move()用法可参考C++ move()函数_chengjian168的博客-CSDN博客_c++ move()

给定一个数组,数组每个位置的值表示该位置的权重,要求按照权重的概率去随机采样。

528. 按权重随机选择 - 力扣(LeetCode) (leetcode-cn.com)

我们可以先使用 partial_sum 求前缀和(partial_sum 对于序列 a,b,c,d 产生序列 a,a+b,a+b+c,a+b+c+d。),这个结果
对于正整数数组是单调递增的。每当需要采样时,我们可以先随机产生一个数字,然后使用二分
法查找其在前缀和中的位置,以模拟加权采样的过程。这里的二分法可以用
lower_bound 实现。
以样例为例,权重数组
[1,3]的前缀和为[1,4]。如果我们随机生成的数字为1,那么 lower_bound
返回的位置为 0;如果我们随机生成的数字是 234,那么 lower_bound 返回的位置为 1

复制代码
class Solution {
    vector<int> sums;
   vector<int> SumsWeight;
public: Solution(vector<int> weights) : sums(std::move(weights)) { partial_sum(sums.begin(),sums.end(),back_inserter(SumsWeight));
}
int pickIndex() { int pos = (rand() % sums.back()) + 1; return lower_bound(sums.begin(), sums.end(), pos) - sums.begin(); } };
复制代码

应用实例:

  • Spring Cloud Ribbon (客户端负载均衡)策略中的 WeightedResponseTimeRule
    • 此题可简述为「按权重,看作多个区间,按区间宽度越大,概率越大」
    • 在 Ribbon 相关架构中,服务端给客户端一个服务列表,类似 Map<String, Set<String>> 结构。若客户端想调用 key = serviceA,可选的具体服务端实例有 Set<String> 的 ["/svc/a1", "/svc/a2", "/svc/a3"],由客户端自行决定
    • Ribbon 作为客户端负载均衡来帮助客户端选择去哪个具体服务实例(a1 / a2 / a3),希望雨露均沾,又希望别运气不好抽到响应慢的服务器,故有了一种根据权重的均衡策略
    • 权重是通过定时统计最近一段时间内,a1 / a2 / a3 各自的访问响应时间如何,如 a1: 10msa2: 20msa3: 40ms
    • 通过算法(不赘述,有兴趣可留言喔)计算得 a1: [0, 60]a2: (60, 110]a3: (110, 140] 的区间对应
    • 下次再需要访问 serviceA 时,随机一个数 [0, 140],看落在哪个区间,就选那个实例
  • RabbitMQ 的 Topic 交换器使用 Trie 匹配
  • MySQL 中的 IN 语法涉及二分算法

4.2.2流动抽奖

给定一个单向链表,要求设计一个算法,可以随机取得其中的一个数字。

不同于数组,在未遍历完链表前,我们无法知道链表的总长度。这里我们就可以使用水库采样:

遍历一次链表,在遍历到第 m 个节点时,有1/m的概率选择这个节点覆盖掉之前的节点选择。

复制代码
class Solution {
    ListNode* head;
public:
    Solution(ListNode* n) : head(n) {}
    int getRandom() {
        int ans = head->val;
        ListNode* node = head->next;
        int i = 2;
        while (node) {
            if ((rand() % i) == 0) {
                ans = node->val;
            }
            ++i;
            node = node->next;
        }
        return ans;
    }
};
复制代码

 

posted @   nntzhc  阅读(1916)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示