常见排序总结


/*void swap(vector<int>& arr,int a,int b){
	if(a == b) return;
	arr[a] = arr[a]^arr[b];
	arr[b] = arr[a]^arr[b];
	arr[a] = arr[a]^arr[b];
	//现在有i,j,i=i^j; j = (i^j)^j = i; i = (i^j)^i=j,就实现了交换。
	//但是有一个致命缺点,若两个数来自同一个地址,则会清零。
}*/
//补充几个异或规则,
//	0 ^ m = m;
// a ^ b ^ c = a ^ c ^ b;异或与顺序无关
// a ^ a = 0;
void swap(vector<int>& arr,int a,int b){
	int tem = arr[a];
	arr[a] = arr[b];
	arr[b] = tem;
}

①冒泡排序

时间复杂度O(N^2);空间复杂度O(1),稳定
算法思想(从小到大排序):1.首先m等于数组大小n;2.对前m个数进行检查,从前往后,若相邻两个元素值,前面大于后面,则交换,经过一轮,最大值会来到最后一个位置上,3.令m等于m-1,重复上述步骤2,直到m等于1;
每一轮会将本轮范围类最大数放在最后。经过n-1次就可以排序完成

void BubbleSort(vector<int>& arr) {
	if (arr.size() < 2)	return;
	for (int i = arr.size(); i > 1; i--) {
		for (int j = 1; j < i; j++) {
			if (arr[j - 1] > arr[j])	
				swap(arr, j - 1, j);
		}
	}
}

②选择排序

时间复杂度O(N^2);空间复杂度O(1),不稳定
算法思想(从小到大排序):1.首先i=0;2.从索引i开始,依次遍历完后续数组索引,若遇到比索引i上值还小的,则交换两者,经过一轮,则最小值会来到索引i上,3.索引i=i+1,在重复上面步骤2,直到i到达数组末尾;
其实和冒泡排序很像,即一次排好一个数字,冒泡排序每次将最大值放在最后,而选择排序则是将最小值放到本轮最前面。

void SelectSort(vector<int> &arr){
	if(arr.size()<2)	return;
	for(int i = 0; i< arr.size()-1;i++){
		for(int j = i+1;j<arr.size();j++){
			if(arr[j] < arr[i])	swap(arr,j,i);
		}
	}
}

③插入排序

时间复杂度O(N^2);空间复杂度O(1),不稳定
算法思想:假设前n-1个数已经有序,现在插入第n个数,判断第n个数应该插入的位置,插入后,插入位置后面的数依次后移,则前n个数就有序,开始判断第n+1个数应该插入的位置。

void InsertSort(vector<int> & arr){
	if(arr.size() < 2) return;
	for(int i = 1;i<arr.size();i++){//默认一个有序,i的作用是将第i个数插入到前面i-1个已经排好序的数组中
		for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--){//判断第i个数应该插入哪里,因为插入到数组中,插入位置后面的元素以此后移,故等同于从后往前依次交换
			swap(arr,j,j+1);
		}
	}
}

④并归排序

时间复杂度O(N *logN);空间复杂度O(N),稳定
算法思想:现在得到两个分别排好序的数组,怎么合成为一个有序的数组;假设数组a = {1,3,5,7,9}; b = {2,3};首先申请一个大小为a.size()+b.size()的数组c; c[i++] = a[j]>b[k]? b[k++]:a[j++]; i,j,k 都是从零开始,直到,j,k有一个等于他们的数组大小,停止,此时c = {1,2,3,3};将另一个没有到达数组大小的后序元素全部添加到c中,这样c就有序了,此时c = {1,2,3,3,5,7,9}。

