【数据结构】详细剖析链表,带你实现单链表,双向链表,附链表编程练习题

news2024/12/29 9:12:15

目录

一. 链表

1. 链表的概念及结构

2. 单链表的实现

2.1 单链表节点结构

2.2 动态申请一个节点

2.3 单链表打印

2.4 单链表尾插

2.5 单链表头插

2.6 单链表尾删

2.7 单链表头删

2.8 单链表查找 

2.9 单链表在pos后一位插入x

2.10 单链表删除pos后一位的值

2.11 单链表销毁 

3. 链表的分类 

3.1 单向或双向

3.2 带头或不带头

3.3 循环或不循环

3.4 最常用 

4. 带头双向循环链表的实现

4.0 节点结构

4.1 创建节点

4.2 双向链表销毁

4.3 双向链表打印 

4.4 双向链表尾插

4.5 双向链表尾删

4.6 双向链表头插

4.7 双向链表头删

4.8 双向链表查找

4.9 双向链表在pos的前面进行插入

4.10 双向链表删除pos位置的节点

5. 链表编程练习题

5.1 移除链表元素

5.2 链表的中间结点

5.3 合并两个有序链表

5.4 反转链表 

5.5 链表分割

5.6 相交链表

5.7 环形链表 

5.8 环形链表返回环节点

5.9 随机链表的复制 

二. 顺序表和链表的区别 


一. 链表

1. 链表的概念及结构

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

2. 单链表的实现

2.1 单链表节点结构

1. 使用typedef重命名数据类型是为了方便类型的更改。

2. 结构包含存放的数据和指向下一个节点的地址。

typedef int SLDataType;
typedef struct SingleListNode
{
	SLDataType data;
	struct SingleListNode* next;
}SLNode;

2.2 动态申请一个节点

1. 使用malloc申请一块节点空间。

2. 将节点内容初始化。 

SLNode* BuySLNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

2.3 单链表打印

1. 通过获取下一个节点地址进行遍历并打印数据。

2. 遇到空节点停下。

