十大排序算法

十大排序

image-20220720193205887

总结对比

排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 排序方式 稳定性
冒泡排序 O(n2) O(n) O(n2) O(1) In-place 稳定
选择排序 O(n2) O(n2) O(n2) O(1) In-place 不稳定
插入排序 O(n2) O(n) O(n2) O(1) In-place 稳定
希尔排序 O(n log n) O(n log2 n) O(n log2 n) O(1) In-place 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) Out-place 稳定
快速排序 O(n log n) O(n log n) O(n2) O(log n) In-place 不稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) In-place 不稳定
计数排序 O(n + k) O(n + k) O(n + k) O(k) Out-place 稳定
桶排序 O(n + k) O(n + k) O(n2) O(n + k) Out-place 稳定
基数排序 O(n x k) O(n x k) O(n x k) O(n + k) Out-place 稳定

相关术语解释:

  • 稳定:如果 a 原本在 b 前面,而 a=b,排序之后,a 仍然在 b 的前面
  • 不稳定:不满足稳定定义
  • 内排序(In-place):所有排序操作都在内存中完成
  • 外排序(Out-place):由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
  • 时间复杂度:一个算法执行所耗费的时间
  • 空间复杂度:运行完一个程序所需内存的大小
  • n:数据规模
  • k:「桶」的个数
  • In-place:不占用额外内存
  • Out-place:占用额外内存

转载(里面也有对每个算法的总结、思路分析)

冒泡排序

每次循环都能找到数组中最大值冒泡至最后一位,以此达到排序的目的

优化:如果循环一遍没有发生值的交换,表明数组已经有序,便直接退出函数

#include<iostream>
using namespace std;

void swap(int &a, int &b) {
	a ^= b;
	b ^= a;
	a ^= b;
}

//冒泡排序
void bubbleSort(int *arr, int length) {
	//传入的数组长度小于2 
	if(length < 2) return;
	
	for(int i = 0; i < length - 1; i++) {
		//记录在一次内循环中是否发生了一次数的交换
		bool flag = false;
		//注意这里lenght-i,因为每一轮排序都会将最大值冒泡到最后的位置,它就不参与排序 
		for(int j = 1; j < length - i; j++) {
			if(arr[j - 1] > arr[j]) {
				swap(arr[j - 1], arr[j]);
				flag = true;	//表示发生了交换,说明数组还未排序完成 
			}
		}
		//在一次内循环中没有发生值的交换,说明以及排序完成,退出循环 
		if(flag == false) {
			break;
		}
	}
} 

int main() {
	int arr[8] = {1, 56, 3, 2, 8, 3, 6, 7};
	int length = sizeof(arr) / sizeof(arr[0]);
	bubbleSort(arr, length);
	for(int i = 0; i < length; i++) {
		cout<<arr[i]<<" ";
	}
	return 0;
}

选择排序

单元选择排序:每次循环都找到数组中的最小值(最大值),将其与首元素(尾元素)交换位置

双元选中排序:每次循环都找到数组中的最小值和最大值,将其与首元素和尾元素交换位置

//单元选择排序
void selectSort(int arr[], int length) {
	if(length < 2) return;
	
	for(int i = 0; i < length - 1; i++) {
		//记录最小元素下标 
		int minIndex = i;
		for(int j = i + 1; j < length; j++) {
			//找到小的数,改变下标 
			if(arr[j] < arr[minIndex]) minIndex = j;
		}
		//如果存在最小值 
		if(minIndex != i) swap(arr[i], arr[minIndex]);
	}
	return ;
}

//双元选择排序 
void selectSort(int arr[], int length) {
	for(int i = 0; i < length; i++) {
		int minIndex = i;
		int maxIndex = length - i - 1;
        //遍历整个数组
		for(int j = i ; j < length - i ; j++) {
			if(arr[j] > arr[maxIndex]) maxIndex = j;
			if(arr[j] < arr[minIndex]) minIndex = j;
		}
		if(maxIndex == minIndex) break;
		if(minIndex != i) swap(arr[minIndex], arr[i]);
		//当最大值与最小值在两端时,上面已经交换过了,所以将maxIndex赋值,不在执行下面的交换两者的值 
		if(maxIndex == i) maxIndex = minIndex;
		if(maxIndex != length - i - 1) swap(arr[maxIndex], arr[length - i - 1]);
	}
	return;
}

