专栏
第一节——排序
介绍比较常用的3种排序算法,以下皆以C语言实现。
桶排序
以简化版桶排序引入排序算法的内容
- 优点:
- 速度最快,最简单
- 缺点:
- 非常浪费空间
如让计算机随机读入5个数然后将这5个数从小到大输出,我们这里只需借助一个一维数组就可以解决这个问题。
#include <stdio.h>
int main()
{
int a[11],i,j,t;
for(i=0;i<=10;i++)
a[i]=0; //初始化为0
for(i=1;i<=5;i++) //循环读入5个数
{
scanf("%d",&t); //把每一个数读到变量t中
a[t]++; //进行计数
}
for(i=0;i<=10;i++) //依次判断a[0]~a[10]
for(j=1;j<=a[i];j++) //出现几次就打印几次
printf("%d ",i);
getchar();getchar();
//这里的getchar();用来暂停程序,以便查看程序输出的内容
//也可以用system("pause");等来代替
return 0;
}
现在我们可以尝试一下输入n个0 ~ 1000之间的整数,将它们从大到小排序。提醒一下,如果需要对数据范围在0 ~ 1000的整数进行排序,我们需要1001个桶,来表示0 ~ 1000之间每一个数出现的次数,这一点一定要注意。另外,此处的每一个桶的作用其实就是“标记”每个数出现的次数,代码实现如下。
#include <stdio.h>
int main()
{
int book[1001],i,j,t,n;
for(i=0;i<=1000;i++)
book[i]=0;
scanf("%d",&n);//输入一个数,表示接下来有n个数
for(i=1;i<=n;i++)//循环读入n个数,并进行桶排序
{
scanf("%d",&t); //把每一个数读到变量t中
book[t]++; //进行计数,对编号为t的桶放一个小旗子
}
for(i=1000;i>=0;i--) //依次判断编号1000~0的桶
for(j=1;j<=book[i];j++) //出现几次就将桶的编号打印几次
printf("%d ",i);
getchar();getchar();
return 0;
}
如果使用简化版的桶排序算法仅仅是把数字进行了排序。最终输出的也仅仅是数字,但没有对数据本身进行排序,也就是说,我们并不知道排序后的数据原本对应着哪一个数字。还有,如果现在需要排序的不再是整数而是一些小数,又该怎么办呢?要解决这个问题请看下面——冒泡排序。
冒泡排序
冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。
- 优点:
- 解决浪费空间问题
- 缺点:
- 时间复杂度高,牺牲执行效率
#include <stdio.h>
int main()
{
int a[100],i,j,t,n;
scanf("%d",&n); //输入一个数n,表示接下来有n个数
for(i=1;i<=n;i++) //循环读入n个数到数组a中
scanf("%d",&a[i]);
//冒泡排序的核心部分
for(i=1;i<=n-1;i++) //n个数排序,只用进行n-1趟
{
for(j=1;j<=n-i;j++) //从第1位开始比较直到最后一个尚未归位的数,想一想为什么到n-i就可以了。
{
if(a[j]<a[j+1]) //比较大小并交换
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for(i=1;i<=n;i++) //输出结果
printf("%d ",a[i]);
getchar();getchar();
return 0;
}
将上面代码稍加修改,就可以解决桶排序遗留的问题,如下。
#include <stdio.h>
struct student
{
char name[21];
char score;
};//这里创建了一个结构体用来存储数据名和数字
int main()
{
struct student a[100],t;
int i,j,n;
scanf("%d",&n); //输入一个数n
for(i=1;i<=n;i++) //循环读入n个数据名和数字
scanf("%s %d",a[i].name,&a[i].score);
//按分数从高到低进行排序
for(i=1;i<=n-1;i++)
{
for(j=1;j<=n-i;j++)
{
if(a[j].score<a[j+1].score)//对数字进行比较
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for(i=1;i<=n;i++)//输出数据名
printf("%s\n",a[i].name);
getchar();getchar();
return 0;
}
最后我们总结一下:如果有n个数进行排序,只需将n-1个数归位,也就是说要进行n-1趟操作。而“每一趟”都需要从第1位开始进行相邻两个数的比较,将较小的一个数放在后面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此步骤,直到最后一个尚未归位的数,已经归位的数则无需再进行比较。
冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的时间复杂度是O(N²)。这是一个非常高的时间复杂度。寻求更好的算法的话请看下面——快速排序。
快速排序
最常用,既不浪费空间,速度也相对较快。
快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边,直到所有的基准数都归位为止。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的,都是O(N²),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。
#include <stdio.h>
int a[101],n;//定义全局变量,这两个变量需要在子函数内使用
void quicksort(int left,int right)
{
int i,j,t,temp;
if(left>right)
return;
temp=a[left];//temp中存的就是基准数
i=left;
j=right;
while(i!=j)
{
//顺序很重要,要先从右往左找
while(a[j]>=temp && i<j)
j--;
//再从左往右找
while(a[i]<=temp && i<j)
i++;
//交换两个数在数组中的位置
if(i<j)//当哨兵i和哨兵j没有相遇时
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
//最终将基准数归位
a[left]=a[i];
a[i]=temp;
quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程
quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程
}
int main()
{
int i,j,t;
//读入数据
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
quicksort(1,n);//快速排序调用
//输出排序后的结果
for(i=1;i<=n;i++)
printf("%d ",a[i]);
getchar();getchar();
return 0;
}
参考:《啊哈!算法》