/*
常用排序算法分析
*/
void swap(int& x,int& y)
{
if(x != y){
x = x + y;
y = x - y;
x = x - y;
}
}
/*
冒泡排序
时间复杂度为o(n^2)
两两比较,交换,每次内层循环在未排序序列中找出最大值放到已排序中
对于部分有序的序列来说,效率较高
稳定
*/
void bubble_sort(int* arr,int start,int end)
{
if(arr == nullptr || start <0 || start >= end)
return;
int pos = -1;
for(int i = start; i <= end; i++){
pos = -1;
for(int j = start; j <= start + end - i - 1; j++){
if(arr[j] > arr[j + 1]){
swap(arr[j],arr[j + 1]);
pos = j;
}
}
if(pos != -1){//在pos后面的都是已经排序好的了
i = end - pos - 1;
}else{//没有经过交换,说明已经是有序的了
break;
}
}
}
/*
选择排序
o(n^2)
在未排序序列中查找最大或最小值,放在已排序序列的尾部或头部
不稳定
*/
void select_sort(int* arr,int start,int end)
{
if(arr == nullptr || start < 0 || start >= end)
return;
for(int i = start; i <= end; i++){
int maxNumPos = start;
for(int j = start; j <= start + end - i; j++){
if(arr[maxNumPos] < arr[j]){
maxNumPos = j;
}
}
swap(arr[maxNumPos],arr[start + end - i]);
}
}
/*
插入排序
o(n^2)
从未排序序列中取一个数值插入已排序序列中对应位置
稳定
*/
void insert_sort(int* arr,int start,int end)
{
if(arr == nullptr || start < 0 || start >= end)
return;
int pos = 0;
for(int i = start + 1; i <= end; i++){
int num = arr[i];
pos = i;
for(int j = i - 1; j >= start; j--){
if(num < arr[j]){
arr[j + 1] = arr[j]; //移动
pos = j;
}
}
arr[pos] = num;
}
}
/*
快速排序
nlog(n)
分治法
不稳定
*/
void quick_sort(int* arr,int left,int right)
{
if(left >= right)
return;
int dw = left;
int up = right;
int key = arr[dw];
while(dw < up)
{
while(dw < up && key <= arr[up])
--up;
arr[dw] = arr[up];
while(dw < up && key >= arr[dw])
++dw;
arr[up] = arr[dw];
}
arr[dw] = key;
quick_sort(arr,left,dw - 1);
quick_sort(arr,dw + 1,right);
}
/*
桶排序
o(n)
需要使用辅助空间,在集合范围较小的情况下推荐使用,效率很高
稳定
*/
void bucket_sort(int* bucket,int* arr,int start,int end)
{
if(bucket == nullptr || arr == nullptr || start < 0 || start >= end)
return;
//先找到最大值和最小值(也就是范围)
int maxNum = arr[start];
int minNum = arr[start];
for(int i = start; i <= end; i++){
if(maxNum < arr[i]){
maxNum = arr[i];
}
if(minNum > arr[i]){
minNum = arr[i];
}
}
for(int i = start; i <= end; i++){
++bucket[arr[i] - minNum];
}
int ncount = start;
for(int i = 0; i <= MAX_LENGTH; i++){
while(bucket[i] > 0){
arr[ncount++] = i + minNum;
--bucket[i];
}
if(ncount > end)
break;
}
}
/*
归并排序
nlog(n)
需要辅助空间
稳定
*/
void merge_cal(int* total,int* arr,int start,int end)
{
//bucket全局数组,长度为100000,
//若使用堆空间,堆空间的申请和释放耗时较大,会影响算法效率的真实性
int mid = start + ((end - start) >> 1);
int firstStart = start;
int firstEnd = mid;
int secondStart = mid + 1;
int secondEnd = end;
int ncount = 0;
while(firstStart <= firstEnd && secondStart <= secondEnd)
{
if(arr[firstStart] < arr[secondStart]){
total[ncount++] = arr[firstStart++];
}else{
total[ncount++] = arr[secondStart++];
}
}
while(firstStart <= firstEnd)
{
total[ncount++] = arr[firstStart++];
}
while(secondStart <= secondEnd)
{
total[ncount++] = arr[secondStart++];
}
for(int i = 0; i < ncount; i++){
arr[start + i] = total[i];
}
}
void merge_sort(int* total,int* arr,int start,int end)
{
if(total == nullptr || arr == nullptr || start < 0 || start >= end)
return;
int mid = start + ((end - start) >> 1);
merge_sort(total,arr,start,mid); //分块
merge_sort(total,arr,mid + 1,end);
merge_cal(total,arr,start,end); //合并
}
之前认为冒泡与插入选择的时间复杂度都是o(n^2),所以以为使用哪一种都是一样的,但是通过对比发现,冒泡排序是最慢的一种。
横坐标为随机数据量,纵坐标为排序时间ms
从图中看出冒泡排序是最慢的,插入和选择所用的时间差不多,快速排序在这点数据量面前所用时间几乎为0.下面是具体数据。
在将快速排序,归并排序,和桶排序一起比较
可以看出,桶排序的时间复杂度几乎为0,快速排序与归并排序差不多,但是快速排序比归并排序要快一些,造成这样的原因可能是因为,快速排序是在原数据上直接修改,而归并排序不是且需要一次拷贝。下面是具体数据,
从上面对比可以看出,如果只论时间复杂度来说一定是桶排序获胜,如果论时间与空间复杂度还是快速排序更胜一筹。
但是快速排序是不稳定的,而桶排序与归并排序是稳定的。桶排序所耗空间复杂度取决于数据的范围而非数据量,适用于数据范围较小的排序,比如1000万学生的成绩排序。而归并排序所耗空间复杂度为o(n)与数据量相关。所以在使用还是要考虑场景和需求。