void SLPrint(SLNode* plist)
{
	SLNode* cur = plist;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2.4 单链表尾插

1. pplist不可能为空,所以加断言。

2. 无节点情况:直接将新节点地址给头指针。

3. 有节点情况:将最后一个节点连接新节点。

void SLPushBack(SLNode** pplist, SLDataType x)
{
	assert(pplist);

	SLNode* newnode = BuySLNode(x);
	if (*pplist == NULL) *pplist = newnode; //无节点
	else                                    //有节点
	{
		SLNode* cur = *pplist;
		while (cur->next != NULL) cur = cur->next;
		cur->next = newnode;
	}
}

2.5 单链表头插

1. 先将新节点连接第一个结点,再将头指针连接新节点。   

void SLPushFront(SLNode** pplist, SLDataType x)
{
	assert(pplist);

	SLNode* newnode = BuySLNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

2.6 单链表尾删

1. 单节点情况:释放节点然后置空。

2. 多节点情况:利用倒数第二个节点,释放倒数第一个节点并置空。

void SLPopBack(SLNode** pplist)
{
	assert(pplist && *pplist);

	SLNode* cur = *pplist;
	if (cur->next == NULL) //单节点
	{
		free(cur);
		*pplist = NULL;
	}
	else                   //多节点
	{
		while (cur->next->next != NULL) cur = cur->next;
		free(cur->next);
		cur->next = NULL;
	}
}

2.7 单链表头删

1. *pplist不能为空,因为空节点不用删。

2. 将头指针指向第二个节点,释放第一个节点。

void SLPopFront(SLNode** pplist)
{
	assert(pplist && *pplist);

	SLNode* del = *pplist;
	*pplist = del->next;
	free(del);
}

2.8 单链表查找 

1. 遍历一遍,比较数据。

SLNode* SLFind(SLNode* plist, SLDataType x)
{
	SLNode* cur = plist;
	while (cur)
	{
		if (cur->data == x) return cur;
		cur = cur->next;
	}

	return NULL;
}

2.9 单链表在pos后一位插入x

1. 先将新节点连接pos的后一个节点,再将pos连接新节点。

void SLInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);

	SLNode* newnode = BuySLNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.10 单链表删除pos后一位的值

1. 空节点不用删。

2. 将pos和pos后面第二个节点连接,释放pos后面第一个节点。

void SLEraseAfter(SLNode* pos)
{
	assert(pos);

	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

2.11 单链表销毁 

1. 遍历链表释放每个节点。

void SLDestroy(SLNode** pplist)
{
	assert(pplist);

	while (*pplist)
	{
		SLNode* cur = *pplist;
		*pplist = (*pplist)->next;
		free(cur);
	}
}

完整代码: SingleList/SingleList · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)

3. 链表的分类 

链表可以分三类,这三类组合起来一共有8种结构。

3.1 单向或双向

3.2 带头或不带头

3.3 循环或不循环

3.4 最常用 

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

4. 带头双向循环链表的实现

4.0 节点结构

1. 两个指针,指向前面和后面。

typedef int ListDataType;
typedef struct ListNode
{
	ListDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;

4.1 创建节点

1. 这里可以创建哨兵位节点,也可以是其他新节点。

2. 通过malloc申请一块节点空间,将两个指针指向自己。

ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	if (head == NULL)
	{
		perror("malloc");
		return NULL;
	}
	head->prev = head;
	head->next = head;

	return head;
}

4.2 双向链表销毁

1. 先获取有效节点也就是哨兵位下一个节点,遍历释放空间,直到循环遇到哨兵位停下。

2. 最后把哨兵位也释放。

void ListDestory(ListNode* plist)
{
	assert(plist);

	ListNode* cur = plist->next;
	while (cur != plist)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(plist);
}

4.3 双向链表打印 

1. 获取哨兵位的下一个节点,遍历打印,直到循环遇到哨兵位停下。

void ListPrint(ListNode* plist)
{
	assert(plist);

	ListNode* cur = plist->next;
	printf("head<==>");
	while (cur != plist)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

4.4 双向链表尾插

1. 获取最后一个节点,将新节点和最后一个节点连接,再将新节点和哨兵位连接。

void ListPushBack(ListNode* plist, ListDataType x)
{
	assert(plist);

	ListNode* newnode = ListCreate();
	newnode->data = x;

	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;
}

4.5 双向链表尾删

1. 只有哨兵位时不允许删。

2. 获取最后一个节点和倒数第二个节点,将倒数第二个节点和哨兵位连接,释放最后一个节点。

void ListPopBack(ListNode* plist)
{
	assert(plist);
	assert(plist->next);

	ListNode* tail = plist->prev;
	ListNode* prev = tail->prev;
	prev->next = plist;
	plist->prev = prev;
	free(tail);
}

4.6 双向链表头插

1. 获取哨兵位的下一个节点,新节点和哨兵位连接,新节点再和哨兵位的下一个节点连接。

void ListPushFront(ListNode* plist, ListDataType x)
{
	assert(plist);

	ListNode* newnode = ListCreate();
	newnode->data = x;

	ListNode* cur = plist->next;
	plist->next = newnode;
	newnode->prev = plist;
	newnode->next = cur;
	cur->prev = newnode;
}

4.7 双向链表头删

1. 只有哨兵位时不允许删。

2. 获取第一个有效节点和第二个有效节点,将哨兵位与第二个有效节点连接,释放第一个有效节点。

void ListPopFront(ListNode* plist)
{
	assert(plist && plist->next);

	ListNode* cur = plist->next;
	ListNode* next = cur->next;
	plist->next = next;
	next->prev = plist;
	free(cur);
}

4.8 双向链表查找

1. 遍历有效节点,比较数据。

ListNode* ListFind(ListNode* plist, ListDataType x)
{
	assert(plist);

	ListNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->data == x) return cur;
		cur = cur->next;
	}
	return NULL;
}

4.9 双向链表在pos的前面进行插入

1. 先获取pos前面的节点,将新节点与pos前面的节点连接,然后新节点再与pos连接。

