C++数据结构--排序算法

一.基础排序算法

1.冒泡排序

(1)算法思想

重复循环遍历,每次比较相邻的两个元素,如果前大于后就交换,这样每次最大的元素都会交换到最后,每次循环遍历时去掉最后的元素。

(2)代码实现

cpp 复制代码
void BubbleSort(int arr[], int size)
{
	for (size_t i = 0; i < size-1; i++)
	{
        bool flag=false;
		for (size_t j = 0; j < size - i - 1; j++) 
		{
			if (arr[j] > arr[j + 1])
			{
				int temp=arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
                flag=true;
			}

		}
        if(!flag)//优化:如果这一趟没有交换元素,说明已经有序,无需再排了
        return;
	}
}

(3)性能分析与优化

最好时间复杂度:O(n),(在加了优化操作后,如果元素原本就已经有序,则只需进行一次遍历)

平均时间复杂度:O(n^2);(双重循环)

最坏时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:稳定

**优化:**定义一个flag记录本次循环是否交换元素,如果没有则说明元素已经有序,直接结束排序。

2.选择排序

(1)算法思想

遍历数组,每次假定第i个为最小,与后面的比较,找到最小的,交换,这样每次都能将最小的元素交换到i的位置(即每次将最小的元素排到正确位置)

(2)代码实现

cpp 复制代码
void ChoiceSort(int arr[], int size)
{
	
	for (size_t i = 0; i < size-1; i++)
	{
		int min = arr[i];
		int k = i;
		for (size_t j = i+1; j < size ; j++)
		{
			if (min > arr[j])
			{
				min = arr[j];
				k = j;
			}
		}
		if (k != i)
		{
			int temp = arr[i];
			arr[i] = arr[k];
			arr[k] = temp;
		}
	}
}

(3)性能分析与优化

最好时间复杂度:O(n^2)

平均时间复杂度:O(n^2)

最坏时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定

注意:虽然冒泡排序与选择排序的平均时间复杂度都是O(n^2),但是选择排序一般更快,原因在于交换次数差异,冒泡排序的交换频繁(交换n^2次),而选择排序交换较少(n次)。

3.插入排序

(1)算法思想

假设第一个元素已经有序,每次遍历将遍历到的元素按顺序插入到前面已经有序的数组中。

(2)代码实现

cpp 复制代码
void InsertSort(int arr[], int size)
{
	for (size_t i = 1; i < size; i++)
	{
		int val = arr[i];
		int j = i - 1;
		for (; j >= 0; j--)
		{
			if (arr[j] <val)
			{
				break;
			}
			arr[j+1] = arr[j];
		}
		arr[j + 1] = val;
	}
}

(3)性能分析与优化

最好时间复杂度:O(n)(原数组已经有序,只需要进行一次遍历)

平均时间复杂度:O(n^2)

最坏时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:稳定

适用场景:数据规模小,相对有序的数据,常用于收尾工作(对于相对有序的数据排序速度较快)

4.希尔排序

(1)算法思想

希尔排序实际上是将插入排序进行分组优化。

选择合适的希尔增量,基础的为每次按半分组(即如果数组中存在10个元素,那么分成5组,每组2个元素,比较这两个元素,交换)。

(2)代码实现

cpp 复制代码
void ShellSort(int arr[], int size)
{
	for (size_t gap = size/2; gap >0 ; gap/=2)
	{
		for (size_t i = gap; i < size; i++)
		{
			int val = arr[i];
			int j = i - gap;
			for (; j >= 0; j-=gap)
			{
				if (arr[j] < val)
				{
					break;
				}
				arr[j + gap] = arr[j];
			}
			arr[j + gap] = val;

		}
	}
	
}

(3)性能分析与优化

最好时间复杂度:O(nlogn)或O(n);

平均时间复杂度:O(n^1.3),(依赖于希尔增量,如果二分的话,就为n^2)

最坏时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定

四种基础排序算法比较

一般情况下:算法效率:希尔>插入>选择>冒泡

对于趋向于有序的数组,我们使用插入排序的效率更高。

