第二章 数据结构(一)——链表,栈和队列与kmp

news2024/9/25 9:29:12

文章目录

    • 链表
    • 栈和队列
      • 表达式运算
    • 单调栈
    • 单调队列
    • kmp
    • 链表练习题
      • 826. 单链表
      • 827. 双链表
    • 栈和队列练习题
      • 828. 模拟栈
      • 3302. 表达式求值
      • 829. 模拟队列
      • 830. 单调栈
      • 154. 滑动窗口
    • kmp练习题
      • 831. KMP字符串

kmp虐我一下午

链表

若用链式结构实现链表,效率低,因为new的开销大
采用数组的方式实现,可以提高某些算法的速度,不会卡时间

  1. 单链表:常被使用为邻接表,存储图和树

实现:
e[i]:数据域,存储val值
ne[i]:指针域,存储下标,指向空节点的ne[i]-1
idx:存储当前使用节点的后一个未使用节点,注意:这样将导致内存泄漏,但算法中不用考虑内存泄漏问题
ene用下标关联,如以下板书
image.png

y总没有仔细区分头节点与头指针两个概念,一开始看注释看得有些懵,后来发现是y总没有特别强调这两个概念。其实y总的链表实现是不带头节点(也就是常说的哨兵位)的实现,也就是说只有头指针。但是代码的注释中却出现了头节点的字眼,以下链表的实现为带头节点版本,因为带上头节点的操作更为灵活

带头节点单链表的模拟:

// ne数组的0号下标作为head指针,e数组的0号下标不存储任何有效数据
// 所以0号位置是头节点的位置,头节点只是为了操作方便,不存储任何有效值
int e[N], ne[N];
int idx = 1;

// 在第k个插入的数之后插入节点
void insert(int k, int x)
{
	e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++;
}

// 头插,在头节点之后插入
void push_front(int x)
{
	insert(0, x);
}

// 删除第k个插入的数之后的节点
void erase(int k, int x)
{
	ne[k] = ne[ne[k]];
}

// 链表的输出
void print()
{
	for (int i = ne[0]; i; i = ne[i]) printf("%d ", e[i]);
}

注意:第k个插入的节点在数组中的下标为k

erasek为0时,表示删除头节点之后的数,也就是第一个插入的数。若链表为不带头节点的实现,那么这个操作就需要特判。带上头节点后,这个操作直接erase(0)即可

  1. 双链表:可用来优化某些问题
    与单链表的指针域不用,双链表的指针域有两个指针。所以使用l[N]r[N]表示其前后(左右)节点,

带头节点双链表的模拟:

int l[N], r[N], e[N];
int idx = 1;

// 初始化
void init()
{
	l[0] = 0, r[0] = 0;
}

// 往第k个插入的节点右边插入节点
void insert(int k, int x)
{
	e[idx] = x;
	r[idx] = r[k], l[idx] = k, l[r[k]] = idx, r[k] = idx ++; 
}

// 删除第k个插入的节点
void erase(int k)
{
	r[l[k]] = r[k], l[r[k]] = l[k];
}

栈和队列

用原生数组模拟实现栈与队列,速度会比STL快一些

栈的模拟:

// 初始化,stk为存储栈元素的原生数组,tt为栈顶指针
// 注意:tt一开始指向0,但是栈元素从1下标开始使用
// 所以当tt指向0时,说明栈为空
int stk[N], tt;

// 入栈
stk[++ tt] = x;

// 出栈
-- tt;

// 判栈
return tt == 0;

// 返回栈顶元素
return stk[tt];

注意,以上代码没有合法性判断,只关注结构的实现

队列的模拟:

// 初始化,q为存储队列元素的原生数组
// tt为队尾指针,hh为队头指针
// tt一开始指向-1,hh一开始指向0,此时队列为空
// 也就是tt < hh时,队列为空,tt >= hh时,队列不为空
int q[N], hh, tt = -1;

// 入队
q[++ tt] = x;

// 出队
++ hh;

// 判空
return tt < hh;

// 返回队头元素
return q[hh];

