【数据结构】动态顺序表(C语言实现)

news2024/11/24 15:50:11

文章目录

  • 0. 前言
  • 1. 线性表
  • 2. 顺序表
    • 2.1 概念及结构
  • 3. 动态顺序表的实现
    • 3.1 定义结构
    • 3.2 接口函数总览
    • 3.3 初始化
    • 3.4 检查增容
    • 3.5 尾插
    • 3.6 尾删
    • 3.7 头插
    • 3.8 头删
    • 3.9 查找
    • 3.10 指定下标位置插入
    • 3.11 指定下标位置删除
    • 3.12 修改
    • 3.13 打印
    • 3.14 销毁
  • 4. 完整代码
    • SeqList.h
    • SeqList.c
    • test.c
  • 5. 结语

0. 前言

好久不见,我是anduin。从今天开始,我将带大家学习数据结构的相关内容。今天的博客的主要内容为对线性表做出一定介绍、静态顺序表结构的简述、以及C语言实现动态顺序表,其中会对函数的接口实现做出讲解。话不多说,我们这就开始。

1. 线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

image-20221002232337723

2. 顺序表

2.1 概念及结构

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

注:顺序表本质上就是数组,但是在数组的基础上,它还要求数据是连续存储,不能跳跃间隔。

顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储。
  2. 动态顺序表:使用动态开辟的数组存储。
// 顺序表的静态存储
#define N 100
typedef int SLDataType;// 让数据存储多种类型

typedef struct SeqList
{
    SLDataType a[N]; // 定长数组
    int size; // 有效数据个数
}SeqList;

// 顺序表的动态存储
typedef struct SeqList
{
    SLDataType* array; // 指向动态开辟的数组
    int size ; // 表示数组中存储了多少个数据
    int capicity ; // 数组实际能存数据的空间容量是多大
}SeqList

注:

  • 静态顺序表使用#define定义长度,让大小修改起来更方便。
  • 为存储多种类型的数据,将数据类型typedef重命名为SLDataType,这样更加直观。当存储类型想改变时,直接更改数据类型即可。
  • 顺序表的类型很繁琐,可以用typedef重命名为SL,更加简洁。

两种顺序表的对比:

静态顺序表的最大的缺陷就是空间问题,它只适用于确定知道要存多少数据的场景。静态顺序表的特点是如果顺序表满了,就不让插入元素。静态顺序表的缺点也很直观:空间开辟多了,浪费空间。空间开辟少了,空间不足。到底给多大的空间?这个很难确定。

所以现实中静态顺序表很少使用,我们基本都是使用动态顺序表,根据需要动态的分配空间大小。下面我们使用C语言实现动态顺序表。

3. 动态顺序表的实现

我们实现一个动态顺序表,分为三部分:

  1. SeqList.h:头文件,结构、常量的定义,接口函数的声明…
  2. test.c:用于顺序表功能的测试
  3. SeqList.c:接口函数的实现

接下来我们的功能主要在SeqList.c中实现~

3.1 定义结构

相比较于静态顺序表,动态顺序表的结构由三个成员组成:

  1. 指向动态开辟空间的data指针
  2. 记录当前有效数据个数的size变量
  3. 记录当前容量的capacity变量
typedef struct SeqList
{
	SLDataType* a;
	int sz;// 表示数组中存储了多少个数据
	int capacity;// 数组实际能存数据的空间容量是多大
}SL;

我们比静态顺序表多了一个capacity成员。我们用这个成员记录当前顺序表的最大容量,当有效数据个数size和capacity相等时。说明当前顺序表空间已满,需要进行扩容。

3.2 接口函数总览

我们实现动态顺序表,就要实现对应的功能,例如增删查改等等…

这些功能我们会封装成函数,而当我们调用时,就会对接到对应函数,所以我们称这些函数为接口函数

动态顺序表的实现需要如下接口:

void SeqListInit(SL* ps);// 初始化
void SeqListCheckCapacity(SL* ps);// 检查增容
void SeqListPushBack(SL* ps, SLDataType x);// 尾插
void SeqListPopBack(SL* ps);// 尾删
void SeqListPushFront(SL* ps, SLDataType x);// 头插
void SeqListPopFront(SL* ps);// 头删
void SeqListPrint(SL* ps);// 打印
void SeqListDestory(SL* ps);// 销毁
int SeqListFind(SL* ps, SLDataType x);// 查找
void SeqListInsert(SL* ps, int pos, SLDataType x);// 指定下标位置插入
void SeqListErase(SL* ps, int pos);// 指定下标位置删除
void SeqListModify(SL* ps, int pos, int x);// 修改

