【C++】冒泡排序、选择排序、插入排序、快速排序、归并排序

引言

排序算法在计算机科学和软件开发中具有重要的地位和应用价值。以下是排序算法的重要性和难点的几个方面:

  1. 数据组织和搜索:排序算法可以对数据进行有效的组织,使得数据可以更快速地被搜索和访问。排序后的数据结构可以提高搜索算法的效率,例如二分查找等。
  2. 数据分析和统计:排序算法对于数据的分析和统计是非常关键的。通过对数据进行排序,我们可以获得有关数据的有序视图,从而更容易进行数据分析、计算统计指标和发现数据规律。
  3. 数据库操作和查询优化:数据库中的索引通常使用排序算法来实现,以提高查询操作的效率。排序算法可以用于构建和维护数据库索引,加速数据的检索和查询。
  4. 程序性能优化:排序算法是优化算法和程序性能的基础。通过选择合适的排序算法,并针对具体的应用场景进行优化,可以提高程序的执行效率和响应速度。

难点

  1. 时间复杂度和空间复杂度:排序算法的性能分析是一个复杂的问题,需要考虑时间复杂度和空间复杂度。不同的排序算法具有不同的时间和空间复杂度特性,需要根据具体的应用场景选择合适的算法。
  2. 稳定性和稳定性:一些排序算法在排序过程中可能改变相等元素的相对顺序,导致不稳定的排序结果。对于某些应用场景,需要保持相等元素的相对顺序不变,这就增加了算法设计的难度。
  3. 适应性和可扩展性:不同的排序算法对于不同的数据规模和数据分布可能具有不同的性能。设计一个适应不同数据规模和数据分布的排序算法是一项挑战性任务。
  4. 实现复杂度和优化:排序算法的具体实现涉及到算法的细节和优化技巧。一些排序算法的实现较为复杂,需要处理边界情况、优化比较和交换操作、处理大数据量等问题。

综上所述,排序算法在数据处理和程序优化中具有重要的作用,但同时也面临着时间复杂度、稳定性、适应性和实现复杂度等难点。选择合适的排序算法,并根据具体的需求进行性能优化是一个需要综合考虑多个因素的问题。

基于此,本文对常用的几种排序算法进行实现和解析。

冒泡排序

原理:

冒泡排序通过不断比较相邻的元素并交换位置,使较大(或较小)的元素逐渐向数组的末尾移动,就像气泡从水中冒出一样,因此得名。具体原理如下:

  • 从数组的第一个元素开始,依次比较相邻的两个元素,如果顺序错误,则交换它们的位置。
  • 重复进行上述步骤,直到整个数组排序完成。
  • 重复的轮数逐渐减少,每轮确定一个元素的最终位置。

复杂度:

  • 时间复杂度:最好情况下是 O(n),平均情况和最坏情况下是 O(n^2)。
  • 空间复杂度:O(1)。
  • 适用情况:适用于小型数组或基本有序的数组。

实现:

冒泡排序
#include<iostream>
#include<vector>
using namespace std;
void print(vector<int> num)
{
    
    
	int s = num.size();
	for (int i = 0; i < s; i++)
	{
    
    
		cout << num[i]<<" ";
	}
}
int main()
{
    
    
	vector<int> num;
	int n;
	int temp;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
    
    
		int p;
		cin >> p;
		num.push_back(p);
	}

	for (int i = 0; i < n; i++)
		for (int j = 0; j < n - i-1; j++)
		{
    
    
			if (num[j] > num[j + 1])
			{
    
    
				temp = num[j];
				num[j] = num[j + 1];
				num[j + 1] = temp;
			}
		}
	print(num);
	return 0;
}

以上代码通过冒泡排序实现了数组中的数由小到大重排。冒泡排序通过相邻元素的比较和交换,将较大的元素逐渐“冒泡”到数组的末尾。它是一种简单但效率较低的排序算法。

选择排序

原理:

选择排序每次从未排序的部分中选择最小(或最大)的元素,并将其放置在已排序部分的末尾。具体原理如下:

  • 将数组分为已排序和未排序两部分,初始状态下已排序部分为空。
  • 从未排序部分中找到最小(或最大)的元素,并将其与未排序部分的第一个元素交换位置。
  • 将交换后的元素归入已排序部分。
  • 重复上述步骤,直到整个数组排序完成。

