【软件设计师备考 专题 】算法探索:排序、查找、数值计算和字符串处理(二)

简介: 【软件设计师备考 专题 】算法探索:排序、查找、数值计算和字符串处理

【软件设计师备考 专题 】算法探索:排序、查找、数值计算和字符串处理(一)https://developer.aliyun.com/article/1467572


4. 字符串处理方法

4.1 字符串匹配

字符串匹配是指在一个文本串中查找一个模式串的过程。常用的字符串匹配算法有暴力匹配、KMP算法、Boyer-Moore算法等。

4.1.1 暴力匹配

暴力匹配算法,也称为朴素匹配算法,是最简单直观的字符串匹配算法。它的原理是从文本串的每个位置开始,逐个字符与模式串进行比较,直到找到匹配或者遍历完整个文本串。

// 暴力匹配算法示例代码
int bruteForce(string text, string pattern) {
    int n = text.length();
    int m = pattern.length();
    for (int i = 0; i <= n - m; i++) {
        int j;
        for (j = 0; j < m; j++) {
            if (text[i + j] != pattern[j]) {
                break;
            }
        }
        if (j == m) {
            return i; // 匹配成功,返回匹配的起始位置
        }
    }
    return -1; // 匹配失败,返回-1
}

暴力匹配算法的时间复杂度为O(n * m),其中n为文本串的长度,m为模式串的长度。

4.1.2 KMP算法

KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,它利用模式串的特点,在匹配过程中避免不必要的回溯。

// KMP算法示例代码
void getNext(string pattern, int next[]) {
    int m = pattern.length();
    next[0] = -1;
    int i = 0, j = -1;
    while (i < m - 1) {
        if (j == -1 || pattern[i] == pattern[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}
int kmp(string text, string pattern) {
    int n = text.length();
    int m = pattern.length();
    int* next = new int[m];
    getNext(pattern, next);
    int i = 0, j = 0;
    while (i < n && j < m) {
        if (j == -1 || text[i] == pattern[j]) {
            i++;
            j++;
        } else {
            j = next[j];
        }
    }
    delete[] next;
    if (j == m) {
        return i - j; // 匹配成功,返回匹配的起始位置
    } else {
        return -1; // 匹配失败,返回-1
    }
}

KMP算法的时间复杂度为O(n + m),其中n为文本串的长度,m为模式串的长度。

4.1.3 Boyer-Moore算法

Boyer-Moore算法是一种高效的字符串匹配算法,它利用模式串的特点和右移规则,在匹配过程中跳过尽可能多的字符。

// Boyer-Moore算法示例代码
void buildBadCharTable(string pattern, int badCharTable[]) {
    int m = pattern.length();
    for (int i = 0; i < 256; i++) {
        badCharTable[i] = m;
    }
    for (int i = 0; i < m - 1; i++) {
        badCharTable[pattern[i]] = m - 1 - i;
    }
}
int boyerMoore(string text, string pattern) {
    int n = text.length();
    int m = pattern.length();
    int* badCharTable = new int[256];
    buildBadCharTable(pattern, badCharTable);
    int i = m - 1, j = m - 1;
    while (i < n) {
        if (text[i] == pattern[j]) {
            if (j == 0) {
                delete[] badCharTable;
                return i; // 匹配成功,返回匹配的起始位置
            }
            i--;
            j--;
        } else {
            i += badCharTable[text[i]];
            j = m - 1;
        }
    }
    delete[] badCharTable;
    return -1; // 匹配失败,返回-1
}

Boyer-Moore算法的时间复杂度为O(n + m),其中n为文本串的长度,m为模式串的长度。

4.2 字符串编辑距离

字符串编辑距离是指通过一系列的插入、删除和替换操作,将一个字符串转换成另一个字符串所需的最少操作次数。常用的字符串编辑距离算法有Levenshtein距离、Damerau-Levenshtein距离等。

4.2.1 Levenshtein距离

Levenshtein距离,也称为编辑距离,是衡量两个字符串相似程度的指标。它定义为将一个字符串转换成另一个字符串所需的最少编辑操作次数,允许的操作包括插入、删除和替换。

// Levenshtein距离算法示例代码
int levenshteinDistance(string word1, string word2) {
    int m = word1.length();
    int n = word2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for (int i = 1; i <= m; i++) {
        dp[i][0] = i;
    }
    for (int j = 1; j <= n; j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1[i - 1] == word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
            }
        }
    }
    return dp[m][n];
}

Levenshtein距离的时间复杂度为O(m * n),其中m和n分别为两个字符串的长度。

4.2.2 Damerau-Levenshtein距离

Damerau-Levenshtein距离是Levenshtein距离的一种扩展,除了插入、删除和替换操作外,还允许相邻字符的交换操作。

// Damerau-Levenshtein距离算法示例代码
int damerauLevenshteinDistance(string word1, string word2) {
    int m = word1.length();
    int n = word2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for (int i = 1; i <= m; i++) {
        dp[i][0] = i;
    }
    for (int j = 1; j <= n; j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1[i - 1] == word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
            }
            if (i > 1 && j > 1 && word1[i - 1] == word2[j - 2] && word1[i - 2] == word2[j - 1]) {
                dp[i][j] = min(dp[i][j], dp[i - 2][j - 2] + 1);
            }
        }
    }
    return dp[m][n];
}