表达式运算

表达式求值也是一个模板,具体看练习题


单调栈

运用场景:给定一个序列 a n a_n an,求这个序列中每一个数的左边,距离它最近,且比它小的数在什么地方?输出这个序列

首先思考暴力解法,遍历序列中的每个数 a k a_k ak,对于每次的遍历,从 a k − 1 a_{k-1} ak1开始往左遍历。直到找到比 a k a_k ak小的数或者遍历完左边的所有数,输出该数(若遍历完所有数,输出-1)并遍历下一个数 a k + 1 a_{k+1} ak+1

a k a_k ak左边的数中,对于其中两个元素 a i a_i ai a j a_j aj,若满足i < j && a i a_i ai > a j a_j aj
因为 a i a_i ai > a j a_j aj,且 a i a_i ai a j a_j aj后遍历到,不论 a j a_j aj a k a_k ak是否满足小于关系,对于 a k a_k ak的遍历,因为 a j a_j aj的存在, a i a_i ai永远不可能是比 a k a_k ak小的数
a j a_j aj大于 a k a_k ak,那么大于 a j a_j aj a i a_i ai肯定是大于 a k a_k ak
a j a_j aj小于 a k a_k ak,那么关于 a k a_k ak的遍历最终会在 a j a_j aj停下,不可能遍历到 a i a_i ai

假设我们用栈完成暴力解法中对 a k a_k ak的一次遍历,栈存储了 a k a_k ak左边所有的数,并且栈顶为 a k − 1 a_{k-1} ak1
此时我们只要不断地将栈顶元素出栈,判断该元素是否小于 a k a_k ak,直到栈为空或者找到小于 a k a_k ak的数

根据刚才的推导,对于 a k a_k ak左边的所有元素,若满足i < j && a i a_i ai > a j a_j aj,在这样的逆序对中 a i a_i ai是没有必要存储的。因此在元素入栈时,若入栈元素与栈顶元素构成了逆序对,那么删除栈顶元素,直到两者不构成你逆序对或者栈为空,将元素入栈
根据以上入栈算法,我们能保证栈中的元素是单调的,单调上升
当我们要找距离 a k a_k ak最近且在 a k a_k ak左边的元素时,需要先用 a k a_k ak左边的元素构建单调栈,然后按照以上入栈算法将 a k a_k ak入栈
若经过入栈算法维护后的栈为空,说明 a k a_k ak左边没有数小于 a k a_k ak
若经过入栈算法维护后的栈不为空,此时的栈顶为距离 a k a_k ak最近且在 a k a_k ak左边的小于 a k a_k ak的元素

int a[N], stk[N];

for (int i = 0; i < n; ++ i)
{
	// a[i]入栈前的维护
	while (栈不为空 && 栈顶元素与a[i]构成逆序对) pop栈;
	if (栈不为空) 输出栈顶元素;
	else 输出-1;
	将a[i]入栈;
}

单调队列

运用场景:求给定一个数组,求长度为k的滑动窗口中的最大值/最小值
比如输出窗口大小为3的最小值

与单调栈一样,先思考暴力解法,挖掘其单调性,后优化算法

若窗口大小为k,那么从第k个元素开始,往后遍历所有的元素
每次的遍历都要往前遍历k个元素,找出其中的最大值/最小值
这样暴力解决,时间复杂度为O(nk)n为数组长度,k为窗口长度

考虑如何优化:求滑动窗口中的最小值时
假设窗口中有k个元素,对于其中两个元素 a i a_i ai a j a_j aj,满足i < j && a i a_i ai > a j a_j aj
那么根据窗口的滑动, a i a_i ai会比 a j a_j aj先退出滑动窗口,可以理解为队列的先进先出,因为 a i a_i ai a j a_j aj先进入滑动窗口,那么 a i a_i ai就会比 a j a_j aj先退出滑动窗口
a i a_i ai > a j a_j aj,且 a i a_i ai a j a_j aj先退出窗口(i < j)。只要 a i a_i ai a j a_j aj同时存在,滑动窗口的最小值只可能是 a j a_j aj,不可能是 a i a_i ai,所以此时的 a i a_i ai是无效元素,没有必要在窗口中记录该元素