这里需要提一下,为什么我函数接口的名称起的这么繁杂:

这其实是C++中STL的命名风格,STL库中,也有数据结构的相关函数。为了以后学习STL更加轻松。所以提前适应起来,方便以后学习。

3.3 初始化

顺序表在进行操作之前,需要先将其内容进行初始化,防止之后操作时出错。

void SeqListInit(SL* ps);

在实现这个接口之前,我们思考一下,参数可不可以为结构体?比如这样:

void SeqListInit(SL ps)
{
	ps.a = NULL;
	ps.sz = ps.capacity = 0;
}

这时绝对不行的!要知道实参在传参时,会形成一份临时拷贝,叫做形参。当我们在函数中对形参的内容进行修改时,不会影响到实参,所以,不可行!

想要正确的初始化结构体,我们需要传sl的地址,通过指针对结构体内容进行修改:

void SeqListInit(SL* ps)
{
	assert(ps);
	ps->a = NULL;// 置空
	ps->sz = ps->capacity = 0;// 初始大小和容量设定为0
}

3.4 检查增容

当顺序表需要插入数据时,可能会遇到三种情况:

  1. 整个顺序表没有空间。
  2. 空间不够,需要扩容。
  3. 空间足够,直接插入数据。

如果顺序表空间足够,那么不需要扩容,通过相关操作插入数据;如果空间不足或者根本没有空间,那么就得扩容。

当顺序表没有空间时,我们开辟四个空间;当顺序表空间不足,我们将当前空间扩大为两倍(扩两倍是为了防止扩容过度,或扩小了频繁扩容,消耗过大)。

当顺序表空间足够,不进行任何操作,if语句判断后,直接返回。

void SeqListCheckCapacity(SL* ps)
{
	assert(ps);
	// 顺序表没有空间or顺序表空间不足
	if (ps->sz == ps->capacity)
	{
		// 没扩容,扩四个整形;空间不足,扩两倍
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		// realloc在起始地址为空指针时,和malloc一样效果
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
}

3.5 尾插

在顺序表尾部插入数据:

void SeqListPushBack(SL* ps, SLDataType x);

在插入数据前,需要检查空间情况,所以要先调用SeqListCheckCapacity函数,对异常状况进行操作。

void SeqListPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);// 检查增容
	ps->a[ps->sz] = x;// 在尾部插入
	ps->sz++;
}

3.6 尾删

在顺序表尾部插入数据:

void SeqListPopBack(SL* ps);

要实现尾删,那么我们只需要让sz--即可,因为sz标识了顺序表存了多少个有效数据。直接减少sz,那么之后的元素想操作也没办法了。

但是请注意,当顺序表没有元素,也就是sz为0时,不可删除。

为什么这么说,举个例子:

假如顺序表已有五个数据,我尾删六个数据,那么这时,我的顺序表sz = -1。这时候调用打印的接口,由于 sz = -1 并不会进行打印,所以不会出错。

如果我继续尾插数据,这时就会出现问题,我们就会访问到-1下标,也就是会在-1下标插入数据,这就越界访问了,进行了越界写。这时程序仍然不会报错。

但是当我们销毁顺序表时,程序会奔溃。因为程序在平时是几乎不对内存进行检查的,当销毁时,会对空间进行检查是否越界。

这也是为什么数组越界总在 free 处报错的原因。

所以我们需要谨记:free 主动报错只有两种情况

  1. free 释放的为野指针。
  2. free 释放的内存不是动态开辟的内存,或者释放的空间不完整。
    如果这两个都没有错误,那一定是越界访问,但是 free 不一定报错。
void SeqListPopBack(SL* ps)
{
	assert(ps);
	// 温柔处理
	//if (ps->sz > 0)// 不做出反应
	//{
	//	//ps->a[ps->sz - 1] = 0 ;// 不需要,sz标识顺序表的元素个数
	//	ps->sz--;
	//} 
	
	// 暴力处理
	assert(ps->sz > 0);// 直接终止程序,报错
	ps->sz--;
}

