双向带头循环链表

news2024/11/17 7:40:46

双向带头循环链表

  • 1.前言
  • 2.带头双向循环链表的初始化
  • 3.创建一个哨兵位头节点
  • 4.链表的打印
  • 5.malloc函数创建节点
  • 5.链表的尾插
  • 6.链表的尾删
  • 7.链表的头插
  • 8.链表的头删
  • 9.链表的查找
  • 10.链表任意位置插入(在给点定位置的前面插入)
  • 11.链表任意位置删除
  • 12.空间释放
  • 13.代码优化
  • 14.整体代码展示
    • 14.1 DList.h展示
    • 14.2 DList.c展示
    • 14.3 Text.c展示

所属专栏:初始数据结构
博主首页:初阳785
代码托管:chuyang785
感谢大家的支持,您的点赞和关注是对我最大的支持!!!
博主也会更加的努力,创作出更优质的博文!!
关注我,关注我,关注我,重要的事情说三遍!!!!!!!!

1.前言

  • 我们之前也已经学完了单链表,并且也做了不少的单链表的OJ题了,想必看到这里的小伙伴对我们的单链表也是比较熟悉了,也可以比较流畅的写出我们的单链表了。所以本章节我们进一步学习我们的链表知识——双向带头循环链表。
  • 可能有点小伙伴一听到双向带头循环链表就开始慌张了,不仅双向带头,还循环,这不得难上加难。但是这里我想对大家说的是”眼见为虚,上手为实“。你不要看他 复杂其实上起手来比我们的单链表简单得多了,而且还很方便。
  • 这里注意一点就是我们这个双向链表是带头的(哨兵位)这一点在我们在写单链表OJ题的时候也有用到,也是比较方便的一种写法的。我们可以对比以下我们之前写单链表的时候是没有用到我们的哨兵位的,这样写的不好的地方的就是我们要传二级指针才能通过形参改变实参。但是如果我们用到了哨兵位的话就不需要传二级指针了,原因很简单——我们不用改变头节点了,我们的头节点就是我我们创建的哨兵位指向的next,所以有了哨兵位我们会方便很多。
  • 话不多说我们直接开始我们的本章节——双向带头循环链表。

2.带头双向循环链表的初始化

  • 我们现在来分析以下什么是双向链表。我们之前的单链表之所以叫做单链表,原因就是单链表是单向的他只能通过next找到下一个节点,找不到他前面一个节点这也是单链表的一个缺陷。那我们的双链表就可以弥补这一缺陷即:它既可以通过next找到下一个节点,还可以通过prev找到前一个节点。这一就说明了我们的双链表在定义结构体的时候不仅要存放一个数据datanext记录下一个节点,还要存放一给指针prev来记录我们前一个节点。
  • 而循环的意思就有点像我们之前做单链表OJ题的带环结构,也就是说我们可以通过尾找到我们的头,再通过头依次重复遍历,也就是说我们的尾节点的next指向phead(头节点) 而我们的pheadprev指向我们的尾节点。

在这里插入图片描述

  • 同样的我们写一个有许多接口函数链接在一起的程序,我们把它分别写在三个文件中分别是:text.c,DList.c和DList.h这三个文件中。
  • 所包含的头文件:
//防止头文件重复定义
#pragma once
#include <stdio.h>
//断言
#include <assert.h>
//动态内存malloc头文件
#include <stdlib.h>
  • 数据结构体创建
//存储数据类型类型
typedef int LTDateType;

typedef struct ListNode
{
	//存储数据
	LTDateType data;
	//记录下一个节点
	struct ListNode* next;
	//记录前一个节点
	struct ListNode* prev;
}LTNode;
  • 各给接口函数的定义,这个都是一些函数的声明,而我们函数的声明是放在后缀名为.h的头文件中的。
//初始化
LTNode* ListInit();
//链表打印
void ListPrint(LTNode* phead);
//尾插
void ListPushBack(LTNode* phead, LTDateType x);
//尾删
void ListPopBack(LTNode* phead);
//头插
void ListPushFront(LTNode* phead, LTDateType x);
//头删
void ListPopFront(LTNode* phead);
//查找
LTNode* ListFind(LTNode* phead,LTDateType x);
//给定位置插入
LTNode* ListTnsert(LTNode* pos, LTDateType x);
//给定位置删除
LTNode* ListErase(LTNode* pos);
//释放空间
void ListDestroy(LTNode* phead);

