【C语言数据结构(基础版)】第三站:链表(二)

news2024/11/26 5:53:51

目录

一、单链表的缺陷以及双向链表的引入

1.单链表的缺陷

2.双向链表的引入

3.八大链表结构

(1)单向和双向

(2)带头和不带头

(3)循环和不循环

(4)八种链表结构

 二、带头双向循环链表的实现

1.链表创建

 2.链表的初始化

2.尾插

3.打印链表

 4.实现头插

5.链表的头删

6.链表的尾删

7.链表的查找与修改

8.在pos之前插入一个数据

9.删除pos位置的值

10.链表的销毁

三、双向带头循环链表的完整代码

总结


一、单链表的缺陷以及双向链表的引入

1.单链表的缺陷

在我们上一节内容中,我们已经学会了单链表的一些基本操作,但是呢其实我们也发现了单链表有很大的缺陷,我们在实现尾插,尾删,在pos前一个位置进行插入,删除pos位置,这几个接口的实现都需要找到前一个结点,而我们找到前一个结点的方法只能是遍历,而且还得分情况,看看空链表会出现什么情况,只有一个结点的链表又会是什么情况。总之很麻烦

如上图所示,无论是PopBack、Insert、还是Erase时间复杂度都达到了O(N),效率不高。

究其根本原因是因为,无法实现从后找前,找不到前驱,必须从头开始遍历寻找

2.双向链表的引入

那么如何解决这种现象呢?答案是采取双向的链表,让它可以从后找到前一个结点。

这样的话,那么我们链表的声明就应该是这样的

 代码为

typedef int LTDateType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDateType date;
}ListNode;

3.八大链表结构

在这里就不得不提一下链表的种类了,链表共有八种。而八种是由三种属性来排列组合形成的

1.单向        双向

2.不带头     带头

3.不循环     循环

如上的三种属性,经过排列组合后刚好可以形成八种结构,而我们上节所说的正是单向不带头不循环链表

(1)单向和双向

单向和双向其实在前面已经说过了。就是一个双向弥补了单向找前一个结点比较麻烦的现象

(2)带头和不带头

什么是带头什么又是不带头呢?我们看下面的图就知道了

这就是两个最明显的区别了,带头的链表比不带头的链表多一个结点,而这个结点其实是不存储有效数据的,它是一个哨兵位

带头结点的好处:

不需要改变传过来的指针,也就是意味着不需要传二级指针

为什么步不需要传二级指针呢,我们可以画图来理解一下

这是普通的不带头链表传参图,我们其实可以看出来,其实plist传参的时候会拷贝成phead,然后我们改变的连接都是phead和newnode的结构,而非plist,因为plist和phead是两个不同的变量,他们的地址都不一样

 

 而如果是带头结点的,我们也画图来理解一下

这样形参和实参即便不一样也无所谓了,因为是我们的哨兵位来控制我们的链表的,plist和phead仅仅只是为了找到这个哨兵位。

 

哨兵位不存储有效数据的的原因:

我们有时候会在书上看到哨兵位存储一个数据,这个数据是代表着有几个结点了。其实这是经典的错误标准的零分,这里是绝对不可以存储这个链表有几个有效数据了,因为假如说我们链表的数据是char类型,那么它最大就是127啊。假如说链表的长度是129呢?显然着哨兵位就不可以存储数据

(3)循环和不循环

这个就比较简单了,不循环的链表最终指向NULL,而循环的链表最后一个结点又指向了头节点

(4)八种链表结构

三大属性我们都有所了解了,那么它的链表就可由这三种排列组合形成八种链表结构。

单向不带头不循环链表        单向不带头循环链表      

单向带头不循环链表           单向带头循环链表

双向不带头不循环链表        双向不带头循环链表

双向带头不循环链表           双向带头循环链表

但是我们常用的就两种链表:

1.单向不带头不循环链表:(在上一篇文章中已经实现了)

