当前位置: 首页 > news >正文

数据结构04:串的存储结构与KMP算法

前言

参考用书:王道考研《2024年 数据结构考研复习指导》

参考用书配套视频:4.1_1_串的定义和基本操作_哔哩哔哩_bilibili

特别感谢: Google Bard老师[解释KMP,修改BUG]、Chat GPT老师[修改BUG]、BING老师[封面图]~

当我请求BING老师:画一张串的图片,于是BING老师画了一幅:“串的图片”;意外地很好看有没有....😶

考研笔记整理,内容包含串的基本定义,暴力匹配、KMP模式匹配C++代码~考研一起加油~ 🐑

第1版:查资料、画导图、配图、写BUG~~

第2版:发现自己在next设置的表格总是被吞...补上原来的表,更改朴素匹配算法、KMP优化算法的描述;更换封面,修改标题~~


目录

前言

目录

思维导图

串的定义和实现

串的定义

串的存储结构

串的基本操作

定长顺序存储(代码)

堆分配顺序存储(代码)

串的模式匹配 

朴素/暴力匹配算法

算法思想

核心代码

举栗实现

KMP算法

算法思想

核心代码

举栗实现

KMP优化算法

算法思想

核心代码

求next数组与nextval数组

推算思路

结语


思维导图

 思维导图备注

  • 考研大纲仅要求掌握字符串模式匹配,重点在KMP匹配算法,以及匹配算法中Next数组的手动推算[贴在了最后一部分]~
  • 考研大纲对于串的定义和实现没有要求,但是为了保证博文的完整性还是写完这部分了,考研小伙伴们随便看看就行~

串的定义和实现

串的定义

串的定义: 由零个或多个字符组成的有限序列。一般记为

其中,a1可以是字母、数字或者其它字符。

串的逻辑结构:和线性表极为相似,区别仅在与串的数据对象为字符集。在基本操作上,串和线性表有很大区别。线性表的基本操作主要以单个元素作为操作未向,如查找、插入或删除某个元素等;而串的基本操作通常以子串作为操作对象,如查找、插入或删除一个子串等。

串的存储结构

串的定长顺序存储:类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,(1)为每个串变量分配一个固定长度的存储区,或(2)串的长度加上空终止符。

串的堆分配存储:堆分配存储表示依然以一组地址连续的存储单元存放串值的字符序列,但他们的存储空间是在程序执行过程中动态分配得到的。

串的块链存储:类似于线性表的链式存储结构,也可以采用链式表存储串值。由于串的特殊性(每个元素只有1个字符),在具体实现时,每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构。

三种串的存储结构如下图所示:

图:串的定长顺序存储、堆分配存储、块链存储示意图

 三种存储方式优缺点比较:

存储方式优点缺点

定长顺序存储

(1)易于实现和管理

(2)为高级设计程序所采用

字符串必须是固定长度的。如果字符串比分配的空间短,会浪费空间;如果字符串比分配的空间长,会截断字符串。
堆分配存储

(1)字符串可以是任意长度

(2)为高级设计程序所采用

实施和管理比定长顺序存储更复杂。
块链存储

(1)字符串可以是任意长度

(2)适合字符串被频繁修改的场合

实现和管理比定长顺序存储或堆分配存储更复杂。

串的基本操作

 块链存储目前不是很常用,在此暂时不作讨论~🫥

  • 第一份代码,不使用C++内置函数实现代码功能,采用定长顺序存储~
  • 第二份代码,使用C++内置函数实现代码功能,此功能默认为动态分配~ 

定长顺序存储(代码)