3.创建一个哨兵位头节点

  • 我们常见以一个头节点间要用到malloc函数。我们分析一下,如果链表里一个数据都没有插入,也就是说我们只有头节点也就是我们的哨兵位,这个是时候哨兵位的== next== 和prev都是指向自己的。
LTNode* ListInit()
{
	//设置哨兵为
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead != NULL)
	{
		phead->next = phead;
		phead->prev = phead;
		return phead;
	}
}

4.链表的打印

  • 我们设计链表的时候最好的检查我们功能函数有没有写对方法就是将数据打印出来,可以直观的观察数据。
  • 既然我们设计了哨兵位,但是我们要要知道的是哨兵位是存储无效数据的,我们是从哨兵位的next开始存储有效数据的,也就是说我们打印也是从哨兵位的next开始打印的。
  • 而我们遍历的停止条件就是到我们遍历到我们的哨兵位的时候停止。
void ListPrint(LTNode* phead)
{
	assert(phead);
	//从头节点的下一个开始遍历
	
	LTNode* cur = phead->next;
	
	//找到头节点后停止遍历
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

5.malloc函数创建节点

  • 我们在进行尾插或者头插的时候都需要向内存申请一块空间来存放我们的数据,然后再将数据插入。所以既然都需要malloc一块空间,而且都是一样的创建方式,不妨创建出一个开辟空间的函数,需要创建的时候直接调用就行了,这样就可以防止代码重复累赘了。
LTNode* newnode(LTDateType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode != NULL)
	{
		\\存入数据
		newNode->data = x;
		newNode->prev = newNode->next = NULL;
		return newNode;
	}
}

5.链表的尾插

  • 尾插的第一步就是先创建我们要插入的节点,这里就需要用到malloc。
  • 我们的尾插其实和单链表的尾插是有点像,但是不一样的就是我们需要将把我们插入的节点的prev指向前面一个节点而我们的前一个节点的next指向我们插入的节点形成双向的链表。
  • 而且插入的节点的next要指向我们的头节点,我们的头节点的prev指向我们的插入的节点,形成循环结构。
  • 这里我们不需要像我们之前写的单链表那样判断链表是空的情况,原因就是我们设计了哨兵位。
void ListPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
    LTNode* newNode = newnode(x);
	LTNode* tail = phead->prev;
	if (newNode != NULL)
	{
		\\链接前后两个节点,形成双向的链表。
		tail->next = newNode;
		newNode->prev = tail;	
		
		\\链接为节点和头节点,形成循环结构。
		newNode->next = phead;
		phead->prev = newNode;
	} 
}

在这里插入图片描述

6.链表的尾删

  • 说到尾删如果我们是单链表的话是相当的麻烦的,因为单链表只能找到后一个节点找不到前一个节点,所以链接的时候比较麻烦。
  • 但是我们的双向链表就不一样了,他是双向的而且还是循环的,也就是说我们要找的尾节点就是我们的head->prev上面的图也是比较清楚的。
  • 而且我们要找的尾节点的前一个也是很好找的,就是我们的尾节点->prev,这样我们为节点找到了,也找到了尾节点的前一个,链接起来就很方便了。
  • 同时我们也需要判断我们的链表是否删完了,这个也是比较好判断的,只要我们的phead->next != phead就说明还有数据可以删除。
void ListPopBack(LTNode* phead)
{
	\\判断是否还有数据可以删除
	assert(phead->next != phead);
	\\找到尾节点
	LTNode* cur = phead->prev;
	
	\\将我们的尾节点指向的prev指向我们的头节点。
	cur->prev->next = phead;
	
	\\phead又之指向回去,形成循环。
	phead->prev = cur->prev;
	
	\\最后释放空间
	free(cur);
	cur = NULL;
}

在这里插入图片描述

7.链表的头插

  • 头插也是比较简单的,只需要将phead->next指向我们malloc出来节点,用我们malloc来的节点的prev指向phead
  • 我们还得用一个Next指针记录下phead->next,这样子我们才能将malloc出来的节点与其后面对链表链接起来。
void ListPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newNode = newnode(x);
	\\记录phead指向的下一个节点,为后面链接做好准备。
	LTNode* Next = phead->next;
	
	\\链接头节点和我们malloc出来的就节点形成双向
	phead->next = newNode;
	newNode->prev = phead;
	
	\\链接malloc出来的节点和Next将链表整体链接起来。
	newNode->next = Next;
	Next->prev = newNode;

}

在这里插入图片描述

8.链表的头删

  • 删除链表的话同样的头删的节点是可以直接通过头节点phead->next找到的,而且我们还得用nextNext找到我们要删除节点的下一个节点,以便删除后再次链表链接起来。
  • 这里我们也需要判断我们的链表是否删完了,这个也是比较好判断的,只要我们的phead->next != phead就说明还有数据可以删除。
void ListPopFront(LTNode* phead)
{
	assert(phead);
	\\判断是否还有数据
	assert(phead->next != phead);

	LTNode* next = phead->next;
	LTNode* nextNext = next->next;

	\\释放空间
	free(next);
	
	\\链接phead和nextNext
	phead->next = nextNext;
	nextNext->prev = phead;
}

9.链表的查找

  • 查找数据就会比较简单了,但是由于这是一个循环链表,所以我们遍历结束条件就是当再次遍历到phead的时候停止。
  • 而且我们开始遍历的节点是phead->next
LTNode* ListFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	\\没找到返回NULL
	return NULL;
}

10.链表任意位置插入(在给点定位置的前面插入)

  • 可以说双向链表的精华之处就值之这里了,为什么这么说呢,这里我们可以对比一下我们的单链表就知道双向链表的好处了。
  • 既然要插入一个数据同样要先malloc一个节点。
  • 而我们要插入数据在给定节点前面的话,只需要将其链接在pos前面的节点和pos之间就行了,而pos前一个节点就是pos->prev这个时候链接起来就很方便了。
LTNode* ListTnsert(LTNode* pos, LTDateType x)
{
	assert(pos);
	\\pos前一个节点
	LTNode* posPrev = pos->prev;
	
	\\malloc一块节点
	LTNode* newNode = newnode(x);
	 
	 \\链接
	posPrev->next = newNode;
	newNode->prev = posPrev;
	newNode->next = pos;
	pos->prev = newNode;
}

在这里插入图片描述

11.链表任意位置删除

  • 可以说双向链表的精华之处就值之这里了,为什么这么说呢,这里我们可以对比一下我们的单链表就知道双向链表的好处了。
  • 因为给点的位置的节点,我们可以通过这个节点找到要删除节点的前一个和后一个,那要删除不是轻而易举吗。
LTNode* ListErase(LTNode* pos)
{
	assert(pos);
	\\找到pos前一个节点
	LTNode* posPrev = pos->prev;

	\\找到pos后一个节点
	LTNode* posNext = pos->next;

	\\链接
	posPrev->next = posNext;
	posNext->prev = posPrev;

	\\释放空间
	free(pos);
	pos = NULL;
}

在这里插入图片描述

12.空间释放

  • 因为我们的节点都是malloc出来的,一旦我们的程序结束了这些空间都需要还给操作系统,不然就会导致内存泄漏。
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
}
  • 这里我们唯一要注意的是既然我们把出来phead以外的节点都free了,那这里为什么没有free(phead)的呢?原因是这里我们传的是一级指针,这里的phead是形参,我们在函数里面改变形参是不会影响实参的,如果要通过形参改变实参的话必须传二级指针,那为什么不传二级指针呢,这个理由可能比较牵强,就是保持接口函数的一致性,毕竟前面的接口函数都是传的一级指针,所以这里我们已处一级指针。那怎么free实参呢,我们可以在创建他的最后面free掉,与就是说在他创建的作用域free。

