速通数据结构第三站 单链表

系列文章目录

速通数据结构与算法系列

1   速通数据结构与算法第一站 复杂度          http://t.csdnimg.cn/sxEGF

2   速通数据结构与算法第二站 顺序表          http://t.csdnimg.cn/WVyDb

感谢佬们支持!


目录

系列文章目录

  • 前言
  • 一、单链表
  •        1 结构体
  •        2 接口声明
  •        3 打印
  •        4 扩容
  •        5 尾插
  •        6 尾删
  •        7 头插
  •        8 头删
  •        9 find
  •       10 insert(在指定位置后一个插)
  •       11 erase(删指定位置的下一个)
  •       12 销毁
  • 二、OJ题
  •        1 删除链表元素
  •        2 反转链表
  •        3 合并两个有序链表 
  •        4 链表分割
  •        5 链表的中间节点
  •        6 链表的回文结构
  •        7 相交链表
  • 总结

前言

    上一节我们学习了顺序表这一数据结构,这一节我们来学习链表,准确来说是单链表。

链表的用途十分广泛,操作系统的大部分队列都是由双链表维护的;但是单链表依旧有他的优势,

由于比双链表少了一个成员,一个节点的大小会大大减小,所以有的结构为了省内存会使用单链表

,比如哈希表会挂单链表。而且大部分的链表算法题都是以单链表为背景的,面试中问的尤为频繁


一、单链表

单链表的结构体由数据域和指针域组成

数据域用来存数据,指针域用来存下一个节点的地址

我们建3个文件,slist.h,slist.c,test.c

typedef int SLDataType;

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;

由于链表没什么好初始化的,所以我们在声明的接口里不用写初始化函数

还是来看一下声明的接口

接口声明

//打印
void SListPrint(SLTNode* phead);

//销毁
void SListDestroy(SLTNode** pphead);

//增加节点
SLTNode* SListBuyListNode(SLDataType x);

//尾插	
void SListPushBack(SLTNode** pphead, SLDataType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头插
void SListPushFront(SLTNode** pphead, SLDataType x);

//头删
void SListPopFront(SLTNode** pphead);

//寻找
SLTNode* SListFind(SLTNode* phead, SLDataType x);

//指定位置后插
void SListInsertAfter(SLTNode** pphead, SLDataType x, SLTNode* pos);

//删指定位置的下一个
void SListEraseAfter(SLTNode** pphead, SLTNode* pos); 

这个时候大家会注意到我们用的全部都是二级指针,为什么呢?

首先,我们定义的就是一个指针,所以传参传的也是指针,如果我们只传原生的指针过去

形参的修改不影响实参,我们在test.c定义的指针还是空

所以我们要传指针的地址过去,也就是二级指针


先来写一波打印

显然,打印并不会修改链表,所以我们只传一级指针就行

由于空的链表也能打印,所以我们不需要assert

void SListPrint(SLTNode* phead)
{
	//由于空的链表也能打印,所以我们不用断言
	//assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

扩容

扩容的逻辑也相对简单,直接malloc即可

//增加节点
SLTNode* SListBuyListNode(SLDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//增加失败
	if (NULL == newnode)
	{
		printf("malloc fail\n ");
		exit(-1);
	}
	//增加成功
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	return newnode;
}

我们先来写一个尾插

尾插

尾插的核心逻辑是先找尾,然后在让 尾->next=newnode;

当然,我们要考虑链表为空的情况由于链表为空是在预期之内的,所以我们不用断言

*pphead,但是要断言pphead

当链表为空时,newnode就是新的头

代码如下

//尾插	
void SListPushBack(SLTNode** pphead, SLDataType x)
{
	assert(pphead);
	SLTNode* newnode = SListBuyListNode(x);
	//链表为空
	if (NULL == *pphead)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		//找到尾
		tail->next = newnode;
	}
}

我们可以简单的做一波测试

SLTNode* plist = NULL;
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	

(确实挺不错的)


尾删

显然,我们不能删空链表,所以需要断言*pphead

尾删要分两种情况:只有一个节点和不只有一个节点

当只有一个节点时,我们就不用找尾了,直接删

而不止一个节点时要先找尾

代码如下

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	//尾删不能为空
	assert(*pphead);
	//只有一个节点
	if (((*pphead)->next) == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	SLTNode* tail = *pphead;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	free(tail->next);
	tail->next = NULL;
}

我们再做一波测试

SLTNode* plist = NULL;
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 6);
	SListPopBack(&plist);
	SListPrint(plist);
	printf("\n");
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 4);
	SListPopBack(&plist);

	SListPrint(plist);