#include <iostream>
#include <string>#define MAXLEN 25
//定长顺序存储表示存储结构:内容与长度
typedef struct{char ch[MAXLEN];int length;
}SString;//初始化
void initString(SString *s) {s->length = 0;s->ch[0] = '\0';
}//输出字符串的内容及长度
void printString(const SString *s) {std::cout << s->length << ": ";for (int i = 1; i <= s->length; i++) {std::cout << s->ch[i];}std::cout << std::endl;
}//读取字符串
void readString(SString *s) {std::string str;std::getline(std::cin, str);for (int i = 0; i < str.length(); i++) {s->ch[++s->length] = str[i];}
}//比较字符串
int compareString(const SString *s1, const SString *s2) {if (s1->length != s2->length) {return s1->length - s2->length;}for (int i = 0; i < s1->length; i++) {if (s1->ch[i] != s2->ch[i]) {return s1->ch[i] - s2->ch[i];}}return 0;
}//连接字符串
void concatString(SString *s1, const SString *s2) {if (s1 == nullptr || s2 == nullptr) {return;}if (s1->length + s2->length > MAXLEN - 1) {std::cout << "String is too long.\n";return;}for (int i = 1; i <= s2->length; i++) {s1->ch[s1->length + i] = s2->ch[i];}s1->length += s2->length;
}//获取字符串索引
int indexOfChar(const SString *s, char c) {for (int i = 0; i < s->length; i++) {if (s->ch[i] == c) {return i;}}return -1;
}//清空字符串
void clearString(SString *s) {s->length = 0;s->ch[0] = '\0';
}//销毁字符串
void destroyString(SString *s) {initString(s);
}int main() {SString s1, s2;initString(&s1);initString(&s2);std::cout << "输入字符串1: ";readString(&s1);std::cout << "输入字符串2: ";readString(&s2);std::cout << std::endl;std::cout << "输出字符串1: ";printString(&s1);std::cout << "输出字符串2: ";printString(&s2);std::cout << std::endl;std::cout << "比较字符串: " << compareString(&s1, &s2) << std::endl;std::cout << std::endl;concatString(&s1, &s2);std::cout << "输出字符串2并入字符串1的内容: ";printString(&s1);std::cout << std::endl;std::cout << "查询字符 'a'在字符串1的位置: " << indexOfChar(&s1, 'a') << std::endl;std::cout << std::endl;clearString(&s1);std::cout << "输出执行清空后字符串1的内容: ";printString(&s1);std::cout << std::endl;destroyString(&s1);return 0;
}

执行结果如下图所示:

堆分配顺序存储(代码)

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>int main() {std::string s1, s2;std::cout << "输入字符串1: ";std::getline(std::cin, s1);std::cout << "输入字符串2: ";std::getline(std::cin, s2);std::cout << "输出字符串1: " << s1 << std::endl;std::cout << "输出字符串2: " << s2 << std::endl;std::cout << "比较字符串:  " << std::strcmp(s1.c_str(), s2.c_str()) << std::endl;s1 = s1 + s2;std::cout << "输出字符串2并入字符串1的内容:  " << s1 << std::endl;std::cout << "查询字符 'a'在字符串1的位置:  " << std::find(s1.begin(), s1.end(), 'a') - s1.begin() << std::endl;s1.clear();std::cout << "输出执行清空后字符串1的内容: " << s1 << std::endl;//std::delete[] s1.c_str(); // 清空字符串这行实际不需要return 0;
}

执行结果如下图所示:


串的模式匹配 

字符串匹配算法:字符串匹配算法是一种计算机算法,可在另一个字符串(也称为搜索字符串或文本)中查找一个或多个字符串(也称为模式)的位置。

模式匹配定义:子串的定位操作通常称为串的模式匹配,它求的是子串(常称“模式串”)在主串中的位置。

朴素/暴力匹配算法

算法思想

朴素的字符串匹配算法是一种用于查找文本中模式首次出现的简单算法。 它的工作原理是将模式的字符与文本的字符一个一个地进行比较,直到找到模式或到达文本的末尾。

该算法的工作原理如下:

  1. 初始化两个指针,i 和 j。i指向主串中的当前字符,j指向模式串中的当前字符,从前向后依次比较;
  2. 比较 i 和 j 处的字符:1)若相等,则将两个指针向后移动一个位置(i++,j++),并重复本步骤。2)若不等,i回溯到主串匹配字符串中首位的下1位(2+i-j),j回溯到子串的第1位(j=1);
  3. 如果 j 指针匹配到末尾,表示找到了匹配的串,返回主指针的位置(i-T.length);
  4. 如果 i 指针匹配到末尾,表示未找到匹配的串,返回-1。

图:串的朴素匹配算法举栗(共5趟)

核心代码

