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

各位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);
}

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【linux】对于权限的理解

权限 Linux权限的概念用户之间的切换 Linux权限管理文件权限操作文件的人Linux文件默认权限的设置权限掩码 所属组/其他删除拥有者创建的文件文件拥有者、所属组的修改修改文件拥有者修改文件所属组一次性修改拥有者和所属组 目录的执行权限 Linux权限的概念 首先&#xff0c;…

电脑怎么远程控制另一台电脑

要从一台电脑远程控制另一台电脑&#xff0c;您可以使用远程桌面软件。 以下是远程控制另一台电脑的步骤&#xff1a; 一、在两台电脑上安装远程桌面软件 有多种远程桌面软件可用&#xff0c;例如 Splashtop、微软远程桌面。 在远程电脑和本地电脑上分别安装软件。访问各自软…

【产品经理】系统上线自查清单

产品上线之前的准备工作&#xff0c;看起来简单&#xff0c;实际做起来是非常繁杂的&#xff0c;如果没有尽早考虑和准备&#xff0c;可能会手忙脚乱甚至导致产品延迟上线。 产品上线前的准备工作听起来简单&#xff0c;但实际做起来非常繁杂。除了要考虑用户需求、商业需求外&…

vue项目 解决el-table自适应高度,vue页面不显示多条滚动条,超出的部分让el-table内部出现滚动条(推荐使用第二种解决方案)

一、需求 后台管理系统&#xff1a;最常见的页面都是由—>左侧菜单、头部tabView页签、主体数据渲染页面&#xff08;AppMain&#xff09;&#xff1b;而一般AppMain页面又分为&#xff1a; 搜索区域、table数据&#xff08;分页&#xff09;&#xff0c;可能也会存在底部&a…

局域网 - 高速以太网(百兆、千兆、万兆)

文章目录 1 概述1.1 802.3 物理层规范1.2 以太网标准中后缀 -T、-F、-X 含义 2 分类2.1 快速以太网&#xff08;802.3μ、百兆&#xff09;2.2 千兆以太网&#xff08;802.3z、802.3ab&#xff09;2.3 万兆以太网&#xff08;802.3ae&#xff09; 3 扩展3.1 网工软考真题 1 概述…

Docker 部署 MySQL 一主多从

主从复制的原理&#xff1a; 1、主库&#xff1a; 创建一个有权访问binlog日志的从库账号&#xff0c;配置需要主从复制的库 有写操作时&#xff0c;可以将写操作或者写操作之后的数据记录到日志文件中(binlog) 通过一个线程通知需要同步数据…

设计模式:UML中的类图(6种关系)

一.UML图介绍 统一建模语言是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。 UML 从目标系统的不同角度出发&#xff0c;定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。 二.类图…

太阳的G2

我已经忘记是怎么喜欢上保罗的 入职腾讯的第一天&#xff0c;同事看到我的英文名cris&#xff0c;就笃信我应该是保罗的球迷。 是的&#xff0c;我是保罗的球迷「当然&#xff0c;不只是保罗的球迷」。 14-15赛季&#xff0c;保罗在的快船跟马刺鏖战7场&#xff0c;硬是在第7场…

4.24~25(总结)

第一周任务 - Virtual Judge 分析&#xff1a;这道题开始想错了&#xff0c;所以错了一次。后来又仔细读了一遍题&#xff0c;才发现&#xff0c;要是最长的那个排序子数组&#xff0c;所以第二次就做出来了&#xff0c;它其实应该分为两大块&#xff0c;第一块找左边的起点&a…

能源管理系统在电子厂房中的应用

摘要&#xff1a;以能耗管理系统在工业厂房的应用为例&#xff0c;介绍了系统架构及功能。重点分析能耗管理系统在工业厂房实施过程中遇到的难点&#xff0c;并对系统采集的数据进行分析&#xff0c;提出了相应的节能措施&#xff0c;帮助该业厂房达到节约能耗和运行费用的目的…