由此可见,尾插尾删由于要找尾,时间复杂度都是O(n)

这个时候有人要问了:尾插尾删并不改变头指针啊,也就是说只传一级指针也没问题

但如果刚开始链表为空调用尾插和只有一个节点时尾删显然就需要修改了,所以我们依然需要传二级指针


头插

头插就简单多了,给大家画一张图就够了

-》

代码很简单

//头插
void SListPushFront(SLTNode** pphead, SLDataType x)
{
	assert(pphead);
	SLTNode* newnode = SListBuyListNode(x);

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

}

头删

头删的逻辑一样简单,我们只需提前保存当前头的下一个,再删头即可

//头删
void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//提前保存头节点的下一个
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = NULL;

	*pphead = next;

}

我们简单的测试一下

SLTNode* plist = NULL;
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPopFront(&plist);
	SListPrint(plist);

由原理可知:链表的头插和头删都是高贵的O(1),这也就是为什么STL的slist选择提供

push_front()和pop_front()而非push_back()和pop_back()的原因


寻找

寻找返回的是某值所在节点的指针

由于同样不修改链表,所以我们传一级指针

代码如下

/寻找
SLTNode* SListFind(SLTNode* phead, SLDataType x)
{
	SLTNode* cur = phead;
	while (cur->next)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//没找见
	return NULL;
}

我们再来实现一下insert

我们实现的时特定位置后插,大体逻辑是这样的

//指定位置后插
void SListInsertAfter(SLTNode** pphead, SLDataType x, SLTNode* pos)
{
	assert(pphead);
	//断言pos
	assert(pos);

	SLTNode* newnode = SListBuyListNode(x);
	//处理链接关系
	newnode->next = pos->next;
	pos->next = newnode;
}

再来搞一下erase

erase的逻辑大同小异,我们只需提前保存下一个,改一下链接即可

但是要注意的是由于要删pos的下一个位置,删的这个位置不能为空

//删指定位置的下一个
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	//断言pos
	assert(pos);
	//删的时候下一个位置不能是空
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
	next = NULL;

}

我们再测试一波

SLTNode* plist = NULL;
	SListPushFront(&plist, 6);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 4);


	SLTNode* pos = SListFind(plist, 2);
	SListInsertAfter(&plist, 7, pos);
	SListPrint(plist);
	printf("\n");

	SListEraseAfter(&plist, pos);
	SListPrint(plist);
	SListDestroy(&plist);


最后我们要来写一下销毁

销毁

销毁的逻辑在于你每次都有保存要销毁节点的下一个,然后再销毁当前节点,最后再给头指针置空

//销毁
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		//提前存好下一个
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//销毁头指针
	*pphead = NULL;
}

二、OJ题

   我们来挑一些比较经典、面试中会常常问到的相对简单的题目来做。


1 删除链表元素

题目链接: . - 力扣(LeetCode)

这道题有两种方法,我们可以就在链表内部删,也可以取不等于val的尾插至新链表

法一:

删除的逻辑非常简单,就是我们实现单链表的中间删逻辑,定义一前一后两个指针

只需考虑一下头删的情况即可

struct ListNode* removeElements(struct ListNode* head, int val)
{
    if (head == NULL)
    {
        return NULL;
    }

    struct ListNode* Node = head;
    struct ListNode* prev;


