算法竞赛模板———上(常规优化,排序,搜索,动态规划)

该博客提供了C++算法竞赛模板,包含主函数与重命名模板、读写与输出优化。介绍多种排序、搜索算法,如选择排序、深度优先搜索等。还涉及字符串处理算法,像KMP算法、字典树等,以及各类动态规划问题,如背包DP、区间DP等,助力算法竞赛编程。

算法竞赛模板

一、主函数与重命名模板

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <unordered_map>
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <list>
#include <bitset>
#include <cmath>

#define fi first
#define se second
#define endl '\n'

using namespace std;

typedef long long ll;

typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<string, string> pss;
typedef pair<string, int> psi;

typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pss> vpss;
typedef vector<vi> vvi;

typedef queue <int> qi;
typedef queue <ll> ql;
typedef queue <pii> qpii;
typedef queue <psi> qpsi;

typedef map<int, int> mii;
typedef map<string, int> msi;

typedef priority_queue<int> pqi;
typedef priority_queue<string> pqs;
typedef priority_queue<pii> pqpii;
typedef priority_queue<psi> pqpsi;

typedef unordered_map<int, int> umapii;
typedef unordered_map<string, int> umapsi;

const int N = 1e5 + 5;

void solve()
{
   
   

}

int main()
{
   
   
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int t = 1;
	//cin >> t;
	while (t--)
	{
   
   
		solve();
	}
	return 0;
}

二、读写与输出的优化

在C++编程中,对输入输出进行优化可以提高程序的执行效率。以下是一些常见的优化技巧:

1. 优化 cincout

关闭 C++ 的 cincout 与 C 的 stdio 的同步,以提高输入输出效率。

通过 cin.tie(nullptr)cout.tie(nullptr) 解除了 cincout 的绑定。

绑定主要是指,每次调用 cin 之后,会自动调用 cout 进行刷新。解除绑定后,它们的刷新可以手动控制,避免不必要的刷新操作,提高性能。

#include <iostream>

int main()
{
   
   
    // 关闭输入输出同步,提高输入输出效率
    std::ios::sync_with_stdio(false);

    // 解除 cin 与 cout 的绑定,防止混用时的性能损失
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    // 此处是程序的主体部分

    return 0;
}

2. 快速读入

通过 getchar() 逐个读取字符,并根据 ASCII 码将字符转换成对应的数字。

支持读取负数,通过记录符号位 y 来实现。

#include <cstdio>

inline int read()
{
   
   
    register int x = 0, y = 1;
    register char ch = getchar();
    while (ch > '9' || ch < '0')
    {
   
   
        if (ch == '-')
            y = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
   
   
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * y;
}

3. 快速输出

通过递归将整数按位输出,避免了多次调用 putchar 的开销,从而提高了输出效率。

#include <cstdio>

inline void write(int x) 
{
   
   
    if (x < 0) 
    {
   
   
        putchar('-');
        x = -x;
    }
    if (x >= 10) 
    {
   
   
        write(x / 10);
    }
    putchar(x % 10 + '0');
}

三、排序

1. 选择排序

时间复杂度: O(n2)O(n^2)O(n2),其中 nnn 为数组长度。

空间复杂度: O(1)O(1)O(1)

功能: 选择排序是一种简单直观的排序算法,它的工作原理是每次从待排序的数据元素中选择最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)的元素,依次类推。

#include <iostream>
#include <vector>

using namespace std;

void selectionSort(vector<int>& arr)
{
   
   
    int n = arr.size();

    for (int i = 0; i < n - 1; i++) 
    {
   
   
        // 找到最小元素的索引
        int minIndex = i;
        for (int j = i + 1; j < n; j++) 
        {
   
   
            if (arr[j] < arr[minIndex]) 
            {
   
   
                minIndex = j;
            }
        }

        // 交换最小元素和当前元素
        swap(arr[i], arr[minIndex]);
    }
}

2. 冒泡排序

时间复杂度: O(n2)O(n^2)O(n2),其中 nnn 为数组长度。

空间复杂度: O(1)O(1)O(1)

功能: 冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,一次比较两个元素,如果它们的顺序错误就把它们交换过来。

#include <iostream>
#include <vector>

using namespace std;