插入排序

从第二个数a[1]开始,每次循环指针依次向后移一位。如果a[i]前面存在比它大的数,那么从比它大的数的位置起依次向后移动一位,移动完毕再将a[i]插入它该插入的位置。

//简单插入排序
void insertSort(int arr[], int length) {
	if(length < 2) return ;
	
	for(int i = 1; i < length; i++) {
		int temp = arr[i];
		int j = i - 1;
		//从后往前遍历 ,因为是从第二个数开始比较的,所以前面的都是有序的 
		for(; j >= 0; j--) {
			//如果temp比前面的数小,那么temp前面的数向后移动 
			if(temp < arr[j]) arr[j + 1] = arr[j];
			else break;
		}
		//发生了移动,将待填充的位置填入temp 
		if(j != i - 1) arr[j + 1] = temp;
	}
	return;
}

//折半插入排序
void insertSort(int arr[], int length) {
	if(length < 2) return ;
	for(int i = 1; i < length; i++) {
		//如果顺序正确,直接跳过 
		if(arr[i - 1] < arr[i]) continue;
		int temp = arr[i];
		int left = 0, right = i - 1;
		//二分查找 ,找到temp应该插入的位置 
		while(left <= right) {
			int mid = (left + right) / 2;
			if(temp >= arr[mid]) left = mid + 1;
			else right = mid - 1;
		}

		for(int j = i; j > left; j--) {
			arr[j] = arr[j - 1];
		}
		arr[left] = temp;
	}
	return ;
}

快速排序

标准快排

#include<iostream>
using namespace std;

void quickSort(int a[], int l, int r) {
	int i, j, temp;
	if(l < r) {
		i = l, j = r;
		temp = a[i];	//将第一个元素保存 
		while(i < j) {
			while(i < j && a[j] >= temp) j--;	//从右向左找到第一个小于temp的数
			a[i] = a[j];	//移动到a[i],
			while(i < j && a[i] <= temp) i++; //从左向右找到第一个大于temp的数
			a[j] = a[i];
		}
		a[i] = temp;
		quickSort(a, l, i - 1);
		quickSort(a, i + 1, r);
	}
}

int main() {
	int a[] = {30,40,60,10,20,50};
	int len = sizeof(a) / sizeof(a[0]);
	quickSort(a, 0, len - 1);
	for(int i = 0; i < len; i++) {
		cout<<a[i]<<" ";
	} 
	return 0;
}

取随机数为基准点的快排,比选第一个元素为基准点快,一般用这个,或者三路快排

include<stdlib.h>

要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;

题目

快排教程

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

const int maxn = 1e5 + 10;
int a[maxn];

void quickSort(int a[], int l, int r) {
	int i, j, temp;
	if(l < r) {
		i = l, j = r;
		//快排优化:随机生成一个[l, r]区间的下标,将该下标的值与a[i]交换 
		int t = (rand() % (r - l + 1)) + l;	//[r, l]的随机数 
		swap(a[i], a[t]);
		temp = a[i];	//将第一个元素保存 
		while(i < j) {
			while(i < j && a[j] >= temp) j--;	//从右向左找到第一个小于temp的数
			a[i] = a[j];	//移动到a[i]
			while(i < j && a[i] <= temp) i++; //从左向右找到第一个大于temp的数
			a[j] = a[i];
		}
		a[i] = temp;
		quickSort(a, l, i - 1);
		quickSort(a, i + 1, r);
	}
}

int main() {
	int n;
	cin>>n;
	for(int i = 0; i < n; i++) {
		cin>>a[i];
	}
	int t = 0;
	//特判是否已经排好序,不然会tle最后一个点 
	while(a[t] <= a[t + 1] && t < n - 1) t++;
	if(t == n - 1);
	
	else quickSort(a, 0, n - 1);
	for(int i = 0; i < n; i++) {
		cout<<a[i]<<" ";
	}
	return 0;
}

归并排序

参考链接

#include<iostream>
using namespace std;

