数据结构_双链表、循环链表、静态链表

news2024/11/24 17:37:03

目录

1. 双链表

1.1 双链表的初始化

1.2 双链表的插入操作

1.3 双链表的删除操作

1.4 双链表的遍历

2. 循环链表

2.1 循环单链表

2.2 循环双链表

3. 静态链表

4. 顺序表和链表的比较

5. 相关练习


1. 双链表

        单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入、删除操作时),只能从头开始遍历,因为需要从头开始遍历,所以访问其前驱结点的时间复杂度为O(n),访问后继结点的时间复杂度为O(1)。

        由于单链表存在上述的缺点,所以引入双链表,双链表能够非常明显的克服以上的缺点,双链表中有两个指针prior和next,分别指向其前驱结点后继结点

typedef struct DNode  //初始化双链表的结点类型
{
	ElemType data;   //双链表的数据域
	struct DNode *prior, *next;   //双链表的前驱指针和后继指针
}DNode,*DLinklist;

双链表在单链表的结点中增加了一个指向其前驱的prior指针,因此双链表中的按值查找和按位查找的操作与单链表相同。但双链表的插入和删除操作的实现上,与单链表有着较大的不同,这是因为 “链” 变化时也需要对prior指针做出修改,关键在于对链进行修改的操作应该建立在链不能断的前提下。此外,双链表不同于单链表的地方在于双链表可以很方便的找到其前驱结点。所以双链表找到前驱结点和后继结点的时间复杂度相同,因此插入和删除操作的时间复杂度仅为O(1)。

1.1 双链表的初始化

//初始化双链表
bool InitDLinkList(DLinkList &L)
{
	L = (DNode *)malloc(sizeof(DNode));  //动态开辟一个头结点
	if (L == NULL)  //内存不足,分配失败
		return false;
	L->prior = NULL; //头结点的前驱指针永远指向NULL
	L->next = NULL; //初始化时头结点的后继暂时还没有结点
	return true;
}

1.2 双链表的插入操作

双链表不同于单链表,双链表存在两个指针,在进行插入操作时,既需要处理好后继指针,也需要同时处理好前驱指针。

①②③④步骤分别对应于图中的一二三四步;

:s->next=p->next;  //p的后继指针原本指向c,把p的后继指针赋值给s的后继指针,那么s的后继指针就会指向c;

:p->next->prior=s; //把s的地址给到p的后继结点的前驱指针,那么p的后继结点的前驱指针也就是c的前驱指针会指向s;

:s->prior=p; //把p指针的地址给到动态开辟空间s的前驱结点,那么动态开辟空间的前驱指针就会指向p;

:p->next=s; //把s给到p的后继结点,那么p的后继指针就会指向动态开辟的空间s;

注意:①和②步必须在④步之前,否则 *p 的后继结点的指针就会丢掉,导致插入失败。 因为如果先执行④,那么p的后继结点就会指向s,而不会指向p的后一个结点;当④执行完以后,再去执行①,s的后继结点指向p的后继结点时,由于p的后继结点已经指向了s,那么①语句会实现s的后继结点指向s,导致插入失败。

//在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s)
{
	if (p == NULL || s = NULL)
		return false;
	s->next = p->next;
	if (p->next != NULL)//在这里判断p是否有后继结点
		p->next->prior = s;
	s->prior = p;
	p->next = s;
	return true;
}

1.3 双链表的删除操作

:p->next=q->next;    //把q的后继指针赋值给p的后继指针,q的后继指针原本指向c,赋值给p的后继指针以后,p的后继指针会指向c;

:q->next->prior=p;    //把p的地址给q的后继指针的前驱指针,q的后继结点就是c,p给到c的前驱指针,c的前驱指针就会指向p;

 //删除p结点的后继结点
bool DeleteNextDNode(DNode *p)
{
	if (p == NULL)
		return false;
	DNode *q = p->next;  //用指针q指向p 的后继结点
	if (q == NULL)
		return false;
	p->next = q->next;
	if (q->next != NULL)//判断被删除的结点q是不是最后一个结点
		q->next->prior = p;
	free(q);
	return true;
}