结构简单,一般不会单独用来存储数据。实际中更多的是作为其他数据结构的子结构。如哈希桶,图的邻接表等

2.双向带头循环链表:

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

当然,我们本节也就是来实现这个双向带头循环链表了,它的图示应该是这样的

 二、带头双向循环链表的实现

1.链表创建

这个就很简单了,我们直接定义一个结点指针让他指向空即可

 2.链表的初始化

我们先来声明一下这两个函数

我们先来实现初始化函数,销毁链表的函数在后面实现

我们想一下,这个带头的循环双向链表初始化后应该是什么样子呢?

它应该是这样的,它只有一个结点,而这个结点就是哨兵位,这个哨兵位的prev和next还得自己指向自己,而且plist还得指向这个哨兵位

 那么我们现在就来实现它吧,我们会发现,我们想要初始化,那么必须得需要创建一个新的结点出来,但是我们知道后面还有尾插,头插等都需要创建新的结点,不妨我们直接将创建结点写成一个函数

//创建一个新结点
ListNode* BuyListNode(LTDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->date = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

 这样我们的结点就创建好了,那么我们接下来就是来完善这个初始化,如下图所示

 当然,细心的人已经发现问题了,这里肯定是行不通的,因为phead是形参,最终的结果是让phead给初始化了,而plist没有被初始化,所以我们这里应该传二级指针。而之前所说的双向循环带头链表不需要二级指针指的是除过哨兵位以外的结点,因为哨兵位还是需要和plist连接起来的,而其他的操作只需要能找到这个哨兵位就可以了

当然我们也可以不通过修改二级指针的方法来完成这个代码,我们可以这样做,我们也不进行传参了,我们初始化好这个链表之后将这个结点的地址给返回去,然后赋值给plist

 

 这样的话我们的声明也会被改变了

 代码为

//创建一个新结点
ListNode* BuyListNode(LTDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->date = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}
//初始化链表
ListNode* ListInit()
{
	ListNode* phead;
	phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.尾插

对于尾插我们先声明它的函数

//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x);

我们这这样想的,假设原来的是我们下图的连接

 我们现在想要尾插一个4过去,那么我们首先要创建这个4的这个结点

 

 然后我们需要的是连接这些点,我们现在有的是4的地址newnode,和哨兵位的地址phead

而我们想要找到尾的话也很简单,因为我们现在是双向的,所以tail=phead->prev

 

 有了这三个地址,接下来就是连接了

我们先让tail->next=newnode

然后让newnode->prev=tail

 

 这样tail和newnode的连接就完成了

然后是newnode和phead的连接,我们先让newnode->next=phead

 

 接下来是phead->prev=newnode

当然我们里面创建结点的时候,不要忘记把4改成了x

  我们发现这个代码其实比单链表的尾插要简单了许多,虽然结构复杂了,但是实现变得简单了。而且这个也不用判断为空链表会怎么样。

当然在这里,我们也知道phead是一定不为空的,所以我们也可以加上一句断言

 

//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x)
{
	assert(phead);
	//创建一个新的结点
	ListNode* newnode = BuyListNode(x);
	//寻找末尾结点
	ListNode* tail = phead->prev;
	//连接
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

3.打印链表

我们已经写完了尾插,那么我们肯定希望能够测试一下,那么我们不得不先写一个打印链表的功能来测试一下了

打印的功能也很简单,我们定义一个cur,并让他一开始指向phead->next,也就是第一个结点,然后只要此时的cur和phead不一样,那么我们就可以继续打印。这也是利用了循环的特性

 这样的话我们的代码实现如下

代码如下

//链表的打印
void ListPrint(ListNode* phead)
{
	//当前的要指向链表的第一个结点,也就是哨兵位的下一个结点
	ListNode* cur = phead->next;
	//判断cur是否为哨兵位,如果不是哨兵位,则打印,是哨兵位
	//则说明循环已经遍历完了
	while (cur != phead)
	{
		printf("%d ", cur->date);
		cur = cur->next;
	}
	printf("\n");
}

而且这个代码即便是空链表,也没有任何问题的。因为如果没有有效结点的话,那么phead->next还是phead,所以根本不会打印的

那么我们现在来测试一下尾插和打印吧,圆满的完成了我们的需求

 4.实现头插

我们现在来实现一下头插吧,下面是函数的声明

//链表的头插
void ListPushFront(ListNode* phead, LTDateType x);

然后我们来用这个图来演示一下,我们想要在这里面插入0这个结点

 那么首先我们肯定得为0创建它的结点

 然后就是开始连接了

我们先记录下来原来的第一个结点

 

然后我们开始连接

对于这个连接其实对于顺序没有什么要求

我们先连接这两个

 

 

 然后连接这两个

然后连接这两个

 

最后连接这两个

 

最后在加上断言

我们这里刚刚写错了,下面红圈已经改为正确了

我们测试一下

 符合我们的逻辑,而且这个函数对于空的链表也是可以连接起来的

代码为

//链表的头插
void ListPushFront(ListNode* phead, LTDateType x)
{
	assert(phead);
	//为x创建一个结点
	ListNode* newnode = BuyListNode(x);
	//先记录一下原来的第一个结点
	ListNode* first = phead->next;
	//连接
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

然后再这里我们在上面说了,那个是我们定义了first去记录了之前的第一个结点,然后才会使得连接顺序可以无所谓的。但是万一我们要是没有定义这个first的话,我们再去连接就必须得注意一下顺序,否则会出问题的,在这里给出顺序的图解和代码

 

 

 

 

 

 

 

 然后我们测试运行一下

 结果是正确的

要考虑顺序的代码为

//链表的头插(需要考虑顺序的话)
void ListPushFront(ListNode* phead, LTDateType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);

	newnode->next = phead->next;
	phead->next->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;
}

其实就是先连接后面的,然后连接前面的,因为如果先连前面的话,就会找不到后面的结点

5.链表的头删

先写出函数声明

//链表的头删
void ListPopFront(ListNode* phead);

我们接下来来实现,对于这个图而言,我们想要删掉头节点,那么其实就相当于将第一个结点给销毁掉,连接起来phead和第二个结点

如下所示就是头删代码的实现,当然也要为了防止链表没有数据的时候删除掉哨兵位。

 测试如下

 代码为

//链表的头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//这是避免删掉我的哨兵位
	assert(phead->next != phead);
	//第一个结点的地址
	ListNode* first = phead->next;
	//第二个结点的地址
	ListNode* second = first->next;
	//连接
	phead->next = second;
	second->prev = phead;
	//释放第一个结点
	free(first);
	first = NULL;
}

6.链表的尾删

其实链表的尾删和头删除基本一致

下面是函数的声明

//链表的尾删
void ListPopBack(ListNode* phead);

测试结果为

代码为

//链表的尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	//定义倒数第一个结点
	ListNode* tail = phead->prev;
	//定义倒数第二个结点
	ListNode* prev = tail->prev;

	//连接
	phead->prev = prev;
	prev->next = phead;

	//销毁原来的结点
	free(tail);
	tail = NULL;
}

7.链表的查找与修改

我们接下来先来实现链表的查找

下面是函数的声明

//查找某一个值的结点地址
ListNode* ListFind(ListNode* phead, LTDateType x);

然后我们是这样思考的,我们定义一个cur指针,让它一开始指向phead->next,也就是第一个结点,让cur一直往下走,当cur不等于phead的时候,让他继续遍历,一旦找到则返回cur,否则就是没找到,返回NULL

代码如下

//查找某一个值的结点地址
ListNode* ListFind(ListNode* phead,LTDateType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->date == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

我们来测试一下

符合我们的预期,那么如果我们想要修改我们找到的这个数据该如何做的?其实很简单,因为已经有这个指针了,直接修改就可以了

 也就是说,查找功能附带着修改功能

8.在pos之前插入一个数据

这个其实也是与查找函数紧密相连,先用查找找到pos,然后才能进行修改

而它的实现其实与头插,尾插基本一致

它的声明为

//在pos之前插入一个值
void ListInsert(ListNode* pos, LTDateType x);

它的实现为

//在pos之前插入一个值
void ListInsert(ListNode* pos, LTDateType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

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

测试为

9.删除pos位置的值

这个也与尾删头删基本一致

下面是函数的声明

//删除pos处的值
void ListErase(ListNode* pos);

 

函数的实现为

//删除pos处的值
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* next = pos->next;
	ListNode* prev = pos->prev;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

测试为

在这里我们其实也能发现,其实Insert和Erase完全可以替代头插尾插头删尾删

10.链表的销毁

这是最后一个接口,我们直接销毁掉整个链表即可

函数声明为

//链表的销毁
void ListDestory(ListNode* phead);

思想是,设置一个cur,让他去遍历整个链表,只要cur!=phead,那么就销毁这个空间。

代码为

//链表的销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

三、双向带头循环链表的完整代码

我们现在所有的接口都已经实现了,那么其实我们也能发现这个双向带头循环链表的时间复杂度都是O(1),(除过查找以外,查找后续会有更优的写法)

完整代码如下:

test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

void TestList1()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	
	ListPrint(plist);

	ListPushFront(plist, 5);
	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);

	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPopFront(plist);

	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		//查找并修改
		pos->date *= 10;
		printf("找到了,它乘10以后是%d\n",pos->date);

	}
	else
	{
		printf("没找到\n");
	}
	ListPrint(plist);
	ListInsert(pos, 300);
	ListPrint(plist);

	ListErase(pos);
	ListPrint(plist);
	ListDestory(plist);
}
int main()
{
	TestList1();
	return 0;
}

