数据结构--》从线性表说起,掌握常用基础算法

news2024/11/25 22:42:09

目录

初识线性表

线性表的基本操作

顺序表的定义

顺序表的基本操作

单链表的定义

单链表的基本操作 

双链表的介绍

循环链表的介绍

静态链表的介绍


初识线性表

线性表是具有相同数据类型的 n (n\geqslant0) 个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为:

{a_i{}}^{} 是线性表中的第 “i” 个元素线性表中的位序{a_1{}}^{}表头元素{a_n{}}^{}表尾元素

除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

线性表的基本操作

实际开发中,可根据实际需求定义其他的基本操作,对数据的操作(记忆思路)为:创销增删改查,函数名和参数的形式都可以改变。那么我们为什么要实现对数据结构的基本操作呢

1)团队合作编程,你定义的数据结构要让别人能够很方便的使用(封装)

2)将常用的操作/运算封装成函数,避免重复工作,降低出错风险。

InitList($L):初始化表。构造一个空的线性表L,分配内存空间。

DestroyList(&L):销毁线性表。销毁操作并释放线性表L所占用的内存空间。

ListInsert(&L,i,e):插入操作。在表L中的第i个位置插入指定元素e。

ListDelete(&L,i,e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。

PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。

Empty(L):判空操作。若L为空表,则返回true,否则返回false。

当我们对参数的修改结果需要带过来时,需要传入引用 “&” 。给出如下代码进行解释:

#include <stdio.h>
void test(int x) {
	x = 1024;
	printf("test函数内部 x=%d\n", x);
}

int main() {
	int x = 1;
	printf("调用test前 x=%d\n", x);
	test(x);
	printf("调用test后 x=%d\n", x);
}

控制台打印的结果如下, 其实test函数里的x是main函数里面x的复制品,两者是两种不同内存地址的数据,test仅仅是改变了其内容x的数据,并没有改变main函数中x的数据,说白了就是main函数的x调用test函数并没有对参数的修改结果带回来,所有结果依然是1。

如果我们在test函数中采用引用类型,test函数对x的修改就会带回到main函数当中,如下:

回顾重点,其主要内容整理成如下内容:

顺序表的定义

顺序表是用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

顺序表的静态分配实现(顺序表的表长刚开始确定就无法更改)

#define MaxSize 10			// 定义最大长度
typedef struct {			
	ElemType data[MaxSize]; // 用静态的“数组”存储数据元素
	int length;				// 顺序表的当前长度
}SqList;					// 顺序表的类型定义

顺序表的动态分配实现

#define InitSize 10			// 顺序表的初识长度
typedef struct {			
	ElemType *data;		    // 指示动态分配数组的指针
	int MaxSize;			// 顺序表的最大容量
	int length;				// 顺序表的当前长度
}SeqList;					// 顺序表的类型定义(动态分配方式)

顺序表的特点

1)随机访问,即可以在O(1)时间内找到第i个元素。

2)存储密度不高,每个节点只存储数据元素

3)拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)

4)插入、删除操作不方便,需要移动大量元素

回顾重点,其主要内容整理成如下内容:

顺序表的基本操作

插入操作:ListInsert(&L,i,e):在表L中的第i个位置上插入指定元素e。其基础代码如下:

#include <stdio.h>

#define MaxSize 10			// 定义最大长度
typedef struct {			
	int data[MaxSize]; // 用静态的“数组”存储数据元素
	int length;				// 顺序表的当前长度
}SqList;					// 顺序表的类型定义