复杂度:

  • 时间复杂度:平均情况、最坏情况和最好情况下都是 O(n^2)。
  • 空间复杂度:O(1)。
  • 适用情况:适用于小型数组或简单排序任务。

实现:

选择排序
#include<iostream>
#include<vector>
using namespace std;
void print(vector<int> &num)
{
    
    
	int s = num.size();
	for (int i = 0; i < s; i++)
	{
    
    
		cout << num[i] << " ";
	}
}
int main()
{
    
    
	vector<int> num;
	int n;
	int temp;
	cin >> n;
	int min = 0;
	for (int i = 0; i < n; i++)
	{
    
    
		int p;
		cin >> p;
		num.push_back(p);
	}

	for (int i = 0; i < n; i++)
	{
    
    
		min = i;
		for (int j = i; j < n ; j++)
				{
    
    
					if(num[min]>num[j])
					{
    
    
						min=j;
					}
				}
		if (i != min)
		{
    
    
			int temp = num[i];
			num[i] = num[min];
			num[min] = temp;

		}
	}
		
	print(num);
	return 0;
}

选择排序每次从未排序的部分中选择最小(或最大)的元素,并将其放置在已排序部分的末尾。它的主要优点是简单易实现。

插入排序

原理:

插入排序将数组分为已排序和未排序两部分,每次从未排序部分中取出一个元素,并将其插入到已排序部分的正确位置。具体原理如下:

  • 将数组的第一个元素视为已排序部分,其余元素为未排序部分。
  • 从未排序部分中取出第一个元素,将其与已排序部分的元素依次比较,找到合适的插入位置。
  • 将元素插入到已排序部分的正确位置,使已排序部分仍然保持有序。
  • 重复上述步骤,直到整个数组排序完成。

复杂度:

  • 时间复杂度:平均情况和最坏情况下是 O(n^2),最好情况下是 O(n)。
  • 空间复杂度:O(1)。
  • 适用情况:适用于小型数组或基本有序的数组。

实现:

插入排序
#include<iostream>
#include<vector>
using namespace std;
void print(vector<int> &num)
{
    
    
	int s = num.size();
	for (int i = 0; i < s; i++)
	{
    
    
		cout << num[i] << " ";
	}
}
int main()
{
    
    
	vector<int> num;
	int n;
	int temp;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
    
    
		int p;
		cin >> p;
		num.push_back(p);
	}

	for (int i = 1; i < n; i++)
	{
    
    
		for (int j = i-1; j>=0; j--)
		{
    
    
			if (num[j] > num[j+1])
			{
    
    
				temp = num[j];
				num[j] = num[j+1];
				num[j+1] = temp;
			}
			else break;
		}
	}
		
	print(num);
	return 0;
}

插入排序通过将数组分为已排序和未排序两部分,每次从未排序部分中取出一个元素并插入到已排序部分的正确位置。它对于小型数组或部分有序的数组表现良好。

快速排序

原理:

快速排序是一种基于分治法的排序算法。它通过选择一个基准元素,将数组分割为小于基准的部分和大于基准的部分,然后对两部分分别进行递归排序。具体原理如下:

  • 选择一个基准元素(通常为数组的第一个或最后一个元素)。
  • 将数组分割为两部分,使得左边的元素都小于基准,右边的元素都大于基准。
  • 对左右两部分分别进行递归排序,重复以上步骤。
  • 将排序后的左半部分、基准元素和排序后的右半部分合并。

复杂度:

  • 时间复杂度:平均情况下是 O(nlogn),最坏情况下是 O(n^2),最好情况下是 O(nlogn)。
  • 空间复杂度:平均情况下是 O(logn),最坏情况下是 O(n)。
  • 适用情况:适用于大型数据集和需要快速排序的情况。

实现:

快速排序
#include<iostream>
#include<vector>
#include <algorithm>
using namespace std;
void print(vector<int> &num)
{
    
    
	int s = num.size();
	for (int i = 0; i < s; i++)
	{
    
    
		cout << num[i] << " ";
	}
}

void sort(vector<int> &num, int low, int high)
{
    
    
	int i = low;
	int j = high;
	int temp = num[low];//flag
	if (i >= j)
		return;
	while (i != j)
	{
    
    
		while (num[j] >= temp && i < j)
			j--;
		while (num[i] <= temp && i < j)
			i++;
		if (i < j)
		{
    
    
			swap(num[i], num[j]);
		}
	}
	swap(num[low], num[i]);
	sort(num, low, i - 1);
	sort(num, i + 1, high);
}
int main()
{
    
    
	vector<int> num;
	int n;
	int low, high;
	low = 0;

	cin >> n;	high = n;
	for (int i = 0; i < n; i++)
		{
    
    
			int p;
			cin >> p;
			num.push_back(p);
		}
	sort(num, low, high-1);
	print(num);
	return 0;
}