由此,我们可以推导更普遍的情况。即窗口中不允许i < j a i a_i ai > a j a_j aj这样的逆序对出现,即窗口中的元素是单调的,单调上升

a i a_i ai进入窗口时, a i a_i ai将比窗口中所有的元素迟出窗口(存在时间更久)。若队尾(窗口右侧)的元素大于 a i a_i ai a i a_i ai 进入窗口后,将出现逆序对。所以此时需要删除队尾元素,重复上面的比较步骤,直到队尾与 a i a_i ai不构成逆序对,或者队列为空时,比较结束,将 a i a_i ai入队

根据以上的入队算法,我们能保证窗口中的序列是单调上升的,此时窗口中的最小值为窗口左侧(队头)元素
所以对于 a n a_n an这个序列,我们只要线性遍历一次,将每个元素按照以上的入队算法依次入队。每个元素入队后(某个滑动窗口)的最小值就是队头元素,其中涉及到一些边界的细节问题,将在练习题的代码中呈现

// 分别存储所有元素以及滑动窗口中的元素在a数组中下标
int a[N], q[N];

for (int i = 0; i < n; ++ i )
{
	if (队列不为空 && 窗口的长度超过了k) hh ++; // 窗口向右滑动时,删除左侧元素
	// 窗口向右滑动时,元素将要入队
	// 采用双端队列的方式,维护窗口的单调性
	while (队列不为空 && 队尾元素与将入队元素构成逆序对) -- tt; 
	// 窗口向右滑动时,维护完单调性,元素入队
	q[++ tt] = i;
	if (窗口长度为k) 输出最小值(队头元素);
}

kmp

  • S串:文本串,P串:模式串
  • 遍历S串,在S串中找P串

kmp的精髓是next数组,代码中用ne[N]表示该数组
kmp使用双指针实现,用i遍历S串,j遍历P串,其中i不会出现回退,当S[i] != P[j + 1]时, j会发生回退
需要注意的是,S串和P串的0号位置不存储任何值,两串都从1号下标开始存储有效值
并且i从1开始,j从0开始,每次都是比较S[i] == P[j + 1],为什么j不从1开始?这个看个人习惯,j从0开始的话,代码比较好写

现有S串:aaaaaaaaa,P串:aaaac。思考暴力做法,用i遍历S串,尝试以S[i]开头的子串与P串进行匹配,若匹配失败,i ++。尝试以S[i + 1]开头的子串与P串的匹配,可以发现在这个过程中,虽然i不会回退,但是产生了多次重复的比较
比如第一次比较时,S串的前4个字符和P串的前4个字符匹配,但第5个字符不匹配。下一次进行的比较将重复比较前4个字符中的后三个,这样的重复比较有很多。为消除这样的重复比较,可以使用kmp算法

