【数据结构之顺序表】

数据结构学习笔记---002

  • 数据结构之顺序表
    • 1、介绍线性表
      • 1.1、什么是线性表?
    • 2、什么是顺序表?
      • 2.1、概念及结构
      • 2.2、顺序表的分类
    • 3、顺序表接口的实现
      • 3.1、顺序表动态存储结构的Seqlist.h
        • 3.1.1、定义顺序表的动态存储结构
        • 3.1.2、声明顺序表各个接口的函数
      • 3.2、顺序表动态存储结构的Seqlist.c
        • 3.2.1、初始化顺序表
        • 3.2.2、销毁顺序表
        • 3.2.3、打印顺序表元素
        • 3.2.4、顺序表的基本操作
      • 3.3、顺序表动态存储结构的main.c
        • 3.3.1、TestSL1()
        • 3.3.2、TestSL2()
        • 3.3.3、TestSL7()
    • 4、顺序表巩固练习
      • 4.1、顺序表巩固练习题01 --- 去掉重复项
      • 4.2、顺序表巩固练习题02 --- 合并两个有序数组
    • 5、顺序表总结

数据结构之顺序表

前言:
前篇了解了数据结构和算法,并认识到学好代码,对于数据结构的核心地位,那么这篇就直接开始数据结构的入门学习。
从认识线性表到掌握好最基础的两个存储结构,那么先学习其中顺序存储的顺序表。
/知识点汇总/

1、介绍线性表

1.1、什么是线性表?

线性表是n(n≥0)个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使用的数据结构,常见的线性表、顺序表、链表、栈、队列、字符串…
本质
在逻辑上是线性结构,也就是连续的一条直线,但是在物理结构上不一定是连续的,在物理上存储通常以数组或链式结构的形式存储。
概念
线性表是一种数据结构,它包含一组有序的元素,每个元素最多只有一个前驱元素和一个后继元素。
线性表可以用数组或链表来实现
在数组中,元素在内存中连续存储,可以通过下标直接访问元素。
在链表中,元素在内存中不一定连续存储,每个元素包含数据域和指针域,其中指针域指向下一个元素。
线性表的特点
元素之间是一对一的关系,可以通过下标访问元素,但是删除或插入元素需要移动其它元素。
线性表是基本的数据结构之一,经常被用于各种算法的实现中。
常见的线性表操作
包括插入、删除、查找、修改等。

2、什么是顺序表?

2.1、概念及结构

顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数据存储。由此在数组上完成数组的增删查改

2.2、顺序表的分类

(1).静态顺序表:使用定长数组存储的元素 ----- 顺序表的静态存储

#define N 7
typedef int SLDataType;
typedef struct Seqlist
{SLDataType array[N];//定长数组size_t size;//有效数据个数
}Seqlist;

静态顺序表的弊端
在实际应用中,对于数据的长度往往是不确定的,所以静态开辟数组空间时,给太大导致浪费空间资源,太小又无法满足数据的存储,不够用。
(2).动态顺序表:使用动态开辟的数组存储 ----- 顺序表的动态存储

typedef struct Seqlist
{SLDataType* array;//指向动态开辟的数组size_t size;//有效数据的个数size_t capacity;//容量空间的大小
};

动态顺序表的弊端:
需要注意空间使用之后的释放,防止内存泄漏等问题。

3、顺序表接口的实现

在实际应用中,总体还是以动态的分配空间为主,所以主要以动态的顺序表为主
但是扩容也存在一定的代价,需要注意及时释放等问题
实现过程建议采用(TestXXX n)函数进行阶段性测试,既方便调试也方便及时解决问题。

3.1、顺序表动态存储结构的Seqlist.h

3.1.1、定义顺序表的动态存储结构

因为是采用的多种类型的数据,所以适用于结构体类型。

