单链表——“数据结构与算法”

news2024/11/24 6:45:56

各位CSDN的uu们你们好呀,今天,小雅兰的内容终于是我们心心念念的单链表啦,这一块呢,是一个很重要的部分,也是一个对目前的我来说,比较困难的部分,下面,就让我们进入单链表的世界吧


之前小雅兰写过顺序表的内容:

顺序表(更新版)——“数据结构与算法”_认真学习的小雅兰.的博客-CSDN博客

顺序表存在一些问题:

  • 中间/头部的插入删除,时间复杂度为O(N)
  • 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  • 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

  

  

  

 思考:如何解决以上问题呢?下面给出了链表的结构来看看。


 链表

链表的概念及结构

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

 

现实中 数据结构中

 


 结构体里面的数据类型:

typedef int SLTDataType;

定义一个结构体,结构体内部嵌套了一个结构体的指针:

这个就是单链表

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

 单链表的打印:

//单链表的打印
void SLTPrint(SLTNode* phead)
{
	//可以不需要断言
	//因为:空链表也是可以打印的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

意思是:首先,定义一个结构体的指针,该指针指向1,然后,对于cur,cur->data表示的是此结构体中的整型数据,cur->next表示的是2的地址,把cur->next赋值给cur,就是把这几块不连续的空间链接起来

表示:phead存的是第一个结点的地址,cur也存的是第一个节点的地址,就是把phead赋值给cur 

 头插

//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

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

 

 

测试一下头插的功能:

void TestSList1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
}
int main()
{
	TestSList1();
	return 0;
}

 

在写这段代码的过程中,很容易犯错误,可能会有很多人这样写代码:

//头插
void SLPushFront(SLTNode* phead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = phead;
	phead = newnode;
}
void TestSList1()
{
	SLTNode* plist = NULL;
	SLPushFront(plist, 1);
	SLPushFront(plist, 2);
	SLPushFront(plist, 3);
	SLPushFront(plist, 4);
	SLPushFront(plist, 5);
	SLTPrint(plist);
}
int main()
{
	TestSList1();
	return 0;
}

这是一个十分经典的错误:

传值调用了!!!

实参是形参的一份临时拷贝,对形参的修改并不会影响实参,所以phead的改变并不会影响plist

举一个简单的例子:Swap

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}

毫无疑问,这样写确实是正确的。

有的人在这边可能就会想:是不是只要用了指针就可以了呢?

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int* px = 10;
	int* py = 20;
	Swap(px, py);
	printf("%d %d\n", px, py);
	return 0;
}

 这样写,那是绝对不行的,接下来,来看看正确的写法:

void Swap(int** p1, int** p2)
{
	int* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int* px = 10;
	int* py = 20;
	Swap(&px, &py);
	printf("%d %d\n", px, py);
	return 0;
}


我们会发现,在后续很多函数中,都需要用到创建结点这样一个功能,所以,可以把此功能封装成一个函数

//创建结点
void BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;//就相当于初始化一下
}

尾插

从指向1的地址变为指向2的地址 

循环所做的事

 

//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);
	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;//tail->next本质是解引用,用tail的值找到指向的那个内存块,在内存块里面找到tail
		}
		tail->next = newnode;
	}
}

 测试一下尾插的功能:

void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
}
int main()
{
	TestSList2();
	return 0;
}

下面,来更好地解释一下为什么这边需要用到二级指针:

void func1(int* p)
{
	*p = 10;
}
void func2(int** pp)
{
	*pp = (int*)malloc(sizeof(int) * 10);
}
void func3(SLTNode* pnode)
{
	pnode->next = NULL;
}
void func4(SLTNode** ppnode)
{
	*ppnode = NULL;
}
int main()
{
	//要改变int,就要传int的地址
	int a = 0;
	func1(&a);
	//要改变int*,就要传int*的地址
	int* ptr = NULL;
	func2(&ptr);
	//要改变结构体,就要传结构体的地址
	SLTNode node;
	func3(&node);
	//要改变结构体的指针,传结构体的指针的地址
	SLTNode* pnode;
	func4(&pnode);
	return 0;
}

尾删

一个典型的错误的写法:野指针问题

 

解决方式:

找到尾结点以及它的前一个结点

 

 

//尾删
void SLPopBack(SLTNode** pphead)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);
	SLTNode* prev = NULL;//前一个结点
	SLTNode* tail = *pphead;
	//找尾
	while (tail->next != NULL)
	{
		prev = tail;
		tail = tail->next;
	}
	free(tail);
	prev->next = NULL;
}

 还可以找倒数第二个

//尾删
//找倒数第二个
void SLPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	//一个结点
	//多个结点
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
	
}

测试一下尾删的功能:

void TestSList3()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
}

 

头删

头删和尾删都有三种情况:

  • 没有结点(也就是空链表)
  • 有一个结点
  • 有多个结点

//头删
void SLPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查
	//链表为空,不能头删
	//一个结点
	//多个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* del = *pphead;//不能直接free掉
		//如果直接free的话,就找不到下一个结点的地址啦
		*pphead = del->next;
		free(del);
	}
}

 