bool ListInsert(SqList& L, int i, int e) {
	if (i<1 || i>L.length + 1) { // 判断i的范围是否有效
		return false;
	}
	if (L.length >= MaxSize) { // 当前的存储空间已满,不能插入
		return false;
	}
	for (int j = L.length; j >= i; j--){ // 将第i个元素及之后的元素后移
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e; // 在位置i处存放e
	L.length++; // 长度+1
	return true;
}

int main() {
	SqList L; // 声明一个顺序表
	InitList(L); // 初始化顺序表
	// ...此处省略代码,插入几个元素
	ListInsert(L, 3, 3);
	return 0;
}

插入操作的时间复杂度分析

最好情况:新元素插入到表尾,不需要移动元素

                  i = n + 1,循环0次,最好的时间复杂度 = O(1)

最坏情况:新元素插入到表头,需要将原有的n个元素全都向后移动

                  i = 1,循环n次,最坏时间复杂度 = O(n)

平均情况:假设新元素插入到任何一个位置的概率相同,即i=1,2,3,..,length+1的概率都是p=\frac{1}{n+1}。i=1,循环n次;i=2,循环n-1次;i=3,循环n-2次 ... i=n+1时,循环0次。

                 平均循环次数 = np + (n-1)p+...+1·p = \frac{n(n+1))}{2}\frac{1}{n+1} = \frac{n}{2},平均时间复杂度 = O(n)

删除操作:ListDelete(&L,i,&e):删除表L中的第i个位置的元素,并用e返回删除元素的值。

bool ListDelete(SqList &L, int i, int &e){
	if (i<1 || i>L.length) { // 判断i的范围是否有效
		return false;
	}
	e = L.data[i - 1]; // 将被删除的元素赋值给e
	for (int j = i; j < L.length; j++){ // 将第i个元素及之后的元素前移
		L.data[j-1] = L.data[j];
	}
	L.length--; // 线性表长度减1
	return true;
}

int main() {
	SqList L; // 声明一个顺序表
	InitList(L); // 初始化顺序表
	// ...此处省略代码,插入几个元素
	int e = -1; // 用变量e把删除的元素“带回来”
	if (ListDelete(L, 3, e)) {
		printf("已删除第3个元素,删除元素值为=%d\n", e);
	}
	else {
		printf("位序i不合法,删除失败\n");
	}
	return 0;
}

删除操作的时间复杂度分析

最好情况:删除表尾元素,不需要移动其他元素。

                  i=n,循环0次;最好时间复杂度 = O(1)

最坏情况:删除表头元素,需要将后续的n-1个元素全都向前移动

                  i=1,循环n-1次;最坏时间复杂度 = O(n)

平均情况:假设删除任何一个元素的概率相同,即i=1,2,3,..,length的概率都是p=\frac{1}{n},i=1,循环n-1次;i=2时,循环n-2次;i=3时,循环n-3次...i=n时,循环0次

                  平均循环次数=(n-1)p+(n-2)p+...+1·p=\frac{n(n-1))}{2}\frac{1}{n}=\frac{n-1}{2},平均时间复杂度=O(n)

回顾重点,其主要内容整理成如下内容:

按位查找: GetElem(L,i):获取表L中第i个位置的元素的值。

ElemType GetElem(SeqList L, int i) {
	return L.data[i - 1];
}

由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素——“随机存储”特性,因此其时间复杂度:O(1)。

按值查找:在表L中查找具有给定关键字值的元素。

int LocateElem(SeqList L, ElemType e) {
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == e) {
			return i + 1;// 数组下标为i的元素值等于e,返回其位序i+1
		}
	}
	return 0; // 退出循环,说明查找失败
}

按值查找操作的时间复杂度分析

最好情况:目标元素在表头

                  循环1次:最好时间复杂度=O(1)

最坏情况:目标元素在表尾

                  循环n次:最坏时间复杂度=O(n)

平均情况:假设目标元素出现在任何一个位置的概率相同,都是\frac{1}{n},目标元素在第1位,循环1次;在第2位,循环2次,...;在第n位,循环n次。

                  平均循环次数=1·\frac{1}{n}+2·\frac{1}{n}+3·\frac{1}{n}+....+n·\frac{1}{n} = \frac{n(n+1))}{2}\frac{1}{n} = \frac{n+1}{2},平均时间复杂度=O(n)

回顾重点,其主要内容整理成如下内容:

单链表的定义

单链表每个节点除了存放数据元素之外,还要存储指向下一个节点的指针。其不要求大片连续空间,改变容量方便,但是不可随机存取,要耗费一定空间存放指针。