//定义顺序表的动态存储
typedef int SLDataType;//这里的重命名主要作用是,不能保证每次使用顺序表都是整型,所以只需要改这里为其它类型更健壮和便利typedef struct SeqList
{SLDataType* a;      //有效数据元素 int size;           //有效数据个数int capacity;       //当前顺序表的容量//考虑扩容的容量是不确定的,需要根据实际的需求而灵活扩容
}SL;
3.1.2、声明顺序表各个接口的函数
//动态顺序表的初始化
void SLInit(SL* ps1);
//顺序表的销毁
void SLDestory(SL* ps1);
//打印顺序表
void SLPrint(SL* ps1);
//检查顺序表当前容量
void SLCheckCapacity(SL* ps1);
//头、尾插入和删除
void SLPushBack(SL* ps1, SLDataType x);//尾插
void SLPushFront(SL* ps1, SLDataType x);//头插
void SLPopBack(SL* ps1);//尾删
void SLPopFront(SL* ps1);//头删
//任意位置的插入和删除
void SLInsert(SL* ps1, int pos, SLDataType x);
void SLErase(SL* ps1, int pos);
//查找元素
//找到后,返回下标
//没找到返回-1
//int SLFind(SL* ps1, int pos, SLDataType x);//从某位开始找
int SLFind(SL* ps1, SLDataType x);

3.2、顺序表动态存储结构的Seqlist.c

主要还是要完成 Seqlist.h 接口对应的 .c 功能函数.
最开始详细的写一下,后面就整体的写了。

3.2.1、初始化顺序表

那么有了基本的动态存储结构,先要产生顺序表,那么就先通过初始化顺序表实现存储空间的开辟。

//动态顺序表的初始化
void SLInit(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空ps1->a = NULL;ps1->size = 0;ps1->capacity = 0;
}

说明:这里的初始化就很简单了,并没有直接使用malloc开辟空间,也并没有写初识的capacity 容量值,是为了在后面的函数中体现一种新颖的写法。
为了方便理解和对比,我会把常规写法也贴出来,如下所示:

#define InitSize 10
typedef int SLDataType;
void InitList(SqList &L)
{L.data = (SLDataType*)malloc(InitSize*sizeof(SLDataType));//用malloc函数申请一片空间L.length = 0;                                             //把顺序表的当前长度设为0L.Maxsize = InitSize;                                     //这是顺序表的最大长度
}
3.2.2、销毁顺序表

为了避免忘记销毁开辟的动态内存空间。所以这里使用动态存储方法,那么通常把初始化和销毁一块就写出来了。

//顺序表的销毁
void SLDestory(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空if (ps1->a != NULL){free(ps1->a);ps1->a = NULL;ps1->size = 0;ps1->capacity = 0;}
}
3.2.3、打印顺序表元素

为了直观的体现数据元素是否成功操作,所以接着写出打印接口函数。

//打印顺序表
void SLPrint(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空for (int i = 0; i < ps1->size; i++){printf("%d ", ps1->a[i]);}printf("\n");
}
3.2.4、顺序表的基本操作

完成了上述函数的功能,那么就可以实现顺序表的基本操作了。插入和删除以及查找(无非就是增删改查)。
头插和头删;尾插和尾删。
其次,我们得有一个意识,比如当一个箱子在放入物品之前,肯定会先检查一下箱子是否已经被放满,同理,也需要检查,被拿物品的箱子是否为空箱子,如果为空就拿不来了。回到顺序表就是需要涉及检查容量和判空的操作,然后因为顺序表的多种操作都涉及到了检查容量,所以独立封装为一个SLCheckCapacity函数,方便调用,具体见如下代码:

//检查顺序表当前容量
void SLCheckCapacity(SL* ps1)
{//暴力检查assert(ps1);//一定不能为空if (ps1->size == ps1->capacity){int newCapacticy = ps1->capacity == 0 ? 4 : ps1->capacity * 2;//因为初始化的时候,初始化为0,所以这里以这样的方式扩容即可,比较巧妙灵活。//使用realloc扩容空间:分为原地扩容和异地扩容。//相较于原地扩容,异地的代价较大,原地的效率高。//判断realloc的返回值判断扩容前后的地址,相同就是原地扩,不同就是异地扩。//另外realloc还有个特点,当对ps1->0为空操作时,就相当于malloc了SLDataType* tmp = (SLDataType*)realloc(ps1->a, sizeof(SLDataType) * newCapacticy);//如果直接realloc操作指针ps1->a的话,会存在一定的问题,如果开辟失败,就会导致ps1->a被更改或者称为野指针等问题。if (tmp == NULL){perror("realloc fail");//exit(0);return;}ps1->a = tmp;ps1->capacity = newCapacticy;}
}

