DS八大排序之冒泡排序和快速排序

news2024/12/22 16:37:22

前言

前两期我们已经对"插入排序"(直接插入排序和希尔排序) 和 "选择排序"(直接选择排序和堆排序)进行了详细的介绍~!这一期我们再来详细介绍一组排序 :"交换排序"即耳熟能详的冒泡排序和赫赫有名的快速排序~!

本期内容介绍

冒泡排序

快速排序(Hoare、挖坑、前后指针、非递归)

交换排序的基本思想

对待排序的序列,进行元素的两两比较,如果满足交换条件交换。即将元素逐步换到合适的位置~!

冒泡排序

从前往后,逐一比较相邻元素,前面的大于或小于后面的则进行交换,每一轮将当前轮最大或最小的元素冒泡到当前论的最后。重复上述过程最后就是有序的~!

OK,还是画个图理解一下:

OK,这就是冒泡排序的过程,我们还是先来写单趟,再来改造整体:

单趟

	//注意,前一个和后一个比,j最大只能走到n-2(倒数第二个),j+1只能走到n-1(倒数第一个)
	for (int j = 0; j < n - 1; j++)
	{
		if (a[j] > a[j + 1])
		{
			Swap(&a[j], &a[j + 1]);
		}
	}

整体

整体的话,控制一下每一趟的交换个数即可~!

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)//n-1是最后一个只能放在第一个即可以不对他排
	{
		//注意,前一个和后一个比,j最大只能走到n-2(倒数第二个),j+1只能走到n-1(倒数第一个)
		for (int j = 0; j < n - 1 - i; j++)//每一趟确定当前趟的最大之到当前趟的最后
		{								//每一趟确定出一个即每一趟少排i个
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
	}
}

OK, 测试一下!看结果:

没问题!但有一种情况就是上述画图的那种例子,已经有序了但不知道还是在两两比较。这其实是很没有必要的~!请我们可以优化一下!

优化思路:在每一趟开始之前进行一个有序的标记,当一趟结束后判断该标记,如果有序直接不用再往后排了,否则继续进行排序~!

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)//n-1是最后一个只能放在第一个即可以不对他排
	{
		int flag = 1;//假设该趟有序
		//注意,前一个和后一个比,j最大只能走到n-2(倒数第二个),j+1只能走到n-1(倒数第一个)
		for (int j = 0; j < n - 1 - i; j++)//每一趟确定当前趟的最大之到当前趟的最后
		{								//每一趟确定出一个即每一趟少排i个
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				flag = 0;//交换了说明是该趟是无序的
			}
		}

		if (flag == 1)//说明已经有序了没有必要再冒泡了
		{
			break;
		}
	}
}

冒泡这是一种写法,其实还有很多。这里再来一个:这里前面与后面比较!

单趟

		//后一个和前一个比较,j最大走到倒数第一个
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])//后面与前一个比较
			{
				Swap(&a[j - 1], &a[j]);
				flag = 0;
			}
		}

整体

//冒泡排序
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int flag = 1;
		//后一个和前一个比较,j最大走到倒数第一个
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])//后面与前一个比较
			{
				Swap(&a[j - 1], &a[j]);
				flag = 0;
			}
		}

		if (flag == 1)//已经有序不要再去冒泡了
		{
			break;
		}
	}
}

测试一下:

一点问题没有~!

总结:冒泡虽然简单,但一定要注意边界点的控制~!

复杂度分析

时间复杂度:O(N^2)

每一趟遍历选出一个最值即每一趟都比前一趟少比较一次,所以他应该是第一趟比较n-1次,第二趟比较n-2次...1很明显这是等差数列求和,最后的时间复杂度为O(N^2)

空间复杂度:O(1)

快速排序

一个序列中先随意选出一个元素,该元素称为基准比基准移动到基准的右边比基准移动到基准的左边,这样基准值就到了他该到的位置。然后对基准值的左右区间分别进行上述相同的操作

