七大排序(含快排+归并的递归版和非递归版)

news2024/9/28 19:26:37

文章目录

  • 前言
  • 一、冒泡排序
  • 二、选择排序
  • 三、插入排序
  • 四、希尔排序
  • 五、堆排序
  • 六、快速排序
    • 快排的递归方式
    • 快排的非递归方式
  • 七、归并排序
    • 自上而下的递归
    • 自下而上的迭代
  • 总结


前言

排序: 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序: 数据元素全部放在内存中的排序。
外部排序: 数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

常见的内部排序算法有:

在这里插入图片描述

一、冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1.算法步骤

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 //这是内层循环所控制的
3.针对所有的元素重复以上的步骤,除了最后一个。 //这是外层循环所控制的
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2.动画演示

在这里插入图片描述

3.效率分析

当数据全是正序时,遍历一次即可,通过flag直接跳出外层循环,此时速度最快。
当数据全是反序时,每次都需要遍历和交换,此时速度最慢。

4.代码设计

//冒泡排序
template<class T>
void bubble_sort(std::vector<T>& arr, int size)
{
	for (int i = 0; i < size; i++)
	{
		int flag = 0;
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (arr[j + 1] < arr[j]) // <是为了保证稳定性
			{
				swap(arr[j + 1], arr[j]);
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

二、选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1.算法步骤

1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕

2.动画演示

在这里插入图片描述

3.代码设计

//选择排序
template<class T>
void select_sort(std::vector<T>& arr, int size)
{
	for (int i = 0; i < size ; i++)
	{
		int min = i;
		for (int j = i + 1; j < size ; j++)
		{
			if (arr[min] > arr[j]) // >是为了保证稳定性
				min = j;
		}
		if (min != i) swap(arr[min], arr[i]);
	}
}

三、插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1.算法步骤

1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面,保证稳定性。)

2.动画演示

在这里插入图片描述
3.代码设计

//插入排序
template<class T>
void insert_sort(std::vector<T>& arr, int size)
{
	for (int i = 1; i < size; i++)
	{
		int key = arr[i];
		int end = i - 1;
		while (end >= 0 && key < arr[end]) // <是为了保证稳定性
		{
			arr[end +1] = arr[end];
			end--;
		}
		arr[end + 1] = key;
	}
}

四、希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序

1.算法步骤

1.选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
2.按增量序列个数 k,对序列进行 k 趟排序;
3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

2.过程演示
在这里插入图片描述

3.算法分析

希尔排序时间复杂度是 O(n(1.3-2)),空间复杂度为常数阶 O(1)。希尔排序没有时间复杂度为 O(n(logn)) 的快速排序算法快 ,因此对在这里插入代码片中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O(n2 ) 复杂度的算法快得多。

步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。

4.代码设计

//希尔排序
template<class T>
void shell_sort(std::vector<T>& arr, int size)
{
	int gap = size;
	while (gap > 1)
	{
		gap /= 2;

		for (int i = 0; i < size - gap; i++)
		{
			int end = i;
			int key = arr[end + gap];
			while (end >= 0 && key < arr[end])
			{
					arr[end + gap] = arr[end];
					end -= gap;
			}
			arr[end + gap] = key;
		}
	}
}

五、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
    在这里插入图片描述

堆排序的平均时间复杂度为 Ο(nlogn)。

1.算法步骤

1.将待排序序列构建成一个堆 H[0……n-1],根据(升序降序需求)选择大顶堆或小顶堆;
2.把堆首(最大值)和堆尾互换;
3.把堆的尺寸缩小 1,并调用 AdjustDown(0),目的是把新的数组顶端数据调整到相应位置;
4.重复步骤 2,直到堆的尺寸为 1。

2.动画演示

在这里插入图片描述

3.代码设计

//堆排序
template<class T>
void AdjustDown(std::vector<T>& arr, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		int max = child;
		if (child + 1 < size && arr[child + 1] > arr[child])
			max = child + 1;
		if (arr[parent] < arr[max])
		{
			swap(arr[max], arr[parent]);
			parent = max;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

template<class T>
void heap_sort(std::vector<T>& arr, int size)
{
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(arr, size, i);

	for (int i = 1; i < size; i++)
	{
		swap(arr[0], arr[size - i]);
		AdjustDown(arr, size - i, 0);
	}
}

六、快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n ogn) 的排序算法表现要更好:

快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

快排的递归方式

1.算法步骤

1.从数列中挑出一个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2.动画演示

在这里插入图片描述

3.代码设计

//快速排序的优化——三数取中(key)
template<class T>
int getKey(std::vector<T>& arr, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (arr[begin] < arr[mid])
	{
		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return begin;
		else
			return end;

	}
	else
	{
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[end] > begin)
			return begin;
		else
			return end;
	}
}

//快速排序
template<class T>
int partition1(std::vector<T>& arr, int left, int right)
{
	int mid = getKey(arr, left, right);
	swap(arr[left], arr[mid]);
	//left会移动,利用key保存最左边的下标;
	int key = left;

	while (left < right)
	{
		while (left < right && arr[right] >= arr[key])//往左找小
			--right;
		while (left < right && arr[left] <= arr[key]) //往右找大
			++left;
		if (left < right)
			swap(arr[left], arr[right]);
	}
	//从右边开始找可以确定相遇位置的值要小于key
	swap(arr[left], arr[key]);

	return left;
}

template<class T>
void quick_sort(std::vector<T>& arr, int begin, int end)
{
	if (begin >= end) return;

	int mid = partition1(arr, begin, end);

	quick_sort(arr, begin, mid - 1);
	quick_sort(arr, mid + 1, end);

}

快排的非递归方式

由于递归方式会大大增加函数栈帧的层数,导致栈溢出,所以利用栈去模拟递归的思想实现:

template<class T>
void quick_sortNoR(std::vector<T>& arr, int left, int right)
{
	std::stack<T> s;
	s.push(left);
	s.push(right);

	while (!s.empty())
	{
		right = s.top();
		s.pop();
		left = s.top();
		s.pop();


		int div = partition1(arr, left, right);
		if (div + 1 < right)
		{
			s.push(div + 1);
			s.push(right);
		}
		if (left < div - 1)
		{
			s.push(left);
			s.push(div - 1);
		}

	}
}

七、归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归;
  • 自下而上的迭代;

1.算法步骤

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;
5.将另一序列剩下的所有元素直接复制到合并序列尾。

2.动画演示
在这里插入图片描述
3.代码设计

自上而下的递归

//归并排序 对于[)区间
template<class T>
void merge(std::vector<T>& arr, int l, int mid, int r) {
	int index = 0;
	int ptrL = l;
	int ptrR = mid;
	static std::vector<T>tempary;
	if (arr.size() > tempary.size()) {
		tempary.resize(arr.size());
	}
	while (ptrL < mid && ptrR < r) {
		if (arr[ptrL] < arr[ptrR]) 
			tempary[index++] = arr[ptrL++];
		else
			tempary[index++] = arr[ptrR++];
	}
	while (ptrL < mid) {
		tempary[index++] = arr[ptrL++];
	}
	while (ptrR < r) {
		tempary[index++] = arr[ptrR++];
	}

	std::copy(tempary.begin(), tempary.begin() + index , arr.begin() + l);
}

template<class T>
void merge_sort(std::vector<T>& arr, int l, int r) { // sort the range [l, r) in arr
	if (r - l <= 1) return;
	int mid = (l + r) / 2;
	merge_sort(arr, l, mid);
	merge_sort(arr, mid, r);
	merge(arr, l, mid, r);

}

自下而上的迭代

template<class T>
void merge_sortNoR(std::vector<T>& arr, int sz)
{
	static std::vector<T> tempary;
	if (tempary.size() < arr.size())
		tempary.resize(arr.size());

	int gap = 1;
	while (gap < arr.size())
	{
		for (int i = 0; i < sz; i += 2 * gap)
		{
			int b1 = i, e1 = i + gap - 1;
			int b2 = i + gap, e2 = i + 2 * gap - 1;

			if (e1 >= sz) break;//第一组越界
			if (b2 >= sz) break;//第二组全部越界
			if (e2 >= sz) e2 = sz - 1;//第二组部分越界,继续归并

			int index = i;
			while (b1 <= e1 && b2 <= e2)
			{
				if (arr[b1] <= arr[b2])
					tempary[index++] = arr[b1++];
				else
					tempary[index++] = arr[b2++];
			}

			while (b1 <= e1)
			{
				tempary[index++] = arr[b1++];
			}
			while (b2 <= e2)
			{
				tempary[index++] = arr[b2++];
			}
			
			std::copy(tempary.begin(), tempary.begin() + index, arr.begin());

		}
		gap *= 2;
	}
	
}

总结

1.选择排序不稳定的原因:

比如 在序列5,3,5,2中,2 是最小值,会和第 1 个 5 进行交换,那第 1 个 5 就去了第 2 个 5 的后面,两个 5 的相对位置发生改变。

2.希尔排序不稳定的原因:

相同元素在不同子序列中可能被分到不同的位置,这导致了相同元素的相对位置可能被改变。

3.堆排序不稳定的原因:

在建立堆的调整步骤里,由于关键字相同的两个记录位置并不会被调换,所以建堆的时候是稳定的。但是,在堆顶与堆尾交换的时候两个相等的记录在序列中的相对位置就可能发生改变。

4.快速排序不稳定的原因:

比如序列为5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。

在这里插入图片描述

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

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

相关文章

二分图带权最大匹配-KM算法详解

文章目录 零、前言一、红娘再牵线二、二分图带权最大完备匹配2.1二分图带权最大匹配2.2概念2.3KM算法2.3.1交错树2.3.2顶标2.3.3相等子图2.3.4算法原理2.3.5算法实现 三、OJ练习3.1奔小康赚大钱3.2Ants 零、前言 关于二分图&#xff1a;二分图及染色法判定-CSDN博客 关于二分…

Vue + JS + tauri 开发一个简单的PC端桌面应用程序

Vue JS tauri 开发一个简单的PC端桌面应用程序 文章目录 Vue JS tauri 开发一个简单的PC端桌面应用程序1. 环境准备1.1 安装 Microsoft Visual Studio C 生成工具[^2]1.2 安装 Rust[^3] 2. 使用 vite 打包工具创建一个 vue 应用2.1 使用Vite创建前端Vue项目2.2 更改Vite打包…

vue中使用高德地图渲染多个不同类型的点,根据勾选数据 类型不同打点显示隐藏

一、在index.html文件中引入高德地图JavaScript API的2.0版本SDK <script src"https://webapi.amap.com/maps?v2.0&key你的高德地图Key"></script>二、创建一个Vue组件&#xff0c;用于渲染地图和点位 html <template><div class"m…

Axure全面指南:正确打开并高效使用的步骤!

AxureRP是目前流行的设计精美的用户界面和交互软件。AxureRP根据其应用领域提供了一组丰富的UI控制。作为Axure的国内替代品&#xff0c;即时设计可以在线协作&#xff0c;浏览器可以在无需下载客户端的情况下打开和使用。如果以前使用Axure&#xff0c;很容易切换到即时设计。…

记录el-select+el-tree复选框,支持模糊查询,懒加载,树父子节点不关联,不全选

需求&#xff1a;一个机构下拉菜单&#xff0c;一个人员下拉菜单&#xff0c;默认带入当前登录用户的机构和人员。机构下拉菜单为两个接口&#xff0c;模糊查询为一个接口不包含懒加载&#xff0c;默认非模糊查询情况下为一个接口&#xff0c;点击节点懒加载。机构下拉菜单数据…

探索FTP:原理、实践与安全优化

引言 在正式开始讲解之前&#xff0c;首先来了解一下文件存储的类型有哪些。 DAS、SAN和NAS是三种不同的存储架构&#xff0c;分别用于解决不同场景下的数据存储需求。 DAS (Direct Attached Storage 直接附加存储)&#xff1a;DAS 是指将存储设备&#xff08;如硬盘&#x…

线程之间如何传递上下文信息

文章目录 源码解读1. 扩展ThreadPoolExecutor2. 扩展Runnable3. 整体流程 源于工作中一个业务场景的需求。 源码 话不多说&#xff0c;先贴完整的源码&#xff1a; public class ContextPassingBetweenThread {private static ThreadLocal<String> CONTEXT new Thread…

倍福嵌入式PLC开发团队建设

倍福嵌入式PLC开发工程师确实比较难找&#xff0c;这是因为这个领域需要具备丰富的专业知识和技能&#xff0c;而且经验越丰富的工程师越难找到。以下是一些可能导致倍福嵌入式PLC开发工程师难找的原因&#xff1a; 具备相关技能的工程师数量相对较少&#xff1a;嵌入式PLC开发…

Linux平台建立GB28181设备模拟器

目录 下载模拟器解决动态库缺少问题运行模拟器抓包参考资料 在没有GB28181摄像机的情况下,在Linux虚拟机中模拟出一台GB28181摄像机用于调试和学习. 下载模拟器 到网站下载Linux 平台版本: https://www.happytimesoft.com/download.html tar -zxvf happytime-gb28181-device…

Python简介-Python入门到精通

Python的创始人为荷兰人吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;。1989年圣诞节期间&#xff0c;在阿姆斯特丹&#xff0c;Guido为了打发圣诞节的无趣&#xff0c;决心开发一个新的脚本解释程序&#xff0c;作为ABC语言的一种继承。之所以选中Python&#xff08;…

Python中元组解构的技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;元组&#xff08;tuple&#xff09;是一种常用的数据结构&#xff0c;它可以将多个值组合在一起。元组的解构是一项强大的特性&#xff0c;快速、方便地将元组中的值分配给多个变量。本文…

【电脑技巧】Win11关闭自动更新

要想彻底关闭Windows电脑的自动更新&#xff0c;仅仅从系统设置里面选择暂停更新是完全不够用的&#xff0c;只有将windows自动更新的服务关闭掉&#xff0c;才能有效阻止其更新。 关闭win11电脑自动更新的办法&#xff0c;具体操作如下&#xff1a; 1.在winr运行框中输入servi…

vivado 使用IP Integrator源

使用IP Integrator源 在Vivado Design Suite中&#xff0c;您可以在RTL中添加和管理IP子系统块设计&#xff08;.bd&#xff09;项目或设计。使用Vivado IP集成程序&#xff0c;您可以创建IP子系统块设计。IP集成程序使您能够通过实例化和将Vivado IP目录中的多个IP核互连。可…

运筹说 第56期 | 整数规划的数学模型割平面法

前几章讨论过的线性规划问题的一个共同特点是&#xff1a;最优解的取值可以是分数或者小数。然而&#xff0c;在许多实际问题中&#xff0c;决策者要求最优解必须是整数&#xff0c;例如公交车的车辆数、员工的人数、机器的台数、产品的件数等。那么&#xff0c;我们能否将得到…

Zynq7020 使用 Video Processing Subsystem 实现图像缩放

1、前言 没玩过图像缩放都不好意思说自己玩儿过FPGA&#xff0c;这是CSDN某大佬说过的一句话&#xff0c;鄙人深信不疑。。。 目前市面上主流的FPGA图像缩放方案如下&#xff1a;1&#xff1a;Xilinx的HLS方案&#xff0c;该方案简单&#xff0c;易于实现&#xff0c;但只能用…

【RTOS】快速体验FreeRTOS所有常用API(4)队列

目录 四、队列2.1 概念2.2 创建队列2.3 写队列2.4 读队列2.5 队列集&#xff08;可跳过&#xff09; 四、队列 该部分在上份代码基础上修改得来&#xff0c;代码下载链接&#xff1a; https://wwzr.lanzout.com/iBNAS1l75bvc 密码:7xy2 该代码尽量做到最简&#xff0c;不添加多…

软件技术写作指南

▲ 搜索“大龙谈智能内容”关注公众号▲ James是一位居住在苏格兰的文档工程师。 2023年12月&#xff0c;他写了一个名为《Advent of Technical Writing》的系列文章。从12月1日到12月24日&#xff0c;他每天都会在其中发表一篇关于技术写作的博客文章。现在&#xff0c;该系…

c语言for循环和水仙花

c语言for循环和水仙花 c语言for循环和水仙花 c语言for循环和水仙花一、for循环语句格式二、for循环案例水仙花 一、for循环语句格式 for(初始值&#xff1b;表达式&#xff1b;表达式) { 代码 }int main() {for (int i 0; i < 10; i){printf("%d\n", i);} }二、f…

Python数据分析案例32——财经新闻爬虫和可视化分析

案例背景 很多同学的课程作业都是需要自己爬虫数据然后进行分析&#xff0c;这里提供一个财经新闻的爬虫案例供学习。本案例的全部数据和代码获取可以参考&#xff1a;财经新闻数据 数据来源 新浪财经的新闻网&#xff0c;说实话&#xff0c;他这个网站做成这样就是用来爬虫的…