//合并 
void Merge(int a[], int l, int mid, int r) {
	int i = l, j = mid + 1, k = 0;	//i表示第一个区间的第一个元素,j表示第二个区间第一个元素 
	int *temp = new int[r - l + 1];	//辅助数组
    while(i <= mid && j <= r) {
        //放入小一点的元素
    	if(a[i] <= a[j]) temp[k++] = a[i++];
    	else temp[k++] = a[j++];
	}
	//合并左右半区剩余元素
	while(i <= mid) {
		temp[k++] = a[i++];
	}
	while(j <= r) {
		temp[k++] = a[j++];
	}
	//将辅助数组赋值到a[]
	for(i = l, k = 0; i <= r;i++, k++) {
		a[i] = temp[k];
	}
	delete []temp;	//释放内存 
}

//递归分块
void MergeSort(int a[], int l, int r) {
	if(l == r) return;	//当子序列长度为1时,结束 
	//有时候相加可能超过int范围可以使用mid = l + (r - l) / 2 
	int mid = (l + r) / 2;
	MergeSort(a, l, mid);
	MergeSort(a, mid + 1, r);
	Merge(a, l, mid, r);	//合并 
}
 
int main() {
    int a[8] = {3,1,2,4,5,8,7,6};
    MergeSort(a, 0, 7);
    for(int i = 0; i < 8; i++)
        cout<<a[i]<<" ";
}

堆排序

先构建一个正经大根堆,再依次将堆顶最大元素和尾部元素交换,这样尾部元素就是最大值,同时将尾部剔除堆,再进行一次大根堆的构建...依次进行到最后,就变成升序了。

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

//构建大根堆
void max_heap(int a[], int start, int end) {	//该大根堆从下标0开始算 
	int dad = start;
	int son = dad * 2 + 1;	//左子节点 
	while(son <= end) {
		if(son + 1 <= end && a[son] < a[son + 1]) {	//找到两个子节点中最大值 
			son++;
		}
		if(a[dad] > a[son]) return ;
		else {
			swap(a[dad], a[son]);	//将大的值上浮
			dad = son;
			son = dad * 2 + 1; 
		}
	}
}

//程序入口
void heap_sort(int a[], int len) {
	for(int i = len / 2 - 1; i >= 0; i--) {	//i从最后一个根节点开始 
		max_heap(a, i, len);
	}
	for(int i = len - 1; i >= 1; i--) {
		swap(a[0], a[i]);
		//每次循环都踢出去最后一个元素,因为它已经说最大的了
		max_heap(a, 0, i - 1); 	//[0, i - 1] 
	}
} 

int main() {
	int a[5] = {2, 1, 5, 3, 4};
	int len = sizeof(a) / sizeof(a[0]);
	heap_sort(a, len);
	for(int i = 0; i < len; i++) {
		cout<<a[i]<<" ";
	}
	return 0;
}

希尔排序

希尔排序是在插入排序的基础上,进行了多次插入排序,每一次都会使数组更加有序。

思路:根据增量分组,每个相邻组的值一一对应,如果左边组对应的值大于右边,则进行一次swap

参考链接1

参考链接2

#include<iostream>
using namespace std;

void shell_sort(int a[], int len) {
	for(int g = len / 2; g > 0; g /= 2) {
		for(int i = g; i < len; i++) {
			int j = i;
			while(j - g >= 0 && a[j] < a[j - g]) {
				swap(a[j], a[j - g]);
				j -= g;
			}
		}
	}
	
} 

int main() {
	int a[7] = {2, 9, 1, 3, 5, 3, 6};
	shell_sort(a, sizeof(a) / sizeof(a[0]));
	for(int i = 0; i < 7; i++) {
		cout<<a[i]<<" ";
	}
	return 0;
}

计数排序

思想很简单,即用一个数组c存储待排序数组a的每一个值并计数,用数组a的值作为数组c的下标并累加。

例如:a[2, 1, 2, 2] --> c[1] = 1, c[2] = 3

参考链接

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

int Max = INT_MIN;
int Min = INT_MAX;

void maxn(int a[], int len) {	//找最大值 
	for(int i = 0; i < len; i++) {
		if(a[i] > Max) Max = a[i];
		if(a[i] < Min) Min = a[i];
	}
} 

