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

news2024/11/24 1:11:42

前言

参考用书:王道考研《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 25

struct 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;
    else
        return -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 25

struct 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;
    else
        return -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优化算法,只有在文本量大且重复率高时有明显的效率提升,实际应用中暴力朴素算法还是蛮常见的~


结语

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/617681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python如何使用空行优化SEO

Python 如何使用空行优化 SEO 在网页排名算法中&#xff0c;空行的使用可以对网页的排名产生影响。在 Python 中&#xff0c;空行的使用也被用来优化代码和提高代码的可读性。本文将介绍如何在 Python 中使用空行来优化代码和优化 SEO。 空行的作用 在 Python 中&#xff0c…

【论文阅读】AlexNet: ImageNet Classification with Deep Convolutional Neural Networks

1. 简介 AlexNet是一个用于图像识别的卷积神经网络&#xff0c;其应用在ILSVRC比赛中&#xff0c;AlexNet所用的数据集是ImageNet&#xff0c;总共识别1000个类别 2. 网络结构 整体网络结果如下图所示&#xff0c;一共有8层&#xff0c;前五层是卷积层&#xff0c;后三层是全…

Kubernetes之pod

Kubernetes之pod 在通过docker运行程序时&#xff0c;我们通常会制作Dockerfile文件构建镜像。也可以基于某个镜像运行容器在容器中安装组件之后&#xff0c;再基于容器生成镜像 使用如下命令可生成镜像&#xff0c;想了解更多参数请添加–help docker build -f Dockerfile路…

(超超详!!)Linux进程间通信-----管道 + 共享内存详解

索引 通信背景管道匿名管道命名管道 共享内存基本概念共享内存如何管理共享内存的相关函数共享内存的删除共享内存的使用 通信背景 进程是具有独立性的,每个进程都有独立的PCB,独立的数据和数据结构,因此进程间想要交互数据,成本会非常高,但有时候需要多进程协同处理同一件事情…

java设计模式(十五)责任链模式

目录 定义模式结构角色职责代码实现适用场景优缺点 定义 责任链模式(Chain of Responsibility) 使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有对象能够处理…

deque介绍

目录 简介&#xff1a; 初识deque deque的底层实现 deque插入 deque的operator[] deque的迭代器 deque的缺陷 与vector比的缺陷 与list相比的缺陷 deque的优势 简介&#xff1a; 这一节不会进行模拟实现&#xff0c;只会聊聊deque的底层 原因是我们学习deque是为了…

RabbitMQ中的AMQP协议与核心组成介绍

前言 在RabbitMQ中为了传输数据&#xff0c;使用的是基于TCP/IP协议构造的AMQP协议。RabbitMQ的核心组成部分包括&#xff1a;Server、Connection、Channel、Message、ExChange、Virtual Host、Bingings、Routing key、Queue AMQP协议 AMQP协议全称&#xff1a;Advanced Mes…

RK3588平台开发系列讲解(驱动基础篇)信号驱动 IO 实验

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、信号驱动 IO 简介二、实验程序2.1、应用程序2.2、驱动程序沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 信号驱动 IO 不需要应用程序查询设备的状态,一旦设备准备就绪,会触发 SIGIO 信号,进而调用注…

论文中文翻译——kAFL Hardware-Assisted Feedback Fuzzing for OS Kernels

本论文相关内容 论文下载地址——26th USENIX Security Symposium论文中文翻译——kAFL Hardware-Assisted Feedback Fuzzing for OS Kernels 文章目录 本论文相关内容前言kAFL&#xff1a;操作系统内核的硬件辅助反馈Fuzzing作者信息论文来源主办方信息摘要1 引言2 技术背景2…

系统U盘制作随记

随身系统U盘制作 最近花了好多时间&#xff0c;废了好多U盘才把这东西搞明白了。 主要是自己的笔记本问题比较多&#xff0c;用实验室的Hp机一下就弄好了。 用这篇博客总结一下自己&#xff0c;然后附上详细的流程以免大家踩坑。 Windows to Go 这个比较容易上手 1. 准备…

EIoT能源物联网在工厂智能照明系统改造项目的应用 安科瑞 许敏