测试一下头删的功能:

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
}

 


 

 单链表查找

//头插、尾插、头删、尾删都要修改链表,所以要传二级指针
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//不用assert,因为空链表也是可以查找的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

测试一下单链表查找的功能:

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 2);
	pos->data = 30;
	SLTPrint(plist);

}

 


任意位置数据的插入(pos之前插入)

 

//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

后插

//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

测试一下前插和后插的功能:

void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 3);
	if (pos != NULL)
	{
		SLTInsert(&plist, pos, 20);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		SLTInsertAfter(pos, 30);
	}
	SLTPrint(plist);
}

 

删除pos位置的值

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

后删

//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}
void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 3);
	if (pos != NULL)
	{
		SLTInsert(&plist, pos, 20);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		SLTInsertAfter(pos, 30);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 7);
	if (pos != NULL)
	{
		SLTErase(&plist,pos);
	}
	SLTPrint(plist);

}


源代码如下:

SList.h的内容:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//单链表的打印
void SLTPrint(SLTNode* phead);
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x);
//头删
void SLPopFront(SLTNode** pphead);
//尾删
void SLPopBack(SLTNode** pphead);
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);
//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos位置之前的值
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos);

SList.c的内容:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//单链表的打印
void SLTPrint(SLTNode* phead)
{
	//可以不需要断言
	//因为:空链表也是可以打印的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = *pphead;
	*pphead = newnode;
}
//创建结点
SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;//就相当于初始化一下
	return newnode;
}
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = BuyLTNode(x);
	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;//tail->next本质是解引用,用tail的值找到指向的那个内存块,在内存块里面找到tail
		}
		tail->next = newnode;
	}
}
尾删
// 找倒数第一个
//void SLPopBack(SLTNode** pphead)
//{
//	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
//	assert(*pphead);
//	SLTNode* prev = NULL;//前一个结点
//	SLTNode* tail = *pphead;
//	//找尾
//	while (tail->next != NULL)
//	{
//		prev = tail;
//		tail = tail->next;
//	}
//	free(tail);
//	prev->next = NULL;
//}
//尾删
//找倒数第二个
void SLPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	//一个结点
	//多个结点
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
	
}
//头删
void SLPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查
	//链表为空,不能头删
	//一个结点
	//多个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* del = *pphead;//不能直接free掉
		//如果直接free的话,就找不到下一个结点的地址啦
		*pphead = del->next;
		free(del);
	}
}
//头插、尾插、头删、尾删都要修改链表,所以要传二级指针
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//不用assert,因为空链表也是可以查找的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}
//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

好啦,小雅兰今天的单链表的内容就到这里啦,内容还是非常多的,也比较难,小雅兰会继续加油学习的,冲冲冲!!!

 

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

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

相关文章

【unity项目实战】3DRPG游戏开发04——导航、人物移动和鼠标指针图片替换

AI导航烘培 还不清楚怎么用的可以看我另一篇文章 零基础带你从小白到超神29——导航系统 将地形调成静态导航的 选中地形,设置为可行区域 点击烘培 可爬坡改为30度,就会发现坑就变为不可行区域了 选择所有的树,为不可行区域,点击烘培 给主角人物添加导航组件

Kafka3.0.0版本——生产者数据可靠性

目录 一、ACK应答原理1.1、应答级别1.1.1、acks 01.1.2、acks 11.1.3、acks -1&#xff08;all&#xff09; 1.2、问题思考 二、数据可靠性2.1、数据可靠性分析2.2、 数据完全可靠条件2.3、ACK应答级别可靠性总结 三、数据可靠性代码示例 一、ACK应答原理 1.1、应答级别 1…

一日一题:第十二题---模拟散列表(三种方法!!)

​作者&#xff1a;小妮无语 专栏&#xff1a;一日一题 &#x1f6b6;‍♀️✌️道阻且长&#xff0c;不要放弃✌️&#x1f3c3;‍♀️ 今天来给大家介绍的是简单的Hash表的应用 目录 关于哈希的知识点 题目描述&#xff08;模拟散列表&#xff09; 代码 1&#xff08;拉链…

基于GPT-4的神仙插件Bito,亲测好用

基于GPT-4的神仙插件&#xff0c;无需魔法,目前免费 一、Bito 简介 最近发现一个可以有效提升coding效率的插件神器&#xff0c;截止当前(20230425)已有65k的下载量了&#xff01; 类似与Cursor一样&#xff0c;可以使用AI辅助写代码&#xff0c;但是又解决Cursor没有语法提…

力扣刷题day35|416分割等和子集

416. 分割等和子集 力扣题目链接 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 示例 1&#xff1a; 输入&#xff1a;nums [1,5,11,5] 输出&#xff1a;true 解释&#xff1a;数组可以分割…

keil设置程序起始地址及生成bin文件的方法

一.keil设置程序起始地址 1.1FLASH APP 的起始地址设置 随便打开一个之前的实例工程&#xff0c;点击 Options for Target→Target 选项卡 默认的条件下&#xff0c;图中 IROM1 的起始地址&#xff08;Start&#xff09;一般为 0X08000000&#xff0c;大小&#xff08;Size&a…

通用el-table 修改样式