3.7 头插

在顺序表头部插入数据:

void SeqListPushFront(SL* ps, SLDataType x);

要实现这一功能,需要保证原来的数据不能被覆盖。因此,我们将数据从后往前依次后挪,使用一个end下标来实现。

image-20221001204302946

但是这里也要考虑增容的问题,否则会越界访问,在销毁顺序表时,程序会奔溃。

void SeqListPushFront(SL* ps, SLDataType x)
{
	assert(ps);
    SeqListCheckCapacity(ps);// 检查增容
	
    // 挪动数据
    int end = ps->sz - 1;
	while (end >= 0)// 一直挪到0下标
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;
	ps->sz++;
}

3.8 头删

从顺序表头部删除数据:

void SeqListPopFront(SL* ps);

要实现头删,那么我们可以给定一个begin下标,让数据从前往后依次前挪,保证原来的数据不被覆盖。

image-20221001204331923
void SeqListPopFront(SL* ps)
{
	assert(ps);
	assert(ps->sz > 0);// 暴力处理,sz为0时,不能头删

	// 挪动数据
	int begin = 1;
	while (begin <= ps->sz - 1)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
    // memmove(ps->a, ps->a + 1, (ps->sz - 1) * sizeof(SLDataType));
    // 这样也可以,将1下标开始的元素,向前移动sz-1个单位
    // 但是并不推荐,还是自己动手实现比较好

	ps->sz--;
}

3.9 查找

查找一个元素在顺序表中的位置,返回下标:

int SeqListFind(SL* ps, SLDataType x);

这个就非常简单了,使用循环遍历顺序表,找到数据返回对应下标,找不到数据返回-1。

int SeqListFind(SL* ps, SLDataType x)
{
	assert(ps);
	// 找到了就返回元素出现的第一个位置
	for (int i = 0; i < ps->sz; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
} 

3.10 指定下标位置插入

在指定pos下标处插入元素:

void SeqListInsert(SL* ps, int pos, SLDataType x);

要实现这一功能,我们依然需要一个end下标,数据从后往前依次后挪,直到pos下标移动完毕。另外,别忘了检查容量。

image-20221002133930359
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	// 温柔处理
	//if (pos > ps->sz || pos < 0)
	//{
	//	printf("pos invaild\n");
	//	return;
	//}
	
	// 暴力处理
	// 断言表达式为真,相安无事
	// 为假,直接报错
	// 这两个表达式只要有一个不满足条件,便报错
	assert(pos >= 0 && pos <= ps->sz);// 包含头插和尾插

	SeqListCheckCapacity(ps);// 检查增容

	int end = ps->sz - 1;
	// 从后往前依次向后挪
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}

	ps->a[pos] = x;
	ps->sz++;
}

该功能其实也可以实现头插和尾插,所以我们可以在头插尾插复用该功能:

// 头插
void SeqListPushFront(SL* ps, SLDataType x)
{
	SeqListInsert(ps, 0, x);
}

// 尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
	SeqListInsert(ps, ps->sz, x);
}

3.11 指定下标位置删除

在指定pos下标处删除元素:

void SeqListErase(SL* ps, int pos);

要实现这一功能,我们需要一个begin下标,数据从前往后依次前挪,直到sz-1下标移动完毕。

image-20221002142003311

同样的,该接口也可复用头删尾删

// 头删
void SeqListPopFront(SL* ps)
{
	SeqListErase(ps, 0);
}

// 尾删
void SeqListPopBack(SL* ps)
{
	SeqListErase(ps, ps->sz - 1);
}

3.12 修改

顺序表指定pos下标进行修改:

void SeqListModify(SL* ps, int pos, int x);

要实现这个功能非常简单,只需要判断修改位置是否合法后,直接修改即可。

void SeqListModify(SL* ps, int pos, int x)
{
	assert(ps);
	assert(pos >= 0 || pos <= ps->sz - 1);

	ps->a[pos] = x;
}

3.13 打印

在每次操作后,可以打印出顺序表,观察操作情况:

void SeqListPrint(SL* ps);// 打印

void SeqListPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->sz; i++)
	{
		printf("%d ", ps->a[i]);
	}
}

3.14 销毁

当操作执行完毕后,需要销毁顺序表:

void SeqListDestory(SL* ps);

销毁顺序表,只需要把动态开辟的空间释放,指针置空,其他变量置0。

void SeqListDestory(SL* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->sz = ps->capacity = 0;
}

4. 完整代码

SeqList.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#define N 1000
typedef int SLDataType;

// 动态顺序表
typedef struct SeqList
{
	SLDataType* a;
	int sz;// 表示数组中存储了多少个数据
	int capacity;// 数组实际能存数据的空间容量是多大
}SL;

/*
 * 为什么使用这种命名风格? 
 * 为了对应C++ STL中的命名风格
 * 方便后续学习STL
 */

// 接口函数
// 接口,就是和别人对接的
void SeqListInit(SL* ps);// 初始化
void SeqListCheckCapacity(SL* ps);// 检查增容
void SeqListPushBack(SL* ps, SLDataType x);// 尾插
void SeqListPopBack(SL* ps);// 尾删
void SeqListPushFront(SL* ps, SLDataType x);// 头插
void SeqListPopFront(SL* ps);// 头删
void SeqListPrint(SL* ps);// 打印
void SeqListDestory(SL* ps);// 销毁
// ...

// 找到了返回x位置下标,没有没到返回-1
int SeqListFind(SL* ps, SLDataType x);// 查找
void SeqListInsert(SL* ps, int pos, SLDataType x);// 指定下标位置插入
void SeqListErase(SL* ps, int pos);// 删除pos位置的数据
void SeqListModify(SL* ps, int pos, int x);// 修改pos位置的数据

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1 

#include "SeqList.h"

// 初始化
void SeqListInit(SL* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->sz = ps->capacity = 0;
}

// 检查增容
void SeqListCheckCapacity(SL* ps)
{
	assert(ps);
	// 顺序表没有空间or顺序表空间不足
	if (ps->sz == ps->capacity)
	{
		// 没扩容,扩四个整形;空间不足,扩两倍
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		// realloc在起始地址为空指针时,和malloc一样效果
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
}

// 尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
	//assert(ps);
	//SeqListCheckCapacity(ps);// 检查增容
	 空间足够,直接在尾部插入
	//ps->a[ps->sz] = x;
	//ps->sz++;
	SeqListInsert(ps, ps->sz, x);
}

// 尾删
void SeqListPopBack(SL* ps)
{
	//assert(ps);
	// 温柔处理
	//if (ps->sz > 0)// 不做出反应
	//{
	//	//ps->a[ps->sz - 1] = 0 ;// 不需要,sz标识顺序表的元素个数
	//	ps->sz--;
	//} 
	
	// 暴力处理
	//assert(ps->sz > 0);// 直接终止程序,报错
	//ps->sz--;

	SeqListErase(ps, ps->sz - 1);
}

// 头插
void SeqListPushFront(SL* ps, SLDataType x)
{
	//assert(ps);
	//SeqListCheckCapacity(ps);// 检查增容

	 挪动数据
	//int end = ps->sz - 1; 
	//while (end >= 0)// 一直挪到0下标
	//{
	//	ps->a[end + 1] = ps->a[end];
	//	end--;
	//}
	//ps->a[0] = x;
	//ps->sz++;

	SeqListInsert(ps, 0, x);
}

// 头删
void SeqListPopFront(SL* ps)
{
	//assert(ps);
	//assert(ps->sz > 0);

	 挪动数据
	//int begin = 1;
	//while (begin <= ps->sz - 1)
	//{
	//	ps->a[begin - 1] = ps->a[begin];
	//	begin++;
	//}
	memmove(ps->a, ps->a + 1, (ps->sz - 1) * sizeof(SLDataType));

	//ps->sz--;
	SeqListErase(ps, 0);
}

// 查找
int SeqListFind(SL* ps, SLDataType x)
{
	assert(ps);
	// 只要找到一个就可以
	for (int i = 0; i < ps->sz; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
} 

// 指定pos下标位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	// 温柔处理
	//if (pos > ps->sz || pos < 0)
	//{
	//	printf("pos invaild\n");
	//	return;
	//}
	
	// 暴力处理
	// 断言表达式为真,相安无事
	// 为假,直接报错
	// 这两个表达式只要有一个不满足条件,便报错
	assert(pos >= 0 && pos <= ps->sz);

	SeqListCheckCapacity(ps);// 检查增容

	int end = ps->sz - 1;
	// 从后往前依次向后挪
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}

	ps->a[pos] = x;
	ps->sz++;
}