List.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDateType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDateType date;
}ListNode;


//链表的初始化
ListNode* ListInit();
//链表的销毁
void ListDestory(ListNode* phead);
//链表的打印
void ListPrint(ListNode* phead);
//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x);
//链表的头插
void ListPushFront(ListNode* phead, LTDateType x);
//链表的头删
void ListPopFront(ListNode* phead);
//链表的尾删
void ListPopBack(ListNode* phead);
//查找某一个值的结点地址
ListNode* ListFind(ListNode* phead, LTDateType x);
//在pos之前插入一个值
void ListInsert(ListNode* pos, LTDateType x);
//删除pos处的值
void ListErase(ListNode* pos);

List.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//创建一个新结点
ListNode* BuyListNode(LTDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->date = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}
//初始化链表
ListNode* ListInit()
{
	ListNode* phead;
	phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//链表的销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}
//链表的打印
void ListPrint(ListNode* phead)
{
	//当前的要指向链表的第一个结点,也就是哨兵位的下一个结点
	ListNode* cur = phead->next;
	//判断cur是否为哨兵位,如果不是哨兵位,则打印,是哨兵位
	//则说明循环已经遍历完了
	while (cur != phead)
	{
		printf("%d ", cur->date);
		cur = cur->next;
	}
	printf("\n");
}
//链表的尾插
void ListPushBack(ListNode* phead, LTDateType x)
{
	assert(phead);
	//创建一个新的结点
	ListNode* newnode = BuyListNode(x);
	//寻找末尾结点
	ListNode* tail = phead->prev;
	//连接
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
//链表的头插
void ListPushFront(ListNode* phead, LTDateType x)
{
	assert(phead);
	//为x创建一个结点
	ListNode* newnode = BuyListNode(x);
	//先记录一下原来的第一个结点
	ListNode* first = phead->next;
	//连接
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}
链表的头插(需要考虑顺序的话)
//void ListPushFront(ListNode* phead, LTDateType x)
//{
//	assert(phead);
//	ListNode* newnode = BuyListNode(x);
//
//	newnode->next = phead->next;
//	phead->next->prev = newnode;
//
//	phead->next = newnode;
//	newnode->prev = phead;
//}

//链表的头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//这是避免删掉我的哨兵位
	assert(phead->next != phead);
	//第一个结点的地址
	ListNode* first = phead->next;
	//第二个结点的地址
	ListNode* second = first->next;
	//连接
	phead->next = second;
	second->prev = phead;
	//释放第一个结点
	free(first);
	first = NULL;
}
//链表的尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	//定义倒数第一个结点
	ListNode* tail = phead->prev;
	//定义倒数第二个结点
	ListNode* prev = tail->prev;

	//连接
	phead->prev = prev;
	prev->next = phead;

	//销毁原来的结点
	free(tail);
	tail = NULL;
}
//查找某一个值的结点地址
ListNode* ListFind(ListNode* phead,LTDateType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->date == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos之前插入一个值
void ListInsert(ListNode* pos, LTDateType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

	newnode->next = pos;
	pos->prev = newnode;
	prev->next = newnode;
	newnode->prev = prev;
}
//删除pos处的值
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* next = pos->next;
	ListNode* prev = pos->prev;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

总结

本节讲解了双向带头循环链表的实现,希望大家都有所收获

如果对你有帮助,不要忘记点赞加收藏哦!!!

想看更多更加优质的内容,一定要关注我哦!!!

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

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

相关文章

牛掰,阿里技术人刷了四年LeetCode才总结出来的数据结构和算法手册

时间飞逝&#xff0c;转眼间毕业七年多&#xff0c;从事 Java 开发也六年了。我在想&#xff0c;也是时候将自己的 Java 整理成一套体系。 这一次的知识体系面试题涉及到 Java 知识部分、性能优化、微服务、并发编程、开源框架、分布式等多个方面的知识点。 写这一套 Java 面试…

ssm+Vue计算机毕业设计校园疫情防控管理软件(程序+LW文档)

ssmVue计算机毕业设计校园疫情防控管理软件&#xff08;程序LW文档&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

数据库拆分3--使用sharding-jdbc 子查询注意事项

最近在使用sharding-jdbc来改造项目的时候遇到了一些问题&#xff0c;主要是有关子查询的&#xff0c;记录一下。 在某一个库中新建两张表 CREATE TABLE user_t ( user_id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, age int(8) DEFAULT NU…

世界杯决赛号角吹响!趁周末来搭一套足球3D+AI量化分析系统吧!

2022年卡塔尔世界杯从11月21日开赛至今&#xff0c;即将在12月18日迎来这次赛事的最后高潮。对于大部分热爱世界杯的朋友来说&#xff0c;无论之前是哪队的球迷&#xff0c;现在都在会师决赛的两支队伍上选择站队。从赛事结果看&#xff0c;最终无论哪支队伍夺冠&#xff0c;都…

01背包和完全背包

01背包 最大约数和 题目链接点击这里 题目描述 选取和不超过 SSS 的若干个不同的正整数&#xff0c;使得所有数的约数&#xff08;不含它本身&#xff09;之和最大。 输入格式 输入一个正整数 SSS。 输出格式 输出最大的约数之和。 样例 #1 样例输入 #1 11样例输出 …

有哪些值得推荐的Python学习网站?

我学习的时候&#xff0c;我发现大部分 Python 课程和资源都太通用了。 马上&#xff0c;我想学习如何使用 Python 制作网站。但是 Python 学习资源要我花几个月的时间学习语法&#xff0c;然后才能进入我感兴趣的领域。 这个问题让人感到恐惧和畏惧。我推迟了几个月。每当我…

大学生化妆品网页设计模板代码 化妆美妆网页作业成品 学校美妆官网网页制作模板 学生简单html网站设计成品

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

为什么人家的开源项目文档如此炫酷?原来用的是这款神器

VuePress简介 VuePress是Vue驱动的静态网站生成器。对比我们的Docsify动态生成网站&#xff0c;对SEO更加友好。 使用VuePress具有如下优点&#xff1a; 使用Markdown来写文章&#xff0c;程序员写起来顺手&#xff0c;配置网站非常简洁。 我们可以在Markdown中使用Vue组件&…

所谓工作能力强,其实就这五点

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; #mermaid-svg-YapmQUqJ0V32EFv6 {font-family:"trebuchet ms",ve…

用三台云服务器搭建hadoop完全分布式集群

用三台云服务器搭建hadoop完全分布式集群一、硬件准备&#xff08;一&#xff09;集群配置&#xff08;二&#xff09;集群规划&#xff08;三&#xff09;Hadoop、Zookeeper、Java、CentOS版本二、基础环境配置&#xff08;一&#xff09;关闭防火墙&#xff08;二&#xff09…

[附源码]Python计算机毕业设计SSM基于Java的在线点餐系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

12.15

JSONP 1) JSONP 是什么 JSONP(JSON with Padding)&#xff0c;只支持 get 请求。 2) JSONP 怎么工作的&#xff1f; 在网页有一些标签天生具有跨域能力&#xff0c;比如&#xff1a;img link iframe script。 JSONP 就是利用 script 标签的跨域能力来发送请求的。 3) JSONP …