1.4 双链表的遍历

//向后遍历
while(p!=NULL)
{
    p=p->next;//依次向后遍历
}
//向前遍历
while(p!=NULL)
{
    p=p->prior;//依次向前遍历
}

2. 循环链表

2.1 循环单链表

循环单链表和单链表的区别在于:表中最后一个结点的指针不是NULL,而是改为指向头结点,如此使得整个链表形成一个环。

单纯的单链表是最后一个结点的指针域是指向NULL,表示链表结束,同时也是判断链表是否结束的一种标志。

循环单链表中,表尾结点 *r 的next域指向L,也就是表尾结点的指针域指向头结点,故表中没有指针域为NULL的结点,因此,循环单链表的判空条件不是头结点的指针是否为空,而是他是否等于头指针。

循环单链表的插入、删除算法与单链表的几乎一致,因为循环单链表是一个 “环” ,所以循环单链表不同的是如果操作在表尾执行,需要让单链表保持继续循环,也就是使单链表始终保持是一个环,因此在任何一个位置上的插入和删除操作都是等价的,无需判断是否是表尾。

在单链表中只能从表头结点开始往后顺序遍历整个链表,而循环单链表可以从表中任意一个结点开始遍历整个链表。

单链表进行操作时,通常设头指针和尾指针;而对循环单链表进行操作时,通常只设尾指针,这是为了使操作效率更高,因为单链表是需要从头指针进行依次遍历的,其时间复杂度为O(n),而从尾指针到头指针,也就是r到r->next,只需要一步操作即可,因此从尾指针找到头指针只需要O(1)的时间复杂度。

2.2 循环双链表

通过对循环单链表的学习,循环双链表建立在双链表的基础之上,双链表具有前驱指针prior和后继指针next,能够弥补单链表单向顺序遍历的缺点,循环双链表就是在双链表的特性基础之上,使双链表形成一个环。循环双链表的头结点prior指针还要指向表尾结点

再循环双链表L中,某结点 *p为尾结点时,p->next=L;当循环双链表为空表时,其头结点的prior域和next域都等于L。

3. 静态链表

静态链表借助数组来描述线性表的链式存储结构,结点也有数据域data和指针域next,不同的是,静态链表的指针是结点的相对地址(数组下标),又称游标和顺序表一样,静态链表也要预先分配一块连续的内存空间。

#define MaxSize 50  //静态链表的最大长度
typedef struct     //静态链表的结构类型定义
{
	ElemType data;  //静态链表的数据域data
	int next;     //静态链表的指针域,静态链表不同于单链表的最大区别就是静态链表的指针是对应数组的下标,官方点的称为游标,需要特别注意
}SLinkList[MaxSize];


//以上的代码等价于


#define MaxSize 10
struct Node
{
    ElemType data;   //静态链表结构类型定义
    int next;        //存储数据元素
}; 
typedef struct Node SLinkList[MaxSize];//定义结构体中一个结构变量
//可以用SLinkList定义“一个长度为MaxSize的Node型数组”

静态链表以next==-1作为其结束的标志。静态链表的插入、删除操作与动态链表的相同,只需要修改指针,而不需要移动元素。

单链表初始化时需要将头结点的指针next指向NULL,静态链表初始化时需要将头结点指针next指向-1,因为-1是其结束标志。

查找:

        静态链表是从头结点出发根据游标挨个往后遍历结点,所以其时间复杂度是O(n)。

插入位序为 i 的结点(这里不妨假设插入位序为5的结点):        

  1.  首先需要申请开辟一个结点,用来存入数据元素;
  2.  从头结点按照游标依次找到位序为i-1的结点,该例子中依次找到位序为4的结点,之所以找到位序为4的结点,是因为位序为4的结点对应下一个一定是位序为5的结点,找到位序为4的结点,更改其游标值,使得下一次遍历时能通过游标找到位序为5的结点;
  3.  修改新结点的next值;将新结点的next值更改为-1,作为静态链表的结束标志;

