【数据结构】C--单链表(小白入门基础知识)

news2024/11/28 2:47:38

前段时间写了一篇关于顺序表的博客,http://t.csdn.cn/0gCRp

顺序表在某些时候存在着一些不可避免的缺点:

问题:
1. 中间 / 头部的插入删除,时间复杂度为 O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈 2 倍的增长,势必会有一定的空间浪费。例如当前容量为 100 ,满了以后增容到 200,我们再继续插入了 5 个数据,后面没有数据插入了,那么就浪费了 95 个数据空间。
寻求其他解决方案:
1、不扩容
2、按需申请释放
3、解决头部/中间插入删除需要挪动数据的问题(一块连续的物理空间)

                       一.单链表的概念、结构和优缺点

1.1概念

概念:链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表
中的 指针链接 次序实现的 。

1.2单链表的结构

单链表是由一系列结点组成的线性结构,每个结点包含两个域:数据域指针域

数据域用来存储数据,指针域用来存储下一个结点的指针。单链表的头结点指向第一个结点,最后一个结点的指针域为空。

一个结点的结构:

逻辑结构:为了方便形象理解,想象出来的。

 物理结构: 实际内存中,真实的样子。

1.3单链表的优缺点

单链表是一种常见的数据结构,它具有以下优缺点:

优点:

  1.         插入和删除操作效率高:由于单链表的节点包含指向下一个节点的指针,因此在插入和删除节点时,只需要修改指针的指向,不需要移动大量的数据元素,因此效率较高。
  2.         空间利用率高:单链表的节点只包含数据和指针两部分,不需要预先分配内存空间,因此空间利用率相对较高。
  3.         长度可变:单链表的长度可以根据需要动态增长或缩小,不需要预先定义数组大小,具有较好的灵活性。

缺点:

  1.         随机访问效率低:由于单链表的节点只包含指向下一个节点的指针,因此无法直接访问某个节点之前的节点,需要从头节点开始遍历到该节点,因此随机访问效率较低。
  2.         存储空间浪费:由于单链表的每个节点都需要存储下一个节点的指针,因此在存储相同数据量的情况下,单链表需要更多的存储空间。
  3.         链接信息的丢失:单链表中只有指向下一个节点的指针,无法直接访问前一个节点,因此在需要反向遍历链表或者删除节点时,需要保存前一个节点的指针,否则将无法完成操作。

二.单链表的实现

单链表各接口函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;//这样做的目的是为了增加代码的可读性和可维护性,以及提高代码的可移植性,
//因为如果将来需要更改 SLTDataType 的类型,只需要在 typedef 语句中修改一处即可,
// 如果我们在程序的其他地方需要修改 SLTDataType 的类型,
//只需在 typedef 语句中修改 int 为其他类型即可,不需要修改其他代码。
//typedef int SLTADataType;

typedef struct SListNode //--single Linked List
{
	SLTDataType data;//成员变量
	struct SListNode* next;
}SLTNode;

void SLTPrint(SLTNode* phead);

//void SLPushFront(SLTNode* pphead,SLTDataType x);
void SLPushFront(SLTNode** pphead, SLTDataType x);//头部插入

//void SLPushBack(SLTNode* phead, SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);//尾部插入

void SLPopFront(SLTNode** pphead);//头部删除

void SLPopBack(SLTNode** pphead);//尾部删除

//单链表查找
SLTNode* STFind(SLTNode* phead, SLTDataType x);

//单链表pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//单链表pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//单链表pos位置删除
void SLErase(SLTNode** pphead, SLTNode* pos);
//单链表pos之后删除
void SLEraseAfter(SLTNode* phead);

2.1结点的定义

英文简写:

单链表的英文为:Single linked list --简写为SL

而顺序表的英文是:Sequence table -- 简写为Seq

结点的英文为:node

typedef的主要作用有:主要用于提高代码的可读性和可维护性,这样代码的可读性会更好,因为SLTDataType这个名字说明了变量x的类型含义,可以为这个数据类型创建一个更简洁、更明了的别名,这样可以使代码更易读、更易维护。

typedef int SLTDataType;
typedef struct SListNode //--single Linked List
{
	SLTDataType data;//成员变量
	struct SListNode* next;
}SLTNode;

定义了一个单链表节点的结构体 SLTNode,其中包含了两个成员变量:一个名为 data 的int变量 SLTDataType,和一个名为 next 的指向下一个节点的指针。

2.2链表的打印