//在并归排序中,一般操作的是一个数组,故我们可以用双指针实现分块,为了效率,一般就是二分。
//将合并过程封装到函数merge中
void Merge(vector<int>& arr, int L,int Mid,int R){//即有序数组分别是arr上的区域[L,Mid]和[Mid+1,R] ,注意都是闭区间
	vector<int> tem(R-L+1,0);//相当于申请了一块R-L+1大小的数组
	int l = L, r = Mid + 1, i = 0;
	while(l <= Mid && r <= R){
		tem[i++] = arr[l] > arr[r] ? arr[r++]:arr[l++];
	}
	while(l<=Mid)
		tem[i++] = arr[l++];
	while(r<=R)
		tem[i++] = arr[r++];
	for(i =0;i< R-L+1;i++)
		arr[L+i] = tem[i];
}
void Process(vector<int> &arr,int L,int R){//对数组L-R上进行排序,注意是闭区间
	if(L ==R)	return;
	int Mid = L + ((R-L)>>1);//不使用 Mid =(R+L)/2;是因为R+L可能会越界
	Process(arr,L,Mid);//对左半区间排序
	Process(arr,Mid+1,R);//对右半区间排序
	Merge(arr,L,Mid,R);//合并有序的两部分为有序的一部分
}
void MergeSort(vector<int>& arr){
	if(arr.size()<2)	return;
	Process(arr,0,arr.size()-1);
}

⑤快速排序

时间复杂度O(N*logN)空间复杂度O(logN),不稳定
算法思想:这里讨论的是高阶版本的快排,即给你一个数组,将数组最后一个元素作为基准,小于该元素的放数组前面,等于该元素的放数组中间,大于该元素的放后面,则等于该元素的确定下来的位置也是数组有序后该元素的位置,故快排一次可以将等于数组最后一个元素的值的位置确定下来,并且缩小了不等于该元素的元素的位置(使得它们接近了数组排序后他们的位置)。下面实现怎么样将数组按照,小于某个数,等于某个数,大于某个数存放。

//将上述过程封装到Sort函数中
vector<int> Sort(vector<int>& arr,int L,int R){//对数组arr上L-R闭区间上实现该操作,返回值是个大小为2的数组,第一个元素返回排序后第一个等于arr[R]索引减1,第二个元素返回排序后最后一个等于arr[R]索引+1
	int l = L-1, r = R + 1;//l记录的是小于arr[R]的索引上限,r记录的是大于arr[R]的索引下限。
	int c = arr[R];//因为在下面循环过程中索引R上的元素会发生变化,故先保存进c中。
	while(L< r){
		if(arr[L] < c){
			swap(arr,L++,++l);//将该元素与小于c得索引上限+1的值交换,交换完,该位置的元素一定是小于等于c的,故不用再判断,L++即可,同时,小于c的索引上限也会加一,在代码中已经体现,
		}
		else if(arr[L] > c){
			swap(arr,L,--r);//将该元素与大于c的索引下限-1的位置交换,交换完后,该位置的元素是大于,等于还是大于c是不确定的,故该位置还需要判断,故L不进行操作,大于c的索引下限减一;
		}
		else
			L++;//对应等于c的子这里不进行操作,对应等于的值在小于时,会同时被操作
	}
	return {l,r}; //返回小于c的索引上限,和大于c的索引下限,则arr[l+1] 是等于 c,arr[r-1]也是等于c
}
void QuickSort(vector<int> &arr,int L,int R){
	if(L >= R)	return;//必须是大于等于对于数组[1,2],执行Sort函数返回[0,2], 	QuickSor(arr,0,0),QuickSort(arr,2,1)---若写等于就会出错
	//swap(arr,随机索引,R);随机交换最后一个元素,因为若不这样做,可以人为设计一些状况的数据,使得快排效果最差
	vector<int> tem = Sort(arr,L,R);
	QuickSort(arr,L,tem[0]);
	QuickSort(arr,tem[1],R);
}
void QuickSort(vector<int> &arr){
	if(arr.size()<2)	return;
	QuickSort(arr,0,arr.size()-1);
}

⑥堆排序

时间复杂度 O(N*logN);空间复杂度O(1),不稳定
知识点:完全二叉树的每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。这里我们使用大根堆为例,我们可以按照层次遍历的顺序将节点值放入对应的数组中。那我们可以发现,节点i会放入对应索引i的数组中。则节点i的左孩子对应索引为2i+1,右孩子2i+2,对应的父亲节点是(i-1)/2;对于堆有两个重要操作,即插入节点操作,和修改节点的值。
算法思想:首先将待排序数组构造成大根堆,则大根堆的索引0上元素一定是当前数据中的最大值。故交换索引0和大根堆最后一个一个,则当前最大值就会来到数组最后,然后堆的大小减一,然后在构造大根堆,在交换,一直重复。