    while (Node)
    {
        if (Node->val == val)
        {

            if (Node == head)//ͷɾ
            {
                head = head->next;
            }

            else
            {
                prev->next = Node->next;
                free(Node);
                Node = prev->next;
            }
        }

        else
        {
            prev = Node;
            Node = Node->next;
        }
    }

    return head;
}

法二:

尾插的逻辑也简单,这个时候有个技巧,为了避免每次尾插时都要找尾,我们直接定义一个尾指针每次记录一下即可

简单是简单,但是如果仅考虑到这样写完代码后提交就直接报错了

struct ListNode* removeElements(struct ListNode* head, int X)
{
    if (head == NULL)
    {
        return NULL;
    }
    struct ListNode* newhead = NULL, * tail = NULL;
    struct ListNode* cur = head;

    while (cur)
    {
        if (cur->val != X)
        {
            if (tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = cur;
                // tail->next=NULL;
            }
            cur = cur->next;
        }
        else
        {
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
    }
    return newhead;
}

如果原链表最后一个元素是要删的那个而前一个不是,当我们删了后一个之后,后一个的next就变成了野指针

如图所示

所以我们需要置空尾指针,也就是在最后

tail=tail->next;

此时你再提交,发现依然编不过,此时你会发现这个用例叫【7,7,7,7】 val=7;

由于每个元素都要被删,所以尾指针根本就是空的,不能解引用

所以我们要这样

if (tail)
        tail->next = NULL;

最后的代码是这样的

struct ListNode* removeElements(struct ListNode* head, int X)
{
    if (head == NULL)
    {
        return NULL;
    }
    struct ListNode* newhead = NULL, * tail = NULL;
    struct ListNode* cur = head;

    while (cur)
    {
        if (cur->val != X)
        {
            if (tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = cur;
                // tail->next=NULL;
            }
            cur = cur->next;
        }
        else
        {
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
    }
    if (tail)
        tail->next = NULL;

    return newhead;
}

(最后终于过了)


2 反转链表

题目链接:. - 力扣(LeetCode)

这个题我们可以有两个思路:1遍历链表,改指针指向

显然,双指针是不够的,如图

如果我们改了1到2的指向,那3这个节点就找不到了,所以我们需要三指针

以prev和cur改指向,以next记录下一个节点的位置

最后改完指向时,next为空,我们返回的是cur

所以由于最后一步next可能为空,所以我们每次迭代next前,都要判空

代码如下

 ListNode* reverseList(ListNode* head)
    {
        if (head == nullptr)
            return nullptr;
        ListNode* prev = nullptr;
        ListNode* cur = head;
        ListNode* next = cur->next;

        while (cur)
        {
            cur->next = prev;
            prev = cur;
            cur = next;
            if (next)
                next = next->next;
        }
        return prev;
    }

也可以有第二种思路

2 遍历原链表,依次头插至新链表

代码如下,也是非常简单

struct ListNode* reverseList(struct ListNode* head)
{
    if (head == NULL)
    {
        return NULL;
    }
    struct ListNode* newhead = NULL;
    //ͷ嵽newhead
    struct ListNode* cur = head;

    while (cur)
    {
        struct ListNode* next = cur->next;

        cur->next = newhead;
        newhead = cur;

        //
        cur = next;
    }
    return newhead;
}

3 合并两个有序链表 

题目链接:. - 力扣(LeetCode)

相较于合并两个有序数组,合并两个有序链表就简单多了,我们可以直接找小的尾插

每次标记一下尾指针,尾插的题做起来还是很舒服的。

仅仅考虑一下头为空的情况即可

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;

    struct ListNode* head=NULL, * tail = NULL;
    while (cur1 && cur2)
    {
        if (cur1->val <= cur2->val)
        {
            if (head == NULL)
            {
                head = tail = cur1;
            }
            else
            {
                tail->next = cur1;
                tail = tail->next;
            }
            cur1 = cur1->next;
        }
        else
        {
            if (head == NULL)
            {
                head = tail = cur2;
            }
            else
            {
                tail->next = cur2;
                tail = tail->next;
            }
            cur2 = cur2->next;
        }
    }
    //cur1先结束了
    if (cur1 == NULL)
    {
        tail->next = cur2;
    }