首先需要理解ne数组,理解ne数组首先要理解前缀与后缀两个概念,一个字符串中,后缀的终点固定,前缀的起点固定。现有字符串:ababa,该字符串中后缀的终点为最后一个字符a,前缀的起点为第一个字符a。现有问题,是否存在长度相等并且内容也相等的前缀与后缀(前缀与后缀不包括字符串本身)?

  • 若存在,请返回长度最长的前缀与后缀的长度(或者是长度最长的前缀的终点
  • 若不存在,返回前缀的起点

显然,ababa中,最长前缀的终点为第三个字符,也就是中间的那个a。以上问题应该返回该字符的下标

对于P串的每个子串,都要解决以上问题,问题的答案将构成ne数组
ne数组的含义:ne[j]保存了以P[j]为终点,和该后缀相等,并且以P[0]为起点的最长前缀的终点
ababa的例子中,ne[0] = 0, ne[1] = 0, ne[2] = 1, ne[3] = 2, ne[4] = 3
保存相同的最长前后缀有什么用?在P串尝试匹配S串时,遇到一个不匹配字符,就说明了在P串中,该字符之前的所有字符和S串的某一部分匹配。为减少重复的比较运算,找出这些字符的最长前后缀,因为这些字符和S串的某部分是相同的,那么这些字符串的后缀和S串的某部分的后缀也是相同的,和后缀相同的前缀与S串的某部分的后缀也是相同的。此时我们就能舍弃最少的尾部字符,从前缀的后一个字符开始比较

因此,当S[i] != P[j + 1]时,需要更新j = ne[j]。当前要匹配的是p + 1,说明P[0, j],和S串的某部分匹配,找到此时最长前缀的终点ne[j],再匹配S[i]P[j + 1]。若两字符不匹配,j将一直更新。也就是说P串和S串匹配的字符越来越少,直到j为0,此时退无可退

所以,kmp的比较本质是字符的比较。用i从下标1开始遍历S串,j从下标0开始遍历P串,当S[i] != P[j + 1]时,需要更新j,找到一个最长前缀,用其之后的字符(同样是P[j + 1])再与S[i]比较
当更新结束,可能是两种情况
一是S[i] == P[j + 1],此时j ++,表示P串有字符和S串匹配。若这时的j走到P串结尾,说明找到S串的某个子串和P串匹配。因为kmp需要在S串中,找出所有和P串匹配的子串,因此此时还需要更新j = ne[j]。用相等的最长前缀再次开始匹配
二是S[i] != P[j + 1],说明在S串种,不论怎样更新j,都没有和P[j + 1]匹配的字符。此时什么都不做

在kmp比较前,需要预处理ne数组。ne数组的构建也是双指针,不过是两个指针遍历同一序列,而kmp的比较是两个指针遍历不同序列。i从P串的第二个字符开始(因为此时才有前缀和后缀),i = 2,j从第一个字符开始,j = 0。同样,每次比较P[i] == P[j + 1],若不相等,更新j。更新完成时
P[i] == P[j + 1],j ++
否则说明不论怎样更新都没有P[j + 1] == P[i],此时j不变
最后,ne[i] = j,表示与以i为终点后缀相等的最长前缀的终点是j

以上,描述可能存在着差一错误,在实现代码时,需要注意边界问题
模板:

const int M = 1e6 + 10;
const int N = 1e5 + 10;
char p[N], s[M];
int m, n, ne[N];

for (int i = 2, j = 0; i <= n; ++ i )
{
	while (j && p[i] != p[j + 1]) j = ne[j];
	if (p[i] == p[j + 1]) ++ j;
	ne[i] = j;
}

for (int i = 1, j = 0; i <= m; ++ i )
{
	while (j && s[i] != p[j + 1]) j = ne[j];
	if (s[i] == p[j + 1]) ++ j;
	if (j == n)
	{
		printf("%d ", i - n);
		j = ne[j];
	}
}

链表练习题

826. 单链表

826. 单链表 - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int ne[N], e[N];
int idx = 1;
char op;
int m, x, k;

void insert(int k, int x)
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++;
}

void erase(int k)
{
    ne[k] = ne[ne[k]];
}

int main()
{
    scanf("%d", &m);
    while (m --)
    {
        cin >> op;
        if (op == 'H') scanf("%d", &x), insert(0, x);
        else if (op == 'D') scanf("%d", &k), erase(k);
        else scanf("%d%d", &k, &x), insert(k, x);
         
    }
    
    for (int i = ne[0]; i; i = ne[i]) printf("%d ", e[i]);
    
    return 0;
}

827. 双链表

827. 双链表 - AcWing题库
image.png

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;
int l[N], r[N], e[N];
int idx = 1;
string op;
int m, k, x;

void init()
{
    l[0] = 0, r[0] = 0;
}

void insert(int k, int x)
{
    e[idx] = x;
    l[idx] = k, r[idx] = r[k], l[r[k]] = idx, r[k] = idx ++;
}

void erase(int k)
{
    l[r[k]] = l[k], r[l[k]] = r[k];
}