void ListInsert(ListNode* pos, ListDataType x)
{
	assert(pos);

	ListNode* newnode = ListCreate();
	newnode->data = x;

	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

4.10 双向链表删除pos位置的节点

1. 获取pos的前后节点,将他们连接,释放pos节点。

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

完整代码:List/List · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)

5. 链表编程练习题

5.1 移除链表元素

链接:. - 力扣(LeetCode)

思路:

1. 遍历链表,删除相同值得节点。

2. 使用前后指针,方便节点的释放。

3. 注意特殊情况,当第一个节点就需要删除的时候。

struct ListNode* removeElements(struct ListNode* head, int val) 
{
    struct ListNode* cur = head;
    struct ListNode* prev = NULL;
    while(cur)
    {
        if(cur->val == val)
        {   
            if(prev == NULL) //这里判断的是当前是不是第一个节点,
            {                   //注意,删除完第一个节点后,第二个节点会变成新的第一个节点。
                cur = cur->next;
                free(head);
                head = cur;
            }
            else
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }

    return head;
}

5.2 链表的中间结点

链接:. - 力扣(LeetCode)

思路:

1. 快慢指针,slow一次走一步,fast一次走两步。

2. 有奇数节点,偶数节点两种情况,奇数节点时fast走到最后一个节点停下,偶数节点时fast走到空停下。

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

    return slow;
}

5.3 合并两个有序链表

链接:. - 力扣(LeetCode)

思路:

1. 将小于或等于的节点尾插到一个新的指针上,返回这个指针。

2. 注意第一个节点尾插需要特殊处理。

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

    struct ListNode* list3 = NULL;
    struct ListNode* cur = NULL;
    while(list1 && list2)
    {
        if(list1->val <= list2->val)
        {
            if(list3 == NULL) list3 = cur = list1;
            else
            {
                cur->next = list1;
                cur = cur->next;
            }
            list1 = list1->next;
        }
        else
        {
            if(list3 == NULL) list3 = cur = list2;
            else
            {
                cur->next = list2;
                cur = cur->next;
            }

            list2 = list2->next;
        }
    }

    if(list1) cur->next = list1;
    if(list2) cur->next = list2;

    return list3;
}

5.4 反转链表 

链接:. - 力扣(LeetCode)

思路1:用三个指针来实现反转。

struct ListNode* reverseList(struct ListNode* head) 
{
    if(head == NULL) return NULL;

    struct ListNode* n1 = NULL;
    struct ListNode* n2 = head;
    struct ListNode* n3 = head->next;

    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3) n3 = n3->next;
    }

    return n1;
}

思路2:头插法,每次cur节点对rhead进行头插。

struct ListNode* reverseList(struct ListNode* head) 
{
    struct ListNode* rhead = NULL;
    struct ListNode* cur = head;
    
    while(cur)
    {
        struct ListNode* next = cur->next;

        cur->next = rhead;
        rhead = cur;

        cur = next;
    }

    return rhead;
}

5.5 链表分割

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

思路:

1. 分两个链表,将小于x的尾插一个链表,大于等于x的尾插另一个链表,最后连接起来。

2. 建议用带哨兵位的链表。

3. 连接起来后第二个链表最后记得指向NULL。

ListNode* partition(ListNode* pHead, int x) 
    {
        ListNode* h1 = (ListNode*)malloc(sizeof(ListNode));
        ListNode* h2 = (ListNode*)malloc(sizeof(ListNode));
        ListNode* h1tail = h1;
        ListNode* h2tail = h2;

        ListNode* cur = pHead;
        while(cur)
        {
            if(cur->val < x)
            {
                h1tail->next = cur;
                h1tail = h1tail->next;
            }
            else 
            { 
                h2tail->next = cur;
                h2tail = h2tail->next;
            }

            cur = cur->next;
        }

        h1tail->next = h2->next;
        h2tail->next = NULL;
        pHead = h1->next;
        free(h1);
        free(h2);

        return pHead;
    }

5.6 相交链表