Damerau-Levenshtein距离的时间复杂度为O(m * n),其中m和n分别为两个字符串的长度。

4.3 字符串压缩与解压缩

字符串压缩是指通过对字符串中重复出现的字符进行编码,减少字符串的存储空间。常用的字符串压缩算法有Run-Length Encoding(RLE)算法、Huffman编码等。

4.3.1 Run-Length Encoding(RLE)算法

Run-Length Encoding(RLE)算法是一种简单的无损压缩算法,它通过将连续出现的相同字符替换为字符和出现次数的组合来进行压缩。

// RLE算法示例代码
string compressRLE(string text) {
    int n = text.length();
    if (n <= 1) {
        return text;
    }
    string compressed;
    int count = 1;
    for (int i = 1; i < n; i++) {
        if (text[i] == text[i - 1]) {
            count++;
        } else {
            compressed += text[i - 1] + to_string(count);
            count = 1;
        }
    }
    compressed += text[n - 1] + to_string(count);
    return compressed;
}

4.3.2 Huffman编码

Huffman编码是一种变长编码方法,它通过根据字符出现的频率构建哈夫曼树,并根据哈夫曼树生成每个字符的编码。

// Huffman编码示例代码
struct TreeNode {
    char val;
    int freq;
    TreeNode* left;
    TreeNode* right;
    TreeNode(char c, int f) : val(c), freq(f), left(nullptr), right(nullptr) {}
};
struct Compare {
    bool operator()(TreeNode* a, TreeNode* b) {
        return a->freq > b->freq;
    }
};
void buildHuffmanTree(string text, unordered_map<char, int>& freqMap, priority_queue<TreeNode*, vector<TreeNode*>, Compare>& pq) {
    for (char c : text) {
        freqMap[c]++;
    }
    for (auto it = freqMap.begin(); it != freqMap.end(); it++) {
        TreeNode* node = new TreeNode(it->first, it->second);
        pq.push(node);
    }
    while (pq.size() > 1) {
        TreeNode* left = pq.top();
        pq.pop();
        TreeNode* right = pq.top();
        pq.pop();
        TreeNode* parent = new TreeNode('\0', left->freq + right->freq);
        parent->left = left;
        parent->right = right;
        pq.push(parent);
    }
}
void generateHuffmanCode(TreeNode* root, string code, unordered_map<char, string>& codeMap) {
    if (!root) {
        return;
    }
    if (!root->left && !root->right) {
        codeMap[root->val] = code;
    }
    generateHuffmanCode(root->left, code + "0", codeMap);
    generateHuffmanCode(root->right, code + "1", codeMap);
}
string compressHuffman(string text) {
    unordered_map<char, int> freqMap;
    priority_queue<TreeNode*, vector<TreeNode*>, Compare> pq;
    buildHuffmanTree(text, freqMap, pq);
    unordered_map<char, string> codeMap;
    generateHuffmanCode(pq.top(), "", codeMap);
    string compressed;
    for (char c : text) {
        compressed += codeMap[c];
    }
    return compressed;
}