void bubbleSort(vector<int>& arr) 
{
   
   
    int n = arr.size();

    for (int i = 0; i < n - 1; i++) 
    {
   
   
        for (int j = 0; j < n - i - 1; j++)
        {
   
   
            // 如果相邻元素逆序,则交换它们
            if (arr[j] > arr[j + 1])
            {
   
   
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

3. 插入排序

时间复杂度: O(n2)O(n^2)O(n2),其中 nnn 为数组长度。

空间复杂度: O(1)O(1)O(1)

功能: 插入排序是一种简单直观的排序算法,其工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

#include <iostream>
#include <vector>

using namespace std;

void insertionSort(vector<int>& arr)
{
   
   
    int n = arr.size();

    for (int i = 1; i < n; i++) 
    {
   
   
        // 从当前元素往前比较,找到合适的插入位置
        int key = arr[i];
        int j = i - 1;

        while (j >= 0 && arr[j] > key) 
        {
   
   
            arr[j + 1] = arr[j];
            j--;
        }
        
        // 插入当前元素到合适的位置
        arr[j + 1] = key;
    }
}

4. 快速排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(n2)O(n^2)O(n2),其中 nnn 为数组长度。

空间复杂度: 平均情况 O(log⁡n)O(\log n)O(logn)

功能: 快速排序是一种高效的排序算法,采用分治法来实现。它选择一个基准元素,将序列分割成两个子序列,然后对子序列进行递归排序。

#include <iostream>
#include <vector>

using namespace std;

// 分割函数
int partition(vector<int>& arr, int low, int high) 
{
   
   
    int pivot = arr[high];
    int i = low - 1;

    for (int j = low; j < high; j++) 
    {
   
   
        if (arr[j] < pivot) 
        {
   
   
            i++;
            swap(arr[i], arr[j]);
        }
    }

    swap(arr[i + 1], arr[high]);
    return i + 1;
}

// 快速排序递归函数
void quickSort(vector<int>& arr, int low, int high) 
{
   
   
    if (low < high) 
    {
   
   
        // 找到分割点
        int pivotIndex = partition(arr, low, high);
        // 分别对左右子序列进行排序
        quickSort(arr, low, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, high);
    }
}

5. 归并排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(nlog⁡n)O(n \log n)O(nlogn),其中 nnn 为数组长度。

空间复杂度: O(n)O(n)O(n)

功能: 归并排序是一种稳定的排序算法,采用分治法来实现。它将待排序的序列分成两个子序列,分别进行递归排序,然后将两个有序的子序列合并成一个有序序列。

#include <iostream>
#include <vector>

using namespace std;

// 归并函数
void merge(vector<int>& arr, int low, int mid, int high) 
{
   
   
    int n1 = mid - low + 1;
    int n2 = high - mid;

    // 创建临时数组
    vector<int> left(n1), right(n2);

    // 将数据复制到临时数组 left[] 和 right[] 中
    for (int i = 0; i < n1; i++) 
    {
   
   
        left[i] = arr[low + i];
    }
    for (int j = 0; j < n2; j++) 
    {
   
   
        right[j] = arr[mid + 1 + j];
    }

    // 归并临时数组到 arr[low..high]
    int i = 0, j = 0, k = low;
    while (i < n1 && j < n2) 
    {
   
   
        if (left[i] <= right[j]) 
        {
   
   
            arr[k] = left[i];
            i++;
        } 
        else 
        {
   
   
            arr[k] = right[j];
            j++;
        }
        k++;
    }

    // 复制 left[] 的剩余元素
    while (i < n1) 
    {
   
   
        arr[k] = left[i];
        i++;
        k++;
    }

    // 复制 right[] 的剩余元素
    while (j < n2) 
    {
   
   
        arr[k] = right[j];
        j++;
        k++;
    }
}

// 归并排序递归函数
void mergeSort(vector<int>& arr, int low, int high) 
{
   
   
    if (low < high) 
    {
   
   
        // 计算中间点
        int mid = low + (high - low) / 2;

        // 递归排序左右两部分
        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);

        // 合并排序好的两部分
        merge(arr, low, mid, high);
    }
}

6. 堆排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(nlog⁡n)O(n \log n)O(nlogn),其中 nnn 为数组长度。

空间复杂度: O(1)O(1)O(1)

功能: 堆排序是一种高效的排序算法,它利用了堆这一数据结构的特性。它将待排序的序列构建成一个最大堆(或最小堆),然后逐步取出堆顶元素,得到排序结果。

#include <iostream>
#include <vector>

using namespace std;

// 堆调整函数,保持最大堆性质
void heapify(vector<int>& arr, int n, int i) 
{
   
   
    int largest = i;    // 初始化最大值的索引
    int left = 2 * i + 1;  // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    // 如果左子节点大于根节点,更新最大值索引
    if (left < n && arr[left] > arr[largest]) 
    {
   
   
        largest = left;
    }

    // 如果右子节点大于根节点,更新最大值索引
    if (right < n && arr[right] > arr[largest])
    {
   
   
        largest = right;
    }

    // 如果最大值索引不等于根节点,交换它们,并递归调整堆
    if (largest != i) 
    {
   
   
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);
    }
}

// 堆排序函数
void heapSort(vector<int>& arr) 
{
   
   
    int n = arr.size();

    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
    {
   
   
        heapify(arr, n, i);
    }

    // 逐步取出堆顶元素,得到排序结果
    for (int i = n - 1; i > 0; i--) 
    {
   
   
        swap(arr[0], arr[i]);   // 将堆顶元素移动到末尾
        heapify(arr, i, 0);     // 重新调整堆
    }
}

7. 计数排序

时间复杂度: 平均情况 O(n+k)O(n + k)O(n+k),最坏情况 O(n+k)O(n + k)O(n+k),其中 nnn 为数组长度,kkk 为数据范围。

空间复杂度: O(n+k)O(n + k)O(n+k)

功能: 计数排序是一种非比较排序算法,它通过统计每个元素在序列中出现的次数,然后根据元素的计数位置来排序。

#include <iostream>
#include <vector>

using namespace std;

// 计数排序函数
void countingSort(vector<int>& arr) 
{
   
   
    int n = arr.size();

    // 找到数组中的最大值
    int maxVal = *max_element(arr.begin(), arr.end());

    // 创建计数数组,并初始化为0
    vector<int> count(maxVal + 1, 0);

    // 统计每个元素的出现次数
    for (int i = 0; i < n; i++) 
    {
   
   
        count[arr[i]]++;
    }

    // 根据计数数组重构原数组
    int index = 0;  // 初始化原数组索引

    // 遍历计数数组,重构原数组
    for (int i = 0; i <= maxVal; i++)
    {
   
   
        while (count[i] > 0) 
        {
   
   
            arr[index] = i;
            index++;
            count[i]--;
        }
    }
}

8. 基数排序

时间复杂度: 平均情况 O(d⋅(n+k))O(d \cdot (n + k))O(d(n+k)),最坏情况 O(d⋅(n+k))O(d \cdot (n + k))O(d(n+k)),其中 nnn 为数组长度,kkk 为数据范围,ddd 为最大数字的位数。

空间复杂度: O(n+k)O(n + k)O(n+k)

功能: 基数排序是一种非比较排序算法,它按照数字的每个位数进行排序。它首先按照最低有效位(个位)排序,然后按照次低有效位排序,直到按照最高有效位排序。

#include <iostream>
#include <vector>

using namespace std;

// 获取数字的指定位数的值
int getDigit(int num, int place) 
{
   
   
    int result = 1;
    for (int i = 0; i < place; i++) 
    {
   
   
        result *= 10;
    }
    return (num / result) % 10;
}

// 获取数字的位数
int getNumDigits(int num)
{
   
   
    int digits = 0;
    while (num > 0) 
    {
   
   
        num /= 10;
        digits++;
    }
    return digits;
}

// 基数排序函数
void radixSort(vector<int>& arr)
{
   
   
    int n = arr.size();

    // 找到数组中的最大值
    int maxVal = *max_element(arr.begin(), arr.end());

    // 计算最大值的位数
    int numDigits = getNumDigits(maxVal);

    // 根据每个位数进行计数排序
    for (int place = 0; place < numDigits; place++)
    {
   
   
        // 创建计数数组,并初始化为0
        vector<int> count(10, 0);

        // 统计每个元素的出现次数
        for (int i = 0; i < n; i++) 
        {
   
   
            count[getDigit(arr[i], place)]++;
        }

        // 根据计数数组重构原数组
        int index = 0;
        for (int i = 0; i < 10; i++) 
        {
   
   
            while (count[i] > 0) 
            {
   
   
                arr[index] = i;
                index++;
                count[i]--;
            }
        }
    }
}

9. 桶排序

时间复杂度: 平均情况 O(n+n2/k+k)O(n + n^2/k + k)O(n+n2/k+k),其中 nnn 为数组长度,kkk 为桶的数量。

空间复杂度: O(n+k)O(n + k)O(n+k)

功能: 桶排序是一种排序算法,它通过将待排序元素分到有限数量的桶中,每个桶再单独进行排序,最后将所有桶中的元素合并得到最终排序结果。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 桶排序函数
void bucketSort(vector<int>& arr, int numBuckets) 
{
   
   
    int n = arr.size();

    // 创建桶
    vector<vector<int>> buckets(numBuckets);

    // 将元素分配到桶中
    for (int i = 0; i < n; i++) 
    {
   
   
        int bucketIndex = arr[i] * numBuckets / (INT_MAX + 1LL);
        buckets[bucketIndex].push_back(arr[i]);
    }

    // 对每个桶进行排序,并合并结果
    int index = 0;
    for (int i = 0; i < numBuckets; i++) 
    {
   
   
        sort(buckets[i].begin(), buckets[i].end());
        for (int j = 0; j < buckets[i].size(); j++) 
        {
   
   
            arr[index++] = buckets[i][j];
        }
    }
}

10. 希尔排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(n2)O(n^2)O(n2),其中 nnn 为数组长度。

空间复杂度: O(1)O(1)O(1)

功能: 希尔排序是插入排序的一种改进版本,它通过将数组分割成若干个子序列,并对子序列进行插入排序,最后再对整个数组进行一次插入排序。

#include <iostream>
#include <vector>

using namespace std;

// 希尔排序函数
void shellSort(vector<int>& arr) 
{
   
   
    int n = arr.size();

    // 逐步缩小增量
    for (int gap = n / 2; gap > 0; gap /= 2) 
    {
   
   
        // 对每个子序列进行插入排序
        for (int i = gap; i < n; i++)
        {
   
   
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) 
            {
   
   
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

11. 锦标赛排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(nlog⁡n)O(n \log n)O(nlogn),其中 nnn 为数组长度。

空间复杂度: O(n)O(n)O(n)

功能: 锦标赛排序是一种排序算法,它通过构建一棵二叉树,每个非叶子节点存储两个叶子节点的最小值,最后根节点存储整个序列的最小值。

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

// 锦标赛排序辅助函数:构建树
void buildTree(vector<int>& arr, vector<int>& tree, int node, int start, int end) 
{
   
   
    if (start == end) 
    {
   
   
        tree[node] = arr[start];
    } 
    else 
    {
   
   
        int mid = (start + end) / 2;
        buildTree(arr, tree, 2 * node, start, mid);
        buildTree(arr, tree, 2 * node + 1, mid + 1, end);
        tree[node] = min(tree[2 * node], tree[2 * node + 1]);
    }
}

// 锦标赛排序辅助函数:查询区间最小值
int queryTree(vector<int>& tree, int node, int start, int end, int left, int right) 
{
   
   
    if (start > right || end < left)
    {
   
   
        return INT_MAX;
    }
    if (start >= left && end <= right) 
    {
   
   
        return tree[node];
    }

    int mid = (start + end) / 2;
    int leftMin = queryTree(tree, 2 * node, start, mid, left, right);
    int rightMin = queryTree(tree, 2 * node + 1, mid + 1, end, left, right);

    return min(leftMin, rightMin);
}

// 锦标赛排序函数
void tournamentSort(vector<int>& arr) 
{
   
   
    int n = arr.size();

    // 构建树
    int treeSize = 2 * n;
    vector<int> tree(treeSize, 0);
    buildTree(arr, tree, 1, 0, n - 1);

    // 查询树并排序
    for (int i = 0; i < n; i++) 
    {
   
   
        arr[i] = queryTree(tree, 1, 0, n - 1, i, n - 1);
    }
}

12. tim排序

时间复杂度: 平均情况 O(nlog⁡n)O(n \log n)O(nlogn),最坏情况 O(nlog⁡n)O(n \log n)O(nlogn),其中 nnn 为数组长度。

空间复杂度: O(n)O(n)O(n)

功能: tim排序是一种融合了归并排序和插入排序的稳定排序算法,它在归并排序的基础上对小规模子数组使用插入排序进行优化。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MIN_MERGE = 32;

// 插入排序函数
void insertionSort(vector<int>& arr, int left, int right) 
{
   
   
    for (int i = left + 1; i <= right; i++) 
    {
   
   
        int key = arr[i];
        int j = i - 1;
        while (j >= left && arr[j] > key)
        {
   
   
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

// 归并排序辅助函数:合并两个有序数组
void merge(vector<int>& arr, int left, int mid, int right)
{
   
   
    int n1 = mid - left + 1;
    int n2 = right - mid;

    // 创建临时数组
    vector<int> leftArr(n1), 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值