算法模版(C++)

1. 排序问题

最好 时间复杂度平均 时间复杂度最坏 时间复杂度空间复杂度
冒泡排序O(n)O(n²)O(n²)O(1)
选择排序O(n²)O(n²)O(n²)O(1)
插入排序O(n)O(n²)O(n²)O(1)
快速排序O(nlogn)O(nlogn)O(n²)O(logn)
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)
希尔排序O(nlogn)O(nlog²n)O(nlog²n)O(1)

        稳定的算法:冒泡排序、插入排序、归并排序、基数排序。

(1)冒泡排序

        简易版

void BubbleSort(std::vector<int>& nums)
{
    int len = nums.size();
    for (int i = 0; i < len; ++i)
        for (int j = 1; j < len; ++j)
            if (nums[j - 1] > nums[j])
                std::swap(nums[j - 1], nums[j]);
}

        改进:不再判断右边有序部分;当数组已经有序,终止排序函数。

void BubbleSort(std::vector<int>& nums)
{
    int len = nums.size();
    int lastIdx = len; // 每趟排序后, 最右边那个被交换的元素的索引

    for (int i = 0; i < len; ++i)
    {
        bool hadExchanged = false; // 发生交换
        int tmpIdx = 0;            // 每次交换, 被交换的右边元素的索引

        for (int j = 1; j < lastIdx; ++j)
        {
            if (nums[j - 1] > nums[j])
            {
                std::swap(nums[j - 1], nums[j]);
                hadExchanged = true;
                tmpIdx = j;
            }
        }

        if (tmpIdx)
            lastIdx = tmpIdx;
        
        if (!hadExchanged) return;
    }
}

(2)选择排序

        简易版

void SelectSort(std::vector<int>& nums)
{
    int len = nums.size();
    for (int i = 0; i < len; ++i)
        for (int j = i + 1; j < len; ++j)
            if (nums[j] < nums[i])
                std::swap(nums[i], nums[j]);
}

        改进:找到右侧最小的数,再与当前数进行交换。 

void SelectSort(std::vector<int>& nums)
{
    int len = nums.size();
    for (int i = 0; i < len; ++i)
    {
        int minNumIdx = i; // 找到的最小值的索引
        for (int j = i + 1; j < len; ++j)
            if (nums[j] < nums[minNumIdx])
                minNumIdx = j;
        std::swap(nums[i], nums[minNumIdx]);
    }
}

(3)插入排序

// 从nums[i]的前一个元素nums[i - 1]向前遍历

// 若遍历到的元素nums[j]大于nums[i]则将nums[j]后移一位

// 否则将nums[i]放在该元素的后面,即nums[j + 1]的位置

// (若nums[i]大于前面所有的元素,则此时i = j + 1)

void InsertSort(std::vector<int>& nums)
{
    int len = nums.size();
    for (int i = 1; i < len; ++i)
    {
        int tmp = nums[i];

        int j = i - 1;
        for (; j >= 0 && nums[j] > tmp; --j)
            nums[j + 1] = nums[j];
        nums[j + 1] = tmp;
    }
}

(4)快速排序

        递归写法

int Partition(std::vector<int>& nums, int left, int right)
{
    int key = nums[left]; // 基准
    while (left < right)
    {
        while (left < right && nums[right] >= key)
            --right;
        nums[left] = nums[right];
        while (left < right && nums[left] <= key)
            ++left;
        nums[right] = nums[left];
    }
    nums[left] = key;
    return left;
}
void QSort(std::vector<int>& nums, int left, int right)
{
    if (left >= right) return;

    int keyIdx = Partition(nums, left, right); // 基准的索引
    QSort(nums, left, keyIdx - 1);
    QSort(nums, keyIdx + 1, right);
}

        迭代写法

