数据结构-图结构

news/2024/4/20 3:30:16/

图是最为复杂的数据结构。如果数据元素之间存在一对多或者多对多的关系,那么这种数据的组织结构就叫作图结构。

图的基本概念

图的定义

Graph是由顶点(图中的节点被称为图的顶点)的非空有限集合V与边的集合E(顶点之间的关系)构成的。
若图G中的每一条边都没有方向,则称G为无向图。
若图G中的每一条边都有方向,则称G为有向图。

图的常见术语

顶点的度

依附于某顶点v的边数称为该顶点的度,记作TD(v)
有向图中还有入度和出度的概念。

  • 有向图的顶点v的入度指以顶点v的终点的弧的数目,记作ID(v)
  • 顶点v的出度指以v为起始点的弧的数目,记作OD(v)

入度和出度的和为有向图顶点v的度,即TD(v)=ID(v)+OD(v)

路径

见图知意。就是顶点之间的连线。
路径上所包含的边数m-1为该路径的长度。如图中V1到V3之间的路径长度为2。
有向图的路径是有向的,其中每一条边均为有向边。
带权图的路径长度为所有边上的权值之和。

子图

对于图G=(V,E)和图G'=(V',E'),若存在V'∈VE'∈E,则称图G'G的子图。

上图中:

  • G'中顶点的集合V'={v0,v1,v2,v4}G中顶点集合V={v0,v1,v2,v3,v4}的子集。
  • G'中边的集合E'={(v0,v4),v1,v2}也是G中边的集合E={(v0,v1),(v1,v2),(v2,v3),(v3,v4),(v4,v0)}的子集。

所以G'G的一个子图。

连通图

若无向图的两个顶点之间有路径,则称这两个顶点之间是连通的。
如果无向图中任意两个顶点都是连通的,则称该无向图为连通图,否则该无向图为非连通图
无向图的最大连通子图为该图的连通分量。连通图的连通分量只有一个,就是它本身。
从图的遍历角度来说,从连通图的任意顶点出发进行深度优先搜索或广度优先搜索,都可以访问到图中的所有顶点。
对于非连通图,则需要分别从不同连通分量中的顶点出发进行搜索,才能访问到图中的所有顶点。

对于有向图,若图中一对顶点之间有双向的路径,则称这两点之间是连通的。
若有向图中任意两点之间都是连通的,则称该有向图是强连通的
有向图中最大连通子图被称为有向图的强连通分量。强连通的有向图只有一个强连通分量,就是它本身。
非强连通的有向图可能存在多个强连通分量,也可能不存在强连通分量。

  • 左图为强连通图。
  • 中间不是强连通图,但有一个强连通分量。
  • 右图既不是强连通图,也没有强连通分量。

生成树

若图G为包含n个顶点的连通图,则G中包含其全部n个顶点的一个极小连通子图被称为G的生成树。
G的生成树一定包含且仅包含Gn-1条边。

左图为图G,右图为G的生成树。
如果连通图是一个网络,图的边上带权,则其生成树中的边也带权。那么称该网络中所有带权生成树中权值总和最小的生成树为最小生成树,也叫作最小代价生成树。

图的存储形式

常见的图的存储形式有两种:

  • 邻接矩阵存储
  • 邻接表存储

一般情况下,稠密图多采用邻接矩阵存储,稀疏图多采用邻接表存储。

邻接矩阵存储法也称数组存储法,其核心思想是利用两个数组来存储一个图

  • 一个是一维数组,用来存放图中顶点的数据。
  • 一个是二维数组,用来表示顶点之间的相互关系。