int main()
{
    scanf("%d", &m);
    while (m -- )
    {
        cin >> op;
        if (op == "L") scanf("%d", &x), insert(0, x);
        else if (op == "R") scanf("%d", &x), insert(l[0], x);
        else if (op == "D") scanf("%d", &k), erase(k);
        else if (op == "IL") scanf("%d%d", &k, &x), insert(l[k], x);
        else scanf("%d%d", &k, &x), insert(k, x);
    }
    
    for (int i = r[0]; i; i = r[i]) printf("%d ", e[i]);
    
    return 0;
}

y总的实现中,将数组的0号与1号位置作为双链表的头尾节点,即带有两个哨兵位的双链表
初始化时:l[1] = 0, r[0] = l,可以发现l[0]以及r[1]没有使用,当然初始化时也可以l[0] = 1, r[1] = 0
这个看个人习惯,我习惯只使用一个头节点,初始化时:l[0] = 0, r[0] = 0
遍历双链表时,从头节点的下一个节点(第一个存储有效数据的节点)开始遍历,直到遇到头节点就结束(因为是循环双链表
y总实现的双链表是不循环的,遍历时遇到尾节点(尾哨兵位)时停下

以及,在双链表的最右侧插入一个节点时,不能insert(idx - 1, x),应该insert(l[0], x)
idx - 1不是链表的最右侧,只有在第idx - 1次插入的节点没有被删除的情况下,该节点才是链表的最右侧
l[0]表示头节点的左侧节点,因为链表的是循环的,所以该节点是链表最右侧的节点

在第k个插入的数左边插入一个节点时,不能insert(k - 1, x),同理,第k个数左边的节点应该是l[k],所以要写insert(l[k], x)


栈和队列练习题

828. 模拟栈

828. 模拟栈 - AcWing题库
image.png

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;
int stk[N], tt;

string str;
int x;

int main()
{
    int m;
    scanf("%d", &m);
    while (m--)
    {
        cin >> str;
        if (str == "push") scanf("%d", &x), stk[++ tt] = x;
        else if (str == "pop") -- tt;
        else if (str == "query") printf("%d\n", stk[tt]);
        else tt ? printf("NO\n") : printf("YES\n");
    }
    
    return 0;
}

3302. 表达式求值

3302. 表达式求值 - AcWing题库
image.png

与后缀表达式求值不同,中缀表达式求值需要使用两个栈模拟。一个数字栈,一个符号栈
遍历表达式,遇到数字直接入栈。遇到符号也要入栈,不过需要判断符号栈的栈顶符号优先级是否大于等于要入栈的符号
若栈顶符号优先级大于等于要入栈的符号,那么需要进行eval运算
eval运算:取出符号栈的栈顶元素一次以及取出数字栈的栈顶元素两次,根据取出的符号进行二元运算,将运算结果入数字栈

遍历表达式时,若符号栈的栈顶符号优先级大于等于要入栈的符号,进行eval运算,直到栈为空或者栈顶符号优先级小于等于要入栈的符号
因为栈的性质决定靠近栈顶的元素先出栈,在表达式计算中,靠近栈顶的符号先被运算
因此我们要使优先级越高的符号越靠近栈顶,从栈底到栈顶,符号的优先级顺序是从低到高升高的
当一个元素入栈后破坏了这种单调性,为了维护单调性,我们需要pop栈顶元素,使栈顶元素的优先级小于要入栈的符号,从而使该符号入栈后,栈仍然具有单调性

当表达式被遍历完,符号栈中还有元素,我们需要不断地进行eval运算,直到符号栈为空
此时数字栈中只有一个元素,该元素就是表达式运算的结果

需要注意的是,表达式中可能含有括号,对于括号我们需要特判。遇到左括号时将其入栈,左括号与右括号之间也具有单调性,注意:括号维护的单调性与原表达式维护的单调性可能不连续
因此维护单调性时,停止维护的情况为:栈空,遇到左括号以及满足了单调性
当遇到右括号时,我们需要不断进行eval运算,直达遇到左括号eval运算停止,最后将左括号出栈

#include <iostream>
#include <unordered_map>
#include <string>
#include <cstring>

using namespace std;

const int N = 1e5 + 10;

int nums[N], ntt, ott;
char op[N];

void eval()
{
    int b = nums[ntt --];
    int a = nums[ntt --];
    int c = op[ott --];
    int x = 0;
    
    if (c == '+') x = a + b;
    else if (c == '-') x = a - b;
    else if (c == '*') x = a * b;
    else x = a / b;
    
    nums[++ ntt] = x;
}

int main()
{
    unordered_map<char, int> pr = { {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2} };
    string str;
    cin >> str;
    int n = str.size();
    for (int i = 0; i < n; ++ i )
    {
        char c = str[i];
        if (isdigit(c))
        {
            int x = 0, j = i;
            while (j < n && isdigit(str[j])) x = x * 10 + str[j] - '0', ++ j;
            nums[++ ntt] = x;
            i = j - 1; // 因为for循环会自增i,所以要-1
        }
        else if (c == '(') op[++ ott] = '(';
        else if (c == ')') 
        {
            while (op[ott] != '(') eval();
            -- ott;
        }
        else 
        {
            while (ott && op[ott] != '(' && pr[c] <= pr[op[ott]]) eval(); 
            op[++ ott] = c;
        }
    }
    
    while (ott) eval();
    
    printf("%d", nums[ntt]);
    
    return 0;
}

829. 模拟队列

829. 模拟队列 - AcWing题库
image.png

#include <iostream>
#include <string>

using namespace std;

const int N = 1e6 + 10;
int q[N], hh, tt = -1;

string op;
int x;

int main()
{
    int m;
    scanf("%d", &m);
    while (m--)
    {
        cin >> op;
        if (op == "push") scanf("%d", &x), q[++ tt] = x;
        else if (op == "pop") ++ hh;
        else if (op == "query") printf("%d\n", q[hh]);
        else printf(tt < hh ? "YES\n" : "NO\n");
    }
    
    return 0;
}

830. 单调栈

830. 单调栈 - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], stk[N];
int tt, n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i ) scanf("%d", &a[i]);
    
    for (int i = 0; i < n; ++ i )
    {
        while (tt && stk[tt] >= a[i]) tt --;
        if (tt) printf("%d ", stk[tt]);
        else printf("-1 ");
        stk[++ tt] = a[i];
    }
    
    return 0;
}

