无法逃脱的十大排序--代码实现与时间复杂度,空间复杂度,稳定性分析

十大排序列表

中文名称英文名称平均时间复杂度空间复杂度稳定性
选择排序Selectionn^21不稳定
冒泡排序Bubblen^21稳定
插入排序Insertionn^21稳定
堆排序Heapn log_2⁡n1不稳定
希尔排序Shelln^1.31不稳定
归并排序Mergen log_2⁡nn稳定
快速排序Quickn log_2⁡nlog_2⁡n不稳定
桶排序Bucketn+kn+k稳定
计数排序Countingn+kn+k稳定
基数排序Radixn*kn+k稳定

选择排序:

#include<iostream>
using namespace std;
void printarry(int a[],int len){
		for(int k=0; k<len; k++){
			cout<<a[k];
			cout<<" ";
		} 
}
int main(){
	int arry[]={3,5,2,1,9,8,7,6,4};
	int n = sizeof(arry)/sizeof(arry[0]);
	for(int j=0; j<n-1; j++){
		int minpos = j;
		for(int i=j+1; i<n; i++){
			//每次找到最小数的下标 
			if(arry[i]<arry[minpos]){
				minpos = i;
			}	
		}
		//将每一次找到的最小数往前放
		//比如将第一次找到的最小数放到arry[0]的位置与arry[0]交换 
		int temp = arry[j];
		arry[j] = arry[minpos];
		arry[minpos] = temp;
		cout<<"minpos is:"<<minpos<<endl;
	}
	cout<<"排序完成的数组:"; 
	printarry(arry,n);
	return 0;
}

冒泡排序

#include<iostream>
using namespace std;
void printarry(int a[],int n){
	for(int i=0; i<n; i++){
		cout<<a[i]<<" "; 
	}
	cout<<endl;
}
int main(){
	int arry[]={9,3,5,7,1,2,8,4,6};
	int len=sizeof(arry)/sizeof(arry[0]);
	for(int j=len-1; j>0; j--){
		//一次循环只能把一个最大数归位到最后
		//所以需要一个外层循环
		//注意下标,每次循环之后,最后一个数不参与比较 
		for(int i=0; i<j; i++){
			//比较相邻的两个数,每一次把大的交换到后面像冒泡一样 
			//每一次循环将最大的数放到最后面了 
			if (arry[i]>arry[i+1]){
				int temp = arry[i];
				arry[i] = arry[i+1];
				arry[i+1] = temp;
			}
		}
		printarry(arry,len);
	}
	return 0;
}

插入排序

#include<iostream>
using namespace std;
void printarry(int arry[],int n){
	for(int i=0; i<n; i++){
		cout<<arry[i]<<" ";
	}
	cout<<endl;
}
int main(){
	int arry[]={9,3,1,4,6,8,7,5,2};
	int len = sizeof(arry)/sizeof(arry[0]);
	//打扑克牌的情形,比如手上有9,3两张牌
	//随机摸一张牌 1,把它与3比较比3小应该放在3前面
	//再与9比较,比9小,应该放在9前面 
	for(int j=1; j<len; j++){
		//可以先写内层循环也就是一个数归位的情况
		//再写外层循环将所有数归位
		//即数组中随机一个数与它前面的数比较
		//比前面的数小就与它交换,直到没有比它小的数出现 
		for(int i=j; i>0;i--){
			if(arry[i]<arry[i-1]){
				int temp = arry[i-1];
				arry[i-1] = arry[i];
				arry[i] = temp;
			}
		}
	}
	printarry(arry,len);
	return 0;
}

希尔排序

#include<iostream>
using namespace std;
void printarry(int arry[],int n){
	for(int i=0; i<n; i++){
		cout<<arry[i]<<" ";
	}
	cout<<endl;
}
int main(){
	int arry[] = {9,6,11,3,5,12,8,7,10,15,14,4,1,13,2};
	int len = sizeof(arry)/sizeof(arry[0]);
	//希尔排序就是插入排序的升级
	//原来j以1递增,现在我们跳着排,以一个间隔取数字排 
	//比如先取9,5,10,1排,这是一次循环
	//再取6,12,15,13排以此类推
	//所以下面的循环j是递增1,而不是递增gap,注意循环边界条件 
	//int gap=4; 然后不断缩小间隔,当然数组长度很大时gap取4就不合适了
	//所以我们可以把gap的值初始取为数组长度一半,每次缩小一倍  
	for(int gap=len/2; gap>0; gap/=2){
		//直到间隔变为1 
		for(int j=gap; j<len; j++){
			for(int i=j; i>gap-1; i-=gap){
				if(arry[i]<arry[i-gap]){
					int temp = arry[i-gap];
					arry[i-gap] = arry[i];
					arry[i] = temp;
				}
			}	
		}
	}
	printarry(arry,len);
}