需要了解到的知识:

                               “指针赋值的本质是将一个指针变量的地址复制给另一个指针变量”

int *p1, *p2; 

p1 = (int*)malloc(sizeof(int)); // 为p1分配内存
*p1 = 10; // 设置p1指向的内存的值为10

p2 = p1; // 将p1的地址赋值给p2

上面代码中,p1和p2都是指针变量。p1被分配了内存,并指向该内存。p2 = p1这行代码将p1的地址复制给了p2,使p2也指向p1所指向的内存。

所以指针赋值后,两个指针变量指向同一块内存,通过任一指针变量访问内存的值都会相同。

指针赋值不会复制指针指向的内存内容,仅仅是将指针变量中的地址值进行复制

画图:

 代码实现:

//函数的作用是遍历单链表,并将每个节点的数据元素打印到屏幕上。
void SLTPrint(SLTNode* phead)//参数是一个指向 SLTNode 类型的指针 phead,表示单链表的头节点。
{
	SLTNode* cur = phead;//头结点存储的地址给cur指针。
	while (cur != NULL)//使用一个while循环对单链表进行遍历,循环条件为 cur 不为 NULL。
	{    //cur 可以表示当前正在处理的节点的地址,
		//通过访问 cur->data 和 cur->next 成员变量,可以获取当前节点的数据元素和下一个节点的地址
		
		printf("%d->", cur->data);
		cur = cur->next;//cur是存着当前结点的地址,cur->next是下一结点地址
	}
	printf("NULL\n");
}


2.3创建一个新结点

SLTNode* BuyLTNode(SLTDataType x)//表示要创建的节点的数据元素。
//函数的作用是创建一个新的单链表节点,并将其初始化为包含数据元素 x 的节点。
{
	//SLTNode node;//这样是不行,处于不同的空间,出了作用域是会销毁的。

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//使用malloc函数动态分配了一个大小为SLTNode的内存块,
	//并将其强制转换为 SLTNode 指针类型,即创建了一个新的节点。
	
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	//需要注意的是,在使用 malloc 函数动态分配内存时,需要手动释放内存,否则会导致内存泄漏
	//因此,在创建单链表节点后,我们应该在适当的时候使用 free 函数释放节点所占用的内存
	
	newnode->data = x;
	newnode->next = NULL;

	return newnode;//返回的是一个结点的地址
}

该函数的作用是创建一个新的单链表节点,并将其初始化为包含数据元素 x 的节点。具体实现过程是通过使用 malloc 函数动态分配内存块,然后将其强制转换为 SLTNode 指针类型,即创建了一个新的节点。然后将该节点的 data 成员设置为 x,next 成员设置为 NULL,最后返回新节点的指针地址。

需要注意的是,在使用 malloc 函数动态分配内存时,需要手动释放内存,否则会导致内存泄漏。因此,在创建单链表节点后,我们应该在适当的时候使用 free 函数释放节点所占用的内存。

2.4单链表尾插

错误代码1:

图解:

void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* tail = phead;
    while (tail != NULL)
    {
          tail = tail->next;
    }

    SLTNode* newnode = BuyLTNode(x);
    tail = newnode;//这个地方没有链接起来
}

原因:
1.链表没有链接起来
2.出了作用域指针变量销毁,内存泄露

 补充一下内存泄露的知识:

如果没有使用free函数释放通过malloc函数分配的内存空间,这些内存空间将一直占用着系统资源,直到程序结束或者操作系统重启。

当程序结束时,操作系统会回收该程序使用的所有内存空间,包括没有通过free函数释放的空间。但是,在程序运行期间,这些没有释放的内存空间会一直占用着系统资源,可能导致系统的内存资源耗尽,从而影响系统的稳定性和性能。

因此,为了避免内存泄漏问题,开发人员需要在程序中显式地使用free函数来释放不再使用的内存空间,以确保系统资源的有效利用。

错误代码2:

void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode* phead, SLTDataType x);
void TestSList1() 
{
    SLTNode* plist = NULL;
    SLPushFront(&plist,1);
    SLPushFront(&plist,2);
    SLPushFront(&plist,3);
    SLPushFront(&plist,4);

    SLTPrint(plist);
    SLPushBack(plist, 5);//这里传了一级指针
    SLTPrint(plist);

}
void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* tail = phead;
    while (tail->next!= NULL)
    {
        tail = tail->next;
    }

    SLTNode* newnode = BuyLTNode(x);
    tail->next = newnode;
}

