【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)

news2024/10/6 8:30:33

目录

一,二叉树的顺序结构实现

        1,二叉树的顺序结构

        2,堆的概念及结构

        3,堆的接口实现

1,堆的创建

2,接口函数

3,初始化

4,销毁

5,是否增容

6,交换数据

7,堆向上调整算法

8,插入数据

9,删除数据

10,堆向下调整算法

11,打印数据

12,取堆顶元素

13,判空

14,数据个数

        4,源代码

1,Heap.h

2,Heap.c

二,建堆的时间复杂度

        1,堆的创建

1,向上调整建堆法:

2,向下调整建堆法

        2,向上调整建堆的时间复杂度

        3,向下调整建堆的时间复杂度

三,堆的应用

        1,堆排序

1,建堆

2,利用堆交换删除思想来进行排序

        2,TOP-K问题


一,二叉树的顺序结构实现

        1,二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储

二叉树的顺序储存结构就是用一堆数组储存二叉树中的结点,并且结点的储存位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等;

        2,堆的概念及结构

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段;

如果有一个关键码的集合K = {k0 ,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足父亲结点的值大于等于或者小于等于子孙结点的值,则称为大堆(或小堆)

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆;

堆的性质:

1,堆中某个结点的值总是不大于或不小于其父结点的值;

2,堆总是一棵完全二叉树

        3,堆的接口实现

1,堆的创建

//堆(二叉树)的基本顺序结构
//动态顺序表
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

首先创建一个结构体表示顺序表,这里我们将数据类型重命名为HPDataType,便于我们后续对顺序表数据类型的修改;

a为类型指针代表一维数组

size为有效数据个数,capacity储存数据的最大容量值

2,接口函数

// 小根堆
// 堆初始化
void HPInit(HP* php);
// 销毁
void HPDestroy(HP* php);
// 是否增容
void HPCapacity(HP* php);
// 交换数据
void swap(HPDataType* x, HPDataType* y);
// 向上调整
void AdJustUp(HPDataType* a, int size);
// 插入数据
void HPPush(HP* php, HPDataType x);
// 向下调整
void AdJustDown(HPDataType* a, int size, int parent);
// 删除数据
void HPPop(HP* php);
// 打印数据
void HPPrint(HP* php);
// 取堆顶元素
HPDataType HPTop(HP* php);
// 判空
int HeapEmpty(HP* php);
// 数据个数
int HPSize(HP* php);

这里我们实现小根堆大小根堆的实现原理都一样;

以上是要实现的接口函数;

3,初始化

	//定义
	HP hp;
	//初始化
	HPInit(&hp);
// 堆的初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
	php->size = 0;
	php->capacity = 4;
}

首先要进行断言php不能为空,如果php为空则下面对php解引用就会报错;

刚开始先给 a 申请4个HPDataType大小的空间,这个自己可以任意修改,然后对sizecapacity进行赋值;

易错点:给指针a申请的是HPDataType大小的空间而不是HP大小的空间,这里由于学习过链表的缘故就容易搞混淆;

4,销毁

// 堆的销毁
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

然后就是销毁顺序表,直接用 free 释放掉 a 即可,再置为空指针,再重置 size 和 capacity ;

5,是否增容

