图的存储及基本操作总结(邻接矩阵、邻接表)及C/C++代码实现

news2024/9/20 10:51:09

文章目录

  • 前言
  • 一、邻接矩阵
    • 1.概念
    • 2.图像示例
    • 3. 代码实现
    • 注意
    • 邻接矩阵的特点
  • 二、邻接表
    • 1.概念
    • 2.图像示例
    • 3.代码实现
    • 邻接表的特点


前言

图是一种比较复杂的数据结构,每个结点之间可以有多种关系。
所以,一个图可以呈现出千奇百怪的形式。
对于不同的形式的图,我们可以用不同的存储方式来进行存储。

比如说:

  • 对于边比较少而结点很多的图,我们需要把更多的存储空间用于存放顶点的信息,如果两个顶点之间不存在边,那么就不需要花费存储空间来说明这个地方没有边。
  • 对于边比较多而顶点相对没那么多的图,在每一个顶点之间,都很有可能存在边,如果每一条边都单独考虑,会显得比较繁琐。
  • 对于插入和删除边的操作做的比较多的图,我们更希望更快的找到这条边的信息在那个位置,于是具有随机存取性质的数据结构更加实用。

像这样的例子还有很多,下面总结一下最常用的两种存储方式——邻接矩阵和邻接表


一、邻接矩阵

1.概念

所谓邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。

  • 顶点数为n的图G的邻接矩阵为   n × n \ n×n  n×n的二维数组,如果记顶点编号为v1, v2, …, vn,则对于顶点vi和vj,若存在一条边(vi, vj)∈E,则A[i][j] = 1, 否则A[i][j] = 0,即