说明:同时这段代码也与前面初始化时的呼应,初始化没有赋予初识空间容量的问题,在这个函数的新颖写法得以体现,巧用了一个三目运算符解决了容量问题(当然扩容的倍数或大小由实际情况决定);其次利用realloc函数解决开辟空间的问题,因为既解决了对空操作的问题,也解决了扩容的问题。(realloc详见注释内容和相关资料)
接着为了体现封装函数的好处,就是解决在多组函数中的反复编写相同代码的好处,所以先写尾插,会发现注释掉的代码直接可以调用SLCheckCapacity函数就能解决了,如下所示:

void SLPushBack(SL* ps1, SLDataType x)//尾插
{//暴力检查assert(ps1);//一定不能为空//if (ps1->size == ps1->capacity)//{//	int newCapacticy = ps1->capacity == 0 ? 4 : ps1->capacity * 2;//因为初始化的时候,初始化为0,所以这里以这样的方式扩容即可,比较巧妙灵活。//	//使用realloc扩容空间:分为原地扩容和异地扩容。//	//相较于原地扩容,异地的代价较大,原地的效率高。//	//判断realloc的返回值判断扩容前后的地址,相同就是原地扩,不同就是异地扩。//	//另外realloc还有个特点,当对ps1->0为空操作时,就相当于malloc了//	SLDataType* tmp = (SLDataType*)realloc(ps1->a, sizeof(SLDataType) * newCapacticy);//	//如果直接realloc操作指针ps1->a的话,会存在一定的问题,如果开辟失败,就会导致ps1->a被更改或者称为野指针等问题。//	if (tmp == NULL)//	{//		perror("realloc fail");//		//exit(0);//		return;//	}//	ps1->a = tmp;//	ps1->capacity = newCapacticy;//}SLCheckCapacity(ps1);ps1->a[ps1->size] = x;ps1->size++;
}
//考虑到扩容在很多操作都需要那么就令其封装一个函数。

头插:

void SLPushFront(SL* ps1, SLDataType x)//头插
{//暴力检查assert(ps1);//一定不能为空SLCheckCapacity(ps1);int end = ps1->size - 1;while (end >= 0){ps1->a[end + 1] = ps1->a[end];--end;}ps1->a[0] = x;ps1->size++;
}

尾删和尾插,判空的操作在顺序表就比较简单了,不必额外写函数,因为直接对ps1->size即可,如下所示:

void SLPopBack(SL* ps1)//尾删
{//暴力检查assert(ps1);//一定不能为空//所以根据调试验证,必须考虑为空,就不再删除了//处理方式一:温柔的检查if (ps1->size == 0){printf("删除失败,为空\n");return;}//处理方式二:暴力的检查//assert(ps1->size > 0);ps1->size--;
}void SLPopFront(SL* ps1)//头删
{//暴力检查assert(ps1);//一定不能为空//暴力检查assert(ps1->size > 0);//数据前挪动int begin = 1;//注意下标的不同,边界就不同while (begin < ps1->size){ps1->a[begin - 1] = ps1->a[begin];++begin;}//有效数据--ps1->size--;
}

任意位置的插入和删除中需要区别一下,pos和size
pos – 定义的是数组下标
size – 定义的是数组元素个数,当作下标需要size-1
补充
数组下标从0开始是因为,主要是需要与指针形成逻辑自洽
比如:a[i] == *(a+i) a[1] == *(a+1)
还有些原因,是因为数组指针从1开始会式的一些应用场景多一次减法操作,会在一定程度上影响性能。