void QSort(std::vector<int>& nums)
{
    if (nums.size() < 2) return;

    int preLeft = 0;                // 每趟快排前的left索引
    int preRight = nums.size() - 1; // 每趟快排前的right索引
    std::stack<int> idxStack;       // 存索引的辅助栈
    idxStack.emplace(preLeft);
    idxStack.emplace(preRight);

    while (!idxStack.empty())
    {
        preRight = idxStack.top();
        idxStack.pop();
        preLeft = idxStack.top();
        idxStack.pop();

        int nextLeft = preLeft;   // 每趟快排后的left索引
        int nextRight = preRight; // 每趟快排后的right索引
        int key = nums[nextLeft]; // 基准

        while (nextLeft < nextRight)
        {
            while (nextLeft < nextRight && nums[nextRight] >= key)
                --nextRight;
            nums[nextLeft] = nums[nextRight];
            while (nextLeft < nextRight && nums[nextLeft] <= key)
                ++nextLeft;
            nums[nextRight] = nums[nextLeft];
        }
        nums[nextLeft] = key;
        // keyIdx = nextLeft;
        if (preLeft < nextLeft - 1)
        {
            idxStack.emplace(preLeft);
            idxStack.emplace(nextLeft - 1);
        }
        if (nextLeft + 1 < preRight)
        {
            idxStack.emplace(nextLeft + 1);
            idxStack.emplace(preRight);
        }
    }
}

(5)堆排序

void Heapify(std::vector<int>& nums, int len, int parent)
{
    if (parent >= len) return;

    int left = (parent << 1) + 1; // 左子节点的索引, parent * 2 + 1
    int right = parent + 1 << 1;  // 右子节点的索引, parent * 2 + 2
    int maxValueIdx = parent;     // 三个节点中值最大的节点对应的索引

    if (left < len && nums[maxValueIdx] < nums[left])
        maxValueIdx = left;
    if (right < len && nums[maxValueIdx] < nums[right])
        maxValueIdx = right;

    if (maxValueIdx != parent)
    {
        std::swap(nums[maxValueIdx], nums[parent]);
        Heapify(nums, len, maxValueIdx);
    }
}

void HeapSort(std::vector<int>& nums)
{
    // 建堆
    int len = nums.size();
    for (int i = (len >> 1) - 1; i >= 0; --i)
        Heapify(nums, len, i);
    // 排序
    for (int i = nums.size() - 1; i >= 1; --i)
    {
        std::swap(nums[i], nums[0]);
        Heapify(nums, i, 0);
    }
}

(6)二路归并排序

void Merge(std::vector<int>& nums, int left, int mid, int right)  //合并函数
{
    std::vector<int> helpArray(right - left + 1);

    int part1_idx = left;    // 第1组的遍历索引
    int part2_idx = mid + 1; // 第2组的遍历索引
    int helpArray_idx = 0;	 // helpArray中待插入元素的位置的索引

    // 两组都未遍历完
    while (part1_idx <= mid && part2_idx <= right)
    {
        if (nums[part1_idx] > nums[part2_idx])
            helpArray[helpArray_idx++] = nums[part1_idx++];
        else
            helpArray[helpArray_idx++] = nums[part2_idx++];
    }
    // 第1组未遍历完
    while (part1_idx <= mid)
        helpArray[helpArray_idx++] = nums[part1_idx++];
    // 第2组未遍历完
    while (part2_idx <= right)
        helpArray[helpArray_idx++] = nums[part2_idx++];

    // 将helpArray中排好序的数放回nums
    for (int i = left; i <= right; i++)
        nums[i] = helpArray[i - left];
}
void MergeSort(std::vector<int>& nums, int left, int right) //归并排序
{
	if (left >= right) return;
	
	int mid = left + (right - left)/ 1;
	MergeSort(nums, left, mid); // 注意这里别写成mid - 1
	MergeSort(nums, mid + 1, right);
	Merge(nums, left, mid, right);
}

2. 排列组合问题

(1)排列

(2)组合(求子集\子序列)

#include <iostream>
#include <vector>

std::vector<int> nums{1, 2, 3, 4, 5};
std::vector<std::vector<int>> ans;
std::vector<int> buf;