这种情况是先头插然后再尾插,头插每次都需要二级指针,然而尾插需要第一次是传二级指针,后面可以传一级指针,但是参数是统一的,无法实现写两个相同的函数,第一次传一级指针,后面传二级指针。

所以一致传二级指针。

这种情况前提条件是链表不为空,可以直接修改结构体的next,存下新结点的地址。

执行:

错误代码3:

画图:

void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode* phead, SLTDataType x);

void TestSList2()
{
    SLTNode* plist = NULL;
    SLPushBack(plist, 1);
    SLPushBack(plist, 2);
    SLTPrint(plist);
}

void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* newnode = BuyLTNode(x);


    if (phead == NULL)
    {
        phead = newnode;
    }
    else
    {
        SLTNode* tail = phead;
        while (tail->next != NULL)
        {
            tail = tail->next;
        }

        tail->next = newnode;
    }
}

输出:

 列举了这么多错误案例接下来到正确案例:

void SLPushBack(SLTNode** pphead, SLTDataType x)
void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
}
//要让新节点和tail链接起来,一定要去改tail的next
void SLPushBack(SLTNode** pphead, SLTDataType x)//尾插的本质是让上一个结点链接下一个结点
{
	SLTNode* newnode = BuyLTNode(x);
	// 1、空链表
	// 2、非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}

}

 代码执行:

2.5单链表头插

头插思路:

头插第二个节点思路:

头插的代码可以写成这样

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//使用malloc函数动态分配了一个大小为SLTNode的内存块,
	//并将其强制转换为 SLTNode 指针类型,即创建了一个新的节点。

	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	//需要注意的是,在使用 malloc 函数动态分配内存时,需要手动释放内存,否则会导致内存泄漏
	//因此,在创建单链表节点后,我们应该在适当的时候使用 free 函数释放节点所占用的内存

	newnode->data = x;
	newnode->next = NULL;

	//return newnode;//返回的是一个结点的地址
	newnode->next = *pphead;
	*pphead = newnode;//将头节点*pphead 更新为新节点的地址,以使新节点成为新的头节点。
}

但是为了避免创建新节点的程序重复编写,可以利用上面的BuyLTNode(x)函数定义新节点达成简写的目的,也可以头插的函数也可以写成这样:

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

在 SLPushFront 函数中,我们首先调用 BuyLTNode(x) 函数创建一个新的节点,然后将新节点的 next 指针指向当前链表头节点,接着将链表头指针指向新节点,从而完成了在链表头部插入新节点的操作。

代码执行:

 

2.6单链表尾删 

错误案例:

 方法1:

方法2:

 代码实例:

void SLPopBack(SLTNode** pphead)
{
	//没有节点(空链表)
	//暴力检查
	assert(*pphead);

	//温柔检查
	if (*pphead == NULL)
	{
		return;
	}
	//一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}//多个节点
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		//找尾
		//方法一
		/*while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;*/

		//方法二
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

执行: 

 

 2.7单链表头删

思路:

void SPopFront(SLTNode** pphead)
{
	//没有节点
	//暴力检查
	assert(*pphead);
	//温柔检查
	if (*pphead == NULL)
		return;// 一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个节点
	else
	{
		SLTNode* del = *pphead;//相当于一个标记,删掉的标记
		//写法一
		//*pphead = del->next;
		//写法二 
		*pphead = (*pphead)->next;
		free(del);
	}



}

执行:

2.8单链表查找/修改某个值

使用尾插将1,2,3,4按先后顺序分别插入链表中,接着找到链表中值为3的元素,并把其改为30(可以通过定义结构体类型的指针访问data为3的结点,并直接通过pos->next=30修改)

注意:直接在main函数内定义一个test函数修改值即可,不必另定义新函数。

SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
	//assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

执行:

2.9单链表在pos之前插入

思路:

void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);//&plist
	assert(pos);
	//assert(*pphead);
	//一个节点
	if (*pphead == NULL)
	{
		SLPushFront(pphead, x);
	}
	else//多个节点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

 执行:

2.10单链表在pos之后插入

思路:

void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.11单链表删除pos的值

思路: 

void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next!=pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

 

2.12单链表删除pos之后的值

思路:

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* next= pos->next;
	pos->next = next->next;
	free(next);
}

 

三.案例函数实现

3.1测试

void TestSList1() 
{
	SLTNode* plist = NULL;
	SLPushFront(&plist,1);
	SLPushFront(&plist,2);
	SLPushFront(&plist,3);
	SLPushFront(&plist,4);

	SLTPrint(plist);
	SLPushBack(plist, 5);
	SLTPrint(plist);

}