void SLInsert(SL* ps1, int pos, SLDataType x)//在任意位置插入
{assert(ps1);assert(pos >= 0 && pos <= ps1->size);SLCheckCapacity(ps1);//挪动数据int end = ps1->size - 1;while (end >= pos)//pos = size就是尾插,不会进入循环{ps1->a[end+1] = ps1->a[end];--end;}ps1->a[pos] = x;ps1->size++;
}void SLErase(SL* ps1, int pos)//在任意位置删除
{assert(ps1);assert(pos >= 0 && pos < ps1->size);//删除的边界不能等于size//挪动覆盖int begin = pos + 1;while (begin < ps1->size){ps1->a[begin - 1] = ps1->a[begin];++begin;}ps1->size--;
}

最后一个基本操作查找元素,可按位查找,也可遍历查找。
因为是顺序表,存储元素的地址是连续的所以可以直接满足遍历操作。

//int SLFind(SL* ps1, int pos, SLDataType x);//从某位开始找
int SLFind(SL* ps1, SLDataType x)
{assert(ps1);for (int i = 0; i < ps1->size; i++){if (ps1->a[i] == x){return i;}}return -1;//若返回0,与首元素下标冲突
}

3.3、顺序表动态存储结构的main.c

简单的写几个测试应用,目的是检测各个接口函数是否满足需求,是否存在一些bug。

3.3.1、TestSL1()

主要检测初始化、尾插、头插、打印和销毁,以及参数的传址调用和传值调用。

#include "Seqlist.h"
//测试1:传参,形参是实参的临时拷贝,形参的改变不会姓影响实参,所以传址调用和传值调用
//所以根据需求,通常传地址。
void TestSL1()
{SL s1;        //定义结构体变量SLInit(&s1);  //传址调用,初始化顺序表SLPushBack(&s1, 1);//尾插SLPushBack(&s1, 2);SLPushBack(&s1, 3);SLPushBack(&s1, 4);  SLPushBack(&s1, 5);SLPushBack(&s1, 6);SLPushBack(&s1, 7);SLPushBack(&s1, 8);SLPushBack(&s1, 9);SLPushFront(&s1, 10);//头插SLPushFront(&s1, 20);SLPushFront(&s1, 30);SLPushFront(&s1, 40);SLPrint(&s1);SLDestory(&s1);//顺序表的销毁
}
int main()
{TestSL1();//TestSL2();//TestSL3();//TestSL4();//TestSL5();//TestSL6();//TestSL7();return 0;
}

测试效果展示
在这里插入图片描述

3.3.2、TestSL2()

主要检测尾删直到空,继续删的处理,分析非法访问等情况,思考数据丢失的原因等。

#include "Seqlist.h"
//测试二:
void TestSL2()
{SL s1;        //定义结构体变量SLInit(&s1);  //传址调用,初始化顺序表SLPushBack(&s1, 1);//尾插SLPushBack(&s1, 2);SLPushBack(&s1, 3);SLPushBack(&s1, 4);SLPushBack(&s1, 5);SLPrint(&s1);//打印SLPopBack(&s1);//尾删SLPopBack(&s1);//尾删SLPopBack(&s1);//尾删SLPopBack(&s1);//尾删SLPrint(&s1);//打印SLPopBack(&s1);//尾删 --- 此时的写法size被减到了0SLPrint(&s1);//打印//SLPopBack(&s1);//尾删 --- 此时的写法size被减到了-1,但是并不会对-1的地址进行访问,所以也不会报错;但是当再进行比如头插操作就会有问题//SLPrint(&s1);//打印SLPushFront(&s1, 10);//头插 --- 插入失败,size为-1,然后end就为size-1=-2,不会进入while循环,就不会非法访问,同时数据也放不进去正确的位置了。SLPushFront(&s1, 20);//头插SLPushFront(&s1, 30);//头插SLPushFront(&s1, 40);//头插SLPrint(&s1);//打印SLDestory(&s1);//顺序表的销毁
}
int main()
{//TestSL1();TestSL2();//TestSL3();//TestSL4();//TestSL5();//TestSL6();//TestSL7();return 0;
}

效果展示
在这里插入图片描述

3.3.3、TestSL7()

主要测试与任意位置的插入和删除函数配合使用的情况