链接:. - 力扣(LeetCode)

思路:

1. 先求两个链表的长度。

2. 长的链表头指针先走,走到和短的链表一样长。

3. 两个指针一起走,直到遇到一样的节点。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    int lenA = 0;
    int lenB = 0;
    struct ListNode* cur = headA;
    while(cur)
    {
        lenA++;
        cur = cur->next;
    }
    cur = headB;
    while(cur)
    {
        lenB++;
        cur = cur->next;
    }

    int gap = abs(lenA-lenB);
    struct ListNode* longlist = headA;
    struct ListNode* shortlist = headB;
    if(lenA < lenB)
    {
        longlist = headB;        
        shortlist = headA;  
    }
    while(gap--) longlist = longlist->next;  

    while(longlist != shortlist)
    {
        longlist = longlist->next;
        shortlist = shortlist->next;
    }
        
    return longlist;
}

5.7 环形链表 

链接:. - 力扣(LeetCode)

思路:

1. 利用快慢指针,如果有环那么会相遇,如果没环就走到链表结束。

bool hasCycle(struct ListNode *head) 
{
    struct ListNode *fast = head;
    struct ListNode *slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast) return true;
    }    

    return false;
}

面试问题:

1. 快指针走两步,慢指针走一步,快指针和慢指针一定会相遇吗?

答:一定会,当他们进入环后,距离不断减1直到0。

快指针走n步,慢指针走一步,假设快指针追到慢指针的距离为N,那么N必须是n-1的倍数才能追上。错过之后,N也会发生变化重新计算。

5.8 环形链表返回环节点

链接:. - 力扣(LeetCode)

思路:

1. 结论:设头节点到入环点的距离为L,入环点到相遇点的距离为X,环的长度为C,

那么有:L = n*C - X,其中n为圈数。

一个指针从头节点开始走,一个指针从相遇点开始走,他们会在入环点相遇。

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode *fast = head;
    struct ListNode *slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            //此时slow是相遇点
            while(head != slow)
            {
                head = head->next;
                slow = slow->next;
            }

            return head;
        } 
    }   

    return NULL;
}

思路2:

1. 将相遇点的下一个节点保存,然后从相遇点开始断开链表,就形成了两条链表。

2. 一条链表是以原本的,另一条是以相遇点的下一个节点为头节点,这两条链表的交点就是入环点。

5.9 随机链表的复制 

链接:. - 力扣(LeetCode)

思路:

1. 将每个节点拷贝一份并在各自节点后面插入。

2. 可得出拷贝的随机指针是源节点的随机指针的下一位。

3. 分开拷贝节点。

struct Node* copyRandomList(struct Node* head) 
{
    if(head == NULL) return NULL;

    struct Node* cur = head;
    while(cur)
    {
        struct Node* tail = cur->next;
        struct Node* new = (struct Node*)malloc(sizeof(struct Node));
        new->val = cur->val;
        cur->next = new;
        new->next = tail;
        cur = tail;
    }

    cur = head;
    while(cur)
    {
        struct Node* tail = cur->next;
        if(cur->random == NULL) tail->random = NULL;
        else tail->random = cur->random->next;
        cur = cur->next->next;
    }

    cur = head;
    struct Node* nwehead = (struct Node*)malloc(sizeof(struct Node));
    struct Node* ret = nwehead;
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* tail = copy->next;
        ret->next = copy;
        cur->next = tail;
        cur = tail;
        ret = copy;
    }
    
    return nwehead->next;
}

 

二. 顺序表和链表的区别 

1. 链表可以任意位置插入删除,顺序表适合尾插尾删。

2. 链表按需申请释放空间,顺序表是扩容空间,可能会浪费。

3. 顺序表支持下标随机访问,链表不支持。

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

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

相关文章

《软件性能测试分析与调优实践之路》(第2版) 读书笔记(一)总体介绍(上)-真正从性能分析与调优来看性能测试