快速排序通过选择一个基准元素,将数组分割为小于基准的部分和大于基准的部分,然后对两部分分别进行递归排序。它是一种高效的排序算法。

归并排序

原理:

归并排序是一种稳定的排序算法,它通过将数组递归地分成两半,对每个子数组进行排序,然后将两个有序子数组合并成一个有序数组。具体原理如下:

  • 将数组递归地分成两半,直到无法再分割(每个子数组只有一个元素)。
  • 对每个子数组进行排序,可以使用递归或其他排序算法(如插入排序)。
  • 将排序好的子数组合并,不断地将两个有序的子数组合并为一个更大的有序数组。
  • 最终得到整个数组排序完成的结果。

复杂度:

  • 时间复杂度:平均情况、最坏情况和最好情况下都是 O(nlogn)。
  • 空间复杂度:O(n)。
  • 适用情况:适用于大型数据

实现:

#include <iostream>
#include <vector>
using namespace std;

void print(vector<int>& num) {
    
    
	int s = num.size();
	for (int i = 0; i < s; i++) {
    
    
		cout << num[i] << " ";
	}
}

void merge(vector<int>& num, int first, int mid, int last, vector<int>& nums) {
    
    
	int i = first, j = mid + 1;
	int n = last;
	int k = 0;

	while (i <= mid && j <= last) {
    
    
		if (num[i] < num[j])
			nums[k++] = num[i++];
		else
			nums[k++] = num[j++];
	}

	while (i <= mid)
		nums[k++] = num[i++];
	while (j <= last)
		nums[k++] = num[j++];

	// 将排序好的子数组复制回原始数组
	for (int p = 0; p < k; p++) {
    
    
		num[first + p] = nums[p];
	}
}

void mergesort(vector<int>& num, int first, int last, vector<int>& nums) {
    
    
	if (first < last) {
    
    
		int mid = (first + last) / 2;
		mergesort(num, first, mid, nums);
		mergesort(num, mid + 1, last, nums);
		merge(num, first, mid, last, nums);
	}
}

int main() {
    
    
	vector<int> num;
	vector<int> nums;

	int n;

	cin >> n;
	for (int i = 0; i < n; i++) {
    
    
		int p;
		cin >> p;
		num.push_back(p);
		nums.push_back(0);
	}
	mergesort(num, 0, n - 1, nums);
	print(num);
	return 0;
}

归并排序分析

print 函数用于打印给定数组 num 中的元素。
merge 函数是归并排序中的关键步骤,用于将两个有序的子数组合并成一个有序数组。
    函数参数 num 是原始数组,first 是第一个子数组的起始索引,mid 是第一个子数组的结束索引,last 是第二个子数组的结束索引,nums 是辅助数组用于存储排序结果。
    首先定义变量 i 和 j 分别指向两个子数组的起始位置,k 用于遍历辅助数组 nums。
    在一个循环中,比较 num[i] 和 num[j],将较小的元素放入 nums[k],然后将相应的索引 i 或 j 增加 1。
    然后,将剩余的未遍历完的子数组中的元素依次放入 nums 中。
    最后,将排序好的子数组复制回原始数组 num 中。
mergesort 函数是归并排序的递归函数。
    函数参数 num 是原始数组,first 是第一个子数组的起始索引,last 是第二个子数组的结束索引,nums 是辅助数组用于存储排序结果。
    首先检查递归结束的条件,即当 first 小于 last 时。
    然后计算出中间索引 mid,并递归调用 mergesort 函数对左半部分和右半部分进行排序。
    最后,调用 merge 函数将排序好的子数组合并成一个有序数组。
main 函数是程序的入口。
    首先定义了两个空的整数向量 num 和 nums。
    输入变量 n 表示待排序数组的大小。
    使用循环从标准输入中读取 n 个整数,并将其添加到 num 向量中,同时将初始值 0 添加到 nums 向量中。
    调用 mergesort 函数对 num 进行归并排序,并传入辅助数组 nums。
    最后调用 print 函数打印排序后的数组。

猜你喜欢

转载自blog.csdn.net/qq_44878985/article/details/130955567