int index(const SString& S, const SString& T) {int i = 1, j = 1;while (i <= S.length && j <= T.length) {if (S.ch[i] == T.ch[j]) { // 如果主串与子串相等i++;                  // 继续比较后续字符j++;} else {                  // 如果主串与子串不相等i = i - j + 2;        // 主串指针移动到下一位j = 1;                // 子串回溯到位置1开始匹配}}if (j > T.length)             // 如果子串匹配到末尾相等return i - T.length;      // 返回匹配成功的主串位序else                          // 如果主串匹配到末尾没有相等return -1;                // 返回-1,表示未找到匹配
}

举栗实现

#include <iostream>
#include <string>#define MAXLEN 25struct SString {char ch[MAXLEN];int length;
};void initString(SString& s) {s.length = 0;s.ch[0] = '\0';
}void readString(SString& s) {std::string str;std::getline(std::cin, str);for (int i = 0; i < str.length(); i++) {s.ch[i + 1] = str[i];s.length++;}
}int index(const SString& S, const SString& T) {int i = 1, j = 1;while (i <= S.length && j <= T.length) {if (S.ch[i] == T.ch[j]) {i++;j++;} else {i = i - j + 2;j = 1;}}if (j > T.length)return i - T.length;elsereturn -1;
}int main() {SString s1, s2;initString(s1);initString(s2);std::cout << "输入主串: ";readString(s1);std::cout << "输入子串: ";readString(s2);int pos = index(s1, s2);if (pos != -1) {std::cout << "子串在主串中的位置: " << pos << std::endl;} else {std::cout << "未找到匹配位置" << std::endl;}return 0;
}

上述程序执行结果如下~

朴素的字符串匹配算法易于实现和理解。 但是,由于他的工作原理是比较主串和子串的每1个字符,因此朴素字符串匹配算法的最坏情况时间复杂度为 O(m*n),其中 m 是模式的长度,n 是文本的长度~

KMP算法

算法思想

Knuth-Morris-Pratt 算法是一种字符串搜索算法,通常用于查找文本中的模式。 该算法首先创建一个模式前缀表 [前缀表推算可见本文:求next数组与nextval数组] 。 然后使用该表快速跳过与模式不匹配的文本部分。

该算法的工作原理如下:

  1. 初始化两个指针,`i` 和 `j`。 `i` 指向文本中的当前位置,`j` 指向模式串中的当前位置。
  2. 比较`i` 和 `j` 处的字符。 如果它们相等,则将两个指针向后移动。
  3. 如果字符不相等,则将`i`向前移动匹配“i”处字符的模式的最长前缀的长度,`j`回溯到next[]指向的字符。
  4. 重复步骤 2 和 3,直到 `i` 到达文本末尾或 `j` 到达模式末尾。
  5. 如果 j 到达模式的末尾,则该模式已在文本中找到。 该算法返回模式的第一个字符在文本中的位置。
  6. 如果 `i` 在 `j` 到达模式末尾之前到达文本末尾,则该模式尚未在文本中找到。 该算法返回 -1。

KMP 算法是一种在文本中查找模式的非常有效的算法。 它通常用于文本编辑器、搜索引擎和其他需要在大量文本中查找模式的应用程序。

图:串的KMP匹配算法举栗(共4趟)

核心代码

//求子串的next值,快速跳过与模式不匹配的文本部分
void get_next(const SString& T, int next[]){        int i=1,j=0;                       //i为子串位序,j为对应的next[]值next[1]=0;                         //若从主串第1位匹配失败,则下次子串从j=0位开始匹配while(i<T.length){                 //当i<子串长度时if(j==0||T.ch[i]==T.ch[j]){    //如果子串为0,或者主串值与子串值相等++i;                       //主串与子串皆后移一位++j;next[i]=j;                 //next[j+1]=next[j]} else {                       //如果子串不为0,且主串值与子串值不相等j=next[j];                 //令j=next[j],继续循环}}
}//求KMP算法
int index_KMP(const SString& S, const SString& T) {int next[MAXLEN];    //声明 next数组get_next(T, next);   //将子串T传入 next数组计算值int i = 1, j = 1;    //i为主串位序,j为子串位序while (i <= S.length && j <= T.length) {   //当主串与子串均没有匹配到末尾时if (j == 0 || S.ch[i] == T.ch[j]) {    //如果j=0或者主串值与子串值相等i++;                               //主串与子串皆后移一位j++;} else {             //如果子串不为0,且主串值与子串值不相等j = next[j];     //子串位序j移动到next[j]所指的位置}}if (j > T.length)        //子串位序j移动到末尾+1,即匹配成功return i - T.length; //返回此时主串匹配部分的首位else                     //匹配失败return -1;           //返回-1
}

举栗实现

#include <iostream>
#include <string>#define MAXLEN 25struct SString {char ch[MAXLEN];int length;
};void initString(SString& s) {s.length = 0;s.ch[0] = '\0';
}void readString(SString& s) {std::string str;std::getline(std::cin, str);for (int i = 0; i < str.length(); i++) {s.ch[i + 1] = str[i];s.length++;}
}void get_next(const SString& T, int next[]){int i=1,j=0;next[1]=0;while(i<T.length){if(j==0||T.ch[i]==T.ch[j]){++i;++j;next[i]=j;} else {j=next[j];}}
}int index_KMP(const SString& S, const SString& T) {int next[MAXLEN];get_next(T, next);int i = 1, j = 1;while (i <= S.length && j <= T.length) {if (j == 0 || S.ch[i] == T.ch[j]) {i++;j++;} else {j = next[j];}}if (j > T.length)return i - T.length;elsereturn -1;
}int main() {SString s1, s2;initString(s1);initString(s2);std::cout << "输入主串: ";readString(s1);std::cout << "输入子串: ";readString(s2);int pos = index_KMP(s1, s2);if (pos != -1) {std::cout << "子串在主串中的位置: " << pos << std::endl;} else {std::cout << "未找到匹配位置" << std::endl;}return 0;
}