要声明一个单链表时,只需声明一个头指针L,指向单链表的第一个结点。

typedef struct LNode { // 定义单链表结点类型
	ElemType data; // 每个节点存放一个数据元素
	struct LNode* next; // 指针指向下一个节点
}LNode, *LinkList;

不带头节点的单链表

// 初始化一个空的单链表
bool InitList(LinkList& L) {
	L = NULL; // 空表,暂时没有任何节点
	return true;
}

带头节点的单链表

// 初始化一个单链表(带头节点)
bool InitList(LinkList& L) {
	L = (Lode * )malloc(sizeof(LNode)); // 分配一个头节点
	if (L == NULL) // 内存不足,分配失败
		return false;
	L->next = NULL; // 头节点之后暂时还没有节点
	return true;
}

回顾重点,其主要内容整理成如下内容: 

单链表的基本操作 

插入操作:ListInsert(&L,i,e):在表L中的第i个位置上插入指定元素e。

按位序插入(带头节点)

按位序插入(不带头节点)

后插操作

前插操作

删除操作:ListDelete(&L,i,&e):删除表L中第i个位置的元素,并用e返回删除元素的值。

按位序删除(带头节点)

按位查找:获取表L中第i个位置的元素的值。

按值查找:在表L中查找具有给定关键字值的元素。

回顾重点,其主要内容整理成如下内容: 

尾插法建立单链表

头插法建立单链表

头插法尾插法:核心就是初始化操作、指定结点的后插操作。头插法的重要应用:链表的逆置。

双链表的介绍

双向链表(Doubly Linked List),也称双链表,是一种常见的线性数据结构,与单向链表类似,但每个节点有两个指针,一个指向前一个节点,一个指向后一个节点,因此可以双向遍历。

每个节点包含三个部分:数据域、指向前一个节点的指针域 prev 和指向后一个节点的指针域 next。第一个节点的 prev 指针一般为空,最后一个节点的 next 指针一般为空。

相比单向链表,双向链表在插入、删除等操作时更为灵活,同时可以双向遍历,方便查找前一个及后一个节点,因此在某些场景下使用双向链表更为方便快捷。但是,双向链表相对于单向链表需要额外的空间来存储指向前一个节点的指针,因此会占用更多的内存空间。

双链表的初始化(带头结点)

双链表的插入

双链表的删除

双链表的遍历(双链表不可随机存取,按位查找、按值查找操作都只能用遍历方式实现)。

回顾重点,其主要内容整理成如下内容:

循环链表的介绍

循环链表(Circular Linked List)是一种特殊的链表结构,它的最后一个节点指向头节点,从而形成一个环形结构。循环链表可以是单向的或双向的。

循环单链表:循环单链表是一种只有一个指针域的单向链表,其最后一个节点的指针指向头节点,实现了链表的循环。下面是一个用C语言实现循环单链表的基本代码:

// 单向循环链表结点定义
typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

// 创建循环单链表
ListNode* createCircularLinkedList(int data[], int n) {
    ListNode *head = (ListNode*)malloc(sizeof(ListNode));
    head->val = 0;  // 头结点不存储数据
    head->next = NULL;
    ListNode *tail = head;
    for (int i = 0; i < n; ++i) {
        ListNode *node = (ListNode*)malloc(sizeof(ListNode));
        node->val = data[i];
        node->next = NULL;
        tail->next = node;
        tail = node;
    }
    tail->next = head;  // 最后一个节点指向头节点,形成循环
    return head;
}

// 遍历循环单链表
void traverseCircularLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        printf("%d ", p->val);
        p = p->next;
    }
}

// 删除循环单链表
void deleteCircularLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        ListNode *temp = p;
        p = p->next;
        free(temp);
    }
    free(head);
}

循环双链表:循环双链表是一种具有两个指针域的链表,除了每个节点都有一个指向下一个节点的指针 next 外,还有一个指向前一个节点的指针 prev。它的最后一个节点的 next 指针指向头节点,头节点的 prev 指针指向最后一个节点,这样就形成了一个环形结构。下面是一个用C++实现循环双链表的基本代码:

// 双向循环链表结点定义
struct ListNode {
    int val;
    ListNode *prev, *next;
    ListNode(int x) : val(x), prev(NULL), next(NULL) {}
};

// 创建循环双向链表
ListNode* createCircularDoublyLinkedList(vector<int>& nums) {
    int n = nums.size();
    ListNode *head = new ListNode(0);  // 头结点不存储数据
    ListNode *tail = head;
    for (int i = 0; i < n; ++i) {
        ListNode *node = new ListNode(nums[i]);
        node->prev = tail;
        node->next = head;
        tail->next = node;
        head->prev = node;
        tail = node;
    }
    return head;
}

// 遍历循环双向链表
void traverseCircularDoublyLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        cout << p->val << " ";
        p = p->next;
    }
}

// 删除循环双向链表
void deleteCircularDoublyLinkedList(ListNode *head) {
    ListNode *p = head->next;
    while (p != head) {
        ListNode *temp = p;
        p = p->next;
        delete temp;
    }
    delete head;
}

静态链表的介绍

静态链表(Static Linked List)是一种利用数组来模拟链表的数据结构,其本质上并不是真正的链表,而是通过数组和指针来实现链表的功能。在静态链表中,用数组来存储节点的数据元素,同时用另外一个数组来存储链表节点的指针。下面是一个用C++实现静态链表的基本代码:

const int N = 100000;

struct ListNode {
    int val, next;
} nodes[N];

// 根据数组创建静态链表
int createStaticLinkedList(int data[], int n) {
    for (int i = 0; i < n; ++i) {
        nodes[i].val = data[i];
        nodes[i].next = i + 1;
    }
    nodes[n - 1].next = -1;
    return 0;  // 返回头指针位置
}

// 向静态链表插入元素
// pos为需要插入的位置,val为要插入的元素值
void insertStaticLinkedList(int pos, int val) {
    int p = 0;  // p初始化为头指针
    while (pos--) p = nodes[p].next;  // 在pos-1的位置停留
    int q = 0;  // q初始化为空闲节点的位置
    while (nodes[q].next != -1) q = nodes[q].next;  // 找到最后一个空闲节点
    nodes[q].val = val;
    nodes[q].next = nodes[p].next;
    nodes[p].next = q;  // 将q插入在p之后
}

// 从静态链表删除元素
// pos为需要删除的位置
void deleteStaticLinkedList(int pos) {
    int p = 0;  // p初始化为头指针
    while (pos--) p = nodes[p].next;  // 在pos-1的位置停留
    int q = nodes[p].next;
    nodes[p].next = nodes[q].next;  // 将q从链表中删除
    nodes[q].next = -1;  // 将q加入到空闲节点链表中
}

静态链表优缺点

增删操作不需要大量移动元素。不能随机存取,只能从头结点开始依次往后查找,容量固定不变。

在实际应用中,使用静态链表需要注意以下问题

静态链表中的空闲节点需要预先分配好,不能像动态链表那样动态申请内存,否则会破坏静态链表的结构。

静态链表的节点数量是有限的,因为数组的大小是固定的,因此应该根据实际情况来设置数组的大小。

顺序表和链表的优缺点

顺序表

优点:存储密度高,因为在内存中是一段连续的空间存储数据,访问元素的时间复杂度为O(1),因此查找、修改等操作效率较高。支持下标访问元素,编程简单直观。

缺点:插入、删除元素时需要移动其他元素,时间复杂度为O(n),因此插入、删除操作效率相对较低。内存空间的分配和释放需要考虑到数组大小,因此当需要频繁的插入、删除操作时可能会浪费内存空间。

链表

优点:插入、删除元素时只需要修改指针指向,不需要移动其他元素,时间复杂度为O(1),因此插入、删除操作效率较高。内存空间的分配和释放更加灵活,可以根据实际需要动态分配。

缺点:存储密度较低,因为每个节点需要额外存储指针信息,占用内存空间较大。访问元素需要遍历整个链表,时间复杂度为O(n),因此查找、修改等操作效率相对较低。不支持下标访问元素,需要通过指针进行访问。