int main() {
	int a[7] = {2, 9, 1, 3, 5, 3, 6};
	int len = sizeof(a) / sizeof(a[0]);
	maxn(a, len);
	int c[Max - Min + 1];	//计数数组 
	memset(c, 0, sizeof(c));
	//计数 
	for(int i = 0; i < len; i++) {
		c[a[i] - Min]++;	//[0, Max - Min]
	}
	//记录结果
	int res[len];
	int index = 0;
	for(int i = 0; i < (Max - Min + 1); i++) {
		while(c[i] > 0) {
			res[index++] = i + Min;
			c[i]--;
		}
	}
	//输出
	for(int i = 0; i < len; i++) {
		cout<<res[i]<<" ";
	} 
	return 0;
}

桶排序

按极值(最大值 - 最小值 + 1),决定桶的数量,

每个数为 / 10就是该数应放入的桶的编号

将不同数放入各自桶后再对各自桶内的数排序,这样将每个桶合并就是有序的了

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

int mmax = INT_MIN;
int mmin = INT_MAX;

void Count(int a[], int len) {
	for(int i = 0; i < len; i++) {
		if(a[i] > mmax) mmax = a[i];
		if(a[i] < mmin) mmin = a[i];
	}
}

int main() {
	int a[]= {1,8,7,44,42,46,38,34,33,17,15,16,27,28,24};
	int len = sizeof(a) / sizeof(a[0]);
	Count(a, len);
	//极值分别为1和46,平均一个桶放10个,所以定义5个桶 
	int n = mmax / 10 - mmin / 10 + 1;
	vector<vector<int>> t(n);	//初始化为n行的二维数组 
	for(int i = 0; i < len; i++) {	//遍历每个数,放入对应的桶内 
		int temp = a[i] / 10;
		t[temp].push_back(a[i]);
	}

	for(int i = 0; i < n; i++) {	//遍历每个桶 
		if(t[i].size() <= 1) continue;
		sort(t[i].begin(), t[i].end());	//sort排序每个桶 
	}
	int index = 0;
	for(int i = 0; i < n; i++) {	//将排序后的桶依次输出 
		for(int j = 0; j < t[i].size(); j++) {
			if(t[i].size() == 0) continue;
			a[index++] = t[i][j];
		}
	}
	//输出结果 
	for(int i = 0; i < len; i++) {
		cout<<a[i]<<" ";
	}
	return 0;
}

基数排序

思路

用10个桶,编号分别是0 ~ 9,每个桶大概放不确定个元素,数据多每个桶就开大一点

先获取数据中的最大值,最大值的位数digits就是我们要循环的次数。每次循环都要对每个数 / n % 10取整,这样经过第一轮入桶,所有的个位数虽然在不同的桶内,但是从左到右每个桶来看就是有序的了。

再进行一次循环对每个数 / n % 10取整,个位数都依次放入了0号桶且0号桶的个位数都是按顺序放入的,十位数的结果1 ~ 9也分别放入对应的编号桶中...这样直到循环到最后对最高位入桶,所有的数就都是有序的了

参考链接

#include<iostream>
using namespace std;

int t[10][7];	//桶[0, 9]
int l[10];	//记录每个桶的长度 

int m(int a[]) {	//获取数组最大值 
	int mmax = INT_MIN;
	for(int i = 0; i < 7; i++) {
		if(a[i] > mmax) mmax = a[i];
	}
	return mmax;
}

int main() {
	int a[7] = {2, 221, 21, 122, 12, 78, 1};
	int mmax = m(a);
	//最大值的位数就是要排序的轮数
	int digits = 0;
	while(mmax != 0) {
		mmax /= 10;
		digits++;
	}
	
	for(int j = 0, n = 1; j < digits; j++, n *= 10) {
		for(int i = 0; i < 7; i++) {
			//比如第一轮2放入2号桶,所以l[2]++;
			int temp = a[i] / n % 10;
			t[temp][l[temp]++] =  a[i];
		}
		//因为每循环一次,桶里的数据会被覆盖,所以我们每循环一次都要将桶的数直接存起来 
		int index = 0;
		for(int i = 0; i < 10; i++) {	//遍历每个桶 
			if(l[i] == 0) continue;	//桶没有元素
			for(int k = 0; k < l[i]; k++) {
				a[index++] = t[i][k];
			}
			l[i] = 0;	//赋值结束初始化每个桶长度 
		}
	}
	for(int i = 0; i < 7; i++) {
		cout<<a[i]<<" ";
	}
	return 0;
}
posted @ 2024-03-13 18:17  Liu-RC  阅读(27)  评论(0)    收藏  举报