删除一个结点:

        首先也是开辟一块空间,用来存储数据元素,然后从头结点出发依次找到其前驱结点,修改前驱结点的游标值,使得前驱结点的游标值不再指向被删除结点;最后将被删除结点的游标值设为一个特殊的数值,该数值用来标记空闲结点。可以设为-2 等;

总 结 :

  •         静态链表是用数组的方式实现的列表。
  •         优点:增删操作不需要大量移动元素
  •         缺点:不能随机存取,只能从头结点开始依次往后查找
  •         切记:静态链表一旦建立,其存储容量是固定不能变的。​​​​​​
  •         适用场景:1. 静态链表在不支持指针的高级语言(例如Basic)中,是一个非常巧妙的设计方法。2. 数据元素的数量固定不变的场景(如操作系统的文件分配表FAT)

4. 顺序表和链表的比较

1. 存取读写方式

顺序表是可以顺序存取的,也可以进行随机存取,而链表只能从表头依靠表头指针依次向后遍历存取元素。比方说:第i个位置上执行存取操作,顺序只需要依次随机存取即可完成操作,而链表则需要从表头依次遍历执行i次完成该操作。

2. 逻辑结构与物理结构

采用顺序存储时,逻辑上相邻的元素,对应物理存储位置也相邻。而采用链式存储时,逻辑上相邻的元素,对应的物理存储位置不一定相邻,对应的逻辑关系是通过指针链来表示的。

3. 查找、插入和删除操作

对于按值查找,顺序表无序时,两者的时间复杂度均为O(n);顺序表有序时,可采用折半查找,此时的时间复杂度为O(log以2为底n的对数)。

对于按位查找,顺序表支持随机访问,时间复杂度为O(1),只需要通过一次访问便可以找到相应的元素,而链表则需要通过头指针依次遍历整个链表才能找到相应的元素,因此链表的平均时间复杂度为O(n)。顺序表的插入、删除操作,平均需要移动半个表长的元素。链表的插入、删除操作,只需要修改相关结点的指针域即可。

链表的每个结点都带有指针域,故存储密度不大。

4. 空间分配

顺序存储静态存储分配的情形下,一旦存储空间装满就不能扩充,若再加入新元素,则会出现内存溢出的情形,因此需要预先分配足够大的存储空间。预先分配过大,则会导致顺序表的后部出现大量闲置,造成空间上的浪费;如果预先分配过小,实际使用时,预先分配的空间不够,这时再加入新元素,会造成内存溢出的情形。动态存储分配虽然可以解决内存溢出的问题,也就是存储空间可以随时扩充,但是顺序表逻辑结构上相邻的元素,其物理结构上也相邻,因此需要移动大量的元素,导致操作效率降低。并且根据C语言内存动态开辟的规则,如果内存中有更大块的连续存储空间,那么动态开辟内存优先使用更大块的连续存储空间,将以前不够存储的那块空间舍弃,并将已经存储的数据转移到更大块存储空间上相对应的位置上。而如果没有更大块的连续存储空间,则会导致分配失败。

链式存储的结点空间只在需要时申请分配,只要有内存空间就可以分配,而不用管内存空间是否连续,操作灵活、搞笑。

实际应用中,需要综合考虑,选取存储结构?

1. 基于存储的考虑

在事先无法估计线性表的长度或存储规模时,不建议采用顺序表;链表不用事先估计存储规模,但是链表既需要数据域data,又需要指针域next,因此链表的存储密度较低,显然链式存储结构的存储密度是小于1的。

2. 基于运算的考虑

在顺序表中按位查找时,因为顺序表可以随机访问,所以其时间复杂度为O(1),而链表则需要借助头指针依次遍历,其时间复杂度显然为O(n),所以如果需要经常访问数据元素,显然顺序表优于链表。

在顺序表中进行插入、删除操作时,平均需要移动一半的元素,这是因为顺序表中逻辑结构上相邻的元素,其物理结构上也相邻,因此在顺序表中进行插入、删除操作后,也需要对其后的元素进行前移或者后移的操作。当数据元素信息量较大时,这一影响因素不应被忽视。