154. 滑动窗口

154. 滑动窗口 - AcWing题库
image.png

代码中检测当窗口左侧所在的位置是否在当前窗口左侧应该在的位置,以此判断是否要更新窗口

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], q[N];
int hh, tt = -1;
int n, k;

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++ i ) scanf("%d", &a[i]);
    
    
    for (int i = 0 ; i < n; ++ i )
    {
        if (tt >= hh && i - k + 1 > q[hh]) hh ++;
        while (tt >= hh && a[i] <= a[q[tt]]) tt --;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    printf("\n");
    
    tt = -1, hh = 0;
    for (int i = 0 ; i < n; ++ i )
    {
        if (tt >= hh && i - k + 1 > q[hh]) hh ++;
        while (tt >= hh && a[i] >= a[q[tt]]) tt --;
        q[++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    printf("\n");
    
    return 0;
}

kmp练习题

831. KMP字符串

831. KMP字符串 - AcWing题库
image.png

#include <iostream>
using namespace std;

const int M = 1e6 + 10;
const int N = 1e5 + 10;
char p[N], s[M];
int m, n, ne[N];


int main()
{
    cin >> n >> p + 1 >> m >> s + 1;
    for (int i = 2, j = 0; i <= n; ++ i )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) ++ j;
        ne[i] = j;
    }
    
    for (int i = 1, j = 0; i <= m; ++ i )
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) ++ j;
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
    }
    
    return 0;
}

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

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

相关文章

软件开发流程

目录 软件软件开发流程的演变 瀑布模型敏捷模型 XPSCRUMDevOps 1.软件 与计算机系统操作有关的计算机程序、可能有的文件、文档及数据。 软件可以分为两种主要类型&#xff1a; 独立软件&#xff1a;独立软件是一种完整的应用程序&#xff0c;可以直接在计算机或移动设备上…