// 问题1:求所有真子集
// 方法一 字典序穷举
void t1f1()
{

    int n = 1 << nums.size();
    ans.reserve(n - 1);         // 真子集个数2的n次方减1
    buf.reserve(nums.size());   // 每个子集最多n个数
    for (int i = 1; i < n; ++i) // 对所有情况逐一输出
    {
        buf.clear();
        int tmp = i, idx = 0;
        while (tmp)
        {
            if (tmp & 1)
                buf.emplace_back(nums[idx]);
            tmp >>= 1;
            ++idx;
        }
        ans.emplace_back(buf);
    }
}

// 方法二 dfs回溯穷举
void dfs1(int idx, int n)
{
    if (idx > nums.size())
        return;
    if (buf.size() == n)
    {
        ans.emplace_back(buf);
        return;
    }
    buf.emplace_back(nums[idx]);
    dfs1(idx + 1, n);
    buf.pop_back();
    dfs1(idx + 1, n);
}
void t1f2()
{
    int n = nums.size();
    ans.reserve((1 << n) - 1); // 真子集个数2的n次方减1
    buf.reserve(n);            // 每个子集最多n个数
    for (int i = 1; i < n + 1; ++i)
        dfs1(0, i);
}

// 问题2:求元素个数为k的所有子集
// 方法一 dfs回溯 + 剪枝
void dfs2(int idx, int k)
{
    int n1 = nums.size(), n2 = buf.size();
    if (idx > n1 || n2 + n1 - idx + 1 < k)
        return;
    if (n2 == k)
    {
        ans.emplace_back(buf);
        return;
    }
    buf.emplace_back(nums[idx]);
    dfs2(idx + 1, k);
    buf.pop_back();
    dfs2(idx + 1, k);
}
void t2f1(int k)
{
    ans.reserve(k);
    dfs2(0, k);
}
// 方法二:字典序
void t2f2(int k)
{
    buf.reserve(k);

    // 朴素方法 空间复杂度O(n)
    // int n = nums.size();
    // std::vector<bool> flag(n, false);
    // for (int i = 0; i < k; ++i)
    //     flag[i] = true;
    // while (true)
    // {
    //     int idx = 0;
    //     while (idx < n - 1)
    //     {
    //         if (flag[idx] && !flag[idx + 1])
    //         {
    //             flag[idx] = false;
    //             flag[idx + 1] = true;
    //             break;
    //         }
    //         ++idx;
    //     }
    //     if (!flag[0] && idx < n - 1)
    //         for (int i = 0; i < idx / 2; ++i)
    //         {
    //             flag[i] = true;
    //             flag[idx - 1 - i] = false;
    //         }
    //     for (int i = 0; i < n; ++i)
    //         if (flag[i])
    //             buf.emplace_back(nums[i]);
    //     ans.emplace_back(buf);
    //     buf.clear();
    //     if (idx == n - 1)
    //         break;
    // }

    // 优化方法 空间复杂度O(k)
    std::vector<int> index;
    index.reserve(k + 1);
    for (int i = 1; i <= k; ++i)
        index.emplace_back(i);
    index.emplace_back(nums.size() + 1);
    int j = 0;
    while (j < k)
    {
        for (int i = 0; i < k; ++i)
            buf.emplace_back(nums[index[i] - 1]);
        ans.emplace_back(buf);
        buf.clear();
        j = 0;
        while (j < k && index[j] + 1 == index[j + 1])
        {
            index[j] = j + 1;
            ++j;
        }
        ++index[j];
    }
}

// 打印结果
void LogSet()
{
    for (const std::vector<int> &buf : ans)
    {
        for (int num : buf)
            std::cout << num << ' ';
        std::cout << '\n';
    }
}

int main()
{

    // t1f1();
    // t1f2();
    // t2f1(3);
    t2f2(3);

    LogSet();

    std::cout << std::endl << '\n';

    std::cin.get();
}

(3)排列 + 组合

3. 字符串

(1)KMP

         参考资料: 很详尽KMP算法(厉害) - ZzUuOo666 - 博客园