3. 基于环境的考虑

顺序表是基于数组的,任何高级语言都有数组类型,因此操作起来也比较简单;链表的操作时基于指针的。相对而言,顺序表的实现更加简单。

5. 相关练习

1. 带头结点的双循环链表L为空的条件是?

L->prior==L&&L->next==L   

因为双循环链表既存在前驱指针prior,也存在后继指针next,所以当两个指针都指向头指针L时表示双循环链表L为空。

2. 一个链表最常用的操作是在末尾插入结点和删除结点,则选用(   )最节省时间?

带头结点的双循环列表

因为在链表的末尾插入和删除一个结点,需要同时更改末尾结点和其前驱结点的指针域,而寻找末尾结点和其前驱结点又需要从头结点依次向后遍历,因此只有带头结点的双循环链表所需要的时间最少。

3. 在双链表中向p所指的结点之前插入一个结点q的操作为?

p->prior->next=q; q->next=p; q->prior=p->prior; p->prior=q;

在双链表中操作时,切记一个原则:先处理插入结点和其后继结点之间的关系,否则就会导致插入失败。

4. 设对n(n>1)个元素的线性表的运算只有4种:删除第一个元素;删除最后一个元素;在第一个元素之前插入新元素;在最后一个元素之后插入新元素,最好使用?

只有头结点指针没有尾结点指针的循环双链表

因为有头结点的情况下才能依次从头结点遍历找到每个结点。

5. 某线性表用带头结点的循环单链表存储,头指针为head,当head->next->next=head成立时,线性表长度可能是(0或者1)。

6. 已知一个带有表头结点的双向循环链表L,结点结构为prev  data  next,其中prev和next分别是指向其直接前驱和直接后继结点的指针。现在要删除指针p所指的结点,正确的语句序列是?

P->next->prev=p->prev; p->prev->next=p->next; free(p); 

7. 已知表头元素为c的单链表在内存中的存储状态如下表所示

现将 f 存放于1014H处并插入单链表,若 f 在逻辑上位于 a 和 e 之间,则a,e,f 的 “链接地址” 依次是 1014H,1004H,1010H。

单链表的链接结构如上图所示:因为题目中说c是表头元素,c的地址为1008H,c的链接地址1000H就等价于c->next,也就是链接到a的地址,依次类推单链表的链接结构如图所示,若此时插入f 元素到a 和 e 之间,则 a f e 三者要保证,a的链接地址一定要指向f 的地址,f 的链接地址一定要指向e 的地址,e 的链接地址并没有规定需要指向什么,在链表中,只有上一个结点的指针指向下一个结点,才能保证上一个结点找到下一个结点。

所以a e f 三者的链接地址依次是 1014H,1004H,1010H。

8. 设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点。

//设f(L,x)的功能是删除以L为首结点指针的单链表中所有值等于x的结点
//显然有f(L->next,x)的功能是删除以L->next为首结点指针的单链表中所有值等于x的结点
void Del_x_3(LinkList &L, ElemType x) //递归实现在表L中删除值为x的结点
{
	LNode *p; //定义结点p
	if (L == NULL)
		return;
	//if判断语句实际意思是这样的:假设链表为0 1 2 3 4 5 3 6 3 7,假设x为3,想要删除3,链表是需要依次从头结点开始遍历的
	//如果找到了数据域为3的结点,进入判断语句,此时,让定义的指针p指向数据域为x的结点L
	//L指向下一个结点L->next,让链表开始遍历
	//此时指针p指向数据域为x的结点,free释放指针p指向的结点,达到删除数据为x结点的目的
	//开始递归调用,因为p指针是指向已经找到数据域为x的下一个结点,L=L->next;
	//所以下一次递归从已经找到的结点为x的下一个结点开始递归
	if (L->data == x)
	{
		p = L;    //删除*L,并让L指向下一个结点
		L = L->next;
		free(p);
		Del_x_3(L, x);  //递归调用
	}
	//else表示下一个结点的数据域不是x,这个时候从已经扫描过的结点的下一个结点开始递归,也就是L->next
	else    //else表示L所指的结点的值不为x
		Del_x_3(L->next, x); //递归调用
}