相信看到这里应该想到快排用的是递归,是的!但我们也会实现非递归版本~!

快排最核心的就是他选基准的那个单趟!这里的版本我会的有三个:Hoare、挖坑法、前后指针。下面一个一个的来!

Hoare

择最左端或最右端作为基准点,使用两个指针从序列两端向中间扫描右指针找到比基准的值,左指针找到比基准的值,然后进行交换。重复这个过程直到左右指针相遇,相遇的位置是基准的最终位置。(为什么相遇的位置就是最终的位置呢?后面会解释!)

OK,还是画个图理解一下:

OK,上代码:

//Hoare
int PartSort1(int* a, int left, int right)
{
	int keyi = left;//选最左端的为基准
	while (left < right)
	{
		//右指针找小
		while (left < right && a[right] >= a[keyi])
			right--;

		//左指针找大
		while (left < right && a[left] <= a[keyi])
			left++;

		//找到了,交换
		Swap(&a[right], &a[left]);
	}
	//当相遇时left或right与基准交换
	Swap(&a[left], &a[keyi]);

	return left;//返回基准下标
}

解释:

1、这里找大或找小时可能在极端情况下找不到,从而导致越界。所以得判断让其不要越界~!

2、为什么在左右指针相遇时就是基准的最终位置?左右指针相交有两种情况,左找右和右找左)

左找右:因为是右先找小,所以当他们相交时,一定是小于基准的。

右找左:因为前一轮已经交换过,所以当前左一定是小于基准的的。 

这就保证了,当左右相交时的位置与基准的位置交换后基准的位置是最终位置!

OK,单趟写好了就可以用相同的方式去处理左右区间了~!而每个区间的处理和上述的处理方式一样,所以使用递归就很方便~!

我们以前在函数那一期介绍递归的时候说过,递归必须有结束条件~!这个的结束条件是啥吧呢?

其实很简单,只需要注意每次递归的那个区间合法即可!即左区间 < 右区间(相等只有一个元素也不需要排了)

整体

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort1(a, begin, end);
	//[begin, key-1] key [key+1, end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

测试一把:

其实介绍到这里,你有没有感觉到。快排很像二叉树的前序遍历~!

OK,我们再来看看,Hoare的优化版(国人优化的)挖坑法!

挖坑法

和Hoare的很相似。左右两指针向中间扫描右找小,左找大最左端或最右端为基准基准位置空出来了形成了坑右指针(左指针)先走,找到小(大)的了,填到左(右)边里面。自身了坑,(右)边找大(小),填到右(左)边的。如此循环,直到相交!然后把基准与左右指针的任意交换即可~!

OK,还是画个图:

OK,上代码:

//挖坑法
int PartSort2(int* a, int left, int right)
{
	int keyi = a[left];//选左端为基准,左端就是坑位
	while (left < right)
	{
		//右指针找小
		while (left < right && a[right] >= keyi)
			right--;
		a[left] = a[right];//找到了,填到左坑,自身成了新坑位

		//左指针找大
		while (left < right && a[left] <= keyi)
			left++;
		a[right] = a[left];//找到了,填到右坑,自身成了新坑位
	}
	//左右指针相遇,交换基准与左右指针的任意
	Swap(&a[left], &keyi);

	return left;//返回基准的下标
}

整体

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort2(a, begin, end);
	//[begin, key-1] key [key+1, end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

测试一下:

OK,没有问题!下面我们在来看一个版本~前后指针。

前后指针

选左端为基准,前指针在第一个位置,后指针的第二个位置。当前指针遇到小于基准值时,当前位置的值与后指针的下一个位置的值进行交换。直到前指针到区间结束,此时后指针与基准交换,后指针的位置就是最终基准的位置!

OK,还是来画个图:

OK,上代码:

//前后指针
int PartSort3(int* a, int left, int right)
{
	int prev = left;//后(慢)指针
	int keyi = left;//基准
	int cur = left + 1;//快指针

	while (cur <= right)//闭区间所以是<=
	{
		if (a[cur] < a[keyi])//快指针如果找到小了
		{
			Swap(&a[++prev], &a[cur]);//与prev的下一个位置交换
		}

		++cur;
	}

	Swap(&a[prev], &a[keyi]);//最后交换基准位置与prev位置的值
	return prev;//返回基准的位置
}

这里其实可以小小的优化一下:和上面画图情况的一样,假设prev和cur是同一个位置时,是不是根本就不用交换啊~!OK,我们可以控制一下

//前后指针
int PartSort3(int* a, int left, int right)
{
	int prev = left;//后(慢)指针
	int keyi = left;//基准
	int cur = left + 1;//快指针

	while (cur <= right)//闭区间所以是<=
	{
		if (a[cur] < a[keyi] && ++prev != cur)//快指针如果找到小了并且prev的位置和cur不同
		{
			Swap(&a[prev], &a[cur]);//与prev的下一个位置交换
		}

		++cur;
	}

	Swap(&a[prev], &a[keyi]);//最后交换基准位置与prev位置的值
	return prev;//返回基准的位置
}

整体

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int key = PartSort3(a, begin, end);
	//[begin, key-1] key [key+1, end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

测试一下:

OK,没有问题~!

以上就是三个版本快排了,他们都是递归版本的。以前介绍的递归时说过递归有个致命的缺陷是:如果递归的太深会栈溢出(每一次调用都要建立函数栈帧,一般的情况下栈区的大小时7M左右)~!为了解决栈溢出的问题我们采用非递归即迭代的方式来解决这个问题~!下面我们来实现一下~!

非递归版本

非递归版本,其实时利用栈来模拟递归的这个过程的~!但C语言没有栈这种数据结构...得手搓,我们就把以前的在栈和队列那一期的那个拿过来。

实现思路:模拟递归的方式!先让数组的 0 和 size-1 左右端点的下标入栈,当栈不为空时分别取栈顶元素,赋值给左右指针。然后调用任意一个版本的单趟获得基准,然后基准左右的两个区间入栈继续上述操作!直到栈为空就排序结束了~!

OK,画个图:

OK,上代码:

//快速排序 非递归版
void QuickSort(int* a, int begin, int end)
{
	ST* s = NULL;
	//先把左右区间入栈
	Push(&s, end);
	Push(&s, begin);

	//栈不为空时,出left和right
	while (!STEmpty(s))
	{
		//获取左端点
		int left = STTop(s);
		Pop(&s);		
		//获取右端点
		int right = STTop(s);
		Pop(&s);
		//获取该区间的基准
		int keyi = PartSort3(a, left, right);
		//右子区间入栈
		if (keyi + 1 < right)
		{
			Push(&s, right);
			Push(&s, keyi + 1);;
		}
		//左子区间入栈
		if (left < keyi - 1)
		{
			Push(&s, keyi-1);
			Push(&s, left);
		}
	}

	STDestory(&s);
}

OK,测试一下:

分析以及优化

我们前面说过快速排序和二叉树的前序遍历很象,然而上述的版本的单趟和一些情况下其实可以做一下一些小优化~!第一当我们的待排序的序列是已经有序的时!我们快速排序的时间复杂度时很高的,接近O(n^2)如下图1分析,避免这种情况我们采用三数取中的方式来解决。第二递归的最后几层是整个递归的80%左右,而递归要建立函数栈帧空间消耗比较大,我们可以在区间小的时候,换成直接插排提高效率~!

图1:快排最差情况(已经有序)

解决已经有序的序列排序效率低的问题 --->三数取中

三数取中:当前待排的序列的最左端、最右端、最中间。三个值中取中间大的那一个~!这样就不怕有序的情况效率低了,而且是越有序越效率高~!

代码实现:

//三数取中
int GetMid(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[left] > a[right])
	{
		if (a[mid] < a[right])
		{
			return right;
		}
		else if (a[mid] > a[left])
		{
			return left;
		}
		else
			return mid;
	}
	else
	{
		//left < right
		if (a[mid] > a[right])
		{
			return right;
		}
		else if (a[mid] < a[left])
		{
			return left;
		}
		else
			return mid;
	}
}

当区间小的时候,我们可以采用指直接插排来优化。

原因:在序列很大时当区间小的时候就说明此小区间已经接近有序了,而接近有序的区间直接插入排序的效率很高的。

代码实现:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 > 10)
	{
		int key = PartSort1(a, begin, end);
		//[begin, key-1] key [key+1, end]
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
	else
	{
		InsertSort(a, end - begin + 1);//区间小于10个待排序的元素后进行直接插排
	}
}

复杂度分析

时间复杂度:O(N*logN)

快排可以看做一棵二叉树,一共有N个节点。每一层确定2^(i-1)(i从1开始)个待排元素的最终位置,总共的确定待排的层数是:2^x = N ---> x = logN,而每一次确定一个元素的位置又是遍历一遍待排的序列即O(N)所以总共合计O(N*logN)

注意:加了三数取中,几乎不可能再出现O(N^2)了

空间复杂度:O(logN)

因为递归是要开销栈帧的,我们前面在复杂度的那一期介绍过,空间可以重复利用而时间不可重复利用。所以这里至多递归到他的深度即h = logN,所以他的空间复杂度是O(logN)

注意:非递归的空间复杂度任然是log(N)原因是他的空间消耗虽然不在栈了,但他利用栈的数据结构转移到了堆上,还是会消耗空间的~!!

优化后的快排源码:

//三数取中
int GetMid(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[left] > a[right])
	{
		if (a[mid] < a[right])
		{
			return right;
		}
		else if (a[mid] > a[left])
		{
			return left;
		}
		else
			return mid;
	}
	else
	{
		//left < right
		if (a[mid] > a[right])
		{
			return right;
		}
		else if (a[mid] < a[left])
		{
			return left;
		}
		else
			return mid;
	}
}

Hoare 
O(N)
int PartSort1(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
			right--;
		
		while (left < right && a[left] <= a[keyi])
			left++;

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	return left;
}


挖坑法
O(N)
int PartSort2(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int keyi = a[left];
	while (left < right)
	{
		while (left < right && a[right] >= keyi)
			right--;
		a[left] = a[right];
		
		while (left < right && a[left] <= keyi)
			left++;
		a[right] = a[left];
	}

	a[left] = keyi;
	return left;
}

 
前后指针
O(N)
int PartSort3(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int prev = left;//后(慢)指针
	int keyi = left;//基准
	int cur = left + 1;//快指针

	while (cur <= right)//闭区间所以是<=
	{
		//if (a[cur] < a[keyi])//快指针如果找到小了
		//{
		//	Swap(&a[++prev], &a[cur]);//与prev的下一个位置交换
		//}

		if (a[cur] < a[keyi] && ++prev != cur)//快指针如果找到小了并且prev的位置和cur不同
		{
			Swap(&a[prev], &a[cur]);//与prev的下一个位置交换
		}
		++cur;
	}

	Swap(&a[prev], &a[keyi]);//最后交换基准位置与prev位置的值
	return prev;//返回基准的位置
}

//快速排序
//O(N*logN)
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 > 10)
	{
		int key = PartSort1(a, begin, end);
		//[begin, key-1] key [key+1, end]
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
	else
	{
		InsertSort(a, end - begin + 1);//区间小于10个待排序的元素后进行直接插排
	}
}