Android系统安全 — 6.2 Ethernet安卓架构

1. Android Ethernet架构介绍 整个Ethernet系统架构如下图所示&#xff1a; 以太网服务&#xff08;EthernetService&#xff09;的启动与注册流程&#xff1b;应用层调用使能ethernet功能的方法流程来分析&#xff0c;从应用层如何将指令一步一步传到底层kernel&#xff1b;…

SAAS-HRM系统概述与搭建环境

SAAS-HRM系统概述与搭建环境 学习目标&#xff1a; 理解SaaS的基本概念 了解SAAS-HRM的基本需求和开发方式掌握Power Designer的用例图 完成SAAS-HRM父模块及公共模块的环境搭建完成企业微服务中企业CRUD功能 初识SaaS 云服务的三种模式 IaaS&#xff08;基础设施即服务…

使用Windows To Go工具制作你的U盘系统【含下载Windows10系统镜像】亲测已成功23.06.21

WinToGo是一款辅助工具&#xff1a;专为能够让你将系统装进U盘&#xff0c;移动硬盘里&#xff0c;让你在任意电脑都能运行U盘里装的系统&#xff01; 一、下载&#xff0c;安装“Windows To Go”工具 1、下载Windows To Go工具 口袋系统WinToGo: 安装Win 10到U盘 2、双击Wi…

从0到1精通自动化测试,pytest自动化测试框架,assert断言(七)

目录 一、前言 二、assert 三、异常信息 四、异常断言 五、常用断言 一、前言 断言是写自动化测试基本最重要的一步&#xff0c;一个用例没有断言&#xff0c;就失去了自动化测试的意义了。什么是断言呢&#xff1f; 简单来讲就是实际结果和期望结果去对比&#xff0c;符…

三分钟学习一个python小知识2-----------我的对python的类(Class)和对象(Object)的理解

文章目录 一、类&#xff08;Class&#xff09;和对象&#xff08;Object&#xff09;是什么&#xff1f;二、Python类和对象的实现1.定义类2.创建对象3.调用类的属性和方法 三、利用python实现了一个动物的类&#xff08;Animal&#xff09;和其两个子类&#xff08;Cat和Dog&…

年轻人存款难,要攒够多少存款才可以体面的养老,结论亮了

这个情况确实值得我们思考。年轻人的经济压力比较大,所以他们普遍存款比较少。而10万元确实是一个比较大的数目,对于一些年轻人来说可能确实很难达到。 然而,我认为这并不是一个“坎”。我们应该鼓励年轻人理财,增加存款,以便应对未来可能出现的各种经济问题。同时,我们…

定义一个一维数组存放10个整数,要求从键盘输入10个数,对其进行求和、求平均、求最大值/最小值及其位置的下标

目录 题目 分析思路 法一&#xff1a;在主函数直接编程 法二&#xff1a;用 调用函数 实现 代码 法一&#xff1a;在主函数直接编程 法二&#xff1a;用 调用函数 实现 题目 定义一个一维数组存放10个整数&#xff0c;要求从键盘输入10个数&#xff0c;对其进行求和、求…

新华三H3C无线控制器AC对接网络准入实现定制化Portal短信认证

随着企业办公信息化的不断发展&#xff0c;企业内网安全也面临着诸多挑战。在包含了无线 WiFi、有线网络的混合网络环境中&#xff0c;员工或访客、外包人员、合作伙伴等用户在接入网络时&#xff0c;如果无需进行身份验证及访问权限的管理&#xff0c;则很可能给不法分子可乘之…

一起Talk Android吧(第五百四十八回:如何创建垂直版SeekBar)

文章目录 概念介绍创建方法示例程序 各位看官们大家好&#xff0c;上一回中咱们说的例子是"蓝牙广播中的厂商数据",本章回中介绍的例子是" 如何创建垂直版SeekBar"。闲话休提&#xff0c;言归正转&#xff0c;让我们一起Talk Android吧&#xff01; 概念介…