上述程序执行结果如下~

由于避免了主串回溯的问题,KMP 优化算法比朴素的字符串匹配算法更有效。 KMP 优化算法的最坏情况时间复杂度为 O(m+n),其中 m 是模式的长度,n 是文本的长度。

KMP优化算法

算法思想

KMP优化算法是在KMP算法的基础上进行改进,主要优化了next数组的计算方法:

在KMP匹配过程中,循环到不匹配的字符时,虽然不匹配的字符是未知的,但是至少可以确定此处模式串 'j' 指针指向的尾字符与主串 'i' 指针指向的字符不匹配。

如果此处模式串 'j' 指针指向的尾字符与主串 'i' 指针指向的首字符相等,说明主串依然不能匹配该字符,因此可以直接跳过该字符的比较,将指针i向后移动一位~

比较某步骤KMP和KMP优化主指针,如下:

 图:串的KMP算法(共3趟)与KMP优化算法举栗比较(共2趟)

为了实现这一优化,KMP优化算法引入了一个新的数组nextval,用于保存字符匹配失败时,子串回溯的位置。

该算法的工作原理如下:

  1. 初始化两个指针,`i` 和 `j`。 `i` 指向文本中的当前位置,`j` 指向模式中的当前位置。
  2. 比较 `i` 和 `j` 处的字符。 如果它们相等,则将两个指针向后移动。
  3. 如果字符不相等,则通过nextval[]获取上一次的回溯位置,并将其赋值给nextval[i],进一步优化主串  `i` 的回溯位置。
  4. 重复步骤 2 和 3,直到 `i` 到达文本末尾或 `j` 到达模式末尾。
  5. 如果 j 到达模式的末尾,则该模式已在文本中找到。 该算法返回模式的第一个字符在文本中的位置。
  6. 如果 `i` 在 `j` 到达模式末尾之前到达文本末尾,则该模式尚未在文本中找到。 该算法返回 -1。

核心代码