/// <summary>
/// 查找str中是否有子串与matchStr匹配
/// </summary>
/// <returns>
///     找到了:返回第一个与match匹配的子串的索引;
///     未找到:返回-1;
/// </returns>
int FindSubStr(std::string str, std::string matchStr)
{
    int strLen = str.size(), subStrLen = matchStr.size();
    if (!subStrLen) return -1;

    // 求next 数组
    std::vector<int> next(subStrLen);
    int strIdx = 0;
    int subStrIdx = next[0] = -1;
    while (strIdx < subStrLen - 1)
    {
        if (subStrIdx == -1 || matchStr[strIdx] == matchStr[subStrIdx])
        {
            ++strIdx, ++subStrIdx;
            next[strIdx] = matchStr[strIdx] == matchStr[subStrIdx] ? next[subStrIdx] : subStrIdx;
        }
        else
        {
            subStrIdx = next[subStrIdx];
        }
    }

    strIdx = subStrIdx = 0;
    while (strIdx < strLen && subStrIdx < subStrLen)
    {
        if (subStrIdx == -1 || str[strIdx] == matchStr[subStrIdx])
            ++strIdx, ++subStrIdx;
        else
            subStrIdx = next[subStrIdx];
    }
    return subStrIdx == subStrLen ? strIdx - subStrIdx : -1;
}

        呃,还是直接用find()函数吧……find()函数返回size_t,按F12就能找到如下定义(IDE是VS),windows64位操作系统下,size_t就是unsigned long long,所以没找到会返回SIZE_MAX。

// Definitions of common types
#ifdef _WIN64
    typedef unsigned __int64 size_t;
    typedef __int64          ptrdiff_t;
    typedef __int64          intptr_t;
#else
    typedef unsigned int     size_t;
    typedef int              ptrdiff_t;
    typedef int              intptr_t;
#endif

        如果没找到要输出-1,稍微处理一下输出就行。

int main()
{
    std::string str = "abc";
    std::string subStr = "213";

    // string.size() <= LLONG_MAX
    std::cout << (long long)str.find(subStr) << '\n';

    // string.size() < ULLONG_MAX
    unsigned long long subStrIdx = str.find(subStr);
    if (subStrIdx == ULLONG_MAX)
        std::cout << -1 << '\n';        // 处理没找到
    else
        std::cout << subStrIdx << '\n'; // 处理找到了
}

(2)Split

        C++ 的 string 为什么不提供 split 函数? - 知乎 (zhihu.com)

// 备注:
//   分割符在开头或结尾的处理结果
//   params{str:".1.1.", spliter:'.'} -> return{"","1","1",""}
vector<string> Split(const string &str, char spliter)
{
    vector<string> strs;
    int first = 0;
    int last;
    while ((last = str.find(spliter, first)) != string::npos)
    {
        strs.emplace_back(str.begin() + first, str.begin() + last);
        first = last + 1;
    }
    strs.emplace_back(str.begin() + first, str.end());

    return strs;
}

4. 树

(1)求树的最大深度

int GetDepth(TreeNode *root)
{
    if (root == nullptr) return 0;

    int leftDepth = GetDepth(root->left);
    int rightDepth = GetDepth(root->right);
        
    return max(leftDepth, rightDepth) + 1; // 该节点的深度
}

(2)二叉树遍历