3.2头插

void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);

	SLTPrint(plist);
	SLPushBack(&plist, 5);

	SLTPrint(plist);
}

3.3尾插

void TestSList3()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLTPrint(plist);
	SLPushBack(&plist, 2);
	SLTPrint(plist);
	SLPushBack(&plist, 3);
	SLTPrint(plist);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
}

3.4头删

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
	//头删

	SLPopFront(&plist);
	SLTPrint(plist);
	
	SLPopFront(&plist);
	SLTPrint(plist);
	
	SLPopFront(&plist);
	SLTPrint(plist);

	SLPopFront(&plist);
	SLTPrint(plist);
}

3.5尾删

void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);
}

3.6查找/修改某值

void TestSList6()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist,3);
	if (pos)
		pos->data = 30;

	SLTPrint(plist);
}

3.7在pos之前插入 

void TestSList7()//pos之前插入 
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist,3);
	if (pos)
	{
		SLInsert(&plist, pos, 30);
	}
	SLTPrint(plist);
}

3.8在pos之后插入 

void TestSList8()//pos之后插入 
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 3);
	if (pos)
	{
		SLInsertAfter(pos, 50);
	}
	SLTPrint(plist);
}

3.9删除pos位置的值

void TestSList9() {
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 3);
	if (pos)
	{
		SLErase(&plist,pos);
	}
	SLTPrint(plist);


}

3.10删除pos之后的值

void TestSList10() {
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 2);
	if (pos)
	{
		SLEraseAfter(pos);
	}
	SLTPrint(plist);
}

本章完,如有错误欢迎各位大佬指点,感谢你的来访。

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

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

相关文章

第110天:免杀对抗-GOC#反VT沙盒逆向调试参数加载资源分离混淆加密

知识点 #知识点&#xff1a; 1、C#-混淆&分离&反调试 2、GO-混淆&分离&反调试 3、成品程序-包含反调试VT#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳…

Progressive Dual-Branch Network for Low-Light Image Enhancement 论文阅读笔记

这是22年中科院2区期刊的一篇有监督暗图增强的论文 网络结构如下图所示&#xff1a; ARM模块如下图所示&#xff1a; CAB模块如下图所示&#xff1a; LKA模块其实就是放进去了一些大卷积核&#xff1a; AFB模块如下图所示&#xff1a; 这些网络结构没什么特别的&#xf…

【团队协作开发】将Gitee项目导入到本地IDEA中出现根目录不完整的问题解决(已解决)

前言&#xff1a;在团队协作开发过程中&#xff0c;通常我们的Gitee完整项目中会包含很多内容&#xff1a;后端代码、前端代码、项目结构图、项目文档等一系列资产。 将Gitee项目导入到本地IDEA中&#xff0c;通常会出现根目录不完整的问题。这是因为项目里面包含了后端代码、前…

Matlab的GUI设计

文章目录 AppDesigner各个版本的特点mlapp文件基本格式AppDesigner的回调函数常见控件的属性MVC模式MVC模式设计GUIMVC简单使用 其他让app designer置顶将Guide的GUI导出为m文件将app编译为exe将app中的多个控件组合在一起 AppDesigner 20200328 各个版本的特点 在2017b版本中…

用 Node.js 手写 WebSocket 协议

目录 引言 从 http 到 websocekt 的切换 Sec-WebSocket-Key 与 Sec-WebSocket-Accept 全新的二进制协议 自己实现一个 websocket 服务器 按照协议格式解析收到的Buffer 取出opcode 取出MASK与payload长度 根据mask key读取数据 根据类型处理数据 frame 帧 数据的发…

【C++模拟实现】string的模拟实现

【C模拟实现】string的模拟实现 目录 【C模拟实现】string的模拟实现string模拟实现的标准代码string模拟实现中的要点string构造函数的实现赋值运算符重载迭代器的实现对流插入和流提取运算符的重载find函数的实现insert函数的实现 作者&#xff1a;爱写代码的刚子 时间&#…

MySQL视图概念及作用、操作语法

1.什么是视图 在数据库中有一些用户的敏感数据字段不方便展示&#xff0c;需要隐藏时&#xff0c;这时候就可以利用视图这个概念来实现。 2.视图操作 如何对视图里的数据进行增删改操作呢&#xff1f; 可以直接通过insert语句向视图里面插入数据&#xff0c;语法和向表里插…