希尔排序Knuth间隔序列:

#include<iostream>
using namespace std;
void printarry(int arry[],int n){
	for(int i=0; i<n; i++){
		cout<<arry[i]<<" ";
	}
	cout<<endl;
}
int main(){
	int arry[] = {9,6,11,3,5,12,8,7,10,15,14,4,1,13,2};
	int len = sizeof(arry)/sizeof(arry[0]);
	int h = 1;
	while(h <= len/3){
		h = 3*h + 1;
	}
	//实践证明,Knuth序列间隔确实快一些,也有人采用质数间隔序列 
	for(int gap=h; gap>0; gap = (gap-1)/3){
		for(int j=gap; j<len; j++){
			for(int i=j; i>gap-1; i-=gap){
				if(arry[i]<arry[i-gap]){
					int temp = arry[i-gap];
					arry[i-gap] = arry[i];
					arry[i] = temp;
				}
			}	
		}
	}
	printarry(arry,len);
}

归并排序

#include<iostream>
using namespace std;
void printarry(int a[], int n){
	for(int i=0; i<n; i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
int main(){
	//归并排序的思想就是将一个数组分为两部分子数组 
	// 这两个子数组是分别排好序的,比如下面的数组
	//一分为二,1478和369 
	int arry[]={1,4,7,8,3,6,9};
	int len = sizeof(arry)/sizeof(arry[0]);
	int mid =  len/2;
	int i=0,j=mid+1,k=0;
	int temp[len];
	while(i<=mid&&j<len){
		//将两部分已排好的数字分别比较,i从0遍历到中间,j从中间遍历到末尾
		//若arry[i]<arry[j],就把arry[i]放到新数组
		if(arry[i]<=arry[j]){
			//这里小于等于是为了确保算法稳定性
			temp[k]=arry[i];
			i++;
			k++; 
		} 
		else{
			temp[k]=arry[j];
			j++;
			k++;
		}
	}
	//当其中一个子数组遍历完而另一个还没有时,就会跳出上面循环
	//于是将未遍历完的子数组一坨移到新数组里
	while(i<=mid){
		temp[k++]=arry[i++];
	} 
	while(j<len){
		temp[k++]=arry[j++];
	}
	printarry(temp,len);
	return 0;
}

归并排序递归:

#include<iostream>
using namespace std;
void printarry(int a[], int n){
	for(int i=0; i<n; i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
//让程序更灵活点,让它给任意子数组排序 
void merge(int arry[], int leftptr, int rightptr, int rightbound){
	//左指针,右指针,右边界 
	int mid = rightptr-1;
	int i=leftptr,j=rightptr,k=0;
	int temp[rightbound-leftptr+1];
	while(i<=mid&&j<=rightbound){
		temp[k++]=arry[i]<=arry[j]?arry[i++]:arry[j++];
	}
	while(i<=mid){
		temp[k++]=arry[i++];
	} 
	while(j<=rightbound){
		temp[k++]=arry[j++];
	}
	//把temp每次排好的序列复制回arry 
	for(int m=0; m<sizeof(temp)/sizeof(temp[0]); m++){
		arry[leftptr+m] = temp[m];
	}
	//printarry(temp,sizeof(temp)/sizeof(temp[0]));
}
//递归 sort
void sort(int arry[], int left, int right){
	if(left==right) return;
	//分成两半 
	int mid = left+(right-left)/2;
	//左边排序
	sort(arry,left,mid);
	//右边排序 
	sort(arry,mid+1,right);
	//左边右边排好了,merge起来 
	merge(arry, left, mid+1, right);
}
int main(){ 
	int arry[]={1,4,7,8,3,6,9};
	int len = sizeof(arry)/sizeof(arry[0]);
	sort(arry, 0, len-1);
	printarry(arry,len);
	return 0;
}

快速排序

#include<iostream>
using namespace std;
void printarry(int a[],int n){
	for(int i=0; i<n; i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
void swap(int arry[],int i, int j){
	int temp = arry[i];
	arry[i] = arry[j];
	arry[j] = temp;
}
//以数组最右端值为基准,比它小的放到左区间,比它大的放到右区间
int part(int arry[], int leftbound, int rightbound){
	if(leftbound>=rightbound) return arry[leftbound];
	int pivot = rightbound;//基准数
	int left = leftbound;//左边从数组开始遍历
	int right = rightbound-1;//右边从基准数左边的一个数开始遍历
	while(left<=right){
		//边界条件要注意,不然会有bug 
		while(left<=right && arry[left]<=arry[pivot]) left++;//当从左边遍历的数比基准数小,left继续往右遍历
		while(left<=right && arry[right]>=arry[pivot]) right--;//当从右边遍历的数比基准数大,right继续往左遍历 
		if(left<right){
			//直到跳出上面循环
			//即left指向的数比基准数大
			//right指向的数比基准数小,这俩数应该交换一下到属于自己的区间 
			swap(arry,left,right);
		}
	}
	//cout<<"before left"<<left<<" "<<right<<endl;
	swap(arry,left,pivot); 
	//printarry(arry,sizeof(arry)/sizeof(arry[0]));
	return left;//返回轴,好递归 
}
//递归 
void quiksort(int arry[], int leftbound, int rightbound){
	if(leftbound>=rightbound) return;
	int mid = part(arry, leftbound, rightbound);
	//把每一次分区完的轴位置返回给mid 
	//递归的对左边区间排序 
	quiksort(arry, leftbound, mid-1);
	//递归的对右边区间排序 
	quiksort(arry, mid+1, rightbound);
}
int main(){
	int arry[]={7,3,2,6,8,1,6,9,5,4,6};
	int len = sizeof(arry)/sizeof(arry[0]);
	quiksort(arry,0,len-1); 
	printarry(arry,len);
	return 0;
} 

计数排序

计数排序实际是桶排序的一种,适用于量大范围小的问题。比如企业数万名员工按年龄排序,年龄一般0-100,相当于需要一个计数数组count[100];
基本代码思想如下,但是有很多问题,比如离散值怎么计数,稳定性不行,比如员工年龄集中在30-50岁,那么计数空间count[100]会造成空间的浪费,因为只有count[30]-count[50]存储了值

#include<iostream>
using namespace std;
int main(){
	int arry[]={9,3,4,8,7,6,2,1,1,3,4,5,3};
	int len=sizeof(arry)/sizeof(arry[0]);
	int count[10]={0};//数的范围是1-10
	int len_count=sizeof(count)/sizeof(count[0]);
	for(int i=0; i<len; i++){
	//遍历数组,对应计数数组索引位置加1 
		count[arry[i]]++;
	}
	for(int j=0; j<len_count; j++){
		for(int k=1; k<=count[j]; k++){
			cout<<j<<endl;
		}
	}
	return 0;
}

稳定的计数排序

#include<iostream>
using namespace std;
int main(){
	int arry[]={9,3,4,8,7,6,2,1,1,3,4,5,3};
	int len=sizeof(arry)/sizeof(arry[0]);
	int count[10]={0};
	int len_count=sizeof(count)/sizeof(count[0]);
	int result[len]={0};//结果数组 
	for(int i=0; i<len; i++){
		//遍历数组,对应计数数组索引位置加1 
		count[arry[i]]++; 
	}
	//稳定性算法 
	for(int m=1; m<len_count; m++){
		//记录每个数字有几个,保证相同的数相对位置不变 
		//累加数组 
		count[m]=count[m]+count[m-1]; 
	}
	for(int n=len-1; n>=0; n--){
		//对原数组进行倒叙遍历,比如对3,就去count累加数组里面
		//找它的位置,然后将这个数放到结果数组相应的位置。 
		result[--count[arry[n]]]=arry[n]; 
	}
	for(int k=0; k<len; k++){
		cout<<result[k]<<" ";
	}
	return 0;
}

基数排序和桶排序

基数排序和计数排序其实都有桶排序的思想,和其他排序不同的是他们是非比较排序
基数排序也叫多关键字排序,是基于计数排序的,比如一组三位数的序列,先排个位数将他们以个位数放入对应的桶,再排十位数,以十位数的数字放入对应的桶,最后排百位上的数,将以百位数放入对应的桶。
真正的桶排序可以叫做区间排序,不像前面说的这两种桶编号对应数组下标是整数,桶排序的桶是一个区间。比如有一组1以内的非整数,那么就可以分成[0,0.25),[0.25,0.5),[0.5,0.75),[0.75,1)这样四个桶,将数与区间作比较放入对应的桶。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值