中文名称 | 英文名称 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
选择排序 | Selection | n^2 | 1 | 不稳定 |
冒泡排序 | Bubble | n^2 | 1 | 稳定 |
插入排序 | Insertion | n^2 | 1 | 稳定 |
堆排序 | Heap | n log_2n | 1 | 不稳定 |
希尔排序 | Shell | n^1.3 | 1 | 不稳定 |
归并排序 | Merge | n log_2n | n | 稳定 |
快速排序 | Quick | n log_2n | log_2n | 不稳定 |
桶排序 | Bucket | n+k | n+k | 稳定 |
计数排序 | Counting | n+k | n+k | 稳定 |
基数排序 | Radix | n*k | n+k | 稳定 |
#include
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
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
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
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
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
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]
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
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
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"<
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
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
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)这样四个桶,将数与区间作比较放入对应的桶。