【Daily Share】没有域名怎么破?手把手教你如何通过hosts配置域名(假域名)

目录 ❌前言&#x1f4c4;hosts文件&#x1f989;DNS解析步骤&#x1f44c;配置伪域名第一步 修改本机hosts配置第二步 配置服务器nginx &#x1f503;流程图 ❌前言 ip记不住&#xff1f;&#xff1f;&#xff1f; 域名不想买&#xff1f;&#xff1f;&#xff1f; 每次当我…

【Linux】Linux开发工具

Linux开发工具 前言Linux编辑器 --- vimvim长啥样vim的基本概念vim的配置 Linux编译器 --- gcc/g编译和链接预处理编译汇编链接 细&#x1f512;链接静态库和动态库 Linux调试器 --- gdbLinux项目自动化构建工具 --- make/Makefile依赖关系和依赖方法 上方工具的简单示例 前言 …

0401概述-最短路径-加权有向图-数据结构和算法(Java)

文章目录 1 最短路径2 最短路径的性质3 加权有向图的数据结构3.1 加权有向边3.2 加权有向图 4 最短路径4.1 最短路径API4.2 最短路径的数据结构4.3 边的松弛4.4 顶点的松弛 结语 1 最短路径 如图1-1所示&#xff0c;一幅加权有向图和其中的一条最短路径&#xff1a; 定义&…

LeetCode 27.移除元素

文章目录 &#x1f4a1;题目分析&#x1f4a1;解题思路&#x1f6a9;思路1:暴力求解 --- 遍历&#x1f514;接口源码&#xff1a;&#x1f6a9;思路2:空间换时间&#x1f514;接口源码&#xff1a;&#x1f6a9;思路3:双指针&#xff08;快慢指针&#xff09;&#x1f514;接口…

【C++】string类的简单模拟实现

目录 string类初识 string模拟实现 string类成员变量 构造函数 拷贝构造 赋值运算符重载 析构函数 深浅拷贝问题 string类初识 由于C语言中的字符串不太符合OOP(面向对象编程)的思想&#xff0c;而且其底层空间需要用户自己管理&#xff0c;经常有访问越界的情况出现。…

解决方案:Zotero实现参考文献中英文混排,将英文文献中的“等”转成“et al.”

Zotero 是一款非常实用且易于使用的参考文献管理工具&#xff0c;可帮助用户收集、整理和引用各种类型的文献&#xff0c;包括图书、期刊文章、网页等。在学术写作中起着重要作用。 但是其在中文世界中&#xff0c;运行起来偶尔会出现问题&#xff0c;这里记录一个问题及其解决…

chmod 命令 (chmod 0660)

chmod的作用: 用于设置文件所有者和文件关联组的命令,就是控制用户的权限命令 注意事项: chown 需要超级用户 root 的权限才能执行此命令。 自己常用chmod 命令是 chmod 777 * 给所有文件权限 chmod 777 文件名 给单独文件权限 这个777 是怎么来的, 或者chmod 0660 这…

java 获取时间的方法

Java的时间是通过字节码指令来控制的&#xff0c;所以 java程序的运行时间是通过字节码指令来控制的。但是由于 Java程序在运行时&#xff0c; JVM会产生一些状态&#xff0c;所以在执行 JVM指令时&#xff0c; JVM也会产生一些状态。 我们在执行 java程序时&#xff0c;主要是…

kafka延时队列内部应用简介

kafka延时队列_悠然予夏的博客-CSDN博客 两个follower副本都已经拉取到了leader副本的最新位置&#xff0c;此时又向leader副本发送拉取请求&#xff0c;而leader副本并没有新的消息写入&#xff0c;那么此时leader副本该如何处理呢&#xff1f;可以直接返回空的拉取结…

IEEE14节点系统在如短路分析,潮流研究,互连电网中的研究(Simulink)

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