字符串解压缩与压缩相反,根据压缩后的字符串和相应的编码表进行解码即可。

以上是字符串处理方法的一些常用算法和示例代码,通过学习这些算法,可以更好地理解和应用字符串处理技术。在实际的软件设计师考试中,掌握这些知识点将有助于解答相关问题和完成相应的编程任务。


第五章:其他常用算法

5.1 贪心算法

5.1.1 原理及步骤

贪心算法是一种通过每一步的局部最优选择来达到全局最优的算法。其基本思想是在每一步选择中都做出当前状态下最优的选择,而不考虑之后的结果。贪心算法一般可以分为以下步骤:

  1. 定义问题的解空间和解空间的度量准则。
  2. 利用贪心的策略进行选择,即每一步都选择当前最优解。
  3. 判断当前选择是否满足问题的约束条件,如果满足,则进入下一步;如果不满足,则回溯到上一步重新选择。
  4. 重复步骤2和3,直到达到问题的终止条件。

5.1.2 时间复杂度和空间复杂度

贪心算法的时间复杂度一般较低,通常为O(nlogn)或O(n),其中n为问题的规模。空间复杂度则取决于具体的算法实现。

5.1.3 应用场景和优化方法

贪心算法常用于求解最优化问题,例如霍夫曼编码、最小生成树、任务调度等。在实际应用中,需要注意贪心策略是否能够得到全局最优解,有时候需要进行优化方法的改进。

以下是一个示例代码,展示了贪心算法在任务调度问题中的应用:

#include <iostream>
#include <vector>
#include <algorithm>
// 定义任务结构体
struct Task {
    int start;
    int end;
};
// 比较函数,按照任务结束时间进行排序
bool compare(Task t1, Task t2) {
    return t1.end < t2.end;
}
// 贪心算法求解任务调度问题
std::vector<Task> scheduleTasks(std::vector<Task>& tasks) {
    std::vector<Task> result;
    
    // 按照任务结束时间进行排序
    std::sort(tasks.begin(), tasks.end(), compare);
    
    // 选择第一个任务
    result.push_back(tasks[0]);
    int lastEnd = tasks[0].end;
    
    // 依次选择后续任务
    for (int i = 1; i < tasks.size(); i++) {
        if (tasks[i].start >= lastEnd) {
            result.push_back(tasks[i]);
            lastEnd = tasks[i].end;
        }
    }
    
    return result;
}
int main() {
    std::vector<Task> tasks = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 8}, {5, 9},
                               {6, 10}, {8, 11}, {8, 12}, {2, 13}, {12, 14}};
    
    std::vector<Task> result = scheduleTasks(tasks);
    
    std::cout << "最优任务调度结果:" << std::endl;
    for (auto task : result) {
        std::cout << "开始时间:" << task.start << ",结束时间:" << task.end << std::endl;
    }
    
    return 0;
}


【软件设计师备考 专题 】算法探索:排序、查找、数值计算和字符串处理(三)https://developer.aliyun.com/article/1467574

目录
相关文章
|
1月前
|
JavaScript 前端开发 算法
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
5月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
240 7
|
5月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
216 8
|
6月前
|
搜索推荐 Shell
解析排序算法:十大排序方法的工作原理与性能比较
解析排序算法:十大排序方法的工作原理与性能比较
192 9
|
6月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
78 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
8月前
|
存储 算法 安全
【第六章】软件设计师 之 数据结构与算法基础
软件设计师 之 数据结构与算法基础 备考资料
【第六章】软件设计师 之 数据结构与算法基础
|
6月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
62 0
|
6月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
125 0
|
8月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
74 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等