//求子串的next值
void get_nextval(const SString& T, int nextval[]) {int i = 1, j = 0;nextval[1] = 0;while (i < T.length) {if (j == 0 || T.ch[i] == T.ch[j]) {  // `i` 和 `j` 处的字符相等,向后移动指针++i;++j;if (T.ch[i] != T.ch[j]) {  // 再次递归,直到查找子串j的字符与next j的字符不匹配nextval[i] = j;  } else {nextval[i] = nextval[j];  }} else {j = nextval[j];  // 令j=nextval[j],继续循环}}
}//求KMP算法
int index_KMP(const SString& S, const SString& T) {int next[MAXLEN];    //声明 next数组get_nextval(T, next);   //将子串T传入 nextval数组计算值int i = 1, j = 1;    //i为主串位序,j为子串位序while (i <= S.length && j <= T.length) {   //当主串与子串均没有匹配到末尾时if (j == 0 || S.ch[i] == T.ch[j]) {    //如果j=0或者主串值与子串值相等i++;                               //主串与子串皆后移一位j++;} else {             //如果子串不为0,且主串值与子串值不相等j = next[j];     //子串位序j移动到next[j]所指的位置}}if (j > T.length)        //子串位序j移动到末尾+1,即匹配成功return i - T.length; //返回此时主串匹配部分的首位else                     //匹配失败return -1;           //返回-1
}

求next数组与nextval数组

推算思路

假设我们有一个子串:"ababaaababaa",求它在KMP算法中的next数组与KMP优化算法中的nextval数组。

在朴素的字符串匹配算法中,我们从主串的第一个字符开始,逐个字符与子串进行比较。如果发现不匹配,我们会回到主串的下一个字符,并重新开始与子串的比较。这种方法的效率不高,特别是在主串和子串相似度高的情况下。

KMP算法通过预处理子串,构建一个部分匹配表(也称为最长前缀后缀表),利用这个表来指导匹配过程。下面是构建next表的步骤:

1)首先,我们观察子串中的每个字符,当前字符未知,找到以当前字符结尾的前1个字符的最长相等前缀和后缀的长度。例如,在子串 "ababaaababaa" 中:

  • 以第一个字符 "a" 匹配失败后,子串j的位置重置到j=0,主串右移,相当于子串从第1个字符之前开始匹配。
  • 以第二个字符 "b" 匹配失败后,子串j的位置重置到j=1,子串的第1个字符a开始匹配。
  • 以第三个字符 "a" 结尾的最长相等前缀和后缀的长度为 0+1(前缀位序1"a"和后缀位序2 "b"并不匹配)。
  • 以第四个字符 "b" 结尾的最长相等前缀和后缀的长度为 1+1(前缀位序1"a"和后缀位序3都是 "a")。
  • 以第五个字符 "a" 结尾的最长相等前缀和后缀的长度为 2+1(前缀位序1、2和后缀位序3、4都是 "ab")。
  • 以第六个字符 "a" 结尾的最长相等前缀和后缀的长度为 3+1(前缀位序1、2、3和后缀位序3、4、5都是 "aba")。
  • 以第七个字符 "a" 结尾的最长相等前缀和后缀的长度为 1+1(前缀位序1"a"和后缀位序6都是 "a")。
  • 以第八个字符 "b" 结尾的最长相等前缀和后缀的长度为 1+1(前缀位序1"a"和后缀位序7都是 "a")。
  • 以第九个字符 "a" 结尾的最长相等前缀和后缀的长度为 2+1(前缀位序1、2和后缀位序7、8都是 "ab")。
  • 以第十个字符 "a" 结尾的最长相等前缀和后缀的长度为 3+1(前缀位序1、2、3和后缀位序7、8、9都是 "aba")。
  • 以第十一个字符 "a" 结尾的最长相等前缀和后缀的长度为 4+1(前缀位序1、2、3、4和后缀位序7、8、9、10都是 "abab")。
  • 以第十二个字符 "a" 结尾的最长相等前缀和后缀的长度为 5+1(前缀位序1、2、3、4\5和后缀位序7、8、9、10、11都是 "ababa")。

2)然后,我们将这些长度填入部分匹配表中。对于子串 "ababaaababaa",对应的部分匹配表如下:

next数组推算
字符ababaaababaa
指针j 位序123456789101112
next[j] 数组011234223456

3)nextval数组:在next数组匹配时,有时可以确定末尾的元素与前缀一定不是可以匹配的元素~

  • 举栗,第五个字符 "a" 结尾的最长相等前缀和后缀的长度为 2+1(前缀位序12与后缀位序34都是 "ab"),但因为在第三轮匹配中,已经可以确定失配的位序5必然为1个不是a的未知数[如果第5位是a,就不会失配]~

  • 因此位序3失配,我们再向前查询位序3的nextval值,发现是位序0,已然是到头没有更靠前的元素了~

  • 位序3填写0,表示主串指针直接后移,不必再向后匹配子串j~

4)同理,在next数组中,发现next数组所指向位序的元素与现在位序相等的元素时,全部置为前1个位序的next值,完成的表如下所示~

nextval数组推算
字符ababaaababaa
指针j 位序123456789101112
next[j] 数组011234223456
nextval[j] 数组010104210104

5)无论是KMP算法还是KMP优化算法,只有在文本量大且重复率高时有明显的效率提升,实际应用中暴力朴素算法还是蛮常见的~