9. 在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。

算法思想一:用p从头至尾扫描单链表,pre指向*p结点的前驱。若p所指结点的值为x,则删除该结点,并让p指向下一个结点,否则让pre、p指针同步后移一个结点。

void Del_x_1(LinkList &L, ElemType x)
{
	LNode *p = L->next, *prev = L, *q;     //这里定义指针p永远指向头指针的下一个结点,指针prev为L的前驱结点,指针q作为过渡指针使用
	while (p != NULL)    //只要L的下一个结点不是NULL空结点,我就执行下述操作
	{
		if (p->data == x)
		{
			q = p;      //让过渡指针(辅助指针)指向这个特殊的结点,也是为了方便后续释放该结点
			p = p->next;      //移动指针p指向下一个结点
			prev->next = p;    //因为上一个操作已经让p指向了下一个结点,此时让前驱指针指向它的下下个结点,就跳过了数据域为x的结点q
			free(q);    //释放结点q,达到删除的目的
		}
		else     //本次扫描的这一个结点的数据域不等于x
		{
			prev = p;     //让前驱指针指向下一个结点
			p = p->next;     //让指针p也指向其下一个结点,其目的是为了保证只要找到数据域为x的结点,就利用其前驱指针指向下下个指针的方法,删除中间结点
		}
	}
}

算法思想二:采用尾插法建立单链表。用p指针扫描L所有结点,当其值不为x时,将其链接到L之后,否则将其释放。

void Del_x_2(LinkList &L, ElemType x)
{
	LNode *p = L->next, *r = L, *q;  //定义结点p指向L的后继结点,r指向尾结点,那么r->next就指向头结点,q作为其辅助结点,也可以称为过渡结点
	while (p != NULL)//只要下一个结点不是空结点NULL,就执行下述操作
	{
		if (p->data!= x)  //p结点的数据域不为x时将其链接到L尾部
		{
			r->next = p;
			r = p;
			p = p->next;  //继续扫描
		}
		else  //else表示此时数据域的内容为x
		{
			q = p;//辅助结点q指向数据域为x的结点
			p = p->next;//继续扫描,一次遍历
			free(q);//释放掉数据域为x的结点
		}
	}
	r->next = NULL;//插入尾结点的后一个结点值为NULL
}

10. 设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

算法思想:可以借助一个栈来实现,每当经过一个结点,就将该结点的数据域内容放进栈中,因为遍历链表一定是从头到尾的,所以栈的最底部一定放的是头结点,依次遍历存放以后,栈的顶部存放的一定是链表尾部结点数据域的内容,这样打印栈即可实现从尾到头输出每个结点值的目的。

同时也可以使用递归来实现,每当访问一个结点时,先递归输出他后面的结点,再输出结点本身,这样循环下来链表就可以实现反向输出了。

void R_Print(LinkList L)
{//实现从尾到头输出的重点在于首先找到最后一个结点,因为链表遍历一定是从头到尾顺序开始遍历的
	if (L->next != NULL)   //只要下一个结点不是空,我就进入下一个结点
	{
		R_Print(L->next);     //因为要反过来输出,所以需要不断递归以找到最后一个结点
	}
	if (L != NULL)
		print(L->data);//判断最后一个结点是否为空结点,如果不是打印该结点的数据域
}

11. 试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。

算法思想:只要是删除结点,一定要去设结点的前驱指针,因为如果让定义的指针指向被删除结点,那么是没有办法删除该结点的,要删除结点,一定要利用其前驱指针指向后继结点进而删除中间结点的方法。

所以用p从头到尾扫描单链表,pre是指向*p结点的前驱,用minp表示保存最小值结点的指针,minpre表示保存最小结点数值的前驱指针。一边扫描,一边进行比较,倘若p->data小于minp->data,就将p pre分别赋值给minp和minpre,使得minp和minpre具有记忆功能,使得两指针永远指向最小值结点和最小值结点的前驱结点,最后利用minpre将minp结点删除,也就是释放该结点。