A [ i ] [ j ] = { 1 ,  若  ( v i , v j )  或  ⟨ v i , v j ⟩  是  E ( G )  中的边  0 ,  若  ( v i , v j )  或  ⟨ v i , v j ⟩  不是  E ( G )  中的边  A[i][j]=\left\{\begin{array}{ll} 1, & \text { 若 }\left(v_{i}, v_{j}\right) \text { 或 }\left\langle v_{i}, v_{j}\right\rangle \text { 是 } E(G) \text { 中的边 } \\ 0, & \text { 若 }\left(v_{i}, v_{j}\right) \text { 或 }\left\langle v_{i}, v_{j}\right\rangle \text { 不是 } E(G) \text { 中的边 } \end{array}\right. A[i][j]={1,0,  (vi,vj)  vi,vj  E(G) 中的边   (vi,vj)  vi,vj 不是 E(G) 中的边 

  • 对于带权图而言,若顶点v,和 v;之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点V和V不相连,则用   ∞ \ \infty  来代表这两个顶点之间不存在边:

A [ i ] [ j ] = { w i j ,  若  ( v i , v j )  或  ⟨ v i , v j ⟩  是  E ( G )  中的边  0  或  ∞ ,  若  ( v i , v j )  或  ⟨ v i , v j ⟩  不是  E ( G )  中的边  A[i][j]=\left\{\begin{array}{ll} w_{i j}, & \text { 若 }\left(v_{i}, v_{j}\right) \text { 或 }\left\langle v_{i}, v_{j}\right\rangle \text { 是 } E(G) \text { 中的边 } \\ 0 \text { 或 }\infty, & \text { 若 }\left(v_{i}, v_{j}\right) \text { 或 }\left\langle v_{i}, v_{j}\right\rangle \text { 不是 } E(G) \text { 中的边 } \end{array}\right. A[i][j]={wij,0  ,  (vi,vj)  vi,vj  E(G) 中的边   (vi,vj)  vi,vj 不是 E(G) 中的边 

2.图像示例

  • 无向图和它的邻接矩阵可以表示为下图形式:
    在这里插入图片描述
  • 有向图和它的邻接矩阵可以表示为下图形式:
    在这里插入图片描述

3. 代码实现

图的邻接矩阵代码实现:

#include<iostream>
#include<string>
#include<assert.h>
using namespace std;

#define MaxVertexNum 100		//顶点数目的最大值
#define INF 0xfffffff
//顶点的数据类型
typedef string VertexType;	
//带权图中边上权值的数据类型	
typedef int EdgeType;
//定义图的类型 
typedef enum GraphType{
	UDG, DG, UDN, DN
}GraphType;	
//邻接矩阵数据结构定义	
typedef struct{
	VertexType Vex[MaxVertexNum];				//顶点表
	EdgeType Edge[MaxVertexNum][MaxVertexNum];	//边表
	int vexnum, arcnum;							//图的当前顶点数和弧数
	GraphType type;								//标记图的类型 
}MGraph, *graph;

void graph_create(MGraph &g);				//图的定义 
int vertex_index(MGraph g, string v);		//返回顶点v的坐标
void graph_add_vertex(MGraph &g, string v);	//添加顶点
bool graph_has_vertex(MGraph &g, string v);	//检查是否存在顶点v
void graph_add_edge(MGraph &g, string v1, string v2);		//添加边 
bool graph_has_edge(MGraph g, string v1, string v2);		//检查是否存在v1->v2的边 
void show_graph(MGraph g);					//打印图 


void graph_create(MGraph &g){	
	string str;
	cout << "请输入要定义的图的类型:" << endl << "UDG(无向图)  DG(有向图)  UDN(无向网)  DN(有向网)" << endl; 
	cin >> str;
	//初始化邻接矩阵 
	for(int i = 0; i < g.vexnum; i++){
		for(int j = 0; j < g.vexnum; j++){
			if(i != j){
				if(str == "UDN" || str == "DN")
					g.Edge[i][j] = INF;
				else g.Edge[i][j] = 0;
			}
			else g.Edge[i][j] = 0;
		}
	}
	if(str == "UDG") g.type = UDG;		//构建无向图
	else if(str == "DG")  g.type = DG;	//构建有向图
	else if(str == "UDN") g.type = UDN;	//构建无向网
	else if(str == "DN")  g.type = DN;	//构建有向网	
}

void graph_add_vertex(MGraph &g, string v){
	if(!graph_has_vertex(g, v)){
		assert(g.vexnum <= MaxVertexNum);
		g.Vex[g.vexnum++] = v;
	}
}
bool graph_has_vertex(MGraph &g, string v){
	for(int i = 0; i < g.vexnum; i++)
		if(g.Vex[i] == v) return true;
	return false;
}

void graph_add_edge(MGraph &g, string v1, string v2){
	if(!graph_has_edge(g, v1, v2)){
		int start = vertex_index(g, v1);
		int end = vertex_index(g, v2);
		if(g.type == UDG){
			g.Edge[start][end] = 1;
			g.Edge[end][start] = 1;
		}else if(g.type == DG){
			g.Edge[start][end] = 1;
		}else if(g.type == UDN){
			cout << "请输入边的权值:";
			cin >> g.Edge[start][end];
			g.Edge[end][start] = g.Edge[start][end]; 
		}else if(g.type == DN){
			cout << "请输入边的权值:";
			cin >> g.Edge[start][end];
		}
	}
}

bool graph_has_edge(MGraph g, string v1, string v2){
	int start = vertex_index(g, v1);
	int end = vertex_index(g, v2);
	assert(start != -1 && end != -1);
	if(g.type == UDG || g.type == UDN){
		//如果是无向图或无向网 
		if(g.Edge[start][end] != 0 && g.Edge[start][end] != INF) return true;
		if(g.Edge[end][start] != 0 && g.Edge[end][start] != INF) return true;	
	}else if(g.type == DG || g.type == DN){
		//如果是有向图或有向网 
		if(g.Edge[start][end] != 0 && g.Edge[start][end] != INF) return true;
	}
	return false;
}

int vertex_index(MGraph g, string v){
	for(int i = 0; i < g.vexnum; i++){
		if(g.Vex[i] == v) return i;
	}
	return -1;
}

void show_graph(MGraph g) {
	cout << "图的邻接矩阵如下所示:" << endl;
    for(int i = 0; i < g.vexnum; i++){
        //cout << g.Vex[i] << " ";
        for(int j = 0; j < g.vexnum; j++){
            if(g.Edge[i][j] == INF)
                cout << "∞" << " ";
            else
            	cout << g.Edge[i][j] << " ";
        }
        cout << endl;
    }
}

void test(MGraph &g){
	int vexNum = 0, edgeNum = 0;
	string str, str1, str2;
	
	cout << "请输入图的顶点的数量:" << endl;
	cin >> vexNum;
	for(int i = 0; i < vexNum; i++){
		cout << "输入顶点" << i+1 << "的信息:"; 
		cin >> str;
		graph_add_vertex(g, str);
	}
	
	cout << "请输入图的边的数量:" << endl;
	cin >> edgeNum;
	for(int i = 0; i < edgeNum; i++){
		cout << "输入第" << i+1 << "条边的首尾顶点:";
		cin >> str1 >> str2;
		graph_add_edge(g, str1, str2);
	}
}

int main(){
	MGraph g;
	graph_create(g);
	test(g); 
	show_graph(g); 
	
	return 0;
}

当然,还可以根据需求,写一些个性化的函数来丰富图的功能。比如graph_destroy用来销毁图,graph_get_edge来获取边的权值,graph_edges_count计算与顶点v有关系的边的数量……

注意

  • 在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可省略)。
  • 当邻接矩阵的元素仅表示相应边是否存在时,EdgeType可采用值为0和1的枚举类型。
  • 无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可采用压缩存储
  • 邻接矩阵表示法的空间复杂度为   O ( n 2 ) \ O(n ^{2})  O(n2),其中n为图的顶点数   ∣ V ∣ \ |V|  V

邻接矩阵的特点

  1. 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素
  2. 对于无向图,邻接矩阵的第   i \ i  i行(或第   i \ i  i列)非零元素(或非   0 \ 0  0元素)的个数正好是顶点   i \ i  i的度   T D ( v i ) \ TD(v _{i})  TD(vi)
  3. 对于有向图,邻接矩阵的第   i \ i  i行非零元素(或非   ∞ \ ∞  元素)的个数正好是顶点   i \ i  i的出度   O D ( v i ) \ OD(v _{i})  OD(vi);第   i \ i  i列非零元素(或非   ∞ \ ∞  元素)的个数正好是顶点i的入度   I D ( v i ) \ ID(v _{i})  ID(vi)
  4. 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大
  5. 稠密图适合使用邻接矩阵的存储表示。
  6. 设图   G \ G  G的邻接矩阵为   A \ A  A   A n \ A ^{n}  An的元素   A n [ i ] [ j ] \ A ^{n}[i][j]  An[i][j]等于由顶点   i \ i  i到顶点   j \ j  j的长度为   n \ n  n的路径的数目。该结论了解即可,证明方法请参考离散数学教材。

二、邻接表

1.概念

当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费。

  • 所谓邻接表,是指对图   G \ G  G中的每个顶点   v \ v  v建立一个单链表,第   i \ i  i个单链表中的结点表示依附于顶点   v \ v  v的边(对于有向图则是以顶点   v \ v  v为尾的弧),这个单链表就称为顶点   v \ v  v的边表(对于有向图则称为出边表)。
  • 边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点。

2.图像示例

  • 顶点表结点的数据结构如下图所示:
    在这里插入图片描述

  • 边表结点的数据结构如下图所示:
    在这里插入图片描述
    顶点表结点顶点域(data)和指向第一条邻接边的指针(firstarc)构成,边表结点(邻接表)由邻接点域(adjvex)和指向下一条邻接边的指针域(nextarc)构成。

  • 无向图和它的邻接表可以表示为下图形式:
    在这里插入图片描述

  • 有向图和它的邻接表可以表示为下图形式:
    在这里插入图片描述

3.代码实现

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<string.h>
//#include<math.h>

#define string char*
#define VertexType string
#define MAXSIZE 100 
#define REALLOCSIZE 50
#define INF 0xfffffff

//边表结点
typedef struct ArcNode{
	int adjvex;		//某条边指向的那个顶点的位置
	ArcNode * next;	//指向下一条弧的指针 
	weight w;		//权值
}ArcNode; 
//顶点表结点
typedef struct VNode{
	VertexType data;	//顶点信息
	ArcNode * first;	//指向第一条依附该顶点的弧的指针
}VNode;
typedef struct GraphRepr{
	VNode * node;		//邻接表
	int vexnum, arcnum;	//图的顶点数和弧数 
}Graph, *graph; 

graph graph_create(void) {
	//初始化一个图的指针 
	graph g = (graph)malloc(sizeof(Graph));
	if(g){
		//初始化邻接表 
		g->node = (VNode*)malloc(MAXSIZE*sizeof(VNode));
		if(!g->node) {
			printf("error\n"); 
			return NULL;
		}
		g->arcnum = g->vexnum = 0;
		return g;
	}
	return NULL;
}

void graph_destroy(graph g) {
    ArcNode *pre, *p;	//定义临时指针 
    char * temp;
	for(int i = 0; i < g->vexnum;i++){
		pre = g->node[i].first;	//指向边表
		temp = g->node[i].data;
		free(temp);
		//等价于链表的销毁
		if(pre != NULL)	{
			p = pre->next;
			while(p != NULL) {
				free(pre);
				pre = p;
				p = pre->next;
			}
			free(pre);
		}
	}
	free(g);
    return;
}

//判断字符串是否相等 
bool is_equal(string s1, string s2){
	//首先判断长度 
	int len_s1 = strlen(s1);
	int len_s2 = strlen(s2);	
	if(len_s1 != len_s2) return false;
	//长度相等后,判断每一个位置的字符 
	for(int i = 0; i < len_s1; i++)
		if(s1[i] != s2[i]) return false;
	return true;
}

void graph_add_vertex(graph g, string v) {	
	if(!graph_has_vertex(g, v)){
		int vlen = strlen(v);
		//判断是否超出邻接表的大小限制 
		if(g->vexnum+1 > MAXSIZE){
			//重新申请一片空间 
			VNode * temp = (VNode*)malloc((g->vexnum+REALLOCSIZE)*sizeof(VNode));
			//将原邻接表的信息复制到新的内存空间 
			for(int i = 0; i < g->vexnum; i++){
				temp[i].data = g->node[i].data;
				temp[i].first = g->node[i].first;
			} 
			g->node = temp;	//新的指针赋给邻接表 
		}
		g->node[g->vexnum].data = (char*)malloc(sizeof(char)*vlen+1);
//		printf("%p\t", strcpy(g->node[g->vexnum].data, v));
//		printf("%p\t", g->node[g->vexnum].data);
//		printf("%p\n", v);
//		int i;
//		for(i = 0; i < vlen; i++)
//			g->node[g->vexnum].data[i] = v[i];
//		v[i] = '\0'; 
		g->node[g->vexnum].first = NULL;		//初始化顶点的依附表结点为空 
		g->vexnum++;
	}	
    return;
}

bool graph_has_vertex(graph g, string v) {
    for(int i = 0; i < g->vexnum; i++)
		if(is_equal(g->node[i].data, v))	//如果能够找到一个顶点的信息为v 
			return true;
	return false;
}

size_t graph_vertices_count(graph g) {
    return g->vexnum;
}

int get_index(graph g, string v){
	for(int i = 0; i < g->vexnum; i++)
		if(is_equal(g->node[i].data, v)) return i+1;	//如果能找到这个结点,返回结点位置
	return -1;	//否则返回-1 
}

void graph_add_edge(graph g, string v1, string v2, weight w){    
	//判断是否存在这两个顶点,如果不存在,添加这些顶点 
	if(!graph_has_vertex(g, v1)) graph_add_vertex(g, v1);
	if(!graph_has_vertex(g, v2)) graph_add_vertex(g, v2); 
	int start = get_index(g, v1);
	int end = get_index(g, v2); 
	//判断是否存在这条边 
	if(!graph_has_edge(g, v1, v2)){	
		//初始化一个边表结点 
		ArcNode * Next = (ArcNode*)malloc(sizeof(ArcNode));
		Next->adjvex = end-1;
		Next->next = NULL;
		Next->w = w;
		//如果start依附的边为空	
		if(g->node[start-1].first == NULL) g->node[start-1].first = Next;
		else{
			ArcNode * temp = g->node[start-1].first;//临时表结点
			while(temp->next) temp = temp->next;	//找到表结点中start-1这个结点的链表的最后一个顶点
			temp->next = Next;						//在该链表的尾部插入这个边表结点 
		}	
		g->arcnum++;	//边的数量++	
	}
    return;
}

bool graph_has_edge(graph g, string v1, string v2) {
    int start = get_index(g, v1);
	int end = get_index(g, v2);
	//如果边表为空,则不存在边 
	if(g->node[start-1].first == NULL) return false;
	
	ArcNode * temp = g->node[start-1].first;	//临时表结点
	while(temp) {
		if(temp->adjvex == end-1) return true;	//如果存在一条v1指向v2的边 
		temp = temp->next;						//指针后移 
	}	
    return false;
}

weight graph_get_edge(graph g, string v1, string v2) {
    double w;
    //如果不存在这条边,返回0 
    if(!graph_has_edge(g, v1, v2)) return 0.0;
    int start = get_index(g, v1);
	int end = get_index(g, v2);
	
	ArcNode * temp = g->node[start-1].first;
	while(temp){
		//找到v1指向v2的边,并返回weight 
		if(temp->adjvex == end-1) return temp->w;
		temp = temp->next;
	} 
	return 0.0;
}

void graph_show(graph g, FILE *output) {
	//先打印每一个顶点信息 
	for(int i = 0; i < g->vexnum; i++){
		fprintf(output, "%s\n", g->node[i].data);
//		printf("%s\n", g->node[i].data);
	}
	//然后打印每一条边 
    for(int i = 0; i < g->vexnum; i++){    	
        ArcNode * Next = g->node[i].first;
        while (Next) {
        	fprintf(output, "%s %s %10.2lf\n", g->node[i].data, g->node[Next->adjvex].data, Next->w);
//        	printf("%s %s %10.2lf\n", g->node[i].data, g->node[Next->adjvex].data, Next->w);
            Next = Next->next;
        }        
    }
    return;
}

邻接表的特点

  1.   G \ G  G无向图,则所需的存储空间为   O ( ∣ V ∣ + 2 ∣ E ∣ ) \ O(|V|+ 2|E|)  O(V+2∣E);若   G \ G  G有向图,则所需的存储空间为   O ( ∣ V ∣ + ∣ E ∣ ) \ O(|V|+ |E|)  O(V+E)。前者的倍数   2 \ 2  2是由于无向图中,每条边在邻接表中出现了两次。
  2. 对于稀疏图,采用邻接表表示将极大地节省存储空间。
  3. 在邻接表中,给定一顶点,能很容易地找出它的所有邻边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为   O ( n ) \ O(n)  O(n)
  4. 但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低。
  5. 有向图的邻接表表示中,求一个给定顶点的出度只需计算其邻接表中的结点个数;但求其顶点的入度则需要遍历全部的邻接表。因此,也有人采用逆邻接表的存储方式来加速求解给定顶点的入度。当然,这实际上与邻接表存储方式是类似的。
  6. 图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序

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

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

相关文章

使用FFMPEG库将YUV编码为H264

准备 ffmpeg 4.4 p准备一段yuv420p的格式的视频原始数据 这里我们使用命令直接提取 ffmpeg -i .\beautlWorld.mp4 -pixel_format yuv420p -s 1280x720 yuv420p_1280x720.yuv 编码流程 大致可以分为以下几步&#xff1a; 1.初始化编码器并设置参数 2.初始化AVPacket和AVFr…

【Java基础 1】Java 环境搭建

&#x1f34a; 欢迎加入社区&#xff0c;寒冬更应该抱团学习&#xff1a;Java社区 &#x1f4c6; 最近更新&#xff1a;2023年4月22日 文章目录 1 java发展史及特点1.1 发展史1.2 Java 特点1.2.1 可以做什么&#xff1f;1.2.2 特性 2 Java 跨平台原理2.1 两种核心机制2.2 JVM…

Activiti入门

目录 一、了解工作流 1、什么是工作流 2、工作流引擎 3、常见工作流引擎 4、Activiti7概述 4.1、Activiti介绍 4.2、建模语言BPMN 4.3、Activiti使用流程 一、了解工作流 1、什么是工作流 工作流&#xff08;Workflow&#xff09;&#xff0c;就是通过计算机对业务流…

SSeg总体思路

1&#xff1a;在train中加载train和test数据集。 2&#xff1a;指定数据集为kitti&#xff0c;确定训练验证的batchsize。 3&#xff1a;提前定义好数据预处理&#xff0c;首先是几何变换&#xff0c;包括随机裁切等。 4&#xff1a;然后是外观变换&#xff0c;包括高斯滤波…

计算同列排斥力的一种可能方法

假设神经网络同列数字之间有一种排斥力&#xff0c;且这种排斥力也与距离的平方成反比。设0是环境&#xff0c;1是粒子&#xff0c;则两个1之间的排斥力就是距离平方的倒数。 考虑任意遥远的两个粒子之间都有排斥力&#xff0c;可以得到同列排斥力的计算方法为 如计算"01…

移动硬盘数据恢复软件实用技巧

在我们日常生活中&#xff0c;移动硬盘已经成为了我们不可或缺的存储设备之一。但是&#xff0c;由于各种原因&#xff0c;移动硬盘中的数据有时会丢失或损坏&#xff0c;这时候我们就需要使用移动硬盘数据恢复软件来帮助恢复数据。那么&#xff0c;移动硬盘数据恢复软件有哪些…

Windows环境下实现设计模式——中介者模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现中介者模式&#xff08;设计模式&#xff09;。 不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff…

modbus指令测试

目录 一.抓包二.modbus与plc三.usb包分析四.编写modbus指令测试五.调试工具 一.抓包 1.串口抓包&#xff1f;wireshark&#xff01;: https://xuxeu.github.io/uart-catch/ 2.Windows&Linux USB抓包方法总结:https://zhuanlan.zhihu.com/p/267820933 3.USB The Setup Pack…

(“树” 之 前中后序遍历 ) 94. 二叉树的中序遍历 ——【Leetcode每日一题】

基础概念&#xff1a;前中后序遍历 1/ \2 3/ \ \ 4 5 6层次遍历顺序&#xff1a;[1 2 3 4 5 6]前序遍历顺序&#xff1a;[1 2 4 5 3 6]中序遍历顺序&#xff1a;[4 2 5 1 3 6]后序遍历顺序&#xff1a;[4 5 2 6 3 1] 层次遍历使用 BFS 实现&#xff0c;利用的就是 BFS…

一文搞懂Java中的异常问题

思考几个问题 1&#xff1a;JavaWeb系统中&#xff0c;我的代码未做任何处理&#xff0c;报错了还会往下执行吗&#xff1f; 2&#xff1a;JavaWeb系统中&#xff0c;我的代码做了 try catch finally, 报错了还会往下执行吗&#xff1f; 3&#xff1a;JavaWeb系统中&#xff0c…

软考高频考点--《项目采购管理》

现在离2023年上半年软考还有一个多月的时间&#xff0c;相信各位小伙伴们已经进入紧张的备考状态了。 小编今天为大家整理了软考的一些高频考点–《项目采购管理》&#xff0c;希望对正在备考软考的你有所帮助&#xff01; 采购是从项目团队外部获得产品、服务或成果的完整的购…

alsa_lib移植到IMX6ULL

简介 ALSA是Advanced Linux Sound Architecture的缩写&#xff0c;目前已经成为了linux下的主流音频体系架构&#xff0c;提供了音频和MIDI的支持。 交叉编译alsa_lib和alsa_utils 下载alsa_lib 在官网中下载AlsaProject 编译 先将文件解压&#xff0c;然后进入alsa_lib…

运筹说 第94期|论文速读之基于关键路径的置换流水车间调度问题

前几期的推送已经讲解了网络计划的基本知识、数学模型和相关算法&#xff0c;相信大家对网络计划已经有了充分的了解&#xff0c;这期小编将带大家一起来读一篇基于关键路径的置换流水车间调度问题的文章。 1.文章信息 题目&#xff1a;An efficient critical path based meth…

手把手教你分析解决MySQL死锁问题

在生产环境中出现MySQL死锁问题该如何排查和解决呢&#xff0c;本文将模拟真实死锁场景进行排查&#xff0c;最后总结下实际开发中如何尽量避免死锁发生。 一、准备好相关数据和环境 当前自己的数据版本是8.0.22 mysql> select version; ----------- | version | --------…

Arduino 多任务软件定时器:Simpletimer库的使用

Arduino 多任务软件定时器:Simpletimer库的使用 📌Simpletimer库Arduino官方介绍信息:https://playground.arduino.cc/Code/SimpleTimer/✨该库也是利用了millis()函数来实现任务轮询的。与之类似的还有ESP8266固件自带的Ticker库,但是Ticker库使用仅限于ESP8266内调用,Si…

Discourse Google Analytics 3 的升级提示

根据 Google 官方的消息&#xff1a; Google Analytics&#xff08;分析&#xff09;4 是我们的新一代效果衡量解决方案&#xff0c;即将取代 Universal Analytics。自 2023 年 7 月 1 日起&#xff0c;标准 Universal Analytics 媒体资源将停止处理新的命中数据。如果您仍在使…

linux-01-基础回顾

文章目录 Linux-Day01课程内容1. 前言1.1 什么是Linux1.2 为什么要学Linux1.3 学完Linux能干什么 2. Linux简介2.1 主流操作系统2.2 Linux发展历史2.3 Linux系统版本 3. Linux安装3.1 安装方式介绍3.2 安装VMware3.3 安装Linux TODO3.4 网卡设置3.5 安装SSH连接工具3.5.1 SSH连…

【Python】【进阶篇】1、Django是什么?

目录 1、Django是什么&#xff1f;1. Django的由来2. Django的命名3. Django的版本发布1) 功能版2) 补丁版3) LTS 版本 4. Django框架的特点 1、Django是什么&#xff1f; Django 是使用 Python 语言开发的一款免费而且开源的 Web 应用框架。由于 Python 语言的跨平台性&#…

IDA调试

IDA动态调试 有时候程序在运行过程中会生成一些关键的数值&#xff0c;而人力通过静态分析的结果模拟程序的运行来推出这些中间的数值可能很麻烦。简单重复的工作是计算机所擅长的而不是人&#xff0c;所以我们可以让这个程序运行起来&#xff0c;得到这些中间过程的数值。这就…

VLAN与access接口、hybrid接口实验

[r1]dhcp enable //开启DHC0功能P [r1-GigabitEthernet0/0/0]int g 0/0/0.1 [r1-GigabitEthernet0/0/0.1]ip add 192.168.1.1 24 [r1-GigabitEthernet0/0/0.1]dhcp select interface //接口地址池 [r1-GigabitEthernet0/0/0.1]dhcp server dns-list 8.8.8.8 [r1-GigabitEthern…