引例:
经过了在山东农业大学一学期的学习,我们迎来了耿霞老师开设C++程序设计课程的期末考试,在程序设计题的第一题给我们出了一道这样的题:
((✺ω✺))给你一组数据(1<n<1000),数据中可能有重复,你的任务是完成去重,并按照从小到大顺序输出数据。(NOIP2006)
考试的时候拿到这个题有两个思路:1.先去重再排序输出 2. 先排序再去重输出(重复的都在一起)
对于第一种思路,我们可以假设有很多桶,然后我们挨着枚举数据,把数据扔进带有编号的桶中,标记该桶,枚举完成后将带有标记的桶的编号进行输出。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000;
int main()
{
bool flag[MAXN+1];
//因为数组下标是少1的,我们多加一个元素表示maxn
memset(flag,0,sizeof(flag));
//初始化该桶
int n=0,temp=0;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>temp;
flag[temp]=true;
//标记该桶
}
for(int i=1;i<=1000;i++)
if(flag[i])
cout<<i<<' ';
return 0;
}
对于第二种思路:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int shuzu[101];
memset(shuzu,0,sizeof(shuzu));
int n=0;
cin>>n;
for(int i=0;i<n;i++)
cin>>shuzu[i];
sort(shuzu,shuzu+n);
cout<<shuzu[0]<<' ';
for(int i=1;i<n;i++)
{
if(shuzu[i]!=shuzu[i-1])//不重复就输出
cout<<shuzu[i]<<' ';
}
return 0;
}
由第一种方法的代码稍加修改可以引出今天学习的第一种排序:简化版的桶排序
#include<iostream>
using namespace std;
const int MAXN=1000;
int main()
{
int flag[MAXN+1];
//因为数组下标是少1的,我们多加一个元素表示maxn
memset(flag,0,sizeof(flag));
//初始化该桶
int n=0,temp=0;
cin>>n;
for(int i=1;i<=MAXN;i++)
{
cin>>temp;
flag[temp]++;
}
for(int i=1;i<=MAXN;i++)
for(int j=1;j<=flag[i];j++)//划重点
cout<<i<<' ';
return 0;
}
简化版桶排序的时间复杂度只有O(M+N),可以看出是一种很快的排序方法,然而却是不完美的,在应用过程中主要有以下几大问题:
- 只能对整数进行排序
- 以空间的方式纵使降低了时间复杂度,然而空间的浪费问题严重
- 无法处理有复杂要求的排序问题
对于第一个问题,别的排序算法能解决这个问题,就不多写了。谈一下第一个问题,假设要对五个数排序,每个数的范围在1-1亿之间,那么且不说系统会不会给你一个1亿大小的数组,为了五个数这样太浪费空间了,也不可行,还是需要别的排序方法(文末会总结各个排序方法的适用情况)。第三个问题:如果每个数据都带着名称,在排序的时候无法同时对别的关联项进行排序。
针对第三个问题,我又学习了冒泡排序和选择排序,再加上结构体关联名称和数据,轻松解决第三个问题;
来聊一聊冒泡排序:
算法思想很简单就不码字了,直接上代码
基本版的冒泡排序:
void bubble_sort(int arr[], int len) {
int i, j;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
冒泡的时间复杂度高达O(n^2),排序速度慢,我们进行如下升级
第一次升级:设立了结束标志的冒泡:
void bubble_sort(int arr[], int len) {
int i, j;
bool flag;
for (i = 0; i < len - 1 && flag!=0; i++)
{
flag=0;
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
flag = 1;
}
}
}
变量flag用于记录每趟冒泡排序是否发生数据元素位置交换。如果没有发生交换,说明序列已经有序了,结束冒泡。
第二次升级:鸡尾酒排序(本段源引自郭威gowill博客)
鸡尾酒排序又叫定向冒泡排序,搅拌排序、来回排序等,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。
鸡尾酒排序在于排序过程是先从低到高,然后从高到低;而冒泡排序则仅从低到高去比较序列里的每个元素。它可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。总结一下:
以序列(2,3,4,5,1)为例,鸡尾酒排序只需要从低到高,然后从高到低就可以完成排序,但如果使用冒泡排序则需要四次。
但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。
还有选择排序:void cocktail_sort(int arr[], int len) { int j, left = 0, right = len - 1; while (left < right) { for (j = left; j < right; j++) if (arr[j] > arr[j + 1]) swap(arr[j], arr[j + 1]); right--; for (j = right; j > left; j--) if (arr[j - 1] > arr[j]) swap(arr[j - 1], arr[j]); left++; } }
void SelectSort(int arr[],int n) { for(int i = 0; i <n - 2; ++i) { int k = i; for(int j = i + 1; j < n - 1; ++j) { //找到最小的数的下标 if(v[j] < v[k]) k = j; } if(k != i) { swap(v[k],v[i]); } } }
选择排序进化!:双极端的选择排序
在每一次查找最小值的时候,也可以找到一个最大值,然后将两者分别放在它们应该出现的位置,这样遍历的次数就会少一半!
今天第一次写训练日志,就先写到这吧,剩下的几种排序算法明天再写,void SelectSort(int arr[],int n) { int left = 0; int right = n - 1; int min = left;//用来存储最小值的下标 int max = left;//用来存储最大值的下标 while(left <= right) { min = left; max = left; for(int i = left; i <= right; i++) { if(arr[i]<arr[min]) min = i; if(arr[i] > arr[max]) max = i; } swap(arr[left],arr[min]); if(left == max) max = min; swap(arr[right],arr[max]); left++,right++; } }
有没有能对不同类的问题有着更好的适应性能并且能保证排序的速度呢?今天学习了简化版桶排序,冒泡排序、选择排序及各自的升级版本,还有插入排序和快速排序(这两个明天写吧)
- 桶排序,以空间换时间,对于处理取值范围小的领域占优势(比如学生成绩),但处理取值大的数据或者有较复杂要求的排序题就弱鸡了,限制较高
- 选择和冒泡,占用空间比桶排序要小,可是时间复杂度超级高,当排序数据量高的时候会慢出翔。
且听下回分解:排序算法(下)
注:本人第一次写博客,难免会有错误,希望能在评论指出!十分感谢