实现线性表时,使用顺序表还是链表好

当我们需要高效地进行查找、修改操作时,可以使用顺序表;当我们需要高效地进行插入、删除操作时,可以使用链表。如果需要频繁地进行插入、删除操作,并且元素数量不确定或者需要动态改变,则应该优先考虑使用链表。但是,顺序表的实现和使用比链表更为简单,因此当元素数量相对固定或者需要使用下标操作时,可以优先考虑使用顺序表。

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

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

相关文章

mysql 将date字段默认值设置为CURRENT_DATE

我们是否可以在mysql中&#xff0c;将Date字段的默认值设置为CURRENT_DATE&#xff08;当前日期&#xff09;&#xff1f; 答案是8.0之前不可以&#xff0c;8.0.13之后可以。 比如在5.7版本中使用如下sql创建表&#xff0c;将会提示语法错误: CREATE TABLE t_order (id bigi…

CentOS 7远程登录jupyter lab

使用cat /etc/redhat-release看到操作系统是CentOS Linux 7.6&#xff0c;使用uname -r看到内核是3.10.0-957.el7.x86_64。 python3 --version看一下python的版本&#xff0c;pip3 --version看一下pip的版本&#xff0c;这是我CentOS 7默认安装好的。 pip3 install jupyterla…

ASEMI代理光宝高速光耦LTV-M601参数,LTV-M601图片

编辑-Z LTV-M601参数描述&#xff1a; 型号&#xff1a;LTV-M601 平均正向输入电流IF&#xff1a;20mA 反向输入电压VR&#xff1a;5V 功耗PI&#xff1a;40mW 输出集电极电流IO&#xff1a;50mA 输出集电极电压VO&#xff1a;7V 输出集电极功耗Po&#xff1a;85mW 电…

【C++从入门到放弃】stack和queue的深度剖析及空间适配器的介绍

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《C从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; stack…

SMT车间贴片机Feeder管理方案

Feeder(飞达或供料器)是电子厂SMT车间贴片机上一个重要的部件&#xff0c;它的可用状态关系着贴片机生产的质量的稳定性&#xff0c;如何有效率的管理是每一位车间主管人员不可忽视的问题。根据行业协会大数据的分析发现导致贴片机大约30%的损失时间及1%的物料浪费都是因为Feed…

【Leetcode60天带刷】day14二叉树——144.二叉树的前序遍历,145.二叉树的后序遍历,94.二叉树的中序遍历

题目&#xff1a; 144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#x…

5.4.1 虚拟专用网VPN

5.4.1 虚拟专用网VPN 我们已经学习了因特网的路由协议&#xff08;5.3.1 因特网的路由协议&#xff08;一&#xff09;、5.3.2 因特网的路由协议&#xff08;二&#xff09;基于距离向量算法的RIP协议、5.3.3 因特网的路由协议&#xff08;三&#xff09;OSPF协议、5.3.4 因特…

【Docker】Docker的优势、与虚拟机技术的区别、三个重要概念和架构及工作原理的详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

开利网络赋能祥兴事业集团推动乡村振兴数字化转型

近日&#xff0c;开利网络到访柳州祥兴实业集团&#xff0c;就即将举办的广西文旅大会数字化部署进行跟踪落地。以“祥兴百朋荷苑”为用户端&#xff0c;祥兴集团针对百朋景区实施了全流程的数字化系统构建&#xff0c;包含景区统一收银、景区导览导航讲解及扫码点餐、预约核销…

Winform模拟Visual Studio工具栏拖拉拽、停靠

背景 随着公司接的业务复杂度提高&#xff0c;软件界面设计需求也相应提升&#xff0c;老板不再满足于单面板的各种跳转&#xff0c;所以明白了吧&#xff0c;不提升自己就等于自愿失业或转行&#xff01;&#xff01;&#xff01; 方案 本来想着自学自写一套控件库来实现&a…

抽象类和接口—javaSE

