首页 > 其他分享 >数据结构入门

数据结构入门

时间:2025-01-12 21:34:09浏览次数:3  
标签:ps 结点 NULL 入门 next pq 数据结构 size

数据结构

数据结构分为顺序表,链表,栈,队列,堆,树,图

顺序表

顺序表的物理结构和逻辑结构都是连续的

去建立一个顺序表,我们需要先去了解底层是什么。

在脑海中很容易就会联想到数组,所以创建一个顺序表,首先要有一个数组。但是仅仅有数组是不够的,我们需要在其中对数据进行处理,那么其有效元素的个数以及容量自然是不可或缺的,所以

顺序表的结构就如下

typedef int SLDataType;

typedef struct SeqList 
{
	 SLDataType* arr;
	 int size;
	 int capacity;
	   
}SL ; 

我们现在并不知道我们需要在顺序表中处理什么类型的数据,所以我们用typedef来将类型重命名,这样就算后面需要更改类型,我们也不需要将类型一个一个 进行修改,只需要在typedef int SLDataType这里将int修改为需要的类型即可。

另外对于在结构体前加typedef是为了在后面需要使用该结构体的时候方便调用。如果没有使用typedef,那么在后期的调用需要以struct SL sl1来声明;但是使用后便只需SL sl1这样即可,在较大项目中就会有很大的用处

首先我们的顺序表肯定是需要先进行初始化的,要是不初始化,那么我们的ps就是NULL了,里面什么也没有,就连顺序表的结构都没有更别提去使用顺序表了

void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

为了防止ps=NULL,我们可以使用函数assert(断言,头文件为<assert.h>)来保证不为NULL,如果为空,那么就会终止程序进行,如图所示

在对顺序表进行修改前,我们需要给它分配一块动态内存,必须要有内存,才可以将数据储存其中,然后在对其中数据进行处理。不仅仅是开始时需要分配动态内存,开始时分配的动态内存不是无限的,在后续加入数据是内存会不足,这时就需要在原有的空间之上另外开辟新的动态内存

void SLcheckCapacity(SL* ps)
{
	assert(ps != NULL);
	//空间不够
	if (ps->size == ps->capacity)
	{
		int newrong = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//二倍增容
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newrong * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newrong;//容量从新转到末尾
	}
}

总之,我们需要先去创建一个顺序表的结构,才能给它开辟一块空间,毕竟我们是要用顺序表的 结构的。换句话说,现在顺序表就像水桶,空间就像水。我们只有先有水桶,我们才可以装水。当然,其他工具也可以装水,但是我们要使用的是水桶!

那么下面来说说关于对顺序表数  据处理的几种常用方法

尾插

顾名思义,就是在顺序表的尾部进行数据的插入。那么该如何实现呢?

首先自然是判空了,即assert(ps!=NULL)

接着就是对于顺序表的空间容量进行检查,若不够自然需要分配更多的空间,通过调用上面提到的 SLcheckCapacity函数来判断空间是否充足,若不足可以在进行空间的开辟。

准备工作完成了之后就可以开始尾插了

对于我们的size,是有效元素个数,由于数组是由0开始的,所以size的位置指向的是最后一个元素后面的位置,我们的新数据就可以直接插到ps->arr[size]这个位置,再让size向后面移动一位,即size++

为了简便,我们可以将其合并为一行代码ps->arr[ps->size++] = x

由于这里是后置++,所以我们会先对原有的size进行使用再使其加1

void SLPushBack(SL * ps, SLDataType x)
{
	assert(ps != NULL);
	SLcheckCapacity(ps);
	ps->arr[ps->size++] = x;
}

其时间复杂度为O(1)

头插

与尾插相反,这是在顺序表的最前面插入数据。

相较于尾插,头插的时间复杂度就大的多了。前面的判空以及空间的检查都和尾插一样,这两个步骤在后面不再进行特殊说明,有区别的在于插入过程。

头插需要将所有的元素都向后移动一位,从而为头插腾出空间,所以其空间复杂度为O(N)