    //cur2先结束了
    if (cur2 == NULL)
    {
        tail->next = cur1;
    }
    return head;
}

还可以用带哨兵位的做法

带了哨兵位的好处就是,我们不用考虑头为空的事情了

虽然力扣不会检测内存泄漏,但我们最好还是free一下我们的头节点

记得临时保存一下,要不然就找不到链表的头啦

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if (list1 == NULL)
    {
        return list2;
    }
    if (list2 == NULL)
    {
        return list1;
    }


    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;

    struct ListNode* guard = NULL, * tail = NULL;
    guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
    tail->next = NULL;

    while (cur1 && cur2)
    {
        if (cur1->val < cur2->val)
        {
            tail->next = cur1;
            tail = tail->next;
            cur1 = cur1->next;
        }
        else
        {

            tail->next = cur2;
            tail = tail->next;
            cur2 = cur2->next;
        }
    }
    //cur1先结束了
    if (cur1 == NULL)
    {
        tail->next = cur2;
    }

    //cur2先结束了
    if (cur2 == NULL)
    {
        tail->next = cur1;
    }

    struct ListNode* head = guard->next;
    free(guard);
    return head;
}

4 链表分割

链表分割_牛客题霸_牛客网

显然,在一个链表上操作较为麻烦,我们可以开两个链表,将小于x的节点插到一个链表中

将大于x的节点插入另一个链表中,再将两个链表穿起来,返回小链表的头即可

注意:将大链表的头接入小链表的尾时,记得给大链表的尾置空。

代码如下~

ListNode* partition(ListNode* pHead, int x)
    {

        struct ListNode* LessHead = (struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* GreaterHead = (struct ListNode*)malloc(sizeof(struct ListNode));


        //遍历链表
        ListNode* cur = pHead;

        ListNode* tail1 = LessHead;
        ListNode* tail2 = GreaterHead;


        while (cur)
        {
            if (cur->val < x)
            {
                //小于x就尾插至Less链表
                tail1->next = cur;
                tail1 = cur;
            }
            else
            {
                //否则就尾插至Greater链表

                tail2->next = cur;
                tail2 = cur;
            }
            cur = cur->next;
        }

        //链接两个链表
        tail1->next = GreaterHead->next;
        tail2->next = nullptr;

        //保存下要返回的节点
        struct ListNode* tmp = LessHead->next;
        free(LessHead);
        free(GreaterHead);

        return tmp;
    }

5 链表的中间节点

题目链接 :. - 力扣(LeetCode)

这个题就简单了,我们可以用快慢指针来做,快指针一次走两步,慢指针一次走一步

当链表长度为奇数时,最后fast->next为空,链表只有一个中间节点

当链表长度为偶数时,最后fast为空,我们的slow将会是两个中间节点的第二个

代码如下

struct ListNode* middleNode(struct ListNode* head)
{
    //ָ
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

6 链表的回文结构

题目链接 . - 力扣(LeetCode)

在前面的铺垫之后,回文链表的思路就简单了,我们先找到链表的中间节点,然后从中间断开

逆置后半段,然后让两个指针遍历,如果有不相同的节点,就不是回文链表。我们可以CV一下之前的中间节点和逆置。

代码如下

class Solution {
public:
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == NULL)
    {
        return NULL;
    }
    struct ListNode* newhead = NULL;
    //ͷ嵽newhead
    struct ListNode* cur = head;

    while (cur)
    {
        struct ListNode* next = cur->next;

        cur->next = newhead;
        newhead = cur;

        //
        cur = next;
    }
    return newhead;
}


    ListNode* middleNode(struct ListNode* head)
{
  //快慢指针
   ListNode*fast=head;
   ListNode*slow=head;
  while(fast&&fast->next)
  {
     fast=fast->next->next;
     slow=slow->next;
  }
  return slow;
}

    bool isPalindrome(ListNode* head) 
    {
        ListNode*middle=middleNode(head);
        ListNode*rmiddle=reverseList(middle);

        while(rmiddle!=nullptr)
        {
            if(rmiddle->val!=head->val)
            {
                return false;
            }
            rmiddle=rmiddle->next;
            head=head->next;
        }
        return true;
    }
};