一般中等数据量的排序都用希尔排序,选择合适的增量序列,效率就已经不错了,如果数据量比较大,可以选择高级的排序算法,如快速排序。

二.高级排序算法

1.快速排序

(1)算法思想

思想:选取一个基准数,把小于基准数的元素都调整到基准数的左边,把大于基准数的元素都调整到基准数的右边,然后对基准数左边和右边的序列继续进行如下的操作,直到整个序列变成有序的。

操作流程:1:选取基准数val(默认选择第一个元素),定义L与R两个下标值,指向数组的首和尾。

2、从R开始往前找第一个<val的数字,放到L的地方,L++

3、从L开始往后找第一个>val的数字,放到R的地方,R --

4、重复上面的过程

(2)代码实现

cpp 复制代码
//快排分割函数
int Partation(int arr[], int L, int R)
{
	int val = arr[L];
	while (L < R)
	{
		while (arr[R]>val&&L<R)
		{
			R--;
		}
		if (L < R)
		{
			arr[L] = arr[R];
			L++;
		}
		while (arr[L] < val && L < R)
		{
			L++;
		}
		if (L < R)
		{
			arr[R] = arr[L];
			R--;
		}
	}
	arr[L] = val;
	return L;
}

//快排函数接口
void QuickSort(int arr[], int L, int R)
{
	if (L >= R)
		return;
	int pos = Partation(arr,  L,  R);
	
	//对分割后的两部分分别进行快排
	QuickSort(arr, L, pos - 1);
	QuickSort(arr, pos+1, R);

}

//快排函数
void QuickSort(int arr[],int size)
{
	return QuickSort(arr,0,size-1);
	
}

(3)性能分析与优化

最好时间复杂度:O(nlogn),递归深度最好为log(n)(完全二叉树),比较过程为O(n);

平均时间复杂度:O(nlogn);

最坏时间复杂度:O(n^2)(数据完全有序且恒选择第一个元素作为基准数,会退化为冒泡排序);

空间复杂度:最好O(nlogn),最坏O(n^2)(数据完全有序且恒选择第一个元素作为基准数)

稳定性:不稳定

快排算法优化

1、随着快排算法执行,数据越来越趋于有序,在一定的范围内,可以采用插入排序代替快速排序

2.选择合适的基准数,三数取中法(L,R,mid三数值在中间的),随机数法。

适用场景:绝大多数通用场景,尤其是处理大量随机元素,标准库的默认排序。

2.归并排序

(1)算法思想

归并排序的核心是分治思想,先递,递到只有一个元素,再在归的过程中,对数据进行合并排序

(2)代码实现

cpp 复制代码
//归并过程函数
void Merge(int arr[], int l, int m, int r)
{
	int* p = new int[r - l + 1];
	int idx = 0;
	int i = l;
	int j = m + 1;
	while (i <= m && j <= r)
	{
		if (arr[i] <= arr[j])
		{
			p[idx++] = arr[i++];
		}
		else
		{
			p[idx++] = arr[j++];
		}
		
	}
	while (i <= m)
	{
		p[idx++] = arr[i++];
	}
	while (j <= r)
	{
		p[idx++] = arr[j++];
	}
	//在不合并好的数据拷贝到原始数组中
	for (size_t i = l,j=0; i <=r ; i++,j++)
	{
		arr[i] = p[j];
	}
	delete[]p;
	p = nullptr;

}

//归并排序
void MergeSort(int arr[], int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	//先递归
	MergeSort(arr, begin, mid);
	MergeSort(arr, mid + 1, end);
	//再归并
	Merge(arr, begin, mid, end);
}

(3)性能分析与优化

最好时间复杂度:O(nlogn)

平均时间复杂度:O(nlogn);

最坏时间复杂度:O(nlogn)(无论原先元素是否有序,递归的深度恒是nlogn)

空间复杂度:O(n)

稳定性:稳定

适用场景:对稳定性有要求,或者处理链表排序、海量数据的外部排序时,归并排序是首选。

3.堆排序

(1)算法思想