LinkList delete_Min(LinkList &L)
{
	LNode *pre = L, *p = pre->next;  //p为工作指针,pre为其前驱
	LNode *minpre = pre, *minp = p;  //minp为最小值的工作指针,minpre为最小值结点的前驱指针
	while (p != NULL)
	{
		if (p->data < minp->data)//我们在写程序时默认minp就是存储链表中最小值的数据域的,一旦还有比我们默认的值还小,那么就将更小的值赋给minp
		{
			minp = p;
			minpre = pre;//将其前驱指针也赋值给最小值前驱指针
		}
		pre = p;
		p = p->next;  //继续扫描下一个结点
	}
	minpre->next = minp->next;   //删除最小值结点
	free(minp);
	return L;
}

12. 试编写算法将带头结点的单链表就地逆置。所谓 “就地” 是指辅助空间复杂度为O(1)。

算法思想:将头结点摘下,然后从第一个结点开始,依次插入到头结点的后面(也就是头插法建立单链表),直到最后一个结点为止,这样下来就实现了链表的逆置。

LinkList Reverse_1(LinkList L)  //L是带头结点的单链表,该算法实现将L就地逆序
{
	LNode *p, *r;  //p为工作指针,r为p的后继
	p = L->next;  //p指针指向L的后继结点,表示从第一个元素结点开始
	L->next = NULL;  //先将头结点L的next域置为NULL
	while (p != NULL)
	{
		r = p->next;  //将p的后继指针暂存到r指针中
		p->next = L->next; //以下两步操作实现插入结点操作
		L->next = p;
		p = r;  //将后继结点指针赋值给p,方便下次插入结点
	}
	reture L;
}

13. 有一个带头结点的单链表L,设计一个算法使其元素递增有序。

void Sort(LinkList &L)
{
	LNode *p = L->next, *pre;     //定义头指针和其前驱指针
	LNode *r = p->next;      //指针r是表示第二部分的第一个结点,因为需要拿第二部分的结点依次和第一部分的结点进行比较排序,所以需要先定义第二部分的首结点,防止断链
	p->next = NULL;    //执行断链操作,保证第一部分只有头结点+头结点的后继结点
	p = r;    //因为上面程序已经执行断链操作了,所以需要给第二部分的首结点一个指针r,否则无法操作第二个结点
	while (p != NULL)
	{
		r = p->next;    //保存p的后继结点的指针
		pre = L;    //pre的用处是判断第二部分比较的结点放在第一部分上的什么位置
		while (pre->next != NULL&&pre->next->data < p->data)//因为是递增有序,pre->next->data表示第二部分首结点的数据元素,
			//p->data表示第一部分结点的元素值,进入while循环的条件是第二部分首结点的元素值小于第一部分结点的元素值
			//当然一旦离开结点,那么一定意味着第二部分扫描的结点数据域大小大于了第一部分结点的大小,因为是递增,所以要的就是大于,放在第一部分结点的后部分
		{
			pre = pre->next;   //进入while循环意味着小于,不符合要求,那么pre指针在第二部分依次向后遍历
		}
		p->next = pre->next;    //以下两条代码实现在第一部分后面插入结点,该结点对应的数据域是递增的,因为如果是比第一部分的结点数据域值还小的话,是不允许离开while循环的
		pre->next = p;
		p = r;  //扫描原单链表中剩下的结点
	}
}

14. 给定两个单链表,编写算法找出两个链表的公共结点。

算法思想:先遍历两个链表得到他们的长度,求出两个链表的长度之差。在长的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。