//将一个大小已知的数组变成大根堆

/*HeapIfy函数是确定索引为index的元素在大根堆中的位置,即判断它与子节点的关系,若它大于子节点,则当前就是他的位置,若小于子节点,就将它与最大的子节点交换,
然后判断交换后的索引和其子节点的关系,直到索引的元素值大于子节点为止.这里只判断该节点与子节点的大小关系,而不去判断该节点与父节点的关系。*/
void HeapIfy(vector<int> &arr, int index, int heapsize) {
	int left = 2 * index + 1;
	while (left < heapsize) {
		int Max = (left + 1 < heapsize && arr[left + 1] > arr[left]) ? left + 1 : left;
		Max =arr[Max]> arr[index] ? Max:index;
		if (Max == index)	return;
		swap(arr, index, Max);
		index = Max;
		left = 2 * index + 1;
	}
}
/*排序,首先生成大根堆,即从后往前依次确定元素应该所处的位置。然后在交换索引0和最后一个元素,在生成索引0上元素的大根堆,
将大根堆大小减一,继续上述步骤,直到大根堆大小为1为止。*/
void HeapSort(vector<int> &arr) {
	if (arr.size() < 2)	return;
	for (int i = arr.size() - 1;i>=0; i--) {
		HeapIfy(arr, i, arr.size());
	}
	int size = arr.size();
	while (size > 1) {
		swap(arr, 0, --size);
		HeapIfy(arr, 0, size);
	}
}

⑦桶排序

思想:首先计算出待排序数组的最大最小值,然后将其进行划分为k个区域,即每个区域算是一个桶,当区域大小比较小的时候,在每个桶可以使用快排等排序算法。

vector<int> BucketSort(vector<int> vec) {
	int Max = INT_MIN;
	int Min = INT_MAX;
	for (int v : vec) {
		Max = std::max(Max, v);
		Min = std::min(Min, v);
	}
	int bucket_size = std::min(10, Max - Min + 1);
	int lev = (Max - Min + 1) / bucket_size + ((Max - Min + 1) % bucket_size)?1:0;//每个桶的数据量
	vector<vector<int>> tem(bucket_size);
	for (int v : vec) {
		tem[(v - Min) / lev].push_back(v);
	}
	int i = 0;
	for (int j = 0; j < bucket_size; j++) {
		QuickSort(tem[j]);//这里的快排应该是传引用才行,不然得接收一下
		for (int k = 0; k < tem[j].size(); k++,i++) {
			vec[i] = tem[j][k];
		}
	}
	return vec;
}

⑧ 计数排序

桶数量等于最大值-最小值+1的桶排序。

⑨基数排序

时间复杂度O(N)
算法思想:首先对应正整数排序,准备10个桶,即一个大小为10的数组,每一个数组位置是队列。首先计算一下待排序的数组的最大值是几位数(假设是m位),首先求出数组中每个元素的个位,按照个位数字依次放入对应的桶中,然后从左往右依次取出,则此时待排序数组的顺序按照个位数字从小到大排列。然后按每个元素的十位进桶,在依次取出,此时会按照十位进行排序,重复上面步骤直到m,则排序完成。
但是这样有点麻烦,每次记录的数据比较多。我们可以同过一个统计数组来实现桶的功能,首先还是申请一个大小为10的统计数组count,首先计算一下待排序的数组的最大值是几位数(假设是m位),从左往右依次按照个位,十位遍历数组,遍历遇到几,则统计数组的对应索引加一,遍历完成后,就记录了该位上每个数组出现的次数,然后从左到右依次,统计数组count[i] +=count[i-1];则此时count[i]表示小于等于i的元素个数有几个。在依次从右往左判断待排序数组的每个元素,假设最后一个元素为23,现在按个位排序,则我们可以去看count[3] 等于多少,假设为2,则说明小于等于3的有2个,而23是最右侧的,故可以确定此轮排序23应该在索引2-1=1的位置,然后将count[3]–,在判断前一个元素。