这里写目录标题 1.抽象类1.1概念1.2语法1.3特性1.4使用 2.接口2.1概念2.2语法2.3特性2.4重要的接口2.4.1给数组对象排序&#xff08;Comparable、Comparator&#xff09;2.4.2 Cloneable&#xff08;浅拷贝&#xff09; 2.5抽象类和接口的区别 3.object类3.1定义3.2equals3.3获…

智慧文旅-VR全景展示助力开启景区数字化管理新方式

导语&#xff1a; 在数字化时代&#xff0c;旅游业面临着新的机遇和挑战。 为了吸引更多游客并提供独特的旅行体验&#xff0c;结合VR全景技术和智慧文旅的创新模式已经成为不可忽视的趋势。 一&#xff0e;提升旅游感官体验 VR全景技术正以惊人的速度在旅游业中崭露头角。通过…

【自监督论文阅读 2】MAE

文章目录 一、摘要二、引言2.1 引言部分2.2 本文架构 三、相关工作3.1 Masked language modeling3.2 Autoencoding3.3 Masked image encoding3.4 Self-supervised learning 四、方法4.1 Masking4.2 MAE encoder4.3 MAE decoder4.4 Reconstruction target 五、主要实验5.1 不同m…

【云原生】· 一文了解docker中的网络

目录 &#x1f352;查看docker网络 &#x1f352;bridge网络 &#x1f352;none网络 &#x1f352;host网络 &#x1f352;自定义容器网络 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;云原生专栏 根据前面的学习&#xff0c;已经对d…

netty http3功能从零开始

1、windows安装jdk和mvn、gradle、gloovy 配置环境变量JAVA_HOME CLASSPATH MVN_HOME GRADLE_HOME GLOOVY_HOME mvn和gradle都是用来管理和编译java项目的&#xff0c;mvn比较老&#xff0c;现在一般用gradle 2、vscode环境 vscode安装extension&#xff1a;Extension Pack fo…

springboot 日志配置(logback)

概述 Java 中的日志框架主要分为两大类&#xff1a;日志门面和日志实现。 Java 中的日志体系&#xff1a; 日志门面 日志门面定义了一组日志的接口规范&#xff0c;它并不提供底层具体的实现逻辑。Apache Commons Logging 和 Slf4j&#xff08;Simple Logging Facade for Jav…

基于Python垃圾短信识别程序(KNN、逻辑回归、随机森林、决策树和多项式分布朴素贝叶斯、伯努利分布朴素贝叶斯等算法进行融合)—含python工程全源码

目录 前言总体设计系统整体结构图请求流程图系统流程图 运行环境Python 环境jieba分词库Scikit-learn 库nginxphp 模块实现1. 前端模块2. 后端模块 系统测试1. 测试结果2. 结果对比3. 可视化部分 工程源代码下载其它资料下载 前言 本项目以Python为基础&#xff0c;旨在开发一…

http 三次握手 四次挥手

网络参考模型 网络层是端点间的&#xff08;客户端、服务端&#xff09; 链路层是跳跃节点间的。 TCP/IP 下一跳模式&#xff08;网关&#xff09; 内存小&#xff0c;速度快&#xff0c;不可靠。&#xff08;网络号、主机号&#xff09; TCP协议是一种面向连接的、可靠的、基…

Docker部署(4)——运行时日志时间不对(时区设置)

当使用docker run 命令时&#xff0c;发现容器虽然成功运行&#xff0c;但是输出的时间却不对 。使用timedatectl命令后发现Linux系统的时间是正常的时间。 于是将问题缩小到是不是docker 在运行时没有使用宿主机的时区的原因&#xff0c;那么解决办法可以将宿主机的时区映射到…

Maven(二):Maven的使用入门

文章目录 使用入门项目骨架pom.xml构建过程编写测试打包和运行常用打包方式maven-shade-plugin使用filter与artifactSet - 过滤jar包Transformer 参考文献 使用入门 项目骨架 对于一个使用Maven来管理的项目&#xff0c;Maven提倡使用一个目录结构标准&#xff1a; ${basedi…