通用el-table 修改样式 el-table实现下图效果: <template><div class"contentbox"><el-table:data"tableData"height"310"style"width: 40%"highlight-current-rowcurrent-change"handleCurrentChange"&g…

利用Floodlight进行DDOS攻击防御实验笔记

Floodlight Floodlight是Apache授权并基于JAVA开发的企业级OpenFlow控制器&#xff0c;当前最新版本是1.2。 Floodlight OpenFlow Controller -ProjectFloodlight&#xff1a;http://www.projectfloodlight.org/floodlight/ 流表 把同一时间&#xff0c;经过同一网络中具有某种…

线程同步方式之二条件变量

Linux线程同步方法之二 条件变量 饥饿状态&#xff1a;由于线程A频繁地申请/释放锁&#xff0c;而导致其他线程无法访问临界资源的情况。 同步synchronized&#xff1a;在保证数据安全的前提下&#xff0c;让线程能够按照某种特定的顺序访问临界资源&#xff0c;从而有效避免…

Spring Security实战(九)—— 使用Spring Security OAuth实现OAuth对接

一、OAuth2.0介绍 OAuth2.0是一种授权协议&#xff0c;允许用户授权第三方应用程序代表他们获取受保护的资源&#xff0c;如个人信息或照片等。它允许用户授权访问他们存储在另一个服务提供商上的资源&#xff0c;而无需将其凭据共享给第三方应用程序。OAuth2.0协议建立在OAuth…

直升机空气动力学基础--004翼型的阻力

来源 1. 空气的粘性 2.阻力的产生 3.形成因素 4.阻力系数曲线

LeetCode-242. 有效的字母异位词

题目链接 LeetCode-242. 有效的字母异位词 题目描述 题解 题解一&#xff08;Java&#xff09; 作者&#xff1a;仲景 首先&#xff0c;满足条件的情况下&#xff0c;两个字符串的长度一定是相等的&#xff0c;不相等一定不满足条件 使用Hash表来存储字符串s中各个字符出现的…

回溯算法——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 系列文章目录 第一章 ❤️ 学习前的必知知识 第二章 ❤️ 二分查找 文章目录 系列文章目录回溯算法&#x1f914;&#x1f914;&#x1f914;回溯算法一般可以解决的问题回溯算法的实现回…

Python语言简介

B站讲解视频&#xff1a;https://www.bilibili.com/video/BV1Mv4y1n7n8/?vd_source515e6808c21c69114a4fae34589dfb0e Python是什么 Python是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。具有很强的可读性&#xff0c;相比其他语言经常使用英文关键字&a…

为什么有时候磁珠会使电源的纹波变大

电路设计时&#xff0c;我们常常在芯片电源的输入放一个磁珠和电容&#xff0c;用以滤除电源上的高频噪声。 但是有时候会发现&#xff0c;加了磁珠后&#xff0c;芯片电源输入处纹波竟然变大了&#xff0c;超出了电源纹波范围&#xff0c;导致芯片工作异常。 把磁珠换成0R电阻…

论文阅读:Heterogeneous Graph Contrastive Learning for Recommendation(WSDM ’23)

论文链接 Motivation&#xff1a; 在推荐系统中&#xff0c;图神经网络在建模图结构数据上已经变成一个强有力的工具。但是现实生活的推荐语义通常涉及异质关系&#xff08;像用户的社交关系&#xff0c;物品知识关系的依赖&#xff09;&#xff0c;这些都包含丰富的语义信息…

Linux下实现C语言程序

一.情况说明 写这篇博客的情况比较复杂&#xff0c;首先我本来是参加新星计划按照规划现在去学习shell脚本语言的&#xff0c;但是博主现在由于其他原因需要了解makefile&#xff0c;makefile是Linux系统下的一种工具&#xff0c;makefile的一些背景要涉及链接库的知识&#xf…

从0开始搭建一个简单的前后端分离的XX系统-vue+Springboot+mybatis-plus+mysql

一、准备工作 1.安装node 2.idea 旗舰版** idea**教程 上述教程中的idea**工具 3.安装mysql任意版本 mysql 4.安装mysql workbench&#xff08;没用上&#xff09; 5.安装navicate 参考文章&#xff1a;百度-从小白到架构&#xff08;作者&#xff09;-Navicat16** Nav…

Thinkphp获取项目最近更改变动的所有文件

导读&#xff1a; 企业级的网站项目都是要不断优化迭代更新的&#xff0c;做为一名后端程序员&#xff0c;在编写更新模块时&#xff0c;如何能快速获取最近修改的文件&#xff0c;然后打包压缩成更新补丁呢&#xff1f;我们先来看一下最终效果图&#xff1a; 步骤&#xff1a…

使用FFMPEG分离mp4/flv文件中的264视频和aac音频

准备 ffmpeg 4.4 一个MP4或flv格式的视频文件 分离流程 大致分为以下几个简单步骤&#xff1a; 1.使用avformat_open_input 函数打开文件并初始化结构AVFormatContext 2.查找是否存在音频和视频信息 3.构建一个h264_mp4toannexb比特流的过滤器&#xff0c;用来给视频avpa…