// 前序遍历
void PreOrder(vector<vector<int>> &res, TreeNode *root) 
{
    if (root == nullptr) return;
    
    res.emplace_back(root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}
// 中序遍历
void InOrder(vector<vector<int>> &res, TreeNode *root) 
{
    if (root == nullptr) return;

    InOrder(root->left);
    res.emplace_back(root->val);
    InOrder(root->right);
}
// 后序遍历
void PostOrder(vector<vector<int>> &res, TreeNode *root) 
{
    if (root == nullptr) return;

    PostOrder(root->left);
    PostOrder(root->right);
    res.emplace_back(root->val);
}
// 层序遍历
void LevelOrder(vector<vector<int>> &res, TreeNode *root, int level)
{
    if (root == nullptr) return;

    if (level >= res.size())
        res.push_back({});

    res[level].emplace_back(root->val);
    LevelOrder(res, root->left, level + 1);
    LevelOrder(res, root->right, level + 1);
}

5. 其他

(1)两数交换(int类型)

inline void Swap(int a, int b) { a ^= b ^= a ^= b; }

(2)vector去重

// sort + unique + erase
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
// 利用unordered_set
unordered_set<int> orderedNums(nums.begin(), nums.end());
nums.assign(orderedNums.begin(), orderedNums.end());

        推荐使用第1种方法。 

(3)二分查找

        nuns为升序数组。

        ① 找到target的索引

int BinarySearch(const vector<int>& nums, int target)
{
    int left = 0;
    int right = nums.size() - 1;

    while (left <= right)
    {
        int mid = (left + right) >> 1;

        if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid - 1;
        else return mid;
    }
    return -1;
}

        ② 找到target的索引,没找到返回大于target的最小值的索引(若不存在大于target的值则会返回非法索引即可)

int BinarySearch(const vector<int>& nums, int target)
{
    int left = 0;
    int right = nums.size() - 1;

    while (left <= right)
    {
        int mid = (left + right) >> 1;

        if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid - 1;
        else return mid;
    }
    return left;
}

         ③ 统计target出现的次数

int BinarySearch(const vector<int> &nums, float target)
{
    int left = 0;
    int right = nums.size() - 1;

    while (left <= right)
    {
        int mid = (left + right) >> 1;

        if (nums[mid] < target)
            left = mid + 1;
        else if (nums[mid] > target)
            right = mid - 1;
        else return mid;
    }
    return left;
}
int GetTargetCnt(const vector<int>& nums, int target)
{
    return BinarySearch(nums, target + 0.5f) -
        BinarySearch(nums, target - 0.5f);
}

(4)cin && cout 的性能优化

        ios_base 类 | Microsoft Learn

        basic_ios 类 | Microsoft Learn

// 核心代码模式,放到class外边
static const nullptr_t io_sync_false = []() -> nullptr_t
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    return nullptr;
}();

// ACM模式,直接放在mian里面就行
ios::sync_with_stdio(false);
cin.tie(nullptr);

6. 例题

(1)两数之和

        题目1:给出一个整型数组nums和一个目标值target,请在数组中找出所有加起来等于目标值的数对,返回这些数对的列表(数对不能重复)。数对中的值按及数对都按升序排列。

        题目2:给出一个整型数组nums和一个目标值target,请在数组中找出1个加起来等于目标值的数对,返回该数对所对应的索引对。索引对中的索引按升序排列。

        补充:若题目1中值相同但索引不同的数视为两个数,即数对可以重复,或者题目2中要求所有符合条件的索引对,感觉还是得用穷举法。

① 返回数对列表

        思路:排序,双索引。

vector<pair<int, int>> GetPairs(vector<int>& nums, int target)
{
    vector<pair<int, int>> ans;
    sort(nums.begin(), nums.end());
    int left = 0, right = nums.size() - 1;
    while (left < right)
    {
        int sum = nums[left] + nums[right];
        if (sum > target) --right;
        else if (sum < target) ++left;
        else
        {
            ans.emplace_back(nums[left], nums[right]);
            int oldLeftNum = nums[left], oldRightNum = nums[right];
            while (left < right && nums[++left] == oldLeftNum) {}
            while (left < right && nums[--right] == oldRightNum) {}
        }
    }
    return ans;
}

  ② 返回索引对

pair<int, int> GetPairs(vector<int>& nums, int target)
{
    vector<pair<int, int>> ans;
    unordered_map<int, int> valToIdx; // 值到索引的映射
    for (int i = 0; i < nums.size(); ++i)
    {
        if (valToIdx.find(target - nums[i]) == valToIdx.end())
            valToIdx[nums[i]] = i;
        else return { target - nums[i], i };
    }
    return { 0,0 };
}

7. 时间复杂度 && 空间复杂度

(1)时间复杂度

        O(k^n)与O(n!)性能很差只能处理少量数据。

        因为f(n) = log(n)时,f(n)' = 1/n,所以O(x*log(n))的性能更接近于O(x)而非O(x*n)。因此,将O(x*n)优化为O(x*log(n))时性能会明显提升。其中x为1或单项式。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来抓月亮啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值