为什么你的接口性能差,实际原因就在这里?

一、前言这篇文章咱们来聊一下&#xff0c;百亿级别的海量数据场景下还要支撑每秒十万级别的高并发查询&#xff0c;这个架构该如何演进和设计&#xff1f;咱们先来看看目前系统已经演进到了什么样的架构&#xff0c;大家看看下面的图&#xff1a;首先回顾一下&#xff0c;整个…

三、Node.js模块化基础 2.0

在Node.js中&#xff0c;模块分为核心&#xff08;原生&#xff09;模块和文件&#xff08;自定义&#xff09;模块&#xff0c;核心模块就是Node.js自带的模块&#xff0c;而自定义模块则是开发者自定义的模块&#xff1b; 核心模块 核心模块有 os&#xff0c;fs&#xff0c;…

发送给Java应用程序的所有参数都必须是字符串吗?

问&#xff1a;发送给Java应用程序的所有参数都必须是字符串吗&#xff1f; 答&#xff1a; 应用程序在运行时&#xff0c;Java将所有参数存储为字符串。要使用整型或其他非字符串参数&#xff0c;必须将其进行转换&#xff0c; 问&#xff1a;既然applet是在Web页面中运行&…

大一作业HTML网页作业:中华传统文化题材网页设计5页(纯html+css实现)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

HTML期末作业,基于html实现中国脸谱传统文化网站设计(5个页面)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Scrapy_redis分布式原理

今天分享一下Scrapy_redis分布式原理&#xff1a; 1 scrapy_redis是什么 Scrapy_redis &#xff1a; Redis-based components for Scrapy. Github地址&#xff1a;<https://github.com/rmax/scrapy-redis> 在这个地址中存在三个demo&#xff0c;后续我们对scrapy_redi…

大学生简单环保环境静态HTML网页设计作品 DIV布局环境介绍网页模板代码 DW学生环境网站制作成品下载 HTML5期末大作业

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

2022_TIP_DSNet

Boosting RGB-D Saliency Detection by Leveraging Unlabeled RGB Images 通过利用未标记的RGB图像来增强rgb-d显着性检测 1. 动机 1) 用于监督学习的像素级注释既昂贵又耗时。 2) 与RGB图像相比&#xff0c;成对的rgb-d图像更难以收集。 2. 解决方法 提出 Dual-Semi RG…