void SLPushHead(SL* ps, SLDataType x)
{
	assert(ps);//等价于assert(ps != NULL)
	SLcheckCapacity(ps);
	//直接头插
	//数据整体向后移一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];

	}
	ps->arr[0] = x;
	++ps->size;
}

对于++ps->size,其含义是ps中的size加1,->的优先级大于++

尾删

顾名思义,就是删除最后面的元素,有手就行

void SLPopBack(SL* ps)
{
	assert(ps && ps->size!=0);
	--ps->size;

}

我们不需要真正的把最尾部的元素删除,我们只需要把size往前移一位就行,这样在后续进行尾插的时候新元素会将要删除的元素覆盖

--ps->size中->的优先级大于--,所以其含义就是将size向后移动一位

时间复杂度是O(1)

头删

其和头插的操作差不多,是将除了第一个元素的其他元素向前移动,由于元素少了一个,所以让size向前移动一位

void SLPopHead(SL* ps)
{
	assert(ps && ps->size);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;
}

其空间复杂度是O(N)

查找

对于顺序表,我们不仅仅要对其中元素进行修改,我们还需要查找我们所需要的元素的位置

我们只需要将自己想要查找的元素分别和所以元素遍历,再返回与其相同元素的地址

int SLFind(SL* ps, int pos, SLDataType x)
{
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	return -1; 
}

时间复杂度为O(N)

在指定位置前插入和删除数据

对于这个插入函数,我们需要三个参数void SLInsert(SL* ps, int pos, SLDataType x),其中pos是指定的位置。我们只需要将从pos(包括pos)开始的元素向后面移动一位。如果我们从pos开始向后移动,那么pos后面的数据将会被覆盖。所以我们从最后面开始,将最后面的元素先向后移动,一直到pos向后面移动后停止,最后再将size++就完成了

空间复杂度为O(N)

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps && pos >= 0 && pos <= ps->size);
	SLcheckCapacity(ps);
	for (int i=ps->size;i>pos;i--)
	{
		ps->arr[i] = ps->arr[i-1];
		
	}
	ps->arr[pos] = x;

	++ps->size;

}

对于删除函数,就只需要两个参数了(因为不需要插入数据了),和插入数据大同小异,只需要从pos开始将pos及其后面的元素向前移动一位,就可以覆盖pos前面的元素,由于减少了一个元素,所以size--