基于深度学习的高精度绵羊检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度绵羊检测识别系统可用于日常生活中或野外来检测与定位绵羊目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的绵羊目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

Java基础知识之异常处理

目录 1.Java 异常处理 2.Exception 类的层次 3.Java 内置异常类 4.异常方法 5.捕获异常 6.多重捕获块 7.throws/throw 关键字 7.1 throw 关键字 7.2 throws 关键字 8.finally关键字 8.1 实例--ExcepTest.java 文件代码&#xff1a; 9.try-with-resources 9.1 try-…

外设驱动库开发笔记54:外设库驱动设计改进的思考

不知不觉中我们已经发布了五十多篇外设驱动的文章。前段时间有一位网友提出了一些非常中肯的建议&#xff0c;这也让我们开始考虑怎么优化驱动程序设计的问题。在这一篇中我们将来讨论这一问题。 1、问题分析 首先我们来分析一下网友提出的几点问题。第一点是说在驱动设计时&a…

[年中总结]真正的精彩都在海里

时间过得可真的是快啊,一眨眼就是六月底了 这半年的经历也是挺多彩 二月底的时候,跟老大提出了离职申请,经过 老大/hr/部门 leader 的谈话之后,依然决定离职 四月底拿到离职证明之后,就出去玩了一圈,六月份刚回来,哈哈哈哈 我不知道有没有人和我一样,非常在意能不能在一份工作…

2023个人简历的范文参考(合集)

2023个人简历的范文参考【篇1】 基本信息 姓名&#xff1a;__ 性别&#xff1a;__ 出生日期&#xff1a;__ 籍贯&#xff1a;__市 目前城市&#xff1a;__市 联系电话&#xff1a;__ E-mail&#xff1a;__ 应聘方向 求职行业&#xff1a;金融/投资/证券&#xff0c;银行&#x…

Python 标准库-CSV 文件读写

目录 【1】读取CSV 【2】字典方式写入 【3】常用自定义 csv --- CSV 文件读写 — Python 3.10.11 文档 涉及方法&#xff1a; csv.DictReadercsv.DictWriter 【1】读取CSV def readToCsvFile(csv_path, u_delimiter,, u_doublequoteTrue):read_file []try:with open(cs…

计算机网络23年6月期末复习【更新】

计算机网络23年6月期末复习 题型分布 循环冗余码&#xff08;CRC校验码&#xff09;子网划分IP数据报分片滑动窗口拥塞避免和快速重传TCP三次握手和四次握手路由选择&#xff08;更新路由表&#xff09;1.&#xff08;简答题&#xff09;网络体系结构为什么要采用分层次的结构&…

签约新闻 | 南方传媒携手AMT企源开启各项管理优化工作

南方出版传媒股份公司&#xff08;本文简称“南方传媒”&#xff09;成立于2009年&#xff0c;2016年在上交所挂牌上市&#xff0c;是广东省文化产业第一股、广东文化产业龙头企业、中国出版业重要力量。 在数字化发展浪潮下&#xff0c;为支撑业务数字化转型变革&#xff0c;…

FreeBSD 开源操作系统 30周年纪念特刊

FreeBSD是一种类UNIX操作系统&#xff0c;是经由 BSD、386BSD 和 4.4BSD 发展而来的Unix的一个重要分支。由一些原来 BSD UNIX 的开发者后来转到 FreeBSD 的开发&#xff0c;使得 FreeBSD 在内部结构和系统 API 上和 UNIX 有很大的兼容性。FreeBSD 是开源Free。 FreeBSD 项目庆…

车间现场管理难!车间主管如何掌握管理进阶?

车间是企业的基本层&#xff0c;搞好车间现场管理&#xff0c;有利于企业增强竞争力&#xff0c;提高产品质量和员工素质&#xff0c;保证安全生产&#xff0c;而车间班组长是生产线的主要管理者&#xff0c;是直接“当家人”&#xff0c;对生产现场状况了如指掌&#xff0c;对…