结语

博文写得模糊或者有误之处,欢迎小伙伴留言讨论与批评~😶‍🌫️

码字不易,若有所帮助,可以点赞支持一下博主嘛?感谢~🫡

相关文章:

数据结构04:串的存储结构与KMP算法

前言 参考用书&#xff1a;王道考研《2024年 数据结构考研复习指导》 参考用书配套视频&#xff1a;4.1_1_串的定义和基本操作_哔哩哔哩_bilibili 特别感谢&#xff1a; Google Bard老师[解释KMP&#xff0c;修改BUG]、Chat GPT老师[修改BUG]、BING老师[封面图]~ 当我请求BI…...

零基础快速搭建私人影音媒体平台

目录 1. 前言 2. Jellyfin服务网站搭建 2.1. Jellyfin下载和安装 2.2. Jellyfin网页测试 3.本地网页发布 3.1 cpolar的安装和注册 3.2 Cpolar云端设置 3.3 Cpolar本地设置 4.公网访问测试 5. 结语 转载自cpolar极点云的文章&#xff1a;零基础搭建私人影音媒体平台【…...

C++map和set

目录&#xff1a; 什么是关联式容器&#xff1f;键值对树形结构的关联式容器 set的概念multiset的使用pair和make_pair map的概念用“[]”实现统计水果的次数 multimap的使用 什么是关联式容器&#xff1f; 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比…...

python接口测试之测试报告

在本文章中&#xff0c;主要使用jenkins和编写的自动化测试代码&#xff0c;来生成漂亮的测试报告&#xff0c;关于什么是CI这些我就不详细的介绍了&#xff0c;这里我们主要是实战为主。 首先搭建java的环境&#xff0c;这个这里不做介绍。搭建好java的环境后&#xff0c;在h…...

HGFormer:用于领域广义语义分割的层级式分组Transformer

文章目录 HGFormer: Hierarchical Grouping Transformer for Domain Generalized Semantic Segmentation摘要本文方法实验结果 HGFormer: Hierarchical Grouping Transformer for Domain Generalized Semantic Segmentation 摘要 目前的语义分割模型在独立同分布条件下取得了…...

async函数用法

目录 1.概念 2.本质 3.语法 4.特点 5.async基本使用 6.async里的await普通函数返回值 7.async里的await Promise函数成功返回值 8.async里的await Promise函数失败返回值 9.解决async里的await Promise函数失败后不执行下面内容 1.概念 真正意义上解决异步回调的问题&am…...

简谈软件版本周期 | Alpha、Beta、RC、Stable版本之间的区别

目录 &#x1f48c; 引言 ⭕ 软件版本周期 &#x1f6e0;️ 软件开发期 ⚖️ 软件完成期 &#x1f4b0; 商业软件版本 &#x1f48c; 引言 定义好版本号&#xff0c;对于产品的版本发布与持续更新很重要&#xff1b;但是对于版本怎么定义&#xff0c;规则如何确定&#x…...

VS2022发布独立部署的.net程序

.net core支持依赖框架部署和独立部署两种方式&#xff0c;之前学习时是在VSCode中使用dotnet命令发布的。但是在VS2022中却不知道该如何设置。以获取PDF文件使用字体的项目为例&#xff0c;VS2022中默认编译的是依赖框架部署方式&#xff08;编译的结果如下图所示&#xff09;…...

5-网络初识——封装和分用

目录 1.数据封装的过程 2.数据分用的过程 PS&#xff1a;网络数据传输的基本流程&#xff08;以QQ为例&#xff0c;A给B发送一个hello&#xff09;&#xff1a; 一、发送方&#xff1a; 二、接收方&#xff1a; 不同的协议层对数据包有不同的称谓&#xff0c;在传输层叫做…...

机器学习——特征工程

对于机器学习特征工程的知识&#xff0c;你是怎样理解“特征” 在机器学习中&#xff0c;特征&#xff08;Feature&#xff09;是指从原始数据中提取出来的、用于训练和测试机器学习模型的各种属性、变量或特点。特征可以是任何类型的数据&#xff0c;例如数字、文本、图像、音…...

ubuntu安装搜狗输入法,图文详解+踩坑解决