【摘要】&#xff1a;随着物联网技术的发展&#xff0c;许多场所针对照明合理应用物联网照明系统&#xff0c;照明作为工厂的重要能耗之一&#xff0c;工厂的照明智能化控制&#xff0c;如何优化控制、提高能源的利用率&#xff0c;达到节约能源的目的。将互联网的技术应用到工…

MySQ基本操作详解

MySQL的基本操作 首先sql操作中的关键字的是大小写不敏感的&#xff0c;create 和CREATE是一样的。 1.库操作 1. 1查看数据库 show databases;show 和databases 之间有一个或者多个空格注意是databases而不是database结尾分号是英文形式&#xff0c;分号在SQL中是表示一行执…

第三节 循环结构

文章目录 1. while循环1.1 什么是循环?1.2 while 循环1.2.1 语法结构1.2.2 循环中的异类 1.3 while循环使用案例1.3.1 求1~100之间的和1.3.2 求1~100之间偶数之和1.3.3 循环中的"标志变量" 1.4 嵌套循环使用1.4.1 嵌套循环语法结构1.4.2 嵌套练习 1.5 知识扩展 --最…

Mobx+Mobx-React快速上手 简单可扩展的状态管理解决方案

Mobx是Redux之后的一个状态管理库&#xff0c;基于响应式状态管理&#xff0c;整体是一个观察者模式的架构&#xff0c;存储state的store是被观察者&#xff0c;使用store的组件是观察者。Mobx可以有多个store对象&#xff0c;store使用的state也是可以变对象&#xff0c;这些都…

LNMP架构搭建实操(终有弱水替沧海,再无相思寄巫山”大概意思就是,你会遇到很多人,但不会有人像我那么爱你了。)

文章目录 一、安装Nginx服务1.安装依赖包2.创建Nginx运行用户3.编译安装Nginx源码包4.优化路径便于使用5、添加 Nginx 系统服务 二、安装Mysql服务1.安装Mysql环境依赖包2.创建Mysql运行用户3.编译安装4.修改mysql配置文件5.更改mysql安装目录和配置文件的属主属组6.设置路径环…

【Leetcode】77 组合 | 掌握回溯的力量吧!

【1】限制&#xff1a;数字只能够使用一次。 77 组合 栗子&#xff0c;从 { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{1,2,3,4,5,6,7,8,9,10\} {1,2,3,4,5,6,7,8,9,10}中选择4个数&#xff1a; 选择1&#xff0c;从 { 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{2,3,4,5,6…

电力需求侧管理和电力负荷管理数字化解决方案 安科瑞 许敏

摘要&#xff1a;近年来全国用电负荷特别是居民用电负荷的快速增长&#xff0c;全国范围内夏季、冬季用电负荷“双峰”特征日益突出&#xff0c;恶劣气候现象多发增加了电力安全供应的压力。具有随机性、波动性、间歇性特征的可再生能源大规模接入电网对电力系统的稳定性带来新…

视频观看行为高级分析(大数据分析)

今天介绍一下我们的视频观看行为高级分析功能。 一、观看行为分析 观看行为分析&#xff0c;基于Polyv大数据分析&#xff0c;能够以秒为粒度展示观众如何观看您的视频。 视频观看热力图是单次观看行为的图形化表示&#xff0c;Polyv云点播视频的每一次播放&#xff0c;都会产…

基于jupyter的多分类问题练习

文章目录 练习3&#xff1a;多分类问题介绍1 多分类1.1 数据集1.2 数据可视化1.3 逻辑回归的向量化1.3.1 代价函数的向量化1.3.2 梯度的向量化1.3.3 正则化逻辑回归的向量化 1.4 多分类-分类器 1.5 使用分类器进行预测 总结 练习3&#xff1a;多分类问题 介绍 在本练习中&…

Leetcode周赛348

第一题&#xff1a;最小化字符串长度 思路分析 通过分析我们可以发现&#xff0c;只要存在重复的元素就可以继续进行操作所以这里本质上是一道去重的题目去重我们可以使用双指针算法和Set&#xff1b;我们选择使用Set进行去重 class Solution {public int minimizedStringLengt…