找到每一个非叶子节点,将二叉堆调整为一个大根堆,之后将堆顶元素与队尾元素交换,由于大根堆堆顶是恒是最大值,所以每一次调整都将一个最大值交换到了一个正确位置。

(2)代码实现

cpp 复制代码
//堆排下沉操作
void siftDown(int arr[], int i, int size)
{
	int val = arr[i];
	while (i < size  / 2)
	{
		int child = 2 * i + 1;
		if (child + 1 < size && arr[child + 1] > arr[child])
		{
			child = child + 1;
		}
		if (val < arr[child])
		{
			arr[i] = arr[child];
			i = child;
		}
		else
			break;
	}
	arr[i] = val;
}

//堆排序
void HeapSort(int arr[], int size)
{
	int n = size - 1;
	//找到每一个非叶子节点
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		siftDown(arr, i, size);
	}
	for (size_t i = n; i > 0; i--)
	{
		int temp = arr[0];
		arr[0] = arr[i];
		arr[i] = temp;

		siftDown(arr, 0,i);
	}
}

(3)性能分析与优化

最好时间复杂度:O(nlogn)

平均时间复杂度:O(nlogn);

最坏时间复杂度:O(nlogn)

空间复杂度:O(1)

稳定性:不稳定

适用场景:内存极其受限的嵌入式系统,或者经典的 Top K 问题

4.基数排序(桶排序)

(1)算法思想

先构建桶(没有负数构建0~9号桶,有负数构建-9~9号桶),从右向左依次看元素的每一位,如果那一位与桶的序号相同,就将其放在对应的桶中,放完一位后,再将所有的元素按桶的顺序取出,如此循环,直到排完所有的位后。

(2)代码实现

cpp 复制代码
//基数排序
void RadixSort(int arr[], int size)
{
	int maxData = arr[0];
	for (size_t i = 1; i < size; i++)
	{
		if (maxData<abs(arr[i]))
		{
			maxData = abs(arr[i]);
		}
	}
	int len = to_string(maxData).size();
	vector<vector<int>>vecs;
	int mod = 10;
	int dev = 1;
	for (size_t i = 0; i < len; mod*=10,dev*=10,i++)
	{
		vecs.resize(20);
		for (size_t j = 0; j < size; j++)
		{
			int index = arr[j] % mod / dev+10;
			
			vecs[index].push_back(arr[j]);
		}
		int idx = 0;
		for (auto vec : vecs)
		{
			for (int v:vec)
			{
				arr[idx++] = v;
			}
		}
		vecs.clear();
	}

(3)性能分析与优化

最好时间复杂度:O(nd)(d为最大数据的长度)

平均时间复杂度:O(nd);

最坏时间复杂度:O(nd)

空间复杂度:O(n)(O(n+k))(k为桶/基数的个数)

稳定性:稳定

适用场景:整数、固定长度的字符串排序(适用范围较窄,在处理海量整数时,速度可以碾压前面三种 O(n log n) 的算法)

四种高级排序算法的比较

对于一般无序的大规模数据一般:快速排序 > 归并排序 ≈ 堆排序 > 基数排序

相关推荐
jieyucx1 小时前
Go 切片核心:子切片详解(下篇)
开发语言·算法·golang·切片
CQU_JIAKE2 小时前
5.5【A】
算法
云qq2 小时前
C++ 原子操作
开发语言·c++·算法
xrgs_shz2 小时前
基于轻量化浅层卷积神经网络的手写数字识别
算法·matlab·cnn
许彰午2 小时前
02-手写链表、栈、队列——不依赖任何集合框架
数据结构·链表·面试
MegaDataFlowers2 小时前
141.环形链表
数据结构·链表
计算机安禾2 小时前
【计算机网络】第10篇:距离矢量路由算法——Bellman-Ford方程与RIP协议的特性分析
计算机网络·算法
机器学习之心2 小时前
基于开普勒优化算法(KOA)优化CNN-BiGRU-Attention混合网络的时间序列预测模型,MATLAB代码
算法·时间序列预测模型·开普勒优化算法
Java成神之路-3 小时前
【LeetCode 刷题笔记】367.有效的完全平方数 | 二分查找经典刷题题解
算法·leetcode