搜狗输入法已支持Ubuntu16.04、18.04、19.10、20.04、20.10&#xff0c;本教程系统是基于ubuntu18.04 一、添加中文语言支持 系统设置—>区域和语言—>管理已安装的语言—>在“语言”tab下—>点击“添加或删除语言”。 弹出“已安装语言”窗口&#xff0c;勾选中文…...

docker 数据持久化

目录 一、将本地目录直接映射到容器里&#xff08;运行成容器时候进行映射&#xff09; 二、数据卷模式 1、创建数据卷 2、查看数据卷列表&#xff0c;有哪些数据卷 3、查看某个数据卷 4、容器目录挂载到数据卷 5、数据卷的优势&#xff1a;多个容器共享一个数据卷 默认…...

Pytest运行指定的case,这个方法真的很高效……

Pytest运行指定的case 在测试工作中&#xff0c;当我们写了较多的cases时&#xff0c;如果每次都要全部运行一遍&#xff0c;无疑是很浪费时间的&#xff0c;而且效率低下。 但是有一种方法可以帮助你快速地运行指定的测试用例&#xff0c;提高测试效率&#xff0c;那就是使用…...

操作系统复习2.3.4-进程同步问题

生产者-消费者 系统中有一组生产者进程和一组消费者进程 两者共享一个初始为空&#xff0c;大小为n的缓冲区 缓冲区没满&#xff0c;生产者才能放入 缓冲区没空&#xff0c;消费者才能取出 互斥地访问缓冲区 互斥要在同步之后&#xff0c;不然会导致想要同步&#xff0c;但由…...

3ds MAX 基本体建模,长方体、圆柱体和球体

3ds MAX基本页面如下&#xff1a; 生成新的几何体在右侧&#xff1a; 选择生成的对象类型即可&#xff0c;以下为例子&#xff1a; 1、长方体建模 选择建立的对象类型为长方形 在 任意一个窗口绘制&#xff0c;鼠标滑动 这里选择左上角的俯视图 松开鼠标后&#xff0c;可以…...

搭建个人博客

个人网站用处有很多&#xff0c;可以写博客来记录学习过程中的各种事&#xff0c;不管是新知识还是踩坑记录&#xff0c;写完就丢在网站上&#xff0c;方便日后复习&#xff0c;也可以共享给他人&#xff0c;让其他人避免踩雷。 当然也不仅限于技术性的文章&#xff0c;生活中有…...

JavaScript进阶(下)

# JavaScript 进阶 - 第3天笔记 > 了解构造函数原型对象的语法特征&#xff0c;掌握 JavaScript 中面向对象编程的实现方式&#xff0c;基于面向对象编程思想实现 DOM 操作的封装。 - 了解面向对象编程的一般特征 - 掌握基于构造函数原型对象的逻辑封装 - 掌握基于原型对…...

基于PyQt5的图形化界面开发——堆栈动画演示

目录 0. 前言1. 了解堆栈2.代码实现3. 演示效果其他PyQt5文章 0. 前言 本文使用 PyQt5制作图形化界面演示数据结构中的堆栈操作 操作系统&#xff1a;Windows10 专业版 开发环境&#xff1a;Pycahrm Comunity 2022.3 Python解释器版本&#xff1a;Python3.8 第三方库&…...

2023 年第三届长三角高校数学建模竞赛赛题浅析

为了更好地让大家本次长三角比赛选题&#xff0c;我将对本次比赛的题目进行简要浅析。数模模型通常分为优化、预测、评价三类&#xff0c;而本次数学题目就正好对应着A、B、C分别为优化、预测、评价。整体难度不大&#xff0c;主要难点在于A题的优化以及B、C的数据收集。稍后&a…...

sqlite3免费加密开源项目sqlcipher简单使用

一、概述 使用sqlite3的免费版本是不支持加密的。为了能使用上加密sqlite3&#xff0c;有一个免费的开源项目sqlcipher提供了免费和付费的加密sqlite功能。我们当然选择免费的版本啦。 官方网站&#xff1a; https://www.zetetic.net/sqlcipher/open-source/ 文档目录&#…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

数据库正常,但后端收不到数据原因及解决

从代码和日志来看&#xff0c;后端SQL查询确实返回了数据&#xff0c;但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离&#xff0c;并且ai辅助开发的时候&#xff0c;很容易出现前后端变量名不一致情况&#xff0c;还不报错&#xff0c;只是单…...