13.代码优化

  • 如果我们仔细点观察的话,我们会发现我们的尾插和头插都是可以用 (给定任意位置插入)来代替的。

  • 而我的尾删和头删都是可以用 (给定任意位置删除)来代替的。

  • 我们的尾插其实就是从phead开始插入的
    在这里插入图片描述

  • 你这里看似我们的newnode是子phead的后面,但是如果我们遍历的话还是从phead->next开始的,到phead停止的,而我们遍历的时候newnode还是最后一个遍历的,这里其实就是画的像这样,但是如果我们换个角度看(把newnode放在最后面)就很明显了。

  • 而我们的头插其实就是在phead-next地方插入。

  • 这个就相对来计较好理解,直接头插。

  • 所以我们的尾插和头插就可改成这个样子:

尾插

void ListPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	ListTnsert(phead, x);
}

头插

void ListPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	ListTnsert(phead->next, x);
}
  • 而我们的尾删其实就是phead-prev开始删除的。
  • 我们的头删就是从phead->next开始删除的。

尾删

void ListPopBack(LTNode* phead)
{
	assert(phead->next != phead);
	ListErase(phead->prev);

}

头删

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListErase(phead->next);
}

14.整体代码展示

14.1 DList.h展示

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int LTDateType;

typedef struct ListNode
{
	LTDateType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//初始化
LTNode* ListInit();
//链表打印
void ListPrint(LTNode* phead);
//尾插
void ListPushBack(LTNode* phead, LTDateType x);
//尾删
void ListPopBack(LTNode* phead);
//头插
void ListPushFront(LTNode* phead, LTDateType x);
//头删
void ListPopFront(LTNode* phead);
//查找
LTNode* ListFind(LTNode* phead,LTDateType x);
//给定位置插入
LTNode* ListTnsert(LTNode* pos, LTDateType x);
//给定位置删除
LTNode* ListErase(LTNode* pos);
//释放空间
void ListDestroy(LTNode* phead);

14.2 DList.c展示

#define _CRT_SECURE_NO_WARNINGS 1
#include "DList.h"

LTNode* ListInit()
{
	//设置哨兵为
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead != NULL)
	{
		phead->next = phead;
		phead->prev = phead;
		return phead;
	}
}

void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

LTNode* newnode(LTDateType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode != NULL)
	{
		newNode->data = x;
		newNode->prev = newNode->next = NULL;
		return newNode;
	}
}
void ListPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	//LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	//LTNode* tail = phead->prev;
	//if (newNode != NULL)
	//{
	//	newNode->data = x;

	//	tail->next = newNode;
	//	newNode->prev = tail;
	//	
	//	newNode->next = phead;
	//	phead->prev = newNode;

	//} 
	ListTnsert(phead, x);
}
void ListPopBack(LTNode* phead)
{
	assert(phead->next != phead);
	//LTNode* cur = phead->prev;
	//cur->prev->next = phead;
	//phead->prev = cur->prev;
	//free(cur);
	//cur = NULL;
	ListErase(phead->prev);

}

void ListPushFront(LTNode* phead, LTDateType x)
{
	//assert(phead);
	//LTNode* newNode = newnode(x);
	//LTNode* Next = phead->next;

	//phead->next = newNode;
	//newNode->prev = phead;

	//newNode->next = Next;
	//Next->prev = newNode;
	ListTnsert(phead->next, x);

}
void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	//LTNode* next = phead->next;
	//LTNode* nextNext = next->next;

	//free(next);
	//phead->next = nextNext;
	//nextNext->prev = phead;
	ListErase(phead->next);

}

LTNode* ListFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos位置之前插入
LTNode* ListTnsert(LTNode* pos, LTDateType x)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* newNode = newnode(x);
	 
	posPrev->next = newNode;
	newNode->prev = posPrev;

	newNode->next = pos;
	pos->prev = newNode;
}
LTNode* ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	pos = NULL;
}

void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
}

14.3 Text.c展示

  • 这个文件主要是用来测试接口函数的,按自己需要添加
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "DList.h"


void textList1()
{
	//初始化
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPopBack(plist);
	ListPopBack(plist);
	//ListPopBack(plist);
	//ListPopBack(plist);
	//ListPopBack(plist);

	ListPrint(plist);

}
void textList2()
{
	LTNode* plist = ListInit();
	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);
	ListPushFront(plist, 9);
	ListPopFront(plist);
	ListPopFront(plist);
	//ListPopFront(plist);
	//ListPopFront(plist);

	ListPrint(plist);


}