#include "Seqlist.h"
//测试七:与任意位置的插入和删除函数配合使用
void TestSL7()
{SL s1;        //定义结构体变量SLInit(&s1);  //传址调用,初始化顺序表SLPushBack(&s1, 1);//尾插SLPushBack(&s1, 2);SLPushBack(&s1, 3);SLPushBack(&s1, 4);SLPushBack(&s1, 5);SLPrint(&s1);//打印SLErase(&s1, 2);SLPrint(&s1);//打印int pos = SLFind(&s1, 2);if (pos != -1){SLErase(&s1, pos);SLPrint(&s1);//打印}SLDestory(&s1);//顺序表的销毁
}
int main()
{//TestSL1();TestSL2();//TestSL3();//TestSL4();//TestSL5();//TestSL6();//TestSL7();return 0;
}

效果展示
在这里插入图片描述

4、顺序表巩固练习

4.1、顺序表巩固练习题01 — 去掉重复项

删除排序数组中的重复项,返回数组中去重后的元素个数。

思路1:去重算法(关键在于排序)
//dest和src相等,则++dest
//dest和src不相等,则++src,a[src] = a[dest],++dest
本质就是dst依次找跟src不相等的元素值,并从前向后依次覆盖

#include <stdio.h>
int removeDuplicates(int* nums, int numsSize)
{int dst = 1;int src = 0;while (dst < numsSize){if (nums[src] != nums[dst])//不相等就覆盖{++src;nums[src] = nums[dst];++dst;//nums[++src] = nums[dst++];}else//否则dst++下一个继续比较{++dst;}}return src + 1;//元素个数加1
}
int main()
{int a[8] = { 0,1,1,2,2,3,3,4 };int ret = removeDuplicates(a, 8);printf("%d\n", ret);return 0;
}

思路2:双指针法

#include <stdio.h>
int removeDuplicates(int* nums, int numsSize)
{int dst = 1;int src = 0;while (dst < numsSize){if (nums[dst-1] != nums[dst])//不相等就覆盖{++src;nums[src] = nums[dst];++dst;//nums[++src] = nums[dst++];}else//否则dst++下一个继续比较{++dst;}}return src + 1;//元素个数加1
}
int main()
{int a[8] = { 0,1,1,2,2,3,3,4 };int ret = removeDuplicates(a, 8);printf("%d\n", ret);return 0;
}

4.2、顺序表巩固练习题02 — 合并两个有序数组

合并两个有序数组,合并后的数组同样按 非递减顺序 排列。
思路1:依次比较,每次取最小的尾插到新数组,这样时间复杂度O(N),空间复杂度O(N)
思路2:依次比较,每次取最大的从后向前覆盖放入,这样时间复杂度O(N),空间复杂度O(1)

#include <stdio.h>
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{int i1 = m - 1;int i2 = n - 1;int j = m + n - 1;//while (i1 >= 0 || i2 >= 0)while (i1 >= 0 && i2 >= 0){if (nums2[i2] > nums1[i1]){nums1[j] = nums2[i2];--i2;--j;}else//注意这里并没有处理,nums2,部分元素小于nums1之后剩余的情况{nums1[j] = nums1[i1];--i1;--j;}}//注意处理nums2剩余元素while (i2 >= 0){nums1[j] = nums2[i2];--j;--i2;}
}
int main()
{int a[6] = { 1,2,3,0,0,0 };int b[3] = { -1,-2,6 };merge(a, 6, 3, b, 3, 3);for (int i = 0; i < 6; i++){printf("%d ", a[i]);}return 0;
}

思路3:qsort

#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void* e1, const void* e2)
{return (*(int*)e1) - (*(int*)e2);
}
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{for (int i = 0; i < n; i++){nums1[m + i] = nums2[i];}qsort(nums1, nums1Size, sizeof(int), cmp_int);
}
int main()
{int a[6] = { 1,2,3,0,0,0 };int b[3] = { 2,5,6 };merge(a, 6, 3, b, 3, 3);for (int i = 0; i < 6; i++){printf("%d ", a[i]);}return 0;
}

思路4:归并算法

#include <stdio.h>void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{int p1 = 0;int p2 = 0;int nums3[6];int cur = 0;while (p1 < m || p2 < n){if (p1 == m)                     //如果num1元素放完继续放num2的元素cur = nums2[p2++];else if (p2 == n)                //如果num2元素放完继续放num1的元素cur = nums1[p1++];else if (nums1[p1] < nums2[p2])  //如果num1元素小于num2的元素,将小的放入num3cur = nums1[p1++];else                            //如果num2元素小于num1的元素,将小的放入num3cur = nums2[p2++];nums3[p1 + p2 - 1] = cur;           //将cur放入num3 }for (int i = 0; i < m + n; i++)            //拷贝{nums1[i] = nums3[i];}
}
int main()
{int a[6] = { 1,2,3,0,0,0 };int b[3] = { -1,-2,6 };merge(a, 6, 3, b, 3, 3);for (int i = 0; i < 6; i++){printf("%d ", a[i]);}return 0;
}

5、顺序表总结

主要有以下两点
1.尾部插入效率还不错,头部或者中间的插入操作,就需要挪动大量数据,效率低下。
2.在顺序表满了后,只能扩容,而扩容是有一定的消耗代价的;且存在一定的空间浪费;还有这样一个弊端,一次性扩容较多,可能导致浪费较多,而一次性扩容少,就出现频繁的扩容。

所以引出单链表的应用
利用链表的结点之间的关系解决大量挪动数据的问题。
结点之间的地址是随机的不是连续的。
所以通过地址进行管理,利用前一个结点的指针域存储下一个节点的地址,依次找到所有结点。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VM进行TCP/IP通信

OK就变成这样 vm充当服务端的话也是差不多的操作 点击连接 这里我把端口号换掉了因为可能被占用报错了&#xff0c;如果有报错可以尝试尝试换个端口号 注&#xff1a; 还有一个点在工作中要是充当服务器&#xff0c;要去网络这边看下他的ip地址 拉到最后面

vllm 加速推理通义千问Qwen经验总结

1. 简介 1.1. 功能说明 vLLM is a fast and easy-to-use library for LLM inference and serving. vLLM is fast with: State-of-the-art serving throughputEfficient management of attention key and value memory with PagedAttentionContinuous batching of incoming r…

TCP/IP:从数据包到网络的演变

引言 TCP/IP协议的起源可以追溯到20世纪60年代末和70年代初&#xff0c;美国国防部高级研究计划局&#xff08;ARPA&#xff09;研究开发一种可靠的通信协议&#xff0c;用于连接分散在不同地点的计算机和资源。 在当时&#xff0c;计算机之间的连接并不像现在这样普遍和便捷…

uniapp中使用封装步骤条组件

针对步骤条封装完终于清清楚楚啦 先看效果&#xff1a; 附上代码&#xff1a;使用可直接复用&#xff1a;数据是写在了当前组件中&#xff0c;如有必须&#xff0c;可以使用其中的props传值stepInfos传递相应的数据&#xff0c;根据steps步数就可以控制走到哪一步啦 <temp…

在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器

在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器 背景 ExoPlayer与Media3的能力结合&#xff0c;为Android应用程序播放多媒体内容提供了强大的解决方案。在本教程中&#xff0c;我们将介绍如何设置带有Media3的ExoPlayer来支持使用M3U8 URL进行直播流。此外&#x…

深度剖析:Golang中结构体方法的高级应用

深度剖析&#xff1a;Golang中结构体方法的高级应用 引言结构体方法的基础回顾结构体的定义和用法方法的定义和绑定基本语法和用法 高级特性与应用封装、继承和多态方法集与接口的关系结构体方法的匿名字段和嵌入结构体 性能优化与最佳实践接收器类型的选择&#xff1a;指针还是…

2016年第五届数学建模国际赛小美赛C题对超级细菌的战争解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 C题 对超级细菌的战争 原题再现&#xff1a; 最近有很多关于我们抗生素耐药性危机的讨论。进化出的能够抵抗抗生素的细菌每年杀死70万人&#xff0c;越来越强大的细菌正在世界各地传播。研究人员担心&#xff0c;我们将进入一个后抗生素时代…

MACBOOK 通过iterm2连接堡垒机跳转服务器

本公司是通过齐治堡垒机连接远程服务器的环境&#xff0c;因为连接过程中需要自动输入密码和选择主机&#xff0c;所以要使用expect工具&#xff0c;编写expect脚本remote.exp #!/usr/bin/expectif { $argc ! 7 } {send_user "usage: expect $argv0 \[JUMP_HOST\] \[JUM…

【贪心算法】之 摆动序列(中等题)

实际操作上&#xff0c;其实连删除的操作都不用做&#xff0c;因为题目要求的是最长摆动子序列的长度&#xff0c;所以只需要统计数组的峰值数量就可以了&#xff08;相当于是删除单一坡度上的节点&#xff0c;然后统计长度&#xff09; 这就是贪心所贪的地方&#xff0c;让峰…

Sentinel 流量治理组件教程

前言 官网首页&#xff1a;home | Sentinel (sentinelguard.io) 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形…

【Spring Security】认证密码加密Token令牌CSRF的使用详解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Security》。&#x1f3af;&#x1f3af; …

日志服务 SLS 深度解析:拥抱云原生和 AI,基于 SLS 的可观测分析创新

云布道师 10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;日志服务 SLS 研发负责人简志和产品经理孟威等人发表了《日志服务 SLS 深度解析&#xff1a;拥抱云原生和 AI&#xff0c;基于 SLS 的可观测分析创新》的主题演讲&#xff0c;对阿里云日志服务 SLS 产品服务创新以…

在Next.js和React中搭建Cesium项目

在Next.js和React中搭建Cesium项目&#xff0c;需要确保Cesium能够与服务端渲染(SSR)兼容&#xff0c;因为Next.js默认是SSR的。Cesium是一个基于WebGL的地理信息可视化库&#xff0c;通常用于在网页中展示三维地球或地图。下面是一个基本的步骤&#xff0c;用于在Next.js项目中…

【设计模式】命令模式

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、什么是命令模式&#xff1f; 二、命令模式的优点和应用场景 三、命令模式的要素和实现 3.1 命令 3.2 具体命令 3.3 接受者 …

51单片机的羽毛球计分器系统【含proteus仿真+程序+报告+原理图】

1、主要功能 该系统由AT89C51单片机LCD1602显示模块按键等模块构成。适用于羽毛球计分、乒乓球计分、篮球计分等相似项目。 可实现基本功能: 1、LCD1602液晶屏实时显示比赛信息 2、按键控制比赛的开始、暂停和结束&#xff0c;以及两位选手分数的加减。 本项目同时包含器件清…

2023 下半年系统架构设计师学习进度

文章目录 复习计划&#xff1a;每周350分钟第一周&#xff08;339分钟&#xff09;第二周&#xff08;265分钟&#xff09;第三周&#xff08;171分钟&#xff09;第四周&#xff08;214分钟&#xff09;第五周&#xff08;274分钟&#xff09;第六周&#xff08;191分钟&#…

图数据库NebulaGraph学习

1.图空间(Space)操作 1.1创建图空间&#xff0c;指定vid_type为整形 CREATE SPACE play_space (partition_num 10, replica_factor 1, vid_type INT64) COMMENT "运动员库表空间"; 1.2创建图空间&#xff0c;指定vid_type为字符串 CREATE SPACE play_space (…

深入解析Python装饰器及*args, **kwargs的妙用

深入解析Python装饰器及*args, **kwargs的妙用 简介&#xff1a; ​ 装饰器&#xff08;Decorator&#xff09;是 Python 中一种强大的语法特性&#xff0c;它允许在不修改原始函数代码的情况下&#xff0c;动态地扩展函数的功能。装饰器是函数或类&#xff0c;用于包装其他函…

Leetcode 剑指 Offer II 058. 我的日程安排表 I

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 请实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内…

DevC++ easyx实现图片拖动,一种悬浮窗实现原理与完整代码

翻出来之前写的代码&#xff0c; EasyxDevC开发地图编辑和游戏编辑代码工程文件附注释_哔哩哔哩_bilibili 每次把代码备份下来&#xff0c;等着有一天能够复用代码&#xff0c;产生新的价值。 结果最近这几天才来回顾记录emm “这是怎么搓出来的&#xff1f;”从10行代码到…