具体来讲。一个具有n个顶点的图G可定义一个数组vertex[n],将该图顶点的数据信息分别存放在对应的数组元素上,也就是将顶点 v i v_i vi的数据信息存放在vertex[i]中。
再定义一个数组A[n][n],称为邻接矩阵,用来存放顶点之间的关系信息。
A [ i ] [ j ] = { 1 当顶点 i 与顶点 j 之间有边时 0 当顶点 i 与顶点 j 之间无边时 A[i][j]=\begin{cases} 1\quad当顶点i与顶点j之间有边时\\ 0\quad当顶点i与顶点j之间无边时 \end{cases} A[i][j]={1当顶点i与顶点j之间有边时0当顶点i与顶点j之间无边时

通过这样一个邻接矩阵就可以把一个图中顶点之间的关系表现出来。
有了邻接矩阵我们就可以对数组vertex中的顶点元素进行操作。
如果通过邻接矩阵表示具有n个顶点的图,则需要占用n×n个存储单元保存顶点之间边的信息,所以空间复杂度为 O ( n 2 ) O(n^2) O(n2)
因此邻接矩阵更适合存储稠密图,如果存储稀疏图,则会造成空间浪费。

邻接表存储法是一种顺序存储的与链式分配相结合的存储方法

由链表和顺序数组组成。其中链表存放边的信息,数组存放顶点的信息。
具体来讲,要为图中每个顶点分别建立一个链表,具有n个顶点的图的邻接表包含n个链表。

每个链表前面设置一个头节点,称为顶点节点。

  • 顶点域data用来存放顶点的数据信息
  • 指针域firstArc指向依附于该顶点的第1条边。

通常将一个图的**n**个顶点节点放到一个数组中进行管理,并用该数组的下标表示顶点在图中的位置。
链表的节点被称为边节点,表示依附于对应的顶点的一条边。

  • adjvex域存放该边的另一端点在顶点数组的下标。
  • weight存放边的权重,对于无权重的图,此项可以忽略。
  • next是指针域,指向下一个边节点。最后一个边节点的next域为null


邻接表由一个顶点数组和一组边链表组成。
顶点数组中存放图的顶点信息,链表中存放图的边信息。
在邻接表中,第 i i i个单链表中的节点表示依附于顶点 v i v_i vi的边。
所谓依附于顶点 v i v_i vi的边,对于有向图来说,就是以顶点 v i v_i vi为尾的边。即从 v i v_i vi指向其他顶点的边。
对于无向图来说。就是与该顶点连接的边 。
所以在无向图的邻接表中,顶点 v i v_i vi的度恰好是第 i i i个链表中边节点的数量。
在有向图的邻接表中,第 i i i个链表中边节点的数量只是顶点 v i v_i vi的出度。

邻接表的实现

邻接表的定义

前面已经说过,邻接表包含由顶点节点构成的数组以及依附于每个顶点的边链表
所以要实现邻接表,需要定义这两部分。
先定义图的顶点类VNode,邻接表中顶点数组的元素就是该类的对象。

// 顶点类型(数组中的结点类型)
class VNode {int data; // 图中顶点中的数据信息,这里是整型(也可定义为其他类型)ArcNode firstarc; // 指向单链表,即指向该顶点的第一条边
}

VNode类中包含两个成员变量:

  • data中存放的是该顶点的数据信息,这里定义的dataint类型,在实际应用中也可以定义为其他类型。
  • firstArc是指向单链表的指针,它是ArcNode类的变量,ArcNode类就是邻接表中单链表的节点类型。

再定义边节点类ArcNode,也就是邻接表中单链表的节点类型。

// 边结点类型(单链表中的结点类型)
class ArcNode {int adjvex; // 该边指向的顶点在数组中的位置(数组下标)ArcNode next; // 指向下一条边的指针ArcNode(int adjvex) {this.adjvex = adjvex;}
}

ArcNode类中也包含两个成员变量:

  • adjvex中保存的是该链表节点代表的边指向的另一端顶点在数组中的下标。
  • next域中保存的是下一个链表节点的地址,也就是指向下一个边节点。

基于以上两个类,就可以定义邻接表存储的图类型。

public class MyGraph {private VNode[] vNodes; // VNode类型的数组,用来存放图中的全部的顶点
}

MyGraph类为我们定义的图类型。在该类中包含了一个VNode类的数组,用来存放每个顶点的信息,包括顶点中的数据和该顶点指向边链表的指针。

图的创建

下面介绍如何用createGraph()函数创建一个图。
先定义好图中顶点之间的连接关系,再使用邻接表结构创建图。

图中包含5个顶点,顶点中的数据分别是2、9、6、3、7,不妨设顶点 v 0 = 2 v_0=2 v0=2, v 1 = 9 v_1=9 v1=9, v 2 = 6 v_2=6 v2=6, v 3 = 3 v_3=3 v3=3, v 4 = 7 v_4=7 v4=7。如果用邻接表存储该图,则邻接表的结构如下图所示:

创建邻接表的过程分为两步:

  • 创建图的顶点数组
  • 创建顶点之间的边,也就是创建每个顶点节点指向的单链表
public void createGraph(int v[], int arc[]) {// 创建一个图,图中的顶点元素由数组v[]指定,边由数组arc[]指定vNodes = new VNode[v.length]; // 创建包含v.length个顶点数组// 首先初始化图中的顶点for (int i = 0; i < v.length; i++) {vNodes[i] = new VNode();vNodes[i].data = v[i]; // 给第i个顶点赋值vNodes[i].firstarc = null; // 初始化firstarc为null}// 创建邻接表中的链表(建立顶点之间边的关系)int index = 0;ArcNode p, q = null;for (int i = 0; i < v.length; i++) {// 内层的for循环负责为顶点vNodes[i]创建存放边信息的链表for (; index < arc.length && arc[index] != -1; index++) {p = new ArcNode(arc[index]);if (vNodes[i].firstarc == null) {firstarc = p; // 顶点vNodes[i]的第一条边} else {q.next = p; // 将依附于vNodes[i]的其他边连接到链表中}q = p;}index++;}
}

函数public void createGraph()的作用是创建一个图的邻接表结构。该函数有两个参数int v[], int arc[]

  • v[]是一个数组,里面存储图中每个顶点的元素,因为图的顶点数据为整型元素,所以这里是一个整型数组。如果顶点中的数据是其他类型,则数组v[]的类型也要随之改变。
  • arc[]是一个整型数组,该数组的作用是定义依附于每个顶点的边信息。

如果我们要创建上图中的邻接表,那么在调用函数时,参数v[]就要指定为{2,9,6,3,7},参数arc[]要指定为{1,2,-1,1,0,-1,0,4,-1,3}。数组arc[]中的-1为分割符,用来分割不同的顶点。数组中的非-1元素为顶点单链表节点中的数据,也就是顶点数组的下标。
通过执行函数createGraph()可以在内存中创建一个图的邻接表结构,该邻接表的顶点数组就是MyGraph类的vNodes成员变量。

图的遍历

图的遍历方式有两种,一种是深度优先搜索遍历,一种是广度优先搜素遍历。

深度优先搜索遍历

深度优先搜索遍历从图中的一个顶点出发,先访问该顶点,再依次从该顶点的未被访问过的邻接点开始继续深度优先搜索遍历。
所以深度优先搜索遍历具有递归结构,是一种基于递归思想的遍历算法。

// 深度优先搜索一个连通图,从vNodes[vIndex]结点开始遍历
private void DFS(int vIndex) {visit(vIndex); // 访问顶点vNodes[vIndex],这里就是打印出该顶点中的数据信息visited[vIndex] = 1; // 将顶点vIndex对应的访问标记置1int w = getFirstAdj(vIndex); // 找到顶点v的第一个邻接点,如果无邻接点,返回-1,如果有则返回该顶点在vNodes中的下标while (w != -1) {if (visited[w] == 0) { // 该顶点未被访问DFS(w); // 递归地进行深度优先搜索*/}w = getNextAdj(vIndex, w); // 找到顶点v的下一个邻接点,如果无邻接点,返回-1,如果有则返回该顶点在vNodes中的下标}
}private void visit(int vIndex) {System.out.print(vNodes[vIndex].data + " ");
}// 对图G=(V,E)进行深度优先搜索的主算法
public void travelByDFS() {visited = new int[vNodes.length]; // 创建数组visited[],用于记录遍历图时已访问的结点for (int i = 0; i < vNodes.length; i++) {visited[i] = 0; // 将标记数组初始化为0}for (int i = 0; i < vNodes.length; i++) {if (visited[i] == 0) { // 若有顶点未被访问,从该顶点开始继续深度优先搜索(访问不同的连同分量)DFS(i);}}
}

为了遍历图中每一个顶点,需要在代码中设置一个访问标志数组visited[],该数组可以定义为MyGraph类的一个成员变量。数组visited[]在主算法函数travelByDFS()中被初始化,其大小要跟图中顶点的数量,即邻接表数组vNode[]的长度一致。
在遍历过程中约定visited[vIndex] == 1,表示图中第vIndex个顶点已经被访问过。visited[vIndex] == 0则表示该顶点未被访问。
在遍历图时,首先要调用主算法函数travelByDFS()。该函数会将visited[]数组中的每一个元素都初始化为0,表示初始时任何顶点 都没有被访问。然后从第1个没有被访问的顶点开始,即满足visited[i]=0的顶点,调用递归函数DFS(i),深度优先搜索遍历整个图。
函数DFS(i)是一个递归函数。在函数DFS(int vIndex)中首先通过visit()函数访问当前顶点vIndex,然后将顶点vIndex对应的访问标记visited[vIndex]置为1,表明该顶点已被访问。
通过getFirstAdj()函数获取当前顶点vIndex的第1个邻接点,将其在vNodes[]中的下标赋值给w,如果顶点vIndex无邻接点,则返回-1.
然后通过一个循环从顶点vIndex的第1个邻接点w开始深度优先搜索。搜索完vIndex的第1个邻接点,调用函数getNextAdj()得到vIndex的下一个邻接点,并将其在vNodes中的下标赋值给w,然后从w开始深度优先搜索,直到getNextAdj()返回-1,表明顶点vIndex的所有邻接点都已被访问。

根据图结构的具体形式给出函数getFirstAdj()getNextAdj()的实现:

// 返回第一邻接点在数组中的下标
private int getFirstAdj(int vIndex) {if (vNodes[vIndex].firstarc != null) {return vNodes[vIndex].firstarc.adjvex;}return -1;
}// 返回顶点v的下一个邻接点在数组中的下标
private int getNextAdj(int vIndex, int w) {ArcNode p;p = vNodes[vIndex].firstarc;while (p != null) {if (p.adjvex == w && p.next != null) {return p.next.adjvex;}p = p.next;}return -1;
}

我们不能仅仅通过一个递归函数DFS()来遍历整个图,是因为DFS()只能遍历到从起始顶点v开始所有与v相通的顶点,即一个连通分量。
如果改图是不连通的,那么仅通过函数DFS()无法遍历所有顶点。所以我们需要对DFS()
外层函数travelByDFS()会通过一个循环操作找出visited[i]==0的顶点,然后调用DFS()从该顶点开始深度优先搜索遍历,这样就可以保证访问到图中的每一个连通分量,从而实现图的遍历。

广度优先搜索遍历

广度优先搜索遍历的思想与深度优先搜索遍历的思想不同,它是一种按层次遍历的算法。
它的遍历顺序是先访问顶点 v 0 v_0 v0,再访问离顶点 v 0 v_0 v0最近的顶点 v 1 v_1 v1 v n v_n vn,然后再访问离顶点 v 1 v_1 v1 v n v_n vn最近的顶点,这样就形成以 v 0 v_0 v0为中心,一层一层向外扩展的访问路径。

void BFS(int vIndex) {visit(vIndex); // 访问顶点vNodes[vIndex],这里就是打印出该顶点中的数据信息visited[vIndex] = 1; // 将顶点v对应的访问标记置1MyQueue queue = new MyQueue(); // 创建一个队列用来存放图顶点对象queue.enQueue(vIndex); // 顶点下标vIndex入队列while (queue.getQueueLength() != 0) {int v = queue.deQueue(); // 将队头元素取出int w = getFirstAdj(v); // 找到顶点v的第一个邻接点,如果无邻接点,返回-1while (w != -1) {if (visited[w] == 0) {visit(w);queue.enQueue(w); /* 顶点w入队列 */visited[w] = 1;}w = getNextAdj(v, w); /* 找到顶点v的下一个邻接点,如果无邻接点返回-1 */}}
}/* 对图G=(V,E)进行广度优先搜索的主算法 */
void travelByBFS() {visited = new int[vNodes.length]; // 创建数组visited[],用于记录遍历图时已访问的结点for (int i = 0; i < vNodes.length; i++) {visited[i] = 0; // 将标记数组初始化为0}for (int i = 0; i < vNodes.length; i++) {if (visited[i] == 0) { // 若有顶点未被访问,从该顶点开始继续深度优先搜索(访问不同的连同分量)BFS(i);}}
}

在遍历图时,首先要调用主算法函数travelByBFS()。该函数将visited[]数组中的每个元素都初始化为0,表示初始时任何顶点都没有被访问。然后从第1个没有被访问的顶点开始,即满足visited[i]==0条件的顶点,调用函数BFS(i),从该顶点开始广度优先搜索遍历整个图。
函数BFS()实现了图的广度优先搜索遍历,可以遍历一个连通图。函数BFS(vIndex)先访问顶点vIndex,再将顶点vIndex对应的访问标记visited[vIndex]置为1,表明该顶点已被访问,然后创建一个队列queue,并将该顶点在顶点数组vNodes[]中的下表vIndex入队列。这里我们使用的是之前我们实现的MyQueue类。
接下来进入二重循环,实现以下操作:

  1. 从队列中取出头元素v
  2. 调用函数getFirstAdj(v)得到该顶点的第1个邻接点在vNodes[]中的下标,并赋值给w
  3. 如果顶点w还未被访问,即visited[vIndex] == 0,则调用函数visit()访问顶点w,并将w入队列,对应的访问标记visited[w]置为1。
  4. 调用函数getNextAdj(v,w)得到顶点v的下一个邻接点w,如果存在邻接点,则跳回到步骤3。如果不存在邻接点,则跳回到步骤1。

循环执行上述操作,直到队列为queue为空,表明该连通图中的每一个顶点都已被访问。这里的函数getFirstAdj()getNextAdj()的实现与深度优先搜索遍历一样,因为构成该图的邻接表数据结构是相同的。

出队列元素入队列元素队列状态
-00
01、2、31、2、3
1-2、3
2-3
344
4--

需要注意的是,入队列和出队列的元素都是顶点在数组**vNode[]**中的下标,并不是顶点中的数据元素。
有些顶点在出队列后并没有继续访问它的邻接点并将邻接点入队列,这是因为它的邻接点在之前已经被访问过了。
只有那些没有被访问过的邻接点才会被访问并进入队列。

为什么不能直接顺序访问顶点数组vNodes[]中的每一个元素?

这里所讲的遍历是按照图的逻辑结构,也就是图中每个顶点之间的关系,对一个图的各个连通分量进行遍历。
在图的遍历过程中,可能存在一些额外操作,比如计算带权有向图的边权之和,计算两顶点之间路径的距离等。
这些操作都必须依赖图的遍历来实现,仅靠访问图中的每个顶点是无法实现的。

案例分析-迷宫问题

如图为一个环形迷宫,S为迷宫的入口,E为迷宫的出口,请给出该迷宫的走法。


迷宫问题有很多解法,将迷宫抽象为图结构,再利用图的遍历来求解是一种比较常用的方法。
图的深度优先搜索遍历是一种试探性地遍历算法。例如,对于相邻的顶点A、B,顶点B已被访问,接下来访问顶点A,如果顶点A没有与之相邻且未被访问的顶点,就要回退到前一个顶点B,然后沿着下一个分支继续探索。这个过程与走迷宫的过程十分相似,当在迷宫中“碰壁”时,就相当于走到了图中的顶点A,此时就要回退到之前的分岔路口,然后沿着另外的路径继续寻找迷宫出口。
我们可以将迷宫的中的起点、分岔路口、阻挡通路的墙壁都抽象为图中的顶点,将迷宫中的路径抽象为图中的边,那么一个迷宫就相当于一个无向图,我们要做的就是在这个无向图中寻找从顶点S到顶点E的通路。
我们这里说的是通路,而非路径,通路商允许包含重复的顶点。
我们可以借助图的深度优先搜索遍历算法解决这个问题,但是要在深度优先搜索遍历的基础上对算法加以改造,以适应题目的要求。
图的遍历要求将图中每个顶点都不重不漏地访问一次,例如在遍历上图中的图结构时。当访问到顶点B时,因为B已没有未被访问过的相邻顶点,所以算法会退回到顶点A,但是并不访问顶点A,因为A已被访问过了,然后访问下一个与A相邻的顶点D,这样顶点A不会被重复访问。但是在走迷宫算法中,A这个顶点仍然要被加入到结果序列,只有这样才能构成一个完整通路,也就是:A-B-A-D
在迷宫中寻找一条从S到E的通路,并不意味着要把图中的每个顶点都访问一遍。这跟现实中走迷宫是一样的,只要找到迷宫的出口即可,无需走完迷宫的每一条路和每一个分岔路口。这也是走迷宫问题和图的遍历最本质的区别。
将迷宫抽象成无向图之后,无向图只包含一个联通分量,所以在解决走迷宫问题时,不需要考虑无向图有多个连通分量的情况。

import java.util.ArrayList;public class Maze {private VNode[] vNodes;int[] visited;ArrayList<Character> res;// 创建一个迷宫public void createMaze(char v[], int arc[]) {vNodes = new VNode[v.length];res = new ArrayList<Character>();for (int i = 0; i < v.length; i++) {vNodes[i] = new VNode();vNodes[i].data = v[i];vNodes[i].firstarc = null;}int index = 0;ArcNode p, q = null;for (int i = 0; i < v.length; i++) {for (; index < arc.length && arc[index] != -1; index++) {p = new ArcNode(arc[index]);if (vNodes[i].firstarc == null) {vNodes[i].firstarc = p;} else {q.next = p;}q = p;}index++;}}// 寻找走迷宫的一条通路public void findPath() {visited = new int[vNodes.length];for (int i = 0; i < vNodes.length; i++) {visited[i] = 0;}res = new ArrayList<Character>();for (int i = 0; i < vNodes.length; i++) {if (vNodes[i].data == 'S') {DFS(i); // 从迷宫入口进入}}}private boolean DFS(int vIndex) {if (visit(vIndex)) {return true;}visited[vIndex] = 1;int w = getFirstAdj(vIndex);while (w != -1) {if (visited[w] == 0) {if (DFS(w)) {return true;}res.add(vNodes[vIndex].data);}w = getNextAdj(vIndex, w);}return false;}private boolean visit(int vIndex) {res.add(vNodes[vIndex].data);if (vNodes[vIndex].data == 'E') {System.out.println(res);return true;}return false;}private int getFirstAdj(int vIndex) {if (vNodes[vIndex].firstarc != null) {return vNodes[vIndex].firstarc.adjvex;}return -1;}private int getNextAdj(int vIndex, int w) {ArcNode p;p = vNodes[vIndex].firstarc;while (p != null) {if (p.adjvex == w && p.next != null) {return p.next.adjvex;}p = p.next;}return -1;}public static void main(String[] args) {char v[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'S' };int arc[] = { 1, 3, 7, -1, 0, -1, 4, -1, 0, 4, 5, -1, 2, 3, -1, 3, -1, 7, -1, 0, 6, -1 };Maze maze = new Maze();maze.createMaze(v, arc);System.out.println();maze.findPath();}}// 边结点类型(单链表中的结点类型)
class ArcNode {int adjvex; // 该边指向的顶点在数组中的位置(数组下标)ArcNode next; // 指向下一条边的指针ArcNode(int adjvex) {this.adjvex = adjvex;}
}// 顶点类型(数组中的结点类型)
class VNode {char data; // 图中顶点中的数据信息,这里是整型(也可定义为其他类型)ArcNode firstarc; // 指向单链表,即指向该顶点的第一条边
}

上述代码中定义了一个迷宫类Maze,它包含三个成员变量:数组vNodes用来保存图结构的顶点信息,数组visited用来记录vNodes中对应顶点是否已被访问过,数组res用来保存最终的结果序列。
函数createMaze()的作用是创建一个迷宫,也就是创建一个图结构。该函数与前面介绍的createGraph函数类似,这里不再赘述。
函数findPath()的作用是寻找一条走迷宫的通路。该函数跟前面介绍过的函数travelByDFS()类似,都是以图中的一个顶点为起点,并调用函数DFS()深度优先搜索图结构。
但是由于该图结构本身只有一个连通分量,所以在函数findPath()只需调用一次**DFS(0)**
函数DFS的作用是深度优先搜索图结构,但是与传统的遍历算法略有不同,在这个DFS()函数中调用的visit()函数不仅会访问顶点信息,还会判断当前访问的顶点是否是迷宫的出口E,如果当前访问的顶点是E,则将结果序列res中记录下来的行走路径打印出来,然后返回true,说明已经找到了一条通路,可以提前结束遍历。
另外在DFS()函数中,当递归调用了函数DFS()并返回后,又回退到第vIndex个顶点,此时需要将顶点信息vNodes[index].data加入结果序列。


http://www.ppmy.cn/news/241101.html

相关文章

【模拟电子技术】理论考核回顾

写在前面&#xff1a; 1&#xff1a;好好学习&#xff0c;早日学会看B站华成英老师的课&#xff0c;不然就会像我一样最后快挂科了。 2&#xff1a;杂谈&#xff1a;我觉得一个“智者”可以因为我不会做题来侮辱我的智商&#xff0c;但是不能借此侮辱我没好好复习。 3&#…

软件测试的案例分析 - 闰年4.1

文章目的 显示不同的博客能获得多少博客质量分 &#xff08;这是关于博客质量分的测试 https://www.csdn.net/qc) 这个博客得了 60 分。 希望获得 70 分左右 正文 我们谈了不少测试的名词, 软件是人写的, 测试计划和测试用例也是人写的, 人总会犯错误。错误发生之后, 总有人…

惠普打印机服务器状态未知,惠普打印机状态显示需要注意

惠普打印机状态显示需要注意&#xff0c;而打印时提示“需要用户干预”&#xff0c;其解决方法如下&#xff1a; 1、删除打印任务。 2、把打印机电源断开一分钟&#xff0c;再连接上及重启电脑。 3、检查usb线连接是否连接牢靠。 4、检查打印机开机后&#xff0c;是否为就绪状态…

安全响应中心 — 垃圾邮件事件报告(6.5)

2023年6月 第二周 样本概况 ✅ 类型1&#xff1a; 携带钓鱼链接的伪造传票邮(URLPhish) 近期&#xff0c;安全团队捕获到一类新的伪造51某票的钓鱼邮件&#xff0c;内容上为伪造的律师事务所传票信息&#xff0c;并诱导收件人点击钓鱼链接。代表样本如下&#xff1a; 结合情…

php 检测密码,密码强度检测_php 密码强度检测代码

摘要 腾兴网为您分享:php 密码强度检测代码&#xff0c;篆刻字典&#xff0c;游戏堂&#xff0c;一加商店&#xff0c;微众银行等软件知识&#xff0c;以及外卖通&#xff0c;变形金刚动态壁纸&#xff0c;唯一优品&#xff0c;会计考试go&#xff0c;玩途旅行&#xff0c;玩鲨…

物联网全栈开发实战系列文章汇总(共880篇,持续更新-2023.06.01)

物联网全栈开发实战(共880篇) 文章目录 物联网全栈开发实战(共880篇)1、Arduino单片机系列1.1 Arduino开发实例(包含各类传感器驱动、通信实例、DIY实例,共114篇)1.2 Arduino与Proteus仿真实例(包含大量设备驱动仿真,共109篇)1.3 Arduino与FreeRTOS编程(共18篇)1.4…

linux dd层次,【linux】dd命令详解

本文转自 上官知识库 http://www.uplook.cn/index-Index-show-view719.html?treeid23 dd是一个linux下的文件复制和转换程序。除非告诉dd命运去进行某种转换操作&#xff0c;否则它只是进行从输入文件到输出文件的复制工作。不过现在dd更多的用来进行磁盘性能的测试&#xff0…

从Linux内核源码到操作系统

Linux源码只有运行起来才能成为操作系统&#xff0c;否则她只能静静的躺在存储介质上沉睡&#xff0c;本文就讲解如何将这个睡美人唤醒&#xff0c;唤醒后给他穿上旗袍她就成为RedHat&#xff0c;给她换上包臀裙她就成为SUSE&#xff0c;再或者给她换上超短裙&#xff0c;她就成…

手工修改HP主机Instance Number三招

有时我们需要更改设备的instance number&#xff0c;&#xff08;例如配置MC的时候&#xff09;&#xff0c;本文给出WTEC的最权威步骤。[more] 其中procedure I比较简单&#xff0c;但并不总是好用&#xff0c;最彻底的还是procedureII。 另外&#xff0c;建议大家先做一个系统…

LoadRunner测试经典案例

LoadRunner测试经典案例使用虚拟用户(Virtualusers)来模拟实际用户对业务系统施加压力。虚拟用户在一个中央控制器(controllerstation)的监视下工作。 在做一个测试方案时&#xff0c;要做的第一件事就是创建虚拟用户执行脚本。LoadRunner提供了Virtual User Generator来录制或…

高可用性oracle配置步骤(HP-Unix环境)

硬件环境&#xff1a; 两台HP Server rp5470小型机&#xff0c;7110磁盘阵列 软件环境&#xff1a; HP-UX B.11.11、MirrorDisk/UX B.11.11、MC / Service Guard A.11.14、Oracle 9i for HP-UX。 3.1准备系统3.1.1编辑安全文件&#xff1a;[/machine01]vi .rhosts文件machin…

如设置oracle在Hp-unix上的自动启动

经常有人会需要Oracle数据库随着操作系统一起启动和关闭&#xff0c;而修改/etc/oratab,设置了oracle SID行启动为Y时实际上是不好用的。本文以一个例子来示范如何去做。[more] 如下给的是一个example&#xff0c;注意部分内容可能要改&#xff0c;比如oracle的用户名。 1、在/…

Hp unix rm文件空间不释放问题解决

1 引言 很多人会遇到在操作系统上rm掉一个大的文件&#xff0c;以解决文件系统超标的问题&#xff0c;可是有的时侯文件删除掉了&#xff0c;而空间却未释放出来。论坛上也常常见这样贴子&#xff0c;为什么会这样&#xff0c;如何解决呢&#xff1f;本文只局限于Hp unix的操作…

Hp-unix下为Oracle配置异步IO

本文给出了hp-unix下为Oracle配置异步IO(AIO)的具体的流程。[more] 源自Metalink Note:139272.1PURPOSE ------- The purpose of this document is to discuss the implementation of asynchronous i/o (aio) on HP-UX, specifically to enable aio for the Oracle RDBMS Serve…

计算机无法发现网络共享打印机,网络打印机,无法被其它电脑识别

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 网络打印机&#xff0c;无法被其它电脑识别&#xff1a;(本文由“浪子海风”经验组编) 我电脑重装过后&#xff0c;其它电脑就识别不了我的网络打印机了。未重装之前都是可以的&#xff0c;每台电脑都能保证正常上网。(大侠们&…

HP FC60 RAID级别更改方案

1 背景 xx用户智能网SDP业务为数据核心部分&#xff0c;现在以一套RP4440双机加一台VA7110和DS2405存储&#xff0c;为了确保SDP业务的在双机同时出现故障时&#xff0c;能向200用户提供业务&#xff0c;特此做一台个SDP业务应急处理机。 2 可行性分析 2.1 现有应急环境硬件配置…

hp-ux UDP 优化

官方文档未有这方面设置&#xff0c;本设置取自于最佳实践[more] 1、永久生效 请将如下几行添加到/etc/rc.config.d/nddconf文件中TRANSPORT_NAME[0]socketsNDD_NAME[0]socket_udp_rcvbuf_defaultNDD_VALUE[0]1048576TRANSPORT_NAME[1]socketsNDD_NAME[1]socket_udp_sndbuf_def…

思科c220 m3服务器准系统,HP DL380G9服务器准系统 P440AR+电池

HDS USP-V P/K ASSY (CACHE) WP641-A HP XP24000 5529251-A 5529220-A HDS USP-V DKC 主电源 HP XP24000 PPD0720 HITACHI AMS 1000 DF700-RKH电源3274575-A HDS USP-V Shared Memory Adapter (U) WP651-A 5529257-A HDS USP-V P/K ASSY (CSW) WP630-A 5529247-A HDS USP-V D…

HPdl580g4服务器电源型号,HP ProLiant DL580 G4

HP ProLiant DL580 G4 语音 编辑 锁定 上传视频 上传视频 本词条缺少概述图&#xff0c;补充相关内容使词条更完整&#xff0c;还能快速升级&#xff0c;赶紧来编辑吧&#xff01; HP ProLiant DL580 G4是由惠普生产的一款服务器&#xff0c;该款服务器的类型为四路处理器的机柜…

ABAP VK11 函数

demo 代码 仅供参考 涉及函数 RV_CONDITION_RESET RV_CONDITION_COPY RV_CONDITION_SAVE CONSTANTS: lc_code_svd1 TYPE kschl VALUE SVD1,lc_mwst TYPE kschl VALUE MWST,lc_cn TYPE aland VALUE CN,lc_valid_to TYPE datum VALUE 99991231,lc_kappl…