//快速排序 非递归版
//void QuickSort(int* a, int begin, int end)
//{
//	ST* s = NULL;
//	//先把左右区间入栈
//	Push(&s, end);
//	Push(&s, begin);
//
//	//栈不为空时,出left和right
//	while (!STEmpty(s))
//	{
//		//获取左端点
//		int left = STTop(s);
//		Pop(&s);		
//		//获取右端点
//		int right = STTop(s);
//		Pop(&s);
//		//获取该区间的基准
//		int keyi = PartSort3(a, left, right);
//		//右子区间入栈
//		if (keyi + 1 < right)
//		{
//			Push(&s, right);
//			Push(&s, keyi + 1);;
//		}
//		//左子区间入栈
//		if (left < keyi - 1)
//		{
//			Push(&s, keyi-1);
//			Push(&s, left);
//		}
//	}
//
//	STDestory(&s);
//}

OK,好兄弟我们本期分享就到这里,我们下一期的归并排序再见~!

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

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

相关文章

lv12 uboot移植深化 9

u-boot-2013.01移植 【实验目的】 了解u-boot 的代码结构及移植的基本方法 【实验环境】 ubuntu 14.04发行版FS4412实验平台交叉编译工具arm-none-linux-gnueabi- 【注意事项】 实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 1 建立自己的平台 1.…

在线客服系统定价因素解析:影响价格的关键因素

跨境电子商务公司必不可少的工具就是在线客服系统。企业选择在线客服系统的时候免不了要对不同产品的功能性、价格、服务等因素进行考量。今天这篇文章&#xff0c;我们就来探讨一下在线客服系统的定价因素有哪些&#xff1f;探究市面上的在线客服系统价格各异的影响因素。为大…

libp2p 快速开始

文章目录 第一部分&#xff1a;libp2p 快速入门一、什么是libp2plibp2p 发展历程libp2p的特性p2p 网络和我们熟悉的 client/server 网络的区别&#xff1a; 二、Libp2p的实现目标三、Libp2p的用途四、运行 Libp2p 协议流程libp2p 分为三层libp2p 还有一个局域网节点发现协议 mD…

27系列DGUS智能屏发布:可实时播放高清模拟信号摄像头视频

针对高清晰度的模拟信号摄像头视频画面的显示需求&#xff0c;迪文特推出27系列DGUS智能屏。该系列智能屏可适配常见的AHD摄像头、CVBS摄像头&#xff0c;支持单路1080P高清显示、两路720P同屏显示&#xff08;同一类型摄像头&#xff09;。用户通过DGUS简单开发即可实现摄像头…

netty-daxin-3(rpc远程调用)

文章目录 nettyRpcObjectEncoder 与 ObjectDecoderjdk动态代理回顾Rpc调用过程简析服务端客户端 nettyRpc ObjectEncoder 与 ObjectDecoder ObjectEncoder继承自MessageToByteEncoder<Serializable>&#xff0c;它内部使用ByteBufOutputStream包装ByteBuf对象&#xff…

SpringBoot 自动装配原理---源码详解

目录 SpringBoot 自动装配原理源码流程详解&#xff1a;流程总结&#xff1a;条件匹配解释&#xff1a;其他解释&#xff1a; SpringBoot 自动装配原理 源码流程详解&#xff1a; 1、先看启动类&#xff0c;启动这个main方法&#xff0c;然后调用这个run方法。 2、把 启动类作…

一文了解Tomcat

文章目录 1、Tomcat介绍2、Tomcat使用配置2.1、Tomcat下载启动2.2、Tomcat启动乱码2.3、Tomcat端口号修改 3、Tomcat项目部署4、IDEA中使用Tomcat方式 1、Tomcat介绍 什么是Tomcat ​ Tomcat是Apache软件基金会一个核心项目&#xff0c;是一个开源免费的轻量级web服务器&#x…

Networkx实现小世界网络的分析

Networkx实现小世界网络的分析 小世界网络 小世界现象&#xff0c;也被称为六度分离原则&#xff0c;即如果你在地球上的任何地方随便选择任何两个人&#xff0c;你会发现一条至多由他们之间的6个熟人形成的路径。在网络科学语言中&#xff0c;六度也被称为小世界性质&#x…