/*
假设现在有待排序数组arr={11,12,1,2,33,13},同时声明一个大小相等的数组tem,作为辅助数组。
首先按照个位:count = {0,2,2,2,0,0,0,...}; 然后 count = {0,2,4,6,6,6,....,6};
从待排序的数组从右往左开始,首先是13,我们可以看到此时count[3] = 6,故tem[5] = 13,,count[3]-- ;  
其次是33,此时count[3] =5,故tem[4] = 33,count[3]--; 
然后是2,此时count[2] = 4,故tem[3] = 2,count[2]--; 
其次是1,count[1] = 2,故tem[1] =1; 
同理可以得到tem = {11,1,12,2,33,13} 将其赋值给待排序数组arr,
在按照十位进行排序,重复上面步骤,直到排完待排序数组最大值的最高位为止。*/
int maxbits(vector<int>& arr) {//返回数组的最大位数
	int max = INT_MIN;
	for (int i : arr)	max = ::max(max, i);
	int res = 0;
	while (max > 0){
		res++;
		max=max/10;
	}
	return res;
}
int getdigit(int a, int num) {//a是要操作的数字,num是指定取出哪一位上的数字,返回对应位置上的数字
	int res = 0;
	while (num-- != 0) {
		res = a % 10;
		a = a/10;
	}
	return res;
}
void RadixSort(vector<int>& arr,int L,int R,int digit) {//L-R是左闭右闭区间上排序,digit是arr该区域的最大值的位数。如100,就是3
	const int radix = 10;//代表0-9的桶,但该代码实际没有使用桶
	int i = 0, j = 0;
	
	int* bucket = new int[R - L + 1]{0};//辅助数组,暂时存放每次的排序结果  //vector<int> bucket(R-L+1,0);
	for (int d = 1; d <= digit; d++) {//d代表了需要进出桶的次数,若现在最高有三位数,则排列三次即可,即控制次数
		int* count = new int[radix] {0};//该数组记录对应位0-9出现的次数//vector<int> count(radix,0);
		for (i = L; i <= R; i++) {
			j = getdigit(arr[i], d);
			count[j]++;
		}//该循环会记录对应位上0-9出现的次数,即count[i] 表示数组元素对应位是i的个数
		for (i = 1; i < radix; i++) 
			count[i] += count[i - 1];//该循环后,count[i]表示数组元素对应位小于等于i的元素个数
		for (i = R; i >= L; i--) {//从右往左遍历
			j = getdigit(arr[i], d);
			bucket[count[j] - 1] = arr[i];//假设现在arr[i]=12,判断个位,则j = 2,假设此时count[2]=3,则说明个位数字小于等于2的有三个,又是从右往左遍历的
			//可以确定arr[i]应该放在bucket[3-1]上,
			count[j]--;
		}
		for (i = L, j = 0; i <= R; i++, j++) 
			arr[i] = bucket[j];
	}
}
void RadixSort(vector<int>& arr) {
	if (arr.size() < 2)	return;
	RadixSort(arr, 0, arr.size() - 1, maxbits(arr));
}

⑩ 希尔排序

//以不同的间隔进行插入排序,在依次缩小间隔,直到间隔为1,排序完完成
vector<int> ShellSort(vector<int> vec) {
	int size = vec.size();
	int gap = size / 2;
	while (gap) {
		for (int i = gap; i < size; i++) {
			for (int j = i; j >= gap; j--) {
				if (vec[j] < vec[j - gap]) swap(vec, j, j - gap);
				else break;
			}
		}
		gap /= 2;
	}
	return vec;
	
}

总结

排序方式时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
冒泡O(n ^2 )O(n ^2 )O(n)O(1)稳定
选择O(n ^2 )O(n ^2 )O(n ^2 )O(1)不稳定
插入O(n ^2 )O(n ^2 )O(n)稳定
归并O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快排O(nlogn)O(n^2)O(nlogn)O(nlogn)不稳定
堆排O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
桶排O(n)
计数O(n+k)O(n+k)O(n+k)O(n+k)稳定
基数O(n)
希尔O(n^(j),j<2)O(n ^2 )O(n ^2 )O(1)不稳定
稳定性通俗将:排序前相等的两个数的相对位置,排序后,不会发生改变。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值