//是否增容
void HPCapacity(HP* php)
{
	assert(php);
	if (php->size == php->capacity)
	{
		php->a = (HPDataType*)realloc(php->a,sizeof(HPDataType)*(php->capacity * 2));
		if (php->a == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		php->capacity *= 2;
	}
}

像后面如果进行数据插入的话,就需要检查空间是否需要增容了,也很好判断,当size等于capacity时就需要增容了,我们这里是选择直接扩容一倍;

然后再更新一下capacity的值就行了;

6,交换数据

//交换数据
void swap(HPDataType* x, HPDataType* y)
{
	assert(x&&y);
	HPDataType z = *x;
	*x = *y;
	*y = z;
}

老样子先对指针xy进行断言,然后定义z完成xy的值交换,交换数据后续会使用到;

7,堆向上调整算法

//向上调整
void AdJustUp(HPDataType* a,int size)
{
	assert(a);
	int child = size-1;
	while (child>0)
	{
		int parent = (child - 1) / 2;
        //孩子与父亲比较值
		if (a[child] < a[parent])
		{
            //交换
			swap(&a[child], &a[parent]);
			child = parent;
		}
		else
		{
			break;
		}
	}
}

先断言,当向堆中插入数据数据需要与父亲结点进行比较调整

我们传入指针a,数据个数sizesize用来确定插入数据的下标

然后建立循环遍历,父亲(parent)结点的下标为孩子(child)结点的下标减一除以二:

parent=(child -1)/ 2

a[child]的值小于a[parent]的值则进行交换,此时parent的身份转变为child继续向上调整,当child小于等于0,此时已到顶点无需再进行调整,退出循环;

8,插入数据

//插入数据
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	//是否增容
	HPCapacity(php);
	php->a[php->size] = x;
	php->size++;
    //向上调整
	AdJustUp(php->a, php->size);
}

首先判断是否需要增容,此时size的值就是末尾的数的下标加一

直接对其下标进行赋值,再让size加一

然后就需要将新数据向上调整,相当于更新维护堆结构

9,删除数据

//删除数据
void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
    //交换数据值
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
    //向下调整
	AdJustDown(php->a, php->size,0);
}

删除堆顶数据;

我们要删除数据不能直接删除否则会破坏堆结构,重新调整代价太大(时间复杂度太大);

我们可以将插入的数据与堆顶的数据进行交换,然后再让size--相当于删除了堆顶数据,然后再让堆顶的数据向下进行调整,以此来更新维护堆结构;

10,堆向下调整算法