《软件性能测试分析与调优实践之路》(第2版) 是清华大学出版社出版的一本图书&#xff0c;作者为张永清&#xff0c;全书共分为9章&#xff0c;如下图所示 图书介绍&#xff1a;《软件性能测试分析与调优实践之路》(第2版) 1、为什么需要性能测试与分析 1&#xff09;、了解…

成功交付西班牙足球俱乐部77英寸透明OLED模块订单

2024年8月初&#xff0c;我们完成了来自西班牙知名足球俱乐部的大宗订单交付。此次交付的10台77英寸透明OLED模块&#xff0c;标志着我们在高端显示技术领域迈出的又一重要步伐。这一订单不仅是我们目前单笔数量最多的77英寸模块订单&#xff0c;也是客户首次大规模采购我们产品…

自动化解决 reCAPTCHA v2:CapSolver 教程

对于那些经常进行网页爬取的人来说&#xff0c;你是否曾觉得 reCAPTCHA v2 就像是互联网版的过于严格的裁判员&#xff0c;总是在质疑你的真实性&#xff1f;但如果你能够轻松且合规地与这些裁判员达成和解&#xff0c;使你的网络搜索和自动化任务变得更顺畅&#xff0c;那该有…

【HarmonyOS NEXT】实现在当前Ability页面,拉起另一个Ability页面

【需求】 实现类似微信拉起支付页面。在手机应用程管理界面&#xff0c;可以看到同一个应用的两个窗口&#xff0c;如下图 【方案】 在EntryAbility的页面&#xff0c;点击按钮拉新的Ability 【步骤】 为EntryAbility准备页面 新建FirstAbilityPage页面将EntryAbility中的启动…

LivePortrait V3版:新增精确的肖像编辑,精准操控五官比如眉毛鼻子摇头眨眼撇嘴等,本地一键整合包下载

LivePortrait&#xff0c;这个名字听起来就像是魔法&#xff0c;但它其实是现实世界中的黑科技。想象一下&#xff0c;你那尘封已久的相册里&#xff0c;那些定格在时间里的笑脸&#xff0c;突然间动了起来&#xff0c;眨眼、微笑、甚至说话&#xff0c;这不再是电影里的场景&a…

企业源代码也需要加密!源代码加密软件推荐,2024十款软件排行榜

在科技飞速发展的2024年&#xff0c;企业的源代码作为核心资产&#xff0c;其安全性至关重要。为了防止源代码泄露带来的巨大损失&#xff0c;选择一款合适的源代码加密软件势在必行。下面为您呈现 2024 年十款优秀的源代码加密软件排行榜。 1. GitGuardian 实时监控&#xf…

从0到1:AI与低代码如何推动企业创新

引言 在当今瞬息万变的商业环境中&#xff0c;创新已成为企业在激烈市场竞争中立于不败之地的关键驱动力。面对快速变化的市场需求、技术进步和全球化竞争&#xff0c;企业亟需打破传统的增长模式&#xff0c;从而实现真正意义上的突破性创新。“从0到1”这一理念&#xff0c;源…

System V IPC奥秘:解锁共享内存、消息队列与信号量的高效通信之路

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f351;system V共享内存 &#x1f352;共享内存的原理共享内存数据结构查看和删除共享内存资源的命令 &#x1f33b;共享内存…

Spacedrive:一款基于VDFS的跨平台文件管理器深度解析

前言 你的文件不再被各种设备、云盘束缚&#xff0c;而是像魔法般汇聚在一个地方&#xff0c;触手可及&#xff0c;那将是怎样的畅快淋漓&#xff1f;Spacedrive&#xff0c;这个名字听起来就像是穿越时空的驱动器&#xff0c;它正悄悄改变着我们对文件管理的认知&#xff1b;…

开发物联网驱动拍卖软件平台:如何实现了服务质量的全面提升

在数字化转型的浪潮下&#xff0c;物联网&#xff08;IoT&#xff09;技术正深刻地改变着各行各业的运作模式&#xff0c;拍卖行业也不例外。通过物联网的集成应用&#xff0c;拍卖平台能够实现更高效、透明和个性化的服务&#xff0c;极大地提升用户体验和服务质量。本文将以“…