void textList3()
{
	LTNode* plist = ListInit();
	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);
	ListPushFront(plist, 9);

	LTNode* pos=ListFind(plist, 9);
	ListTnsert(pos, 60);

	ListPrint(plist);
	ListDestroy(plist);
	plist = NULL;
}
int main()
{
	textList1();
	textList2();
	//textList3();
	return 0;
}

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

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

相关文章

IPWorks Bluetooth ! IPWorks BLE 2022 C++ Edition Crack

蓝牙库-IPWorks Bluetooth ! IPWorks BLE 2022 C Edition 一个蓝牙低功耗组件库&#xff0c;提供对 BLE 操作的直接访问。IPWorks BLE 组件提供简单的服务发现和对支持 BLE 的设备的访问。 最新的 IPWorks BLE 现已推出&#xff01;最新版本的 IPWorks BLE 具有现代化和简化的…

【MySQL】MySQL 知识点总结

文章目录 前言关系型数据库和非关系型数据库关系型数据库非关系型数据库关系型数据库与非关系型数据库之间的区别 MySQL整体架构SQL 的执行步骤MySQL 的架构图示连接器分析优化和执行查询缓存分析器优化器&执行器 存储引擎MyISAM和InnoDB的区别 事务事务的四大特性隔离级别…

【电动车】基于双层凸优化的燃料电池混合动力汽车研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

聚观早报 | 巴菲特后悔减持苹果;羊了个羊侵害用户权利被通报;

今日要闻&#xff1a;巴菲特两年前减持苹果是个愚蠢的决定&#xff1b;羊了个羊侵害用户权利被通报&#xff1b;英特尔预计二季度营收同比下滑&#xff1b;陆奇最称中国要先赶上GPT-3.5&#xff1b;任天堂对GitHub展开大规模DMCA行动 巴菲特两年前减持苹果是个愚蠢的决定 在伯…

【Linux】Redis数据库、实例项目搭建redis服务器环境下mysql实现la/nmp架构缓存

一、Redis简介 Redis 是当前互联网世界最为流行的 NoSQL&#xff08;Not Only SQL&#xff09;数据库。NoSQL 在互联网系统中的作用很大&#xff0c;因为 它可以在很大程度上提高互联网系统的性能。 Redis 具备一定持久层的功能&#xff0c;也可以作为一种缓存工具。对于 NoSQL…

【论文复现】基于区块链的分布式光伏就地消纳交易模式研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

盖雅案例入选「首届人力资源服务国际贸易交流合作大会20项创新经验」

近日&#xff0c;首届人力资源服务国际贸易交流合作大会顺利召开。为激励企业在人力资源服务贸易领域不断创新&#xff0c;加快培育对外贸易新业态、新模式&#xff0c;形成人力资源服务领域国际竞争新优势&#xff0c;大会评选出了「首届人力资源服务国际贸易交流合作大会20项…

第2天学习——Docker安装

一、前言 Docker 是一个用于开发、传送和运行应用程序的开放平台。Docker 使您能够将应用程序与基础设施分开&#xff0c;以便您可以快速交付软件。使用 Docker&#xff0c;您可以像管理应用程序一样管理基础设施。通过利用 Docker 的快速交付、测试和部署代码的方法&#xff0…

sqli-labs通关(十四)(十五)(十六)

第十四关 输入1",出现如下报错信息&#xff0c;告诉我们要双引号闭合 所以我们输入 1" or 11# 没有任何返回信息&#xff0c;这一关和十三关一样&#xff0c;利用报错信息爆出数据 1" and extractvalue(1,concat(0x7e,(select database())))# 第十五关 输入1是这…

【软件测试】第二章 黑盒测试

系列文章目录 文章目录 系列文章目录前言第二章 黑盒测试2.1 等价类划分2.2 边界值分析2.3 因果图法2.4 判定表驱动2.5 正交试验法2.6 其他黑盒测试方法2.7 功能性测试总结 总结 前言 第二章 黑盒测试 黑盒测试&#xff1a;功能测试或数据驱动测试测试对象&#xff1a;需求规格…