C++相关闲碎记录(16)

1、正则表达式 &#xff08;1&#xff09;regex的匹配和查找接口 #include <regex> #include <iostream> using namespace std;void out (bool b) {cout << ( b ? "found" : "not found") << endl; }int main() {// find XML/H…

【C++干货铺】继承后的多态 | 抽象类

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 多态的概念 多态的定义和实现 多态的定义条件 虚函数 虚函数的重写 特殊情况 协变&#xff08;基类和派生类的虚函数返回值不同&#xff09; 析构函数的重…

如果你找不到东西,请先确保你在正确的地方寻找

之前我们在几篇文章中描述了如何进行”思想”调试&#xff0c;今天的文章我将不会这样做。 因为下面的编程错误大部分人都会遇到&#xff0c;如果你看一眼下面的代码&#xff0c;你不会发现有什么问题&#xff0c;这仅仅是因为你的的大脑只给你希望看到的&#xff0c;而不是那…

分数约分-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第20讲。 分数约分&#xf…

算法模板之单链表图文讲解

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;算法模板、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️使用数组模拟单链表讲解1.1 &#x1f514;为什么我们要使用数组去模拟单链表…

appium2.0.1安装完整教程+uiautomator2安装教程

第一步&#xff1a;根据官网命令安装appium&#xff08;Install Appium - Appium Documentation&#xff09; 注意npm前提是设置淘宝镜像&#xff1a; npm config set registry https://registry.npmmirror.com/ 会魔法的除外。。。 npm i --locationglobal appium或者 npm…

多线程 (上) - 学习笔记

前置知识 什么是线程和进程? 进程: 是程序的一次执行,一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间&#xff0c;一个进程可以有多个线程&#xff0c;比如在Windows系统中&#xff0c;一个运行的xx.exe就是一个进程。 线程: 进程中的一个执行流&#xff0…

Element-Ui定制Dropdown组件

1.效果 说明&#xff1a;移入后新增图标&#xff0c;然后移入后图标变色。当然大家可以想到用mouseover移入事件来实现移入颜色的变化&#xff0c;但是在使用Dropdown组件的时候&#xff0c;不支持这种写法。因此采用了原生的遍历对象的形式&#xff0c;为每一个item对象绑定鼠…

通过WinCC基本功能实现批次查询及批次报表

谈到WinCC中的批次数据处理和批次报表&#xff0c;也许有人会想到PM-Quality这款专业的批次报表软件。但如果你的银子有限&#xff0c;批次报表要求又比较简单&#xff0c;不妨看看此文。 —《通过 WinCC 基本功能实现批次数据过滤查询以及打印批次数据报表》 实现的功能描述 …

一维数组的定义

什么是数组&#xff1f; &#xff08;1&#xff09;数组是具有一定顺序关系的若干变量的集合&#xff0c;组成数组的各个变量统称为数组的元素 &#xff08;2&#xff09;数组中的各元素的数据类型要求相同&#xff0c;用数组名和下标确定&#xff0c;数组可以是一维的&#…

无经验小白开发一个 JavaWeb项目,需要注意哪些要点?

大家好我是咕噜铁蛋 &#xff0c;我收集了许多来自互联网的宝贵资源&#xff0c;这些资源帮助我学习和理解如何从零开始开发JavaWeb项目。今天&#xff0c;我将与大家分享一些关键的要点&#xff0c;包括项目规划、技术选型、数据库设计、代码编写和测试部署等。如果你有任何问…

大数据存储技术(3)—— HBase分布式数据库

目录 一、HBase简介 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;特点 &#xff08;三&#xff09;HBase架构 二、HBase原理 &#xff08;一&#xff09;读流程 &#xff08;二&#xff09;写流程 &#xff08;三&#xff09;数据 flush 过程 &#xf…