void SLErase(SL* ps, int pos)
{
	assert(ps && pos >= 0 && pos <= ps->size);
	for (int i = pos ; i < ps->size -1;i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	--ps->size;

}

销毁

现在已经完成了常见的顺序表操作了,那就该讲讲销毁了。

只需要将ps中的arr的空间释放,再将arr赋值为NULL(若直接释放arr而不将其赋值为NULL,那么arr就会变为野指针,十分危险,就像野狗一样),size和capacity赋值为0就完成了顺序表的销毁

void SLDesTroy(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

顺序表的储存方式是顺序储存,在内存中申请一块连续的空间,通过下标来进行储存。这也导致了插入和删除数据的效率低下,而且其在扩容时会二倍扩容,这也就导致了空间的浪费,但是顺序表也有一定的优点,其查找数据可以直接使用下标进行查找,非常方便,且其CPU缓存利用率高,所以顺序表的使用场景一般在需要元素高效储存和频繁访问的领域

链表

链表是由许多个结点构成的。所谓链表,顾名思义,就是像锁链一般的结构,其中的锁链就是链接每个结点之间的指针,所以其每个结点的结构中必然存在指针结构,去指向后面的结点。

链表有单链表和双链表之分,首先我们来聊一下单链表怎么实现

单链表需要储存一个和一个指针,所以他的的结点结构如下

​
typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SLTNode;

​

既然我们已经构建好了结点的结构,那么接下来,我们需要对其进行引用

首先我们为结点开辟一块动态内存,当然开辟后我们需要判断是否开辟成功,若成功则让该函数的打他等于0,再让它的next指向下一个结点的地址,最后返回该结点的地址

SLTNode* SLTBuyNode(SLDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	node->data = x;
	node->next = NULL;
	return node;


}

对于链表的遍历,只需要通过上一个节点中的next找到下一个结点的地址。再打印出data即可,在后面的每一个操作都需要判空,在后面的操作中不在特殊说明

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

尾插

在链表中,每一个元素都是一个结点,所以只需要在最后一个结点的后面加上一个结点就实现尾插了

在详细一点就是先申请一个结点,在这个结点中将data赋值,再将原链表的最后一个结点指向刚刚申请的结点,最后让刚刚申请的结点的next指向NULL,代码如下

void SLTpushBack(SLTNode** pphead, SLDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	//链表非空,找尾结点
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}

 头插

头插只需要申请结点,将该结点的next指向原链表的第一个结点就可以了。

这里需要传二级指针是因为我们不仅仅要使用头结点的地址,还需要对其进行修改,在传参的时候,一旦涉及到要对该参数进行修改,而且想要这种修改会影响到实参,那么就需要传比该参数更深层次的参数。例如想要改变变量就要传一级指针,想要改变一级指针就要传二级指针

void SLTpushFront(SLTNode** pphead, SLDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

尾删

这里的二级指针和头插的效果是一样的,如果只有一个结点,那么需要将空间释放并且将头结点的地址赋值为NULL

如果不是只有一个结点,那么只需要通过next找到尾结点,然后将尾结点释放并赋值为NULL

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	//如果只有一个结点,需要特殊处理
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		SLTNode* prev = NULL;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

头删

只需要将头结点释放,再将头结点的next指向下一个结点

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

前插

所谓前插就是在指定结点前面插入一个结点,实现方法也是十分简单,若是只有头结点,那么就需要改变头结点的地址了,若要插入的结点不是头结点,那么只需要让该结点连接在中间即可

void SLTInset(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
	assert(pphead && pos);
	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	newnode->next = pos;
	prev->next = newnode;
}

后插

如果只有一个结点,那么相当于尾插

对于一个具有多个结点的链表,我们只需要将它连接在该结点和其后面的结点之间即可

void SLTInsetAfter(SLTNode* pos, SLDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode = pos->next;
	pos->next = newnode;
}

删除结点

要分两种情况,一种是有多个结点的链表,这种情况并不需要二级指针,只需要将该结点释放,再将其前后的结点相连接即可;第二中情况是只有一个结点的链表,这种情况需要用到二级指针,因为需要改变头结点的地址

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);
	if (*pphead != pos)
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{
		SLTPopFront(pphead);
	}
}

后删

只需要将通过该结点next找到后面的结点然后将其释放,再将其赋值为NULL,再将删除结点的前后相连即可

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

销毁

现在常见的对链表的操作已经实现了。最后就需要销毁链表了,由于需要将头结点赋值为NULL,所以我们需要将二级指针传参,在逐一将其释放即可

void SListDestroy(SLTNode** pphead)
{
	SLTNode* pcur = *pphead;
	SLTNode* next = NULL;
	while (pcur)
	{
		next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

在计算机的内存中也有栈,但是我们所说的栈和它有所不同,内存中的栈只是一块特殊的内存区域,用于储存函数调用时的相关信息,如局部变量, 函数参数,返回地址等,由系统自动管理;而数据结构中的栈是一种抽象的数据类型,具有先进后出的特点,仅仅允许在一端进行插入和删除

对于栈,上面也说到了栈的特点:(1)只在一端插入和删除数据(栈顶),另一端叫做栈底 (2)所有的数据都遵循先进后出的原则

栈的实现我们可以用链表或者顺序表的结构,这里我们选择顺序表结构,所以其结构为

typedef struct Stack
{
	STDataType* arr;
	int capacity;     //栈的空间大小
	int top;          //栈顶
}ST;

初始化

void STInit(ST* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

入数据

首先我们先用assert函数来判空,接着判断空间是否充足,若不够,则开辟新的空间,同时判断是否开辟成功,若成功,则将新地址赋值给arr,再将新的容量赋值给capacity

若空间充足,则直接将值插入,然后将top++,这里是top++,也就是先用再加

void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	//1.判断空间是否足够
	if (ps->capacity == ps->top)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
	//空间足够
	ps->arr[ps->top++] = x;
}

出数据

出数据只需要将top向后面移动一位,这样下一次入数据就会将其覆盖了

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	--ps->top;
}

取栈顶数据

在取栈顶元素前,我们需要判断栈是否定义和是否为空,对于是否为空,我们定义一下函数StackEmpty来判断,其返回值类型是一个布尔类型

bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

top指向的位置是栈顶的后面,所以取top-1的元素即可

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->arr[ps->top - 1];
}

获取栈中有效元素个数

top就是有效元素的个数,所以直接返回top即可(数组是从0开始的,所以最后一个元素的下标比实际元素少一个,而top又是在最后一个元素后面,所以top就是有效元素个数

int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

队列

 队列相较于栈而言,其队尾也可以处理数据

队列当然也可以用顺序表或链表的结构实现,这里我们用链表实现

声明

typedef struct Queue
{
	QueueNode* phead;  //队头
	QueueNode* ptail;  //队尾
	int size;          //队列有效元素个数
}Queue;

初始化

只需要将队头和队尾的指针指向NULL即可

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
}

插入

首先自然是开辟空间,然后将值存在data中,其中发next指向NULL

接着在判断原队列是否为空队列,pq指向的是队列的队头,若为空队列,则让队头队尾等于刚刚申请的结点;若不为空队列,让队尾中的next指向刚刚申请的结点,再让队尾为刚刚申请的结点即可

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//队列为空,队头和队尾都是newnode
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else {
		//pq->ptail newnode
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
}

队头删除

若队列只有一个结点,那么将它释放,再将队头队尾指向NULL

若队列中有多个结点,将该结点释放,并将队头向后面移动一位

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//只有一个结点
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else {
		//多个结点
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	--pq->size;
}

有效元素个数

要想找到有效元素,只需要将队列遍历,当该结点的next指向NULL时结束即可

int QueueSize(Queue* pq)
{
	//int size = 0;
	//QueueNode* pcur = pq->phead;
	//while (pcur)
	//{
	//	size++;
	//	pcur = pcur->next;
	//}
	//return size;

	return pq->size;
}

取堆头数据

直接取队头的data即可

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

取队尾数据

直接取队尾的data即可

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

销毁

这里不需要传二级指针,我们并不需要将整个队列结构销毁,我们只需要恢复其初始化状态即可

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

树是一种非线性的数据结构,是由n(n>=0)个有限结点组成的一个具有层次关系的集合

之所以叫做树是因为它长的很像树,其由一个根节点延伸出许多的结点构成,如图

根结点:根节点没有前驱结点。

除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

因此树是被递归定义的

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2

叶节点:度为0的节点称为叶节点; 如上图:G、H、I节点为叶节点

非终端节点或分支节点:度不为0的节点; 如上图:B、D、C、E、F节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为2

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m棵互不相交的树的集合称为森林;

对于树的表示,有很多方法,如双亲表示法,孩子表示法,孩子兄弟表示法等

二叉树

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:

每个结点最多有两棵子树,即二叉树不存在度大于2的结点。

二叉树的子树有左右之分,其子树的次序不能颠倒。

在二叉树中还有一些特殊的二叉树

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

这里我们采用双亲表示法

typedef char BTDataType;
//二叉树结点结构的定义
typedef struct BinaryTreeNode
{
	BTDataType data;//值
	struct BinaryTreeNode* left;//左
	struct BinaryTreeNode* right;//右
}BTNode;

那怎么遍历呢

这里有4中遍历方法,前序遍历,中序遍历,后序遍历

遍历
前序遍历

前序遍历的顺序是根左右,经过不断的递归从而达到将整个二叉树遍历的效果

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
中序遍历

中序遍历的顺序是左根右,经过不断的递归从而达到将整个二叉树遍历的效果  

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}
后序遍历