7 相交链表

题目链接 :. - 力扣(LeetCode)

显然,两个链表如果相交,那他们的最后一个节点一定相同。

我们可以先遍历一遍两个链表,求出各自的长度,顺便判断一下最后一个节点是否相同,不相同也是直接返回NULL

然后让长的链表先走差距步,再让两个指针一起走,直到遇到相同的节点

值得注意的是,这里两个链表的长度最小为1,所以我们在判断时不能用slow->next和fast->next;而应用slow和fast

代码如下

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
    int lenA = 1;
    int lenB = 1;

    struct ListNode* tailA = headA;
    struct ListNode* tailB = headB;

    while (tailA)
    {
        lenA++;
        tailA = tailA->next;
    }

    while (tailB)
    {
        lenB++;
        tailB = tailB->next;
    }
    //如果相交,尾节点一定相同  //这个条件很有必要
    if (tailA != tailB)
    {
        return NULL;
    }

    //快的先走差距步,再一起走
    struct ListNode* fast = lenA > lenB ? headA : headB;
    struct ListNode* slow = lenA > lenB ? headB : headA;

    int gap = abs(lenA - lenB);

    while (gap--)
    {
        fast = fast->next;
    }



    while (fast != slow)
    {

        fast = fast->next;
        slow = slow->next;
    }

    return fast;

}

总结

 做总结,单链表由于"不能倒着走"的特性使其在运用上多有限制,这也就是为什么OJ题中的背景

都是单链表,所以在C++11中STL更新了单链表slist用的人也不多。

下篇博客我们将学习双链表,将会轻松很多,但是会接触三个不那么轻松的OJ题。

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee侠:今天你交gitee了嘛

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

踏上机器学习之路:探索数据科学的奥秘与魅力

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

hxp CTF 2021 - A New Novel LFI(新颖的解法)

一、环境 unbentu&#xff0c;docker https://2021.ctf.link/assets/files/includers%20revenge-25377e1ebb23d014.tar.xz 二、解析 PHP Filter 当中有一种 convert.iconv 的 Filter &#xff0c;可以用来将数据从字符集 A 转换为字符集 B &#xff0c;其中这两个字符集可以…

记录pycharm配置Anaconda环境时没有反应的问题

记录pycharm配置Anaconda环境时没有反应的问题 背景 下载最新pycharm后在设置中配置add interpreter Anaconda环境时&#xff0c;x选中conda.ba文件点击Load Enviroments后&#xff0c;没有反应&#xff0c;就闪了一下&#xff0c;也有添加成功 探索路程 试过了重启&#x…

NineData与StarRocks商业化运营公司镜舟科技完成产品兼容认证

近日&#xff0c;镜舟科技与NineData完成产品兼容测试。在经过联合测试后&#xff0c;镜舟科技旗下产品与NineData云原生智能数据管理平台完全兼容&#xff0c;整体运行高效稳定。 镜舟科技致力于帮助中国企业构建卓越的数据分析系统&#xff0c;打造独具竞争力的“数据护城河”…

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…

leetcode热题100.柱状图中最大的矩形

Problem: 84. 柱状图中最大的矩形 文章目录 题目思路复杂度Code 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;hei…

RAM IP核

1.原理 数据使能信号充当掩码的作用。1表示1字节就是8个位有效。

答题小程序功能细节揭秘:如何提升用户体验和满足用户需求?

答题小程序功能细节体现 随着移动互联网的快速发展&#xff0c;答题小程序成为了用户获取知识、娱乐休闲的重要平台。一款优秀的答题小程序不仅应该具备简洁易用的界面设计&#xff0c;更应该在功能细节上做到极致&#xff0c;以提升用户体验和满足用户需求。本文将从题库随机…