LinkList Search_1st_Common(LinkList L1, LinkList L2)
{
	int len1 = Length(L1), len2 = Length(L2);  //计算两个链表的表长
	LinkList longList, shortList;   //分别指向表长较长和较短的链表
	if (len1 > len2)  //如果L1的表长较长
	{
		longList = L1->next;
		shortList = L2->next;  //使两个表示表长短的指针分别指向L1和L2

		dist = len1 - len2;  //计算表长之差
	}
	else  //否则就是len1小于len2
	{
		longList = L2->next;
		shortList = L1->next;

		dist = len2 - len1;   //计算表长之差
	}
	while (dist--)  //让L1和L2中较长的链表先遍历到第dist个结点,然后同步
	{
		longList = longList->next;  //指针依次向后遍历
		while (longList != NULL)      //同步寻找共同结点
		{
			if (longList == shortList)  //找到第一个公共结点
			{
				return longList;
			}
			else
			{
				longList = longList->next;
				shortList = shortList->next;
			}
		}
	}
	return NULL;
}

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

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

相关文章

电子电气架构——车辆E/E架构软硬件解耦

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 人只有在举棋不定,无从把握的时候才感到疲惫。只有去行动就能获得解放,哪怕做的不好也比无所作为强! 本文主要介绍车辆E/E架构常识,主要涉及内容是行业最…

Python实现LBP纹理提取

1、什么是LBP纹理特征&#xff1f; LBP&#xff08;Local Binary Patterns&#xff0c;局部二值模式&#xff09;是提取局部特征作为判别依据的&#xff0c;一种有效的纹理描述算子&#xff0c;度量和提取图像局部的纹理信息。它具有旋转不变性和灰度不变性等显著的优点&#…

uniapp中使用vuex(解决uniapp无法在data和template中获取vuex数据问题)

uniapp中使用vuex&#xff08;解决uniapp无法在data和template中获取vuex数据问题&#xff09; 1. uniapp中引入vuex2. uniapp中使用vuex3. 解决uniapp无法在data和template中获取vuex数据问题 1. uniapp中引入vuex 1 .在根目录下新建文件夹store,在此目录下新建index.js文件&…

第五章——动态规划3

蒙德里安的梦想 我们在黑框内横着放红框&#xff0c;我们发现当横向小方格摆好之后&#xff0c;纵向小方格只能一次纵向摆好&#xff0c;即纵向小方格只有一种方案&#xff0c;即整个摆放小方格的方案数等于横着摆放小方格的方案数 f[i,j]表示的是现在要在第i列摆&#xff0c;j…

代码随想录Day64(一刷完结)

今天学习单调栈解决最后一道题 84.柱状图中的最大矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,…

C++中的list容器

文章目录 list的介绍list的使用list的构造list iterator的使用list capacitylist元素访问list modifierslist的迭代器失效 list与vector的对比 list的介绍 list是可以在常数范围内的任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代&#xff1b;   …

FFMPEG中的filter使用二

上一篇我们在使用滤镜时是手动创建各种滤镜&#xff0c;然后根据处理链路手动链接不同的过滤器&#xff0c;有助于我们理解滤镜的流程。这一篇我们使用参数形式&#xff0c;让ffmpeg自动帮我们创建和链接过滤器&#xff0c;这样可以减少代码量&#xff0c;同时我们可以先使用参…

学系统集成项目管理工程师(中项)系列15_质量管理

1. 质量&#xff08;Quality&#xff09;的定义 1.1. 反应实体满足主体明确和隐含需求的能力的特性总和 1.2. 明确需求是指在标准、规范、图样、技术要求、合同和其他文件中用户明确提出的要求与需要 1.3. 隐含需求是指用户和社会通过市场调研对实体的期望以及公认的、不必明…

thinkphp路由,请求和响应

文章目录 定义获取路由后面的参数跨域请求请求响应 定义 thinkphp定义路由一般在route路由下的app.php中 下面这是一个简单的路由 Route::rule(admin/login,/app/controller/Admin/login)->middleware(\app\middleware\MyMiddleware::class);该路由表示当访问admin/login时…

人工智能课程笔记(7)强化学习(基本概念 Q学习 深度强化学习 附有大量例题)

文章目录 1.强化学习与深度学习的区别2.强化学习中的基本概念3.强化学习、有监督学习和无监督学习的区别4.强化学习的特点5.离散马尔可夫过程6.马尔可夫奖励过程7.马尔可夫决策过程8.策略学习8.1.策略学习概念8.2.策略评估与贝尔曼方程 9.强化学习的最优策略求解10.基于价值的强…

