【数据结构】速速收藏,一文带你参透双向链表各接口实现

news2024/11/15 15:44:58

目录

🥕前言🥕:

🌽一、双向链表概述🌽:

1.双向链表概念:

2.双向链表结构:

🍆二、双向链表接口实现🍆:

1.工程文件建立:

2.接口实现(本文重点):

Ⅰ.双向链表初始化:

Ⅱ.打印双向链表:

Ⅲ.申请新节点:

Ⅳ.双向链表尾插:

Ⅴ.双向链表尾删:

Ⅵ.双向链表头插:

Ⅶ.双向链表头删:

Ⅷ.双向链表查找:

Ⅸ.双向链表给定节点前插:

Ⅹ.双向链表给定节点后插:

ⅩⅠ.双向链表删除给定节点:

ⅩⅡ.双向链表销毁:

🍄三、完整接口实现代码🍄:

1.List.h:

2.List.c:

3.test.c:

🌶️四、顺序表与链表对比🌶️:

1.两者差异:

2.存储器层次结构(辅图):

🥬总结🥬:


🛰️博客主页:✈️銮同学的干货分享基地

🛰️欢迎关注:👍点赞🙌收藏✍️留言

🛰️系列专栏:🎈 数据结构

                       🎈【进阶】C语言学习

                       🎈  C语言学习

🛰️代码仓库:🎉数据结构仓库

                       🎉VS2022_C语言仓库

        家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真重要,各位路过的友友麻烦多多点赞关注,欢迎你们的私信提问,感谢你们的转发!

        关注我,关注我,关注我,你们将会看到更多的优质内容!!


🏡🏡 本文重点 🏡🏡:

🚅 双向链表 🚃 顺序表与链表对比 🚏🚏

🥕前言🥕:

        上节课中我们完整的实现了无头单向链表的各个功能接口,但是我们也注意到,由于单向链表只保存指向下一节点的指针,于是我们在进行前插等操作时,还需要遍历链表来找到前一个节点,效率不高且步骤繁琐。于是为了克服类似这样的问题,我们更多的会使用另一种数据结构,即带头双向循环链表,本文就将带领各位小伙伴们一起实现带头双向循环链表的各接口功能

🌽一、双向链表概述🌽:

1.双向链表概念:

        双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向直接后继和直接前驱。所以从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表

        并且在上节课中我们就说过

  • 无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,哈希桶、图的邻接表等。
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。虽然它结构复杂,但在实际使用中使用代码实现后,优秀的结构会带来很多优势,实现反而更加简单。并且在我们的实际中所使用的链表数据结构,一般都是带头双向循环链表

         所以今天我们就主要研究带头双向循环链表的各个接口功能的实现

2.双向链表结构:

🍆二、双向链表接口实现🍆:

1.工程文件建立:

        我们仍使用模块化开发格式,使用 List.h 、List.c 、test.c 三个文件进行代码书写:

  • List.h存放函数声明、包含其他头文件、定义宏
  • List.c书写函数定义,书写函数实现
  • test.c书写程序整体执行逻辑

        这其中,我们的接口实现主要研究的是函数实现文件 List.c 中的内容,对 test.c 文件中的内容分不关心

2.接口实现(本文重点):

        这里是本文重点中的重点,即 List.c 文件中的接口具体实现:

Ⅰ.双向链表初始化:

  • 双向链表初始化:
  • 动态申请首节点,并使两个指向直接前置节点与直接后继节点的指针均指向自己,形成循环结构。
  • 最后返回初始化完成的头节点
