算法设计与分析第二章期末总结

一. 欧几里得算法(Euclidean Algorithm)

算法描述

欧几里得算法,又称辗转相除法,用于计算两个整数的最大公约数(GCD)。算法基于这样一个事实:两个整数的最大公约数与其中较小的数和两数的差的最大公约数相同。通过递归或迭代地应用这一性质,我们可以找到两个整数的最大公约数。

算法步骤

  1. 确定两个非负整数ab,其中a > b
  2. 使用a除以b得到余数r
  3. b赋值给a,将余数r赋值给b
  4. 重复步骤2和3,直到b为0。此时a即为所求的最大公约数。

代码模板

cpp 复制代码
//模板一:
int gcd(int a, int b)
{
    while(b)
    {
        int r = a % b;
        a = b;
        b = r;
    }
    
    return a;
}

//模板二:
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

拓展题目链接

⌈ AcWing 872. 最大公约数 ⌋


二. Fibonacci 数列

递归解法(不推荐使用)

cpp 复制代码
int fibonacci(int n)
{
	//数列由 0 和 1 开始
    if(n <= 2) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

递推解法

cpp 复制代码
const int N = 100000000;

int f3(int n)
{
    a[1] = a[2] = 1;
    for (int i = 3; i <= n; i ++ )
    {
        a[i] = a[i - 1] + a[i - 2];
    }
    
    return a[n];
}

递归+滚动变量

cpp 复制代码
int fibonacci(int n)
{
    int a = 1, b = 1;
    int sum;
    for(int i = 3; i <= n; i++)
    {
        sum = a+b;
        a = b;
        b = sum;
    }
    
    return b;
}

拓展题目链接

⌈821. 跳台阶Ⅰ - AcWing题库⌋

⌈5292. 跳台阶Ⅱ - AcWing题库⌋


三. 归并排序

归并排序(Merge Sort)是一种典型的分治(Divide and Conquer)算法,它将一个大问题分解成两个或更多个相同或相似的小问题,递归地解决这些小问题,然后将解决方案组合起来,以解决原始问题。

算法描述

归并排序的主要思想是将待排序的序列分割成若干个子序列,每个子序列是一个有序的序列。然后再把有序子序列合并为整体有序序列。

算法步骤

  1. 分解:将当前需要排序的数组按照中间索引分成两个子数组,直到子数组的大小为1。

  2. 递归进行排序:递归地对子数组进行排序。由于数组被分解成单个元素,所以它们被认为是有序的。递归进行,直到合并成1个完整的数组。

  3. 合并:将两个有序子数组合并成一个有序数组。这是归并排序算法的核心步骤。

合并步骤详解

  1. 有数组q, 左端点l, 右端点r

  2. 确定划分边界 mid

  3. 递归处理子问题 q[l..mid]q[mid+1..r]

  4. 合并子问题

    1. 主体合并:至少有一个小数组添加到tmp数组中;
    2. 收尾:可能存在的剩下的一个小数组的尾部直接添加到tmp数组中;
    3. 复制回来:tmp数组覆盖原数组;

代码模板

cpp 复制代码
void merge_sort(int q[], int l, int r)
{
	//递归的终止情况
    if(l >= r) return ;
    
    //第一步:分成子问题
    int mid = r + l >> 1;

	//第二步:递归处理子问题
    merge_sort(q, l, mid);
    merge_sort(q, mid+1, r);
    
    //第三步:合并子问题
    int k = 0, i = l, j = mid+1;
    while(i <= mid && j <= r)
        if(q[i] <= q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
        
    for(k = 0, i = l; i <= r; k++, i++) q[i] = tmp[k];
}

题目链接

⌈787. 归并排序 - AcWing题库⌋


四. 快速排序算法

算法描述

快速排序是一种采用分治策略的排序算法。它选择一个基准元素(pivot),通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比基准元素小,另一部分的所有数据都比基准元素大,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。

算法步骤

  1. 选择基准 :从待排序序列中选择一个元素作为基准(x)。基准的选择有多种方式,比如选择序列的第一个元素、最后一个元素、中间元素或随机选择。

  2. 分割操作

    • 初始化两个指针,一个指向序列的开始(l),一个指向序列的末尾(r)。
    • l < r时进行循环:
      • 如果l指向的元素小于或等于基准,则l自增。
      • 如果r指向的元素大于基准,则r自减。
      • 如果l指向的元素大于基准且r指向的元素小于基准,则交换这两个元素,并继续移动lr指针。
    • 以基准元素为界,将序列分割为两个子序列:基准左侧所有元素均比基准小,右侧的所有元素都比基准大。
  3. 递归排序

    • 对基准左侧的子序列进行快速排序。
    • 对基准右侧的子序列行快速排序。
  4. 合并结果:由于每次分割操作后基准元素都处于其最终排序后的正确位置,因此无需合并操作,递归调用结束后,整个序列就已经有序了。

代码模板

cpp 复制代码
void quick_sort(int q[], int l, int r)
{
    //递归的终止情况
    if(l >= r) return ;
    
    //第一步:分成子问题
    int i = l - 1, j = r + 1;
    int x = q[l + r >> 1];
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }
    
    //第二步:递归处理子问题
    quick_sort(q, l, j), quick_sort(q, j+1, r);
    
    //第三步:子问题合并.快排这一步不需要操作,但归并排序的核心在这一步骤
}

题目链接

785. 快速排序 - AcWing题库


五. 整数二分查找算法

二分查找算法(Binary Search)是一种在有序数组中查找某一特定元素的搜索算法。

算法描述

二分查找算法通过不断将待查找的区间减半,从而快速定位到目标元素的位置。算法的基本思想是将数组的中间元素与目标值进行比较,如果中间元素正好是要查找的元素,则搜索过程结束;如果目标值大于或小于中间元素,则在数组大于或小于中间元素的那一半中继续查找,直到找到目标值或搜索区间为空。

算法步骤

  1. 初始化 :设定数组的起始索引 left 为 0,终止索引 right 为数组长度减 1(即 len(arr) - 1)。

  2. 循环条件 :当 left 小于等于 right 时,执行循环体内的步骤。

  3. 计算中间索引 :计算当前搜索区间的中间索引 mid,可以通过 (left + right) / 2 来得到。

  4. 比较目标值与中间元素

    • 如果 arr[mid] 等于目标值 target,则找到了目标值,返回 mid
    • 如果 arr[mid] 小于目标值 target,说明目标值在 mid 的右侧,因此更新 leftmid + 1,继续在右半部分搜索。
    • 如果 arr[mid] 大于目标值 target,说明目标值在 mid 的左侧,因此更新 rightmid - 1,继续在左半部分搜索。
  5. 循环结束 :如果循环结束都没有找到目标值,则返回 -1 或其他表示未找到的值。

代码模板

cpp 复制代码
int bsearch(int l, int r)
{
    l = l-1, r = r+1;

    while(l + 1 != r)
    {
    	int mid = l + r >> 1;
    	if(IsBlue(mid)) l = mid;
    	else r = m;
    }

    return l or r;
}

函数IsBlue(mid)的确定:

注意事项

  • 二分查找要求数组必须是有序的
  • 二分查找的时间复杂度为O(log n),其中n是数组的长度。

题目链接

789. 数的范围 - AcWing题库


六. 快速幂

快速幂算法(Binary Exponentiation)是一种用于计算幂的高效算法。它的核心思想是将指数表示为二进制形式,并利用二进制数的每一位来快速计算幂。

算法描述

快速幂算法通过不断将指数减半和取底数的平方来减少计算幂所需的乘法次数。具体来说,它将指数转换为二进制形式,然后对于二进制形式中的每一位,如果它是1,则将当前累积的结果乘以底数;如果它是0,则不做任何操作。同时,每次迭代都将底数平方,并将指数右移一位。

算法步骤

  1. 初始化 :设置res为1(因为任何数的0次幂都是1),base为给定的底数,exponent为给定的指数。

  2. 当指数不为0时

    • 如果exponent的二进制表示的最低位是1(即exponent是奇数),则将result乘以base
    • base平方(即base = base * base)。
    • exponent右移一位(即exponent = exponent >> 1),这相当于将exponent除以2。
  3. 返回结果 :返回result作为最终的计算结果。

代码模板

cpp 复制代码
//求 m^k,时间复杂度 O(logn)

int qmi(int m, int k)
{
    int res = 1, t = m;
    while (k)
    {
        if (k&1) res = res * t;
        t = t * t;
        k >>= 1;
    }
    return res;
}

带取模的代码模板(用于大数计算)

在实际应用中,当处理大数时,为了防止溢出,通常会在每次乘法后进行取模操作。这可以通过在每一步的乘法后添加一个% mod操作来实现。

cpp 复制代码
//求 m^k mod p,时间复杂度 O(logk)

int qmi(int m, int k, int p)
{
    int res = 1 % p, t = m;
    while (k)
    {
        if (k&1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    return res;
}
typedef long long LL;

LL pmi(int a, int k, int p)
{
    LL res = 1 % p, t = a;
    while(k)
    {
        if(k&1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    
    return res;
}

这样,即使底数和指数都非常大,也可以通过快速幂算法在合理的时间内计算出结果的模值。

题目链接 & 视频链接

⌈875. 快速幂 - AcWing题库⌋

⌈快速幂都能做什么?小小的算法也有大大的梦想_哔哩哔哩_bilibili⌋

相关推荐
VertexGeek25 分钟前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
石小石Orz26 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
jiao_mrswang1 小时前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca1 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱1 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子2 小时前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
是阿建吖!2 小时前
【优选算法】二分查找
c++·算法
王燕龙(大卫)2 小时前
leetcode 数组中第k个最大元素
算法·leetcode
不去幼儿园3 小时前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh3 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法