//向下调整
void AdJustDown(HPDataType* a, int size,int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child<size)
	{
		//左孩子与右孩子比较值
		if (child+1<size && a[child]>a[child + 1])
		{
			child++;
		}
		//孩子与父亲比较值
		if (a[child] < a[parent])
		{
			//交换
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

孩子(child) 的下标等于父亲(parent) 下标×2+1:child=parent*2+1

建立循环遍历数组,先令左孩子为要与父亲比较的孩子,然后如果右孩子存在再让左孩子与右孩子进行比较,选出值小的孩子;

然后让孩子与父亲进行比较,如果孩子小于父亲则进行值的交换,此时孩子的身份变成父亲,继续循环;

当孩子的值大于等于size时循环结束,或者当孩子大于等于父亲则跳出循环;

11,打印数据

//打印数据
void HPPrint(HP* php)
{
	assert(php);
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
}

堆是个顺序表;

size是数据个数,然后进行遍历打印即可;

12,取堆顶元素

//取堆顶元素
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

直接返回堆顶的数据即可

13,判空

//判空
int HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

直接返回 size是否等于0的判断结果即可,如size等于0则为真,不等于0则为假;

14,数据个数

//数据个数
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

直接返回size即可;

        

        4,源代码

1,Heap.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

//堆(二叉树)的基本顺序结构
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

// 小根堆

// 堆初始化
void HPInit(HP* php);
// 销毁
void HPDestroy(HP* php);
// 是否增容
void HPCapacity(HP* php);
// 交换数据
void swap(HPDataType* x, HPDataType* y);
// 向上调整
void AdJustUp(HPDataType* a, int size);
// 插入数据
void HPPush(HP* php, HPDataType x);
// 向下调整
void AdJustDown(HPDataType* a, int size, int parent);
// 删除数据
void HPPop(HP* php);
// 打印数据
void HPPrint(HP* php);
// 取堆顶元素
HPDataType HPTop(HP* php);
// 判空
int HeapEmpty(HP* php);
// 数据个数
int HPSize(HP* php);

2,Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

//堆初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
	php->size = 0;
	php->capacity = 4;
}
//销毁
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}
//交换数据
void swap(HPDataType* x, HPDataType* y)
{
	assert(x&&y);
	HPDataType z = *x;
	*x = *y;
	*y = z;
}
//向上调整
void AdJustUp(HPDataType* a,int size)
{
	assert(a);
	int child = size-1;
	while (child>0)
	{
		int parent = (child - 1) / 2;
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			child = parent;
		}
		else
		{
			break;
		}
	}
}
//是否增容
void HPCapacity(HP* php)
{
	assert(php);
	if (php->size == php->capacity)
	{
		php->a = (HPDataType*)realloc(php->a,sizeof(HPDataType)*(php->capacity * 2));
		if (php->a == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		php->capacity *= 2;
	}
}
//插入数据
void HPPush(HP* php, HPDataType x)
{
	assert(php);
	//是否增容
	HPCapacity(php);
	php->a[php->size] = x;
	php->size++;
	AdJustUp(php->a, php->size);
}

//向下调整
void AdJustDown(HPDataType* a, int size,int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child<size)
	{
		//左孩子与右孩子比较值
		if (child+1<size && a[child]>a[child + 1])
		{
			child++;
		}
		//孩子与父亲比较值
		if (a[child] < a[parent])
		{
			//交换
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//删除数据
void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
	AdJustDown(php->a, php->size,0);
}
//打印数据
void HPPrint(HP* php)
{
	assert(php);
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
}
//取堆顶元素
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

//判空
int HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

//数据个数
int HPSize(HP* php)
{
	assert(php);
	return php->size;
}

二,建堆的时间复杂度

        1,堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆根节点左右子树不是堆,我们怎么调整呢?

这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆

int a[] = { 7,8,3,5,1,9,5,4 };
HPSort(a, sizeof(a) / sizeof(a[0]));

1,向上调整建堆法:

//建堆--向上调整建堆 
int num = n;
for (int i = 1; i <= num; i++)
{
    //向上调整
	AdJustUp(a,i);
}

直接一直循环插入数据即可;

2,向下调整建堆法

//建堆--向下调整建堆  O(N)
int num = n;
for (int i = (num - 1 - 1) / 2;i >= 0; i--)
{
	//向下调整
	AdJustDown(a,n,i);
}

需要先把根结点下面的子树都搞成堆,所以先从倒数的第一个非叶子节点的子树开始向下调整,然后循环遍历让结点逐渐缩减(逐渐往上走)向下调整,如图所示;

        2,向上调整建堆的时间复杂度

  F(h)=2^1*1+2^2*2+...+2^(h-2)*(h-2)+2^(h-1)*(h-1)

2*F(h)=2^2*1+2^3*2+...+2^(h-1)*(h-2)+2^h*(h-1)

两式错位相减:

F(h)= - 2^1-2^2-2^3-...-2^(h-2)-2^(h-1)+2^h*(h-1)

F(h)= - 2^h+2-2^h+2^h*h

F(h)=2^h(h-2)+2

假设树有N个结点:2^h-1=N      h=log(N+1)

F(N)=(N+1)*(log(N+1)-2) + 2 ≈ N*logN

所以用向上调整建堆的时间复杂度为O(N*logN);

        3,向下调整建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

则需要移动结点总的移动步数为:

T(n) = 2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+2^3*(h-4)+...+2^(h-3)*2+2^(h-2)*1

2*T(n)=2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+2^4*(h-4)+...+2^(h-2)*2+2^(h-1)*1

两式错位相减:

T(n)=1-h+2^1+2^2+2^3+2^4+...+2^(h-2)+2^(h-1)

T(n)=2^h-1-h

n=2^h-1  ==  h=log(n+1)

T(n)=n-log2(n+1)≈n

所以用向下调整建堆的时间复杂度为O(N);

由此可见,建堆的时间复杂度为O(N)!

三,堆的应用

        1,堆排序

1,建堆

升序:建大堆

降序:建小堆

2,利用堆交换删除思想来进行排序

int num = n;
while (num>1)
{
	//交换数据
	swap(&a[0], &a[num-1]);
	num--;
	//向下调整
	AdJustDown(a,num,0);
}

其实很简单,比如我们要整一个升序数组,有人会说建小堆,其实这是错的;

如果建小堆:堆顶的数不变,找次大的数要将堆顶以下的数全部重新排序,这不现实,所以pas;

建大堆:堆顶的数据与末尾的数据交换,然后令num--(将末尾的数隔离开),再将堆顶的数向下调整,然后循环遍历一直找次大的数,当num等于1时堆顶的数为最小值,此时的数组便是升序数组!

这就是堆的交换删除思想,这个循环的时间复杂度为O(N*logN)

        2,TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决;

正常思路:

把这个N建成大堆,PopK次,即可找出最大的前K

但是,当N非常大时,假设N时10亿,那就是10亿个整数,所需的空间太大了,不可取!

优化思路:

1,将前K个数建成小堆

2,后面将N-K个数依次与堆顶的数据进行比较,如比堆顶的数据大,则替换他为堆顶,然后向下调整

3,最后比较完了,这个小堆的值就是最大的前K个

第二阶段就到这里了,下面更新二叉树的链式结构的实现;

如有不足之处欢迎来补充交流!

完结。。。


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

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

相关文章

Redis基础详解

目录 一、概述 redis作用 redis特性 二、Redis基础知识 为什么Redis是单线程的&#xff1f; Redis的高并发和快速原因&#xff1f; Redis6.0 之后为何引入了多线程&#xff1f; 三、Redis五大数据类型&#xff0c;三种特殊数据类型 启动Redis Redis-Key String&#…

UMA 2 - Unity Multipurpose Avatar☀️三.给UMA设置默认服饰Recipes

文章目录 🟥 项目基础配置🟧 给UMA配置默认服饰Recipes🟨 设置服饰Recipes属性🟥 项目基础配置 将 UMA_DCS 预制体放到场景中创建空物体,添加DynamicCharacterAvatar 脚本,选择 HumanMaleDCS作为我们的基本模型配置默认Animator 🟧 给UMA配置默认服饰Recipes 服饰Re…

【web开发】5.Mysql及python代码执行数据库操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、MYSQL二、MySQL管理查看已有数据库创建数据库删除数据库进入数据库创建表删除表展示表的行列插入数据查看表中的数据删除数据修改数据 三、python代码执行数据库…

【数据结构与算法系列5】螺旋矩阵II (C++ Python)

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输入&#xff1a;n 1 输出&am…

4.3.3.1 【MySQL】CHAR(M)列的存储格式

我们知道 Compact 行格式在 CHAR(M) 类型的列中存储数据的时候还挺麻烦&#xff0c;分变长字符集和定长字符集的情况&#xff0c;而在 Redundant 行格式中十分干脆&#xff0c;不管该列使用的字符集是啥&#xff0c;只要是使用 CHAR(M) 类型&#xff0c;占用的真实数据空间就是…

2023年财务顾问行业研究报告

第一章 行业概况 1.1 定义及分类 财务顾问&#xff08;Financial Advisor&#xff0c;FA&#xff09;也被称为融资顾问&#xff0c;主要为创业公司提供投资和融资的专业服务。他们在创业者和投资者之间扮演着至关重要的中介角色&#xff0c;为双方搭建桥梁&#xff0c;确保投…

Python - 队列【queue】task_done()和join()基本使用

一. 前言 task_done()是Python中queue模块提供的方法&#xff0c;用于通知队列管理器&#xff0c;已经处理完了队列中的一个项目。 queue.task_done()是Queue对象的一个方法&#xff0c;它用于通知Queue对象&#xff0c;队列中的某一项已经被处理完毕。通常在使用Queue对象时…

【数据结构】 树和二叉树概念

1.树概念及结构 树概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff0c;…

Android lint配置及使用

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、将 lint 配置为不显示警告3.1 在 A…

Hadoop的第三大组成:YARN框架

Hadoop的第三大组成--YARN框架 一、YARN基本概念二、YARN的基本架构组成1、ResourceManager&#xff1a;YARN集群的管理者2、NodeManager3、Container4、ApplicationMaster 三、YARN的详细工作流程--运行MapReduce四、YARN的资源调度器问题五、YARN的web网站问题 一、YARN基本概…

数字展厅有什么优势,一文了解数字展厅建设方案

引言&#xff1a; 在当今数字化风潮的席卷下&#xff0c;企业们正积极寻求创新的方式来吸引和互动他们的客户。数字展厅作为一种新型的虚拟宣传工具&#xff0c;已经开始引起广泛的关注。 一&#xff0e;什么是数字展厅&#xff1f; 数字展厅是一种基于虚拟现实&#xff08;V…

C++数据结构类的自实现,封装栈,循环队列

my_Queue.h #ifndef MY_QUEUE_H #define MY_QUEUE_Hclass My_Queue { private:int* m_queue; //队列空间指针int front; //队头int tail; //队尾int m_length; //队列长度public://构造函数My_Queue(int len);//构造拷贝函数My_Queue(const My_Queue& obj);//队列长度in…

“构建高效的SpringMVC增删改查应用“

目录 引言1.配置SpringMVCpom.xmlweb.xmlzking.tldgeneratorConfig.xmljdbc.propertieslog4j2.xmlspring-context.xmlspring-mvc.xmlspring-mybatis.xmlStudentBizImplPagerAspectPageTag 2.实现代码功能StudentController 3.JSP页面代码List.jspedit.jspheard.jsp 4. 运行截图…

Linux--进程--进程-父进程退出

1.进程退出函数 进程退出分为正常退出&异常退出 正常退出&#xff1a; 1、main函数调用return 2、进程调用exit(),标准c库 3、进程调用_exit()或者_Exit(),属于系统调用 补充 1、进程最后一个线程返回 2、最后一个线程调用pthread_exit 异常退出&#xff1a; 1、调用abo…

十九、MySQL外键删除更新行为如何实现?

1、行为约束 &#xff08;1&#xff09;基础概念&#xff1a; 要学习外键约束&#xff0c;就要先了解外键约束。 比如说&#xff0c;现在这里有两张表&#xff0c;其中一张存储着用户的信息(子表)&#xff0c;另外一张存储着用户所属的部门(父表) &#xff0c;但现在…

c++的引用和指针

我们要清楚的知道&#xff0c;使用指针和引用都可以的传入函数的main函数的变量在局部函数改变值时&#xff0c;main函数里面相应的变量也会改变值。但他俩的方式不同。 我们先来说指针&#xff0c;指针传入局部参数时&#xff0c;他会在创建个局部指针变量&#xff0c;然后把…

UMA 2 - Unity Multipurpose Avatar☀️一.让UMA角色动起来

文章目录 🟥 项目基础配置1️⃣UMA_DCS 预制体2️⃣创建 UMA 角色🟧 让UMA动起来1️⃣ 新建空场景,添加UMA_DCS预制体2️⃣配置 vBasicController_Template🟥 项目基础配置 1️⃣UMA_DCS 预制体 将 UMA_DCS 预制体放到场景中 2️⃣创建 UMA 角色 创建空物体,添加 Dy…

C++编译静态成员函数报错: “osgGA::DriveManipulator::setEye”: 非静态成员函数的非法调用

来看代码 .h文件中 static void computePosition(const osg::Vec3d& eye,const osg::Vec3d& lv,const osg::Vec3d& up); void setEye(const osg::Vec3d& eye); void setRotation( const osg::Quat& rotation );osg::Vec3d _eye; osg::Quat _rotation…

Sentinel 部署 配置

目录 概述什么是 SentinelSentinel的特性hystrix与sentinel的区别Sentinel客户端Sentinel服务端 下载安装服务熔断(openfeign) 1. 添加依赖2. 创建 Feign 接口3. Controller调用接口流控规则 概述测试接口准备流控规则配置 1. QPS2. 线程数3. 关联4. 链路5. Warm up&#xff0…

基于Bert+Attention+LSTM智能校园知识图谱问答推荐系统——NLP自然语言处理算法应用(含Python全部工程源码及训练模型)+数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境服务器环境 模块实现1. 构造数据集2. 识别网络3. 命名实体纠错4. 检索问题类别5. 查询结果 系统测试1. 命名实体识别网络测试2. 知识图谱问答系统整体测试 工程源代码下载其它资料下载 前言 这个项目充分利用了…