LNode* LInit()
{
	//哨兵位头节点:
	LNode* phead = (LNode*)molloc(sizeof(LNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

Ⅱ.打印双向链表:

  • 执行操作前需对传入指针进行非空判断,防止对空指针进行操作。 
  • 双向链表的打印方式与单链表相似,采用方式均为由头节点开始,通过节点指针指向寻找下一节点的方式循环进行遍历打印
  • 不同点是,由于双向循环链表首尾相连,形成闭环,因此终止循环打印条件将不再是执行至空指针而是执行回到头节点(即完成整个循环)
void LPrint(LNode* phead)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

Ⅲ.申请新节点:

  • 新节点的申请与单链表基本相同,动态申请新节点后,再对新节点进行操作。
  • 不同点是双向链表多出一个指向直接前驱节点的指针,因此该指针也需要在使用前进行置空操作,防止造成野指针错误。
LNode* BuyListNode(LDataType x)
{
    LNode* newnode = (LNode*)malloc(sizeof(LNode));
    newnode->data = x;
    newnode->next = NULL;
    newnode->prev = NULL;
    return newnode;
}

Ⅳ.双向链表尾插:

  • 执行操作前应当进行非空判断,防止传入空指针
  • 在进行尾插操作前应当首先找到尾节点,采用的方式是,通过双向循环链表中头节点的前驱指针指向来找到尾节点。
  • 接着动态申请新节点
  • 最后执行尾插操作。首先使前面找到的尾节点的后继指针指向新节点,并使新节点的前驱指针也指向尾节点;接着使头节点的前驱指针指向新节点,并使新节点的后继指针指向头节点
void LPushBack(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾节点
    LNode* newnode = BuyListNode(x);
    //新尾互指:
    tail->next = newnode;
    newnode->prev = tail;
    //新头互指:
    phead->prev = newnode;
    newnode->next = phead;
}
  • 测试尾插接口功能实现:

Ⅴ.双向链表尾删:

  • 在双向链表进行尾删时,不仅要防止传入空指针,同时也要注意避免链表为空(只含有哨兵节点)的情况发生
  • 首先找到尾节点及尾节点的前驱节点,接着使该前驱节点与头节点跳过尾节点互指,最后释放原尾节点并置空即可
void LPopBack(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾
    LNode* tailprev = tail->prev;    //找到尾的前驱
    tailprev->next = phead;    //尾前驱节点的后继指针指向头
    phead->prev = tail->prev;  //头的前驱指针指向尾的前驱
    free(tail);
    tail = NULL;
}
  • 测试尾删接口功能实现:

Ⅵ.双向链表头插:

  • 执行操作前应当对传入指针进行判断,防止传入空指针
  • 在改变指向前,应当首先保存哨兵节点的后继节点,因为当我们插入新节点后链表结构将会发生改变,再想要找到该节点将变得麻烦
  • 进行插入操作时,使哨兵节点的后继指针指向新节点,再使新节点的前驱指针指向哨兵节点,接着使用同样的操作使新节点与哨兵节点的原后继节点互指
void LPushFront(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* newnode = BuyListNode(x);
    LNode* next = phead->next;    //保存哨兵节点的后继节点
    phead->next = newnode;    //哨兵点的后继指针指向新节点
    newnode->prev = phead;    //新节点的前驱指针指向哨兵节点
    newnode->next = next;    //新节点的后继指针指向哨兵节点的原后继节点
    next->prev = newnode;    //哨兵节点的原后继节点前驱指针指向新节点
}
  • 测试头插接口功能实现:

Ⅶ.双向链表头删:

  • 首先进行非空判断,并排除链表为空(只含有哨兵节点)的情况。
  • 开始进行头删操作前找到并保存头节点,防止改变指向后链表结构发生变化难以找到原本的头节点,以便于最后进行释放
  • 并且应当找到并保存原头节点的后继节点,防止防止改变指向后链表结构发生变化而难以找到
  • 具体操作便是使哨兵节点与头节点的后继节点跳过头节点互指,再将要删除的头节点释放并置空即可。
void LPopFront(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* next = phead->next;    //保存头节点,便于释放
    LNode* nextNext = next->next;    //保存头节点的后继节点
    phead->next = nextNext;
    nextNext->prev = phead;
    free(next);
    next = NULL;
}
  • 测试头删接口功能实现:

Ⅷ.双向链表查找:

  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 查找逻辑很简单,遍历整个链表,直至链表完整循环一遍,若比对存在匹配元素返回该节点,否则返回空。
LNode* LFind(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }
    return NULL;
}
  • 测试查找接口功能实现:

Ⅸ.双向链表给定节点前插:

  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 执行逻辑很简单,将数据存入通过动态申请的来的新节点中后,找到目标节点,使目标节点的前驱节点与新节点互指,再使新节点与目标节点互指接即可。
void LInsert(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* newnode = BuyListNode(x);
    posPrev->next = newnode;
    newnode->prev = posPrev;
    newnode->next = pos;
    pos->prev = newnode;
}
  • 测试前插接口功能实现:

Ⅹ.双向链表给定节点后插:

  • 执行操作前需进行非空判断,防止对空指针进行操作。
  • 执行逻辑与前插高度类似,不同的是使目标节点的后继节点与新节点互指,再使新节点与目标节点互指
void LInsertBack(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->next;
    LNode* newnode = BuyListNode(x);
    posPrev->prev = newnode;
    newnode->next = posPrev;
    newnode->prev = pos;
    pos->next = newnode;
}
  • 测试后插接口功能实现:

ⅩⅠ.双向链表删除给定节点:

  • 执行操作前需进行非空判断,防止操作空指针。
void LErase(LNode* pos)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* posNext = pos->next;
    posPrev->next = posNext;
    posNext->prev = posPrev;
    free(pos);
    pos = NULL;
}
  • 测试删除接口功能实现:

ⅩⅡ.双向链表销毁:

  • 哨兵节点为空,即表示链表内没有有效数据节点,则无需进行销毁、释放与置空操作
  • 含有有效节点则遍历所有节点,将每一个节点均进行释放,特别注意所有数据节点释放完毕之后,不要忘记释放哨兵节点
void LDestroy(LNode* phead)
{
    if(phead==NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead);
    {
        LNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(phead);
    phead = NULL;
}

🍄三、完整接口实现代码🍄:

1.List.h:

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//自定数据类型LDataType:
typedef int LDataType;

//双向链表节点结构:
typedef struct ListNode
{
	LDataType data;
	struct LNode* next;
	struct LNode* prev;
}LNode;

LNode* LInit();    //初始化双向循环链表
void LPrint(LNode* phead);    //打印双向循环链表
LNode* BuyListNode(LDataType x);//双向循环链表新节点申请
void LPushBack(LNode* phead, LDataType x);    //双向循环链表尾插
void LPopBack(LNode* phead);    //双向循环链表尾删
void LPushFront(LNode* phead, LDataType x);    //双向循环链表头插
void LPopFront(LNode* phead);    //双向循环链表头删
LNode* LFind(LNode* phead, LDataType x);    //双向循环链表查找
void LInsertFront(LNode* pos, LDataType x);    //双向循环链表给定节点前插
void LInsertBack(LNode* pos, LDataType x);    //双向循环链表给定节点后插
void LErase(LNode* pos);    //双向循环链表给定节点删除
void LDestroy(LNode* phead);    //双向循环链表的销毁

2.List.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

//初始化双向循环链表初始化:
LNode* LInit()
{
	//哨兵位头节点:
	LNode* phead = (LNode*)malloc(sizeof(LNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

//打印双向循环链表
void LPrint(LNode* phead)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

//双向循环链表申请新节点:
LNode* BuyListNode(LDataType x)
{
    LNode* newnode = (LNode*)malloc(sizeof(LNode));
    newnode->data = x;
    newnode->next = NULL;
    newnode->prev = NULL;
    return newnode;
}

//双向循环链表尾插:
void LPushBack(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾节点
    LNode* newnode = BuyListNode(x);
    //新尾互指:
    tail->next = newnode;
    newnode->prev = tail;
    //新头互指:
    phead->prev = newnode;
    newnode->next = phead;
}

//双向循环链表尾删:
void LPopBack(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* tail = phead->prev;    //找到尾
    LNode* tailprev = tail->prev;    //找到尾的前驱
    tailprev->next = phead;    //尾前驱节点的后继指针指向头
    phead->prev = tail->prev;  //头的前驱指针指向尾的前驱
    free(tail);
    tail = NULL;
}

//双向循环链表头插:
void LPushFront(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* newnode = BuyListNode(x);
    LNode* next = phead->next;    //保存哨兵节点的后继节点
    phead->next = newnode;    //哨兵点的后继指针指向新节点
    newnode->prev = phead;    //新节点的前驱指针指向哨兵节点
    newnode->next = next;    //新节点的后继指针指向哨兵节点的原后继节点
    next->prev = newnode;    //哨兵节点的原后继节点前驱指针指向新节点
}

//双向循环链表头删:
void LPopFront(LNode* phead)
{
    if ((phead == NULL) || (phead->next == phead))    //排除为空的情况
    {
        return;
    }
    LNode* next = phead->next;    //保存头节点,便于释放
    LNode* nextNext = next->next;    //保存头节点的后继节点
    phead->next = nextNext;
    nextNext->prev = phead;
    free(next);
    next = NULL;
}

//双向循环链表查找:
LNode* LFind(LNode* phead, LDataType x)
{
    if (phead == NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        else
        {
            cur = cur->next;
        }
    }
    return NULL;
}

//双向循环链表给定节点前插:
void LInsert(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* newnode = BuyListNode(x);
    posPrev->next = newnode;
    newnode->prev = posPrev;
    newnode->next = pos;
    pos->prev = newnode;
}

//双向循环链表给定节点前插:
void LInsertFront(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* newnode = BuyListNode(x);
    posPrev->next = newnode;
    newnode->prev = posPrev;
    newnode->next = pos;
    pos->prev = newnode;
}

//双向循环链表给定节点后插:
void LInsertBack(LNode* pos, LDataType x)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->next;
    LNode* newnode = BuyListNode(x);
    posPrev->prev = newnode;
    newnode->next = posPrev;
    newnode->prev = pos;
    pos->next = newnode;
}

//双向循环链表给定节点删除:
void LErase(LNode* pos)
{
    if (pos == NULL)
    {
        return;
    }
    LNode* posPrev = pos->prev;
    LNode* posNext = pos->next;
    posPrev->next = posNext;
    posNext->prev = posPrev;
    free(pos);
    pos = NULL;
}

//双向循环链表销毁:
void LDestroy(LNode* phead)
{
    if(phead==NULL)
    {
        return;
    }
    LNode* cur = phead->next;
    while (cur != phead);
    {
        LNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(phead);
    phead = NULL;
}

3.test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

void LTest()
{
	LNode* plist = LInit();
	//双向循环链表尾插:
	LPushBack(plist, 1);
	LPushBack(plist, 2);
	LPushBack(plist, 3);
	LPushBack(plist, 4);
	LPrint(plist);    //打印链表
	LNode* ret = LFind(plist, 3);
	LErase(ret);
	LPrint(plist);    //打印链表
}

int main()
{
	LTest();

	return 0;
}

//双向循环链表尾插与尾删:
//LNode* plist = LInit();
双向循环链表尾插:
//LPushBack(plist, 1);
//LPushBack(plist, 2);
//LPushBack(plist, 3);
//LPushBack(plist, 4);
//LPrint(plist);    //打印链表
双向循环链表尾删:
//LPopBack(plist);
//LPopBack(plist);
//LPopBack(plist);
//LPrint(plist);    //打印链表

//双向循环链表头插与头删:
//LNode* plist = LInit();
//双向循环链表头插:
//LPushFront(plist, 1);
//LPushFront(plist, 2);
//LPushFront(plist, 3);
//LPushFront(plist, 4);
//LPrint(plist);    //打印链表
双向循环链表头删:
//LPopFront(plist);
//LPopFront(plist);
//LPopFront(plist);
//LPrint(plist);    //打印链表

//双向循环链表查找:
//LNode* plist = LInit();
//双向循环链表尾插:
//LPushBack(plist, 1);
//LPushBack(plist, 2);
//LPushBack(plist, 3);
//LPushBack(plist, 4);
//LPrint(plist);    //打印链表
//LNode* ret = LFind(plist, 3);

//双向循环链表给定位置前插:
//LNode* plist = LInit();
双向循环链表尾插:
//LPushBack(plist, 1);
//LPushBack(plist, 2);
//LPushBack(plist, 3);
//LPushBack(plist, 4);
//LPrint(plist);    //打印链表
//LNode* ret = LFind(plist, 3);
//LInsertFront(ret, 5);
//LPrint(plist);    //打印链表

//双向循环链表给定位置后插:
//LNode* plist = LInit();
双向循环链表尾插:
//LPushBack(plist, 1);
//LPushBack(plist, 2);
//LPushBack(plist, 3);
//LPushBack(plist, 4);
//LPrint(plist);    //打印链表
//LNode* ret = LFind(plist, 3);
//LInsertBack(ret, 5);
//LPrint(plist);    //打印链表

//双向循环链表给定节点删除:
//LNode* plist = LInit();
双向循环链表尾插:
//LPushBack(plist, 1);
//LPushBack(plist, 2);
//LPushBack(plist, 3);
//LPushBack(plist, 4);
//LPrint(plist);    //打印链表
//LNode* ret = LFind(plist, 3);
//LErase(ret);
//LPrint(plist);    //打印链表

🌶️四、顺序表与链表对比🌶️:

1.两者差异:

不同点顺序表链   表
连续性物理上一定连续逻辑上一定连续
是否支持随机访问
任意位置节点修改可能需要搬移元素,效率低只需改变指针指向,效率高
插入方式动态顺序表,使用时需扩容无容量概念
应用场景数据高效存储+频繁访问节点及数据修改频繁
缓存利用率

2.存储器层次结构(部分差异成因辅图):

🥬总结🥬:

        至此我们关于带头双向循环链表,乃至链表全部内容的学习和讲解就到此为止喽~不知道各位小伙伴们掌握了多少呢?希望各位小伙伴们下去以后多加练习,夯实基础,牢固掌握链表与顺序表的相关接口实现与使用,为后面的 C++ 高阶数据结构学习打好坚实的基础!!!

        🔥🔥相信自己拥有无限的潜力,只要你有一刻渴望成长,它就会支撑着你开花结果🔥🔥

        更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~  你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

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

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

相关文章

【Docker应用篇】Docker安装RocketMQ

【Docker应用篇】Docker安装RocketMQCentos 7.4安装DockerDocker安装RocketMQ技术架构安装Centos 7.4安装Docker 1、较旧版本的Docker被称为docker或docker-engine&#xff0c;如果已安装这些&#xff0c;请卸载它们&#xff1a; yum remove -y docker \docker-client \docke…

ubuntu18.04 cuda卸载及安装

1.若电脑上已经安装了其他版本的cuda及显卡驱动&#xff0c;需要完全卸载并删除相关文件&#xff0c;否则会导致安装不成功&#xff0c;执行如下&#xff1a; 1.1卸载cuda,步骤如下: cd /usr/local/cuda-xx.x/bin/ &#xff08;进入你的cuda文件夹下&#xff09; sudo ./cuda-…

流程控制|使用循环结构等完成重复性工作(文末附视频)

本节介绍Go语言中的程序流程控制结构&#xff0c;具体包括以下内容&#xff1a; 循环结构流程控制语句条件分支结构 经过前面两个小节的学习&#xff0c;我们初步掌握了Go语言的语法知识。本小节将介绍循环和条件分支&#xff0c;从而使用少量代码完成大量重复性的操作&#x…

NestJS 项目实战 需求分析(文末附视频)

前言 一般常规的项目立项之初会有一份 MRD&#xff08;Market Requirements Document&#xff0c;市场需求文档&#xff09;用来判断产品的必需性以及价值等。 对于基础项目开发来说&#xff0c;使用 MRD 可能有些重量级&#xff0c;但我们也需要对一个新的基建类型项目做一个…

【快速幂】875. 快速幂

875. 快速幂 文章目录题目描述输入格式&#xff1a;输出格式&#xff1a;数据范围输入样例输出样例方法&#xff1a;快速幂解题思路代码复杂度分析&#xff1a;题目描述 给定 nnn 组 ai,bi,pia_i,b_i,p_iai​,bi​,pi​&#xff0c;对于每组数据&#xff0c;求出 aibimodpia_i…

Numpy常用random随机函数

Numpy常用random随机函数 seed 向随机数生成器传递随机状态种子 只要random.seed( * ) seed里面的值一样&#xff0c;那随机出来的结果就一样。所以说&#xff0c;seed的作用是让随机结果可重现。也就是说当我们设置相同的seed&#xff0c;每次生成的 随机数相同。如果不设置…

冯 • 诺依曼体系结构与操作系统

目录 一、冯 • 诺依曼体系结构 1.1 冯 • 诺依曼体系结构推导 1.2 内存提高效率 1.3 具体案例理解冯 • 诺依曼体系结构 1.4 其他认识 二、操作系统 2.1 操作系统概念 2.2 操作系统的上下层 2.3 管理理念:先描述&#xff0c;再组织 一、冯 • 诺依曼体系结构 1.1 冯 …

Acwing - 算法基础课 - 笔记(数学知识 · 四)(补)

数学知识&#xff08;四&#xff09; 这一小节讲的是容斥原理和简单博弈论。 容斥原理 定义 最基本的&#xff0c;假设有3个两两相交的圆。那么三个圆所覆盖的面积大小为 S1S2S3−S1∩S2−S2∩S3−S1∩S3S1∩S2∩S3S_1S_2S_3 - S_1 \cap S_2 - S_2 \cap S_3 - S_1 \cap S_3…

【JavaEE】如何开始最基础的Servlet编程(借助Tomcat实现)

什么是Servlet我们知道服务器工作的三部曲&#xff1a;接收请求 -> 处理请求并计算响应 -> 发送响应Servlet是个接口&#xff0c;实现这个接口的类就是用来进行中间的一个步骤“处理请求并计算响应的”&#xff0c;应用于HTTP传输的中间层。借助Tomcat服务器进行Servlet编…

【论文速递】WACV2022 - 从边界框标注学习小样本分割

【论文速递】WACV2022 - 从边界框标注学习小样本分割 【论文原文】&#xff1a;Learning Few-shot Segmentation from Bounding Box Annotations 获取地址&#xff1a;https://openaccess.thecvf.com/content/WACV2023/papers/Han_Learning_Few-Shot_Segmentation_From_Bound…

Word文档和PDF文件如何互相转换?

工作中&#xff0c;有时候我们需要把Word转换成PDF格式&#xff0c;转换后不但更美观、专业&#xff0c;也可以防止文档被修改。 那Word文档如何转换成PDF文件呢&#xff1f;其实在Word里面就可以直接转换。 文档编辑好后&#xff0c;在菜单中点击【文件】选项&#xff0c;然…

搭建 Go 语言的开发环境(文末附视频讲解)

从本小节开始&#xff0c;我们就要正式动手实践了。 类比现实生活&#xff0c;我们若要钉钉子&#xff0c;就需要准备锤子&#xff1b;想要烧菜&#xff0c;就需要准备灶具和食材…… 类似地&#xff0c;若要在电脑上编写 Go 语言程序&#xff0c;便要先配置开发环境。 下载和…

干货 | 互联网广告数据的匿名化方案研究

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;背景介绍一、匿名化必要性互联网广告具有非常重要的商业价值&#xff0c;同时也是涉及数据处理十分密集的行业&#xff0c;出现了操作规范化、个人信息保护和商业数据安全等…

[GXYCTF2019]禁止套娃(无参数RCE)

目录 信息收集 知识讲解 涉及函数 PHP的正则表达式 无参rce 用到的函数 思路分析 方法一 方法二 信息收集 拿到这道题&#xff0c;抓包看了看&#xff0c;啥也没有&#xff0c;用dirsearch爆破目录发现.git目录&#xff0c;猜测存在.git源码泄露&#xff0c;用githac…

Web Spider XHR断点 堆栈跟值 逆向案例(四)

声明 此次案例只为学习交流使用&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;切勿用于其他非法用途&#xff1b; 文章目录声明前言一、任务说明二、网站分析三、XHR断点调试&#xff0c;扣JS加密代码四、代码实现1、JS加密代码&#xff1a;encode.js2、…

安卓逆向:基础入门(一)

前言随着app的广泛应用&#xff0c;使用过程中&#xff0c;难免会遇到些很不友好的功能&#xff0c;比如&#xff1a;游戏充值、间断性弹窗广告、续费解锁下一回等等。而随之会产生如何将这些功能取消掉&#xff0c;而Android逆向就可以做到&#xff0c;纵向丝滑。当然这只是安…

Kinect与TOF、双目、结构光相机比较相机国产、非国产统计参数对比分析

Kinect与TOF、双目、结构光相机比较相机国产、非国产统计参数对比分析 Kinect v1和Kinect v2之间的参数比较 从图中可以看出&#xff0c;Kinect v2的表现比Kinect v1要好得多&#xff1a;首先最令人印象深刻的是分辨率的提高&#xff0c;v2达到了1080p&#xff0c;甚至视野也大…

JavaWeb | JDBC概述及IDEA连接MySQL

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JavaWeb Java入门篇&#xff1a; &#x1f525;Java基础学习篇 文章目录一、前言二…

java基础学习 day36(字符串相关类的底层原理)

字符串存储的内存原理 直接赋值会复用字符串常量池中已有的new出来的不会复用&#xff0c;而是开辟一个新的空间来创建 “”号比较的到底是什么 基本数据类型比较数据值引用数据类型比较地址值 PS. 所以以后对引用数据类型&#xff0c;不要用“”&#xff0c;改用.equals()…

RS485接线方式小科普

欢迎来到东用知识小课堂&#xff01;RS-485采用平衡发送和差分接收方式实现通信&#xff1a;发送端将串行口的ttl电平信号转换成差分信号a&#xff0c;b两路输出&#xff0c;经过线缆传输之后在接收端将差分信号还原成ttl电平信号。RS-485总线网络拓扑一般采用终端匹配的总线型…