发布会实录|悠易科技CTO李旸:洞察新引擎 品牌新增长

4月26日&#xff0c;悠易科技LinkFlow对其核心的 “洞察”产品能力进行了升级。作为开场嘉宾&#xff0c;悠易科技CTO李旸做了题为《洞察新引擎 品牌新增长》的精彩分享&#xff0c;为我们阐释了本次发布升级的背景和出发点&#xff0c;帮助我们更好地理解LinkFlow CDP如何借助…

【postgresql】一些函数记录

1、coalesce函数&#xff1a;合并&#xff0c;coalesce(值1&#xff0c;值2&#xff0c;值3……)&#xff0c;返回第一个不为null的值&#xff1b; 2、coalesce和ifnull的区别&#xff0c;ifnull只有两个参数&#xff0c;返回第一个不为null的参数 3、if和casewhen的区别&…

信息安全评估

安全评估基础 概念、作用、安全评估标准 安全评估基本概念 什么是安全评估 针对潜在影响资产正常执行其职能的行为产生干扰或者破坏的因素进行识别、评价的过程 对安全评估的理解 狭义指对一个具有特定功能的工作系统中固有的或潜在的危险及其严重程度所进行的分析与评估&a…

通用路由封装协议GRE

通用路由封装协议原理 通用路由封装协议GRE(Generic Routing Encapsulation)可以对某些网络层协议(如IPX、ATM、IPv6、AppleTalk等)的数据报文进行封装,使这些被封装的数据报文能够在另一个网络层协议(如IPv4)中传输。 GRE提供了将一种协议的报文封装在另一种协议报文中…

国产AI大模型酣战,科大讯飞打响“智慧涌现”第一枪

配图来自Canva可画 ChatGPT问世半年之久&#xff0c;人与AI“你问我答”的游戏热度不降反升&#xff0c;AI大模型技术需求也随之水涨船高&#xff0c;成为科技企业重点关注的方向。 在海外&#xff0c;OpenAI、谷歌、微软的AI大模型战争正打得火热&#xff1b;在国内&#xf…

PowerShell系列(二):PowerShell和Python之间的差异介绍

目录 1、Python定义 2、Python用途 4、PowerShell用途 5、PowerShell和Python对比 5.1 共同点 5.2 不同点 6、总结 今天给大家聊聊PowerShell和Python之间有哪些共同之处&#xff0c;各自有哪些优势&#xff0c;希望对运维的朋友了解两种语言能提供一些有用的信息。 1、Python定…

Unit 为啥还能当函数参数?面向实用的 Kotlin Unit 详解

视频先行 下面是视频内容的脚本文案原稿分享。 文案原稿 很多从 Java 转到 Kotlin 的人都会有一个疑惑&#xff1a;为什么 Kotlin 没有沿用 Java 的 void 关键字&#xff0c;而要引入这个叫 Unit 的新东西&#xff1f; // Java public void sayHello() {System.out.println(&qu…

Unity大面积草地渲染——2、草地的动态交互

大家好&#xff0c;我是阿赵。 这里继续讲大面积草地渲染的第二个部分&#xff0c;草地动态交互。这里主要有风吹效果和球体碰撞效果2种。 一、风吹效果 Unity使用shader控制草的渲染和动画 风吹动草的效果&#xff0c;主要还是使用顶点程序来控制顶点的偏移 回顾一下之前的基…

全方位揭秘!大数据从0到1的完美落地之Shuffle和调优

MapReduce高级 shuffle阶段 概述 MapReduce会确保每个reducer的输入都是按键排序的。从map方法输出数据开始、到作为输入数据传给reduce方法的过程称为shuffle。在此&#xff0c;我们将学习shuffle是如何工作的&#xff0c;因为它有助于我们理解工作机制&#xff08;如果需要…

MYSQL数据库进阶多表查询,MYSQL数据库主键和外键

目录 友情提醒第一章&#xff1a;MYSQL数据库多表主键和外键1&#xff09;外键介绍&#xff08;FOREIGN KEY&#xff09;2&#xff09;外键约束作用2&#xff09;三种情况下添加外键约束①一对一关系②一对多关系多对多关系 4&#xff09;删除外键约束 第二章&#xff1a;MYSQL…