移动测试(二)

功能测试点 用户使用习惯 权限问题 硬件问题 比如双卡双待、摄像头、GPU等。 操作习惯 用户常用的有菜单键、Home键、返回键、Home键长按&#xff08;显示当前进程列表&#xff09;、调整音量、待机等。相应的作为测试工程师我们需要考虑的项就变成了&#xff1a; • 应用中的…

Docker——基本管理

Docker 基本管理 Docker——基本管理 一、Docker 概述1.Docker的设计理念2.容器的优势3.Docker与虚拟机的区别4.容器在内核中支持2种重要技术5.Docker核心概念5.1 镜像5.2 容器5.3 仓库 二、安装 Docker1.关机防火墙2.安装依赖包3.设置阿里云镜像源4.安装 Docker-CE并设置为开…

PostgreSQL 的事务管理和并发控制机制解析

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Istio Pilot源码学习(三):xDS的异步分发

本文基于Istio 1.18.0版本进行源码学习 5、xDS的异步分发 DiscoveryService主要包含下述逻辑&#xff1a; 启动GRPC Server并接收来自Envoy端的连接请求接收Envoy端的xDS请求&#xff0c;从ConfigController和ServiceController中获取配置和服务信息&#xff0c;生成响应消息…

使用 ChatGPT 碰到的坑

最近在使用 ChatGPT 的时候碰到一个小坑&#xff0c;因为某些特殊情况我需要使用 syslog 向 logbeat 中发送日志。 由于这是一个比较古老的协议&#xff0c;确实也没接触过&#xff0c;所以就想着让 ChatGPT 帮我生成个例子。 原本我已经在 Go 中将这个流程跑通&#xff0c;所…

快速排序qsort讲解

hello大家好&#xff0c;我是c语言boom家宝&#xff0c;今天为大家分享的博客内容是qsort快速排序&#xff0c;简称快排的一个知识点的讲解。 在讲到快排之前&#xff0c;允许博主先提一嘴冒泡排序。大家在c语言的学习过程中&#xff0c;冒泡排序是必不可少会学习到的一个思想&…

Kafka - Primie Number of Partitions Issue Consumer Group Rebalance

文章目录 生产者&#xff1a;将数据写入 Kafka 的客户端。 消费者&#xff1a;从 Kafka 中读取数据的客户端。 Topic&#xff1a;Kafka 中用于组织和存储数据的逻辑概念&#xff0c;类似于数据库表。 Record&#xff1a;发送到 Topic 的消息称为 Record。 Partition&#x…

基于深度学习的高精度交通信号灯检测系统(PyTorch+Pyside6+YOLOv5模型)

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

斯坦福数据挖掘教程·第三版》读书笔记(英文版)Chapter 13 Neural Nets and Deep Learning

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 13 Neural Nets and Deep Learning In this chapter, we shall consider the design of neural nets, which are collections of perceptrons, or nodes, where the outputs of one rank (or lay…

C# 学习笔记

不再是学生了&#xff0c;成了社畜了&#xff0c;公司主要技术栈是C# 大一时候学C#学的很迷糊&#xff0c;总要重新学一下 入职已经20天了&#xff0c;也开始上手简单增删改查了 记录了一些C#相关的东西&#xff0c;只是还没有系统整理 WinForm 控件命名规范 ADO.NET 连接…

爬虫-微博个人主页的获取

我们在利用爬虫爬取微博个人主页的时候&#xff0c;我们需要获取到个人页面的cookie才能进入到微博的个人主页&#xff0c;否则的话将会是一直跳转到登录页面而导致不能进入个人主页。 import urllib.request url #自己微博个人主页的源代码 headers {User-Agent:Mozilla/5.…

办公软件ppt的制作

毕业找工作太难了&#xff0c;赶紧多学点什么东西吧&#xff0c;今天开始办公软件ppt的制作学习。 本文以WPS作为默认办公软件&#xff0c;问为什么不是PowerPoint&#xff0c;问就是没钱买不起&#xff0c;绝对不是不会破解的原因。 一.认识软件 在快捷工具栏中顾名思义就是一…

什么是框架?为什么要学框架?

一、什么是框架 框架是整个或部分应用的可重用设计&#xff0c;是可定制化的应用骨架。它可以帮开发人员简化开发过程&#xff0c;提高开发效率。 项目里有一部分代码&#xff1a;和业务无关&#xff0c;而又不得不写的代码>框架 项目里剩下的部分代码&#xff1a;实现业务…