八大技术趋势案例(虚拟现实增强现实)

科技巨变,未来已来,八大技术趋势引领数字化时代。信息技术的迅猛发展,深刻改变了我们的生活、工作和生产方式。人工智能、物联网、云计算、大数据、虚拟现实、增强现实、区块链、量子计算等新兴技术在各行各业得到广泛应用,为各个领域带来了新的活力和变革。 为了更好地了解…

day56 动态规划part13

300. 最长递增子序列 中等 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,…

【FedCoin: A Peer-to-Peer Payment System for Federated Learning】

在这篇论文中&#xff0c;我们提出了FedCoin&#xff0c;一个基于区块链的点对点支付系统&#xff0c;专为联邦学习设计&#xff0c;以实现基于Shapley值的实际利润分配。在FedCoin系统中&#xff0c;区块链共识实体负责计算SV&#xff0c;并且新的区块是基于“Shapley证明”&a…

Linux 入门及其基本指令(上)

目录 0 .引言 1. XShell 远程登录 Linux 1.1 云服务器 1.2. XShell 远程登陆 Linux 2. 详解 Linux 基本指令 2.1 ls 指令 2.2 pwd 指令 2.3 cd 指令 2.4 touch 指令 2.5 mkdir指令 2.6 rmdir指令 && rm 指令 0 .引言 如今&#xff0c;Linux 在服务器…

公众号的AI聊天机器人已修复!谷歌Gemini Pro 10大使用场景解析

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

Kafka重要配置参数全面解读(重要)

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Kafka重要配置参数全面解读(重要 前言auto.create.topics.enableauto.leader.rebalance.enablelog.retention.{hour|minutes|ms}offsets.topic.num.partitions 和 offsets.topic.replication.factorlo…

Long long类型比较大小

long 与 Long long类型和Long类型是不一样&#xff0c;long类型属于基本的数据类型&#xff0c;而Long是long类型的包装类。 结论 long是基本数据类型&#xff0c;判断是否相等时使用 &#xff0c;即可判断值是否相等。&#xff08;基本数据类型没有equals()方法&#xff0…

JVM之EhCache缓存

EhCache缓存 一、EhCache介绍 在查询数据的时候&#xff0c;数据大多来自数据库&#xff0c;咱们会基于SQL语句的方式与数据库交互&#xff0c;数据库一般会基于本地磁盘IO的形式将数据读取到内存&#xff0c;返回给Java服务端&#xff0c;Java服务端再将数据响应给客户端&am…

Ubuntu下使用vscode进行C/C++开发:进阶篇

在vscode上进行C/C++开发的进阶需求: 1) 编写及调试源码时,可进行断点调试、可跨文件及文件夹进行函数调用。 2) 可生成库及自动提取对应的头文件和库文件。 3) 可基于当前工程资源一键点击验证所提取的库文件的正确性。 4) 可结合find_package实现方便的调用。 对于第一…

LLM之RAG实战(三十五)| 使用LangChain的3种query扩展来优化RAG

RAG有时无法从矢量数据库中检索到正确的文档。比如我们问如下问题&#xff1a; 从1980年到1990年&#xff0c;国际象棋的规则是什么&#xff1f; RAG在矢量数据库中进行相似性搜索&#xff0c;来查询与国际象棋规则问题相关的相关文档。然而&#xff0c;在某些情况下&#xff0…

mysql修改用户权限

https://blog.csdn.net/anzhen0429/article/details/78296814

Elasticsearch 和 Kibana 8.13:简化 kNN 和改进查询并行化

作者&#xff1a;Gilad Gal, Tyler Perkins, Srikanth Manvi, Aris Papadopoulos, Trevor Blackford 在 8.13 版本中&#xff0c;Elastic 引入了向量搜索的重大增强&#xff0c;并将 Cohere 嵌入集成到其统一 inference API 中。这些更新简化了将大型语言模型&#xff08;LLM&a…