一起学习LeetCode热题100道(44/100)

44.二叉搜索树中第 K 小的元素(学习) 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#…

u盘启动选择uefi还是legacy_u盘启动选择uefi还是legacy详细分析

最近有很多网友问我想用U盘安装系统&#xff0c;按照网上教程按快捷方式(一般是f12)出现选择U盘菜单时&#xff0c;我到底是选择legacy开头的U盘还是uefi开头的U盘&#xff0c;其实这个取决你要安装什么系统或是磁盘分区类型是gpt还是mbr&#xff0c;比如2016年后出来的笔记本和…

EasyRecovery17中文版永久汉化版电脑数据恢复工具下载

&#x1f388;&#x1f389;安利时间到&#xff01;今天要跟大家分享的是——EasyRecovery17中文版的最新功能&#xff01;&#x1f389;&#x1f388; &#x1f31f;✨ “数据恢复小能手” ✨&#x1f31f; 让我来介绍一下这款软件的主打特点。 EasyRecovery17中文版是一款强…

谷歌账号活动异常,或者申诉回来以后需要手机验证的原因,以及验证手机号的错误操作和正确操作

有一些朋友在使用谷歌账号的时候&#xff0c;会遇到无法直接登录的情况&#xff0c;输入用户名、密码以后&#xff0c;提示说账号活动异常&#xff0c;需要验证手机号。 通常有以下两种情形和界面&#xff0c;出现这种情形的原因分别如下。 一、谷歌账号登录需要输入手机号码…

教你如何训练多模态理解模型

出发点&#xff1a;最近因为工作的需要&#xff0c;在研究多模态理解模型&#xff0c;看了最近一两年比较火的一些论文&#xff0c;感觉Nvidia的VILA工作可以作为比较好的多模态理解模型训练教程&#xff0c;也在这里介绍给大家。 多模态理解模型&#xff1a;也叫Large Vision…

玩游戏的时候怎么录屏?三种实用技巧

在数字化娱乐时代&#xff0c;录制游戏视频已成为玩家分享游戏体验的重要方式。录屏不仅能够记录精彩的游戏瞬间&#xff0c;还能用于制作教程、分析游戏策略或进行游戏直播。那么&#xff0c;玩游戏的时候怎么录屏呢?本文将介绍三种实用的录屏方法&#xff0c;帮助玩家们在畅…

WPF篇(20)- Menu菜单+ContextMenu上下文菜单+StatusBar状态栏

Menu菜单 Menu控件继承于MenuBase&#xff0c;而MenuBase继承于ItemsControl。所以学习Menu之前&#xff0c;要先了解一下MenuBase基类。它是一个抽象类&#xff0c;拥有一个ItemContainerTemplateSelector模板选择器&#xff0c;并重写了一些关于键盘和鼠标的方法。 Menu的子…

react的pdf转图片格式上传到后端

这个东西做的我真的是头昏脑涨 主要需求是,upload上传pdf,pdf转图片格式展示,以图片格式上传到后端 封装了组件代码,父组件直接放就可以了 使用的插件pdfjs-dist,版本是 "pdfjs-dist": "2.5.207",node:14.13.0/18.17.0/16.14.2都可以你们要注意n…

c语言学习,malloc()函数分析

1&#xff1a;malloc() 函数说明&#xff1a; 申请配置size大小内存空间 2&#xff1a;函数原型&#xff1a; void *malloc(size_t size) 3&#xff1a;函数参数&#xff1a; 参数size&#xff0c;为申请内存大小 4&#xff1a;返回值&#xff1a; 配置成功则返回指针&#…

吃透前端文件上传与文件相关操作 多文件上传 大文件切片上传 拖拽上传 后续还会更新 断点续传等等

最近在学文件上传的操作,所以想把学习到东西写成一文章 这片文章是我以小白视角 慢慢学习并熟悉前端文件相关操作的流程总结出来的 前端文件上传 我首先想到是 <input type"file">**选择文件**</input>如果我们想限制上传文件的格式,大小或进行裁剪分片…