// 删除pos位置的数据
void SeqListErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->sz - 1);  
	
	int begin = pos + 1;
	while (begin < ps->sz)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->sz--;
}

// 修改pos位置的数据
void SeqListModify(SL* ps, int pos, int x)
{
	assert(ps);
	assert(pos >= 0 || pos <= ps->sz - 1);

	ps->a[pos] = x;
}

// 打印
void SeqListPrint(SL* ps)
{
	assert(ps);	
	for (int i = 0; i < ps->sz; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

// 销毁 
void SeqListDestory(SL* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->sz = ps->capacity = 0;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1 

#include "SeqList.h"

// 测试尾插、尾删
void TestSeqList1()
{
	SL s1;
	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListPrint(&s1);
	
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPrint(&s1);

	SeqListPushBack(&s1,6);
	SeqListPushBack(&s1,7);

	// 编译器也是人写的,总会有疏忽的时候
	// 当越界时,可能会检查不出来
	// 只有当销毁时,才会发现错误
	SeqListDestory(&s1);
}

// 测试头插、头删
void TestSeqList2()
{
	SL s1;
	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	//SeqListPrint(&s1);

	SeqListPushFront(&s1, 10);
	SeqListPushFront(&s1, 20);
	SeqListPushFront(&s1, 30);
	SeqListPushFront(&s1, 40);// 程序会崩,越界了
	SeqListPrint(&s1);

	SeqListPopFront(&s1);
	SeqListPopFront(&s1);
	SeqListPopFront(&s1);
	SeqListPopFront(&s1);
	SeqListPrint(&s1);

	SeqListDestory(&s1);

}

// 测试指定位置插入
void TestSeqList3()
{
	SL s1;
	SeqListInit(&s1);
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListPrint(&s1);

	SeqListInsert(&s1, 2, 30);
	SeqListPrint(&s1);

	int pos = SeqListFind(&s1, 4);
	if (pos != -1)
	{
		SeqListInsert(&s1, pos, 40);
	}
	SeqListPrint(&s1);
	SeqListInsert(&s1, 0, -1);
	SeqListInsert(&s1, (&s1)->sz, 8);
	SeqListPrint(&s1);


	SeqListDestory(&s1);
}

// 测试指定位置删除
void TestSeqList4()
{
	SL s1;
	SeqListInit(&s1);

	/*
	SeqListInsert可以被头插尾插复用
	*/
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListPrint(&s1);
	
	SeqListPushFront(&s1, 10);
	SeqListPushFront(&s1, 20);
	SeqListPushFront(&s1, 30);
	SeqListPushFront(&s1, 40);
	SeqListPrint(&s1);

	// SeqListErase可以被头删尾删复用
	SeqListErase(&s1, 1);
	SeqListPrint(&s1);

	SeqListPopBack(&s1);
	SeqListPopBack(&s1);
	SeqListPopBack(&s1);

	SeqListPrint(&s1);

	int pos = SeqListFind(&s1, 10);

	if (pos != -1)
	{
		SeqListErase(&s1, pos);
	}
	SeqListPrint(&s1);


}

// 测试指定位置修改
void TestSeqList5()
{
	SL s1;
	SeqListInit(&s1);

	/*
	SeqListInsert可以被头插尾插复用
	*/
	SeqListPushBack(&s1, 1);
	SeqListPushBack(&s1, 2);
	SeqListPushBack(&s1, 3);
	SeqListPushBack(&s1, 4);
	SeqListPushBack(&s1, 5);
	SeqListPrint(&s1);

	
	SeqListModify(&s1, 1, 4);

	SeqListPrint(&s1);

	SeqListDestory(&s1);
}


int main()
{
	//TestSeqList1();
	//TestSeqList2();
	//TestSeqList3();
	//TestSeqList4();
	TestSeqList5();
	return 0;
}

5. 结语

看到这里,相信大家对顺序表、以及它的接口实现也大概了解了。

下篇博客我会为大家带来顺序表的三道OJ题,我们敬请期待~

如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!

我是anduin,一名C语言初学者,我们下期见!

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

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

相关文章

HTML常见标签总结

目录 1.标题标签 2.段落标签 3.字体修饰标签 4.图片标签 5.超链接标签 6.表格标签 7.列表标签 8.表单标签 9.下拉菜单 10 多行文本框 1.标题标签 一级标题是<h1></h1>中间填上标题的内容,一共可以设置六级标题,数字越小,标题就越大越粗 我们测试一段代码 …

深度残差收缩网络(Deep Residual Shrinkage Networks for Fault Diagnosis )

摘要-本文开发了新的深度学习方法&#xff0c;即深度残余收缩网络&#xff0c;提高来自高噪声振动信号的特征学习能力&#xff0c;并实现较高的故障诊断准确性。软阈值作为非线性转换层插入到深层体系结构中&#xff0c;以消除不重要的特征。此外&#xff0c;考虑到通常为阈值设…

大数据编程实验一:HDFS常用操作和Spark读取文件系统数据

大数据编程实验一&#xff1a;HDFS常用操作和Spark读取文件系统数据 文章目录大数据编程实验一&#xff1a;HDFS常用操作和Spark读取文件系统数据一、前言二、实验目的与要求三、实验内容四、实验步骤1、HDFS常用操作2、Spark读取文件系统的数据五、最后我想说一、前言 这是我…

Swift基础——字典

Swift基础——字典 嗯。。。前面我们已经学习了数组&#xff08;相关文章地址&#xff09;&#xff0c;我们知道了在Swift中&#xff0c;苹果提供了两种集合类型来存储集合的值即Array和Dictionary。 Dictionary字典 字典&#xff1a;一种存储多个相同类型值的容器&#xff…

谈谈Java对象的生命周期

经过前面的分析 &#xff0c;我们现在来看一下创建的对象到底是什么东西&#xff0c;并且完整的总结一下一个对象从创建到回收到底经过了哪些阶段。 1 对象的创建 对象创建的主要流程: 1.类加载检查 虚拟机遇到一条new指令时&#xff0c;首先将去检查这个指令的参数是否能在常…

【趣学算法】贪心算法、海盗古董装船问题

14天阅读挑战赛 努力是为了不平庸~ 算法学习有些时候是枯燥的&#xff0c;这一次&#xff0c;让我们先人一步&#xff0c;趣学算法&#xff01; 文章目录贪心本质贪心选择最优子结构最优装载问题sort函数总结贪心本质 一个贪心算法总是做出当前最好的选择&#xff0c;也就是说…

R语言“优雅地“进行医学统计分析

本文首发于公众号&#xff1a;医学和生信笔记&#xff0c;完美观看体验请至公众号查看本文。 医学和生信笔记&#xff0c;专注R语言在临床医学中的使用&#xff0c;R语言数据分析和可视化。 文章目录主要函数描述性统计比较均值增强R中的ANOVA事后检验&#xff08;post-hoc&…

嘉立创EDA的一些使用技巧

立创EDA专业版-使用教程 (lceda.cn):https://prodocs.lceda.cn/cn/faq/editor/index.html绘制板框&#xff1a;https://blog.csdn.net/gutie_bartholomew/article/details/122936253和 mil 的切换&#xff0c;按【Q】切换单位测量 AltM&#xff0c;方便地测量物件之间的距离。按…

MySQL调优之索引在什么情况下会失效?

MySQL中提高性能的一个最有效的方式是对数据表设计合理的索引。索引提供了高效访问数据的方法&#xff0c;并且加快查询的速度&#xff0c;因此索引对查询的速度有着至关重要的影响。 使用索引可以快速地定位表中的某条记录&#xff0c;从而提高数据库查询的速度&#xff0c;提…

Spring JdbcTemplate.queryForObject()

Spring JdbcTemplate 是JDBC核心包中的中心类。它简化了 JDBC 与 Spring 的使用&#xff0c;并有助于避免常见错误。在此页面上&#xff0c;我们将学习使用它的queryForObject 方法。 JdbcTemplate.queryForObject不同参数的方法。1. <T> T queryForObject(String sql, …

继承-安全-设计模式

继承 与 原型、原型链 1. 继承是什么&#xff1f; 继承就是一个对象可以访问另外一个对象中的属性和方法 2. 继承的目的&#xff1f; 继承的目的就是实现原来设计与代码的重用 3. 继承的方式 java、c等&#xff1a;class**javaScript&#xff1a; 原型链 ** ES2015/ES6 中…

数据导入与预处理-拓展-pandas可视化

数据导入与预处理-拓展-pandas可视化1. 折线图1.1 导入数据1.2 绘制单列折线图1.3 绘制多列折线图1.4 绘制折线图-双y轴2. 条形图2.1 单行垂直/水平条形图2.2 多行条形图3. 直方图3.1 生成数据3.2 透明度/刻度/堆叠直方图3.3 拆分子图4. 散点图4.1生成数据4.2 绘制大小不一的散…

自动化测试的使用场景有哪些?如何正确使用?

目录 前言 什么是自动化测试&#xff1f; 自动化测试的使用场景有哪些&#xff1f; 自动化测试有什么好处&#xff1f; 总结 前言 本文将通过介绍 自动化测试是什么 &#xff0c; 哪些场景适用于自动化测试 &#xff0c; 自动化测试的好处 &#xff0c; 以及通过 具体的自…

vue如何二次封装一个高频可复用的组件

在我们的业务里&#xff0c;我们通常会二次封装一些高频业务组件&#xff0c;比如弹框&#xff0c;抽屉&#xff0c;表单等这些业务组件&#xff0c;为什么要二次封装&#xff1f;我们所有人心里的答案肯定是&#xff0c;同样类似的代码太多了&#xff0c;我想复用组件&#xf…

2004-2020中小企业板上市公司财务报表股票交易董事高管等面板数据

1200变量&#xff01;中小企业板上市公司面板数据大全 2004-2020年 1、时间&#xff1a;2004-2020年 2、数据范围&#xff1a;共计973家上市公司 3、数据指标&#xff1a;包括财务报表、股票交易、董事高管等1200变量 4、用途&#xff1a;进行上市公司高管股权激励与公司绩…

C语言刷题系列——1.将三个整数按从大到小输出

将三个整数按从大到小输出1.输入三个整数2.最大的值放在a中&#xff0c;最小值放在c中&#xff0c;剩余的一个放在bstep1&#xff1a;a和b比较step2&#xff1a;a和c比较step3&#xff1a;b和c比较3.最终的代码1.输入三个整数 先写好main函数、头文件 #include <stdio.h&g…

用高并发技巧解决redis热key问题

​ 这篇文章我将介绍工作中处理热key问题的常用手段&#xff0c;可能介绍的不是很全&#xff0c;毕竟不同的业务场景可能有不同的解决方案&#xff0c;但是相信通过这部分的介绍能提供一个热key问题的思路。 热key问题&#xff0c;简单来说就是对某一资源的访问量过高问题&…

Unity学习shader笔记[一百零八]简单萤火效果

之前用粒子系统基于原有萤火虫的粒子改了一波慢萤火效果就被惊艳到了&#xff0c;开始大家讨论&#xff0c;就都觉得这样大数量的粒子消耗挺大的&#xff0c;后面测试过才发现单纯的粒子系统在总粒子数量3000&#xff0c;每秒300的生成数量&#xff0c;屏幕呈现有1000多个粒子的…

【黄啊码】MySQL入门—17、在没有备份的情况下,如何恢复数据库数据?

大家好&#xff01;我是黄啊码&#xff0c;MySQL的入门篇已经讲到第16个课程了&#xff0c;今天我们继续讲讲大白篇系列——科技与狠活之恢复数据库 在没做数据库备份&#xff0c;没有开启使用 Binlog 的情况下&#xff0c;尽可能地找回数据。 今天的内容主要包括以下几个部分…

2022NISCTF--web

easyssrf 打开题目&#xff0c;显示的是 尝试输入&#xff0c; 发现输入flag有东西 读取文件 访问下一个网站 读取文件 不能以file开头 直接伪协议&#xff0c;base64解码 checkIn 奇怪的unicode编码 当选中左边的时候右边也会被选中 我们在vscode看看 这样的额 展示的是UTF-1…