后序遍历是左右根,经过不断的递归从而达到将整个二叉树遍历的效果

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

以上是部分常用的数据结构 

标签:ps,结点,NULL,入门,next,pq,数据结构,size
From: https://blog.csdn.net/2401_87311360/article/details/143351794

相关文章

  • 大语言模型入门指南:一篇掌握学习计划和路线,给自己三个月然后惊艳所有人
    课题介绍一、方向介绍在不到四年时间里,Transformer模型以其强大的性能迅速在NLP社区崭露头角,进而,基于Transformer这一标准架构的BERT、T5,乃至当下热门的GPT模型已经逐渐成为各个领域中的不可或缺的模型架构。深入理解Transformer模型背后的底层原理,以及Transformer相关的......
  • 大模型技术学习指南:从入门到精通_大模型从入门到高级的学习路线!
    “技术学习无非涵盖三个方面,理论,实践和应用**”**大模型技术爆火至今已经有两年的时间了,而且大模型技术的发展潜力也不言而喻。因此,很多人打算学习大模型,但又不知道该怎么入手,因此今天就来了解一下大模型的学习路线。‍‍丁元英说:“透视社会有三个层面,技术,制度与文化”;同......
  • 认识Pandas,以及pandas的数据结构Series和DataFrame
    以下是关于pandas数据结构部分的详细讲解和案例:SeriesSeries是pandas中的一种一维数组结构,可以存储任意类型的数据(整数、字符串、浮点数、Python对象等),并且每个数据点都有一个对应的索引标签。创建Series案例:创建一个包含水果数量的Series对象。代码:importpandasa......
  • 多 Agent 框架入门:开启智能协作新时代(24/30)
    一、引言:多Agent系统的崛起在当今科技飞速发展的时代,人工智能已深深融入人们生活的方方面面,多Agent系统作为其中的璀璨明珠,正散发着独特魅力。从智能家居设备的默契协作,到工业生产线上各环节的智能调度;从复杂交通网络的流量优化,再到金融市场的风险预测与策略制定,多Agent......
  • 单智能体入门:开启智能新世界的钥匙(23/30)
    一、智能体的神秘面纱近年来,智能体(Agent)成为科技领域炙手可热的话题。从OpenAI首席执行官奥特曼对智能体的前瞻布局,到各大科技巨头与初创公司纷纷涌入赛道,智能体已然站在科技浪潮之巅。它宛如一个灵动的数字化精灵,能感知环境、自主决策并采取行动,以达成预设目标。在智能体......
  • C++基础入门(一)
    目录前言C语言和C++的关系和区别一、命名空间1.命名空间的作用2.自定义命名空间二、从C语言快速入门C++1.输入输出2.基本变量类型3.内联函数4.Lambda表达式三.类1.类的初探2.结构体引入类3.新建C++工程来使用结构体(类)4.真正的成员函数四、权限初识五、引用1.......
  • 数据结构:栈(Stack)和队列(Queue)—面试题(一)
    目录1、括号匹配2、逆波兰表达式求值 3、栈的压入、弹出序列4、最小栈 1、括号匹配习题链接https://leetcode.cn/problems/valid-parentheses/description/描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必......
  • Redis 是一个开源的高性能键值对存储数据库,通常被用作缓存、消息队列和持久化数据库。
    Redis服务器是什么?Redis是一个开源的高性能键值对存储数据库,通常被用作缓存、消息队列和持久化数据库。Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合、位图等。它被广泛用于需要快速读写操作、低延迟的场景。Redis可以作为一个独立的数据库使用,也可以作为缓......
  • 数据结构实验
    链表单链表的操作https://blog.csdn.net/zengyawen1225/article/details/142904337#include<stdio.h>#include<stdlib.h>typedefstructNode{intdata;//数据structNode*next;//指向下一个节点的指针}Node;//插入数据到链表的末尾voida......
  • 【轻松掌握数据结构与算法】递归与回溯:解决复杂问题的利器
    在数据结构与算法的世界中,递归和回溯是两种强大的技术,它们可以帮助我们解决许多看似复杂的问题。本文将详细介绍递归和回溯的基本概念、应用场景以及它们的实现方法,并通过示例和图表来帮助你更好地理解这些概念。递归:自我调用的力量递归是一种函数调用自身的技术。它允许我......