K8s基础1——发展起源、资源对象、集群架构

文章目录 一、发展起源二、资源对象2.1 集群类2.2 应用类2.3 存储类2.4 安全类 三、集群架构 一、发展起源 K8s官方文档 K8s怎么来的&#xff1f; 十几年来&#xff0c;谷歌内部使用的大规模集群管理系统是Brog&#xff0c;基于容器技术实现了资源管理的自动化和跨多个数据中心…

基于Python的连锁超市收银系统的开发与研究_kaic

基于Python的连锁超市收银系统的开发与研究 摘要&#xff1a;近几年来&#xff0c;国内的连锁超市收银系统也在不断的发展与完善&#xff0c;超市收银系统是一个超市管理的核心&#xff0c;他决定了超市的安全性。目前&#xff0c;大大小小的超市基本上由传统的人工管理逐渐过渡…

IT 面试手册 - 序

IT 面试手册 - 序 前言 首先&#xff0c;感谢你阅读我的文章。作为在计算机互联网行业摸爬滚打近十载的半个过来人&#xff0c;在这里分享一些关于求职面试和自我提升的心得感悟&#xff0c;希望能够给你一些启发。 背景 对于 IT 从业者来说&#xff0c;当今这个时代&#x…

Docker的四种网络模式

1.Host 模式 通常来讲&#xff0c;启动新的Docker容器&#xff0c;都会分配独立的Network Namespace隔离子系统&#xff0c;如果在运行是指定为host模式&#xff0c;那么Docker容器将不会获得一个独立的Network Namespace&#xff0c;而是和宿主机共用一个Network Namespace子…

计算机网络知识复习

目录 TCP/IP协议群做了哪些事情&#xff1f; TCP协议为什么是3次握手&#xff0c;4次挥手&#xff1f; 如果网络延迟是30ms&#xff0c;那么Ping(基于UDP的)一个网站需要多少ms&#xff1f; 如果请求一个HTTP协议的网站&#xff0c;TTFB至少ms&#xff1f; CDN更换图片&am…

WeakMap 与 WeakSet

WeakSet WeakSet 结构与 Set 类似&#xff0c;也是不重复的值的集合。 成员都是数组和类似数组的对象&#xff0c;WeakSet 的成员只能是对象&#xff0c;而不能是其他类型的值。 若调用 add() 方法时传入了非数组和类似数组的对象的参数&#xff0c;就会抛出错误。 const b …

Linux进程间通信 - 信号(signal) 与 管道(pipe) 与 消息队列

什么是进程间通信&#xff0c;就是进程与进程之间进行通信&#xff0c;互相发送消息&#xff1b;可以通过 信号 或者 管道 或者 消息队列 或者 信号量 去通信&#xff01; 目录 一、信号 1. 信号简介 2. 都有那些信号&#xff1f; 3. 注册信号的函数 1). signal 2). sig…

十一、通过六个因素对织物起球等级进行预测

一、需求分析 根据之前做训练的模型&#xff0c;对不同等级的标准样卡进行测试 测试样本有48张&#xff0c;其中包括起球个数、起球总面积、起球最大面积、起球平均面积、对比度、光学体积六个指标&#xff0c;最终确定出织物的等级 数据集fiber.csv大致结构如下&#xff1a; …

微服务保护 笔记分享【黑马笔记】

微服务保护 1.初识Sentinel 1.1.雪崩问题及解决方案 1.1.1.雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 如图&#xff0c;如果服务提供者I发生了故障&#xff0c;当前的应用的部分业务因为依赖于服务I&#xff…

Notion——构建个人知识库

前言 使用Notion快三年了&#xff0c;它All in one的理念在使用以后确实深有体会&#xff0c;一直想找一个契机将这个软件分享给大家&#xff0c;这款笔记软件在网上已经有很多的教程了&#xff0c;所以在这里我主要想分享框架这方面给大家&#xff0c;特别对于学生党、准研究生…