神的规范:排序算法(二):简单选择排序
来源:互联网 发布:宁波seo入门教程 编辑:程序博客网 时间:2024/05/22 07:53
写在前面:
排序又称为分类,它是数据处理中经常用到的一种重要运算。
虽然并未列入世界最伟大的几大算法之一,但毫无疑问,在各行各业的各个时期排序都是作为奠基者般的存在为程序所调用,也为编程者所敬仰。只是,也许正是因为它与我们息息相关,以至于我们竟然时常忽略它的存在。
事实上我们生活中无时无刻不在做排序:考试成绩排名,按身高、年龄、能力高低去评判他人,划分任务处理的优先级,等等·······
上一篇博客我们讨论了简单插入排序算法,而今天我们进入“简单选择排序”的学习,一步步去探索排序算法如何一步步在反思中前进,在山重水复处柳暗花明。
为简单起见,我们采用顺序存储的结构存放所排序的数据元素序列。
虽然简单选择排序与简单插入排序都是“简单的排序”,但是我们好奇的是:它们的思想和实现有什么区别呢?
我们已经知道,所谓简单插入排序,就是将所有待排序元素分为有序无序两个部分,每次取出无序部分的第一个元素,通过数据比较,插入到有序序列的合适位置,依次类推直到所有数据有序,它是一个稳定的算法。
而简单选择排序,重在“选择”二字,怎么个选法呢?
大家试想一下公司(比如谷歌)是如何选择招聘优秀员工的?
在一群(无序的)应聘者中,首先把表现最好(排序码最小)的员工挑出来(形成有序部分的第一个元素),然后在剩下的所有员工中再选出表现最后的(放入到有序部分的第二个位置),以此类推直到选出所有公司需要的职员。
相信这一流程非常容易理解,因为它很符合人类的思维(但有缺点,缺点后面详述)。
我们将简单选择排序的思想规范化描述一下:第一趟排序是在无序的{R1,R2,…Rn}中按排序码选出最小的元素,将它与R1交换;第二趟排序是在无序的{R2,R3,…Rn}中选出最小的元素,将它与R2交换;而第i趟排序时{R1,R2,…R(i-1)}已排好序,在当前无序的{Ri,…Rn}中再选出最小的元素,将它与Ri交换,使{R1,…Ri}有序。值得注意的是,i-1趟排序后,整个数据元素就是递增有序的(为什么?)
我们可以给出一个简单选择排序的C语言描述(假设待排序元素为double型数据):
void selectsort1(double a[],int n) { int i,j; for (i = 0;i < n-1;i++) { double small = a[i]; //Assume that a[i] is the smallest for (j = i+1;j < n;j++) { if (a[j] < small) { //Swap if a[j] is smaller double temp = a[j]; a[j] = small; small = temp; } } a[i] = small; //It is the ith smallest element }}
以上代码经测试(此处略)实现了递增排序,但是我们注意到有一个问题:它可能要多次交换数据,即a[j]与small,而它并没有对我们的排序结果(根据这一排序算法)做出实际贡献。这势必会导致整体执行效率的低下。那么有没有改良的方案呢?
有。我们考虑不移动数据,只把排序码做个变化,代码如下:
void selectsort2(double a[],int n) { int i,j,small; for (i = 0;i < n-1;i++) { small = i; for (j = i+1;j < n;j++) { if (a[j] < a[small]) small = j; } if (i != small) { //only needs n-1 at most double temp = a[small]; a[small] = a[i]; a[i] = temp; } }}
这样一来,交换数据的动作在最坏情况下也就只需n-1个,大大减少了交换所需的开销。
简单评估一下简单选择排序:
函数selectsort中外层for循环需执行n-1次,而对于找出排序码最小的数据元素的内层for循环,最坏情况下需要执行n-i次数据比较,因此简单选择排序最坏情况下需要执行n-1+n-2+…1 = n(n-1)/2次比较,算法效率为O[n^2](注意,与简单插入排序效率等级相同)。
如此一来,我们有了一个疑问,一个是简单选择排序,一个是简单插入排序,这两种“简单排序”谁更快呢?
有人会想,应该是一样快吧,毕竟算法效率都一样。
非也非也,是骡子是马,牵出来遛遛。我们用代码进行测试(包括针对第一种不完善的简单选择排序的测试),测试代码如下:
#include "stdio.h"#include "stdlib.h"#include "time.h"void print(double a[],int n);void selectsort1(double a[],int n);void selectsort2(double a[],int n);void insertsort(double a[],int n);#define NUM 20000int main(int argc, char* argv[]){ clock_t t_s,t_e; double t; double array1[NUM]; srand(time(NULL)); for (int i = 0;i < NUM;i++) array1[i] = rand(); double array2[NUM]; for (i = 0;i < NUM;i++) array2[i] = array1[i]; double array3[NUM]; for (i = 0;i < NUM;i++) array3[i] = array1[i]; printf("Before sort,elements are:\n");// print(array,NUM); printf("After selectsort1,elements are:\n"); t_s = clock(); selectsort1(array1,NUM); t_e = clock(); t = (t_e - t_s)/(double)CLOCKS_PER_SEC;// print(array1,NUM); printf("Time used:%f s\n",t); printf("After selectsort2,elements are:\n"); t_s = clock(); selectsort2(array2,NUM); t_e = clock(); t = (t_e - t_s)/(double)CLOCKS_PER_SEC;// print(array2,NUM); printf("Time used:%f s\n",t); printf("After insertsort,elements are:\n"); t_s = clock(); insertsort(array3,NUM); t_e = clock(); t = (t_e - t_s)/(double)CLOCKS_PER_SEC;// print(array3,NUM); printf("Time used:%f s\n",t); return 0;}void print(double a[],int n) { for(int i = 0;i < n;i++) { printf("%f\t",a[i]); } printf("\n");}void selectsort1(double a[],int n) { int i,j; for (i = 0;i < n-1;i++) { double small = a[i]; //Assume that a[i] is the smallest for (j = i+1;j < n;j++) { if (a[j] < small) { //Swap if a[j] is smaller double temp = a[j]; a[j] = small; small = temp; } } a[i] = small; //It is the ith smallest element }}void selectsort2(double a[],int n) { int i,j,small; for (i = 0;i < n-1;i++) { small = i; for (j = i+1;j < n;j++) { if (a[j] < a[small]) small = j; } if (i != small) { double temp = a[small]; a[small] = a[i]; a[i] = temp; } }}void insertsort(double a[],int n) { int i,j; for (i = 0;i < n-1;i++) { double temp = a[i+1]; j = i; while(j > -1 && temp < a[j]) { a[j+1] = a[j]; //move bigger number to a bigger location j--; //Search downside for number to move } a[j+1] = temp; //Insert element temp }}
测试大量(代码中是20000,可更改)数据的结果如下图(不妨多测几次,排除干扰):
三次测试中,我们发现:对于相同的数据样本,简单插入排序的效果每次都好于简单选择排序。这说明,实际上简单插入排序算法平均效率最高(而不是最坏情况下效率最高,我们有理由相信,最坏情况下简单选择排序与简单插入排序算法效率相等或相近)。
至于为什么,一个直觉的答案是:简单插入排序是稳定的,而简单选择排序则不稳定(为什么?)。
行文至此,对于简单选择排序也可以告一段落了,下一篇我们来讨论一个同一维度的竞争者——冒泡排序。当然,我们还要比比三者之中,谁是简单排序算法之王。
To be continued…
- 神的规范:排序算法(二):简单选择排序
- 排序算法(二)简单选择排序
- 【基础算法】排序-简单排序之二(选择排序)
- 神的规范:排序算法(一):简单插入排序
- Java排序算法(二):简单选择排序
- 排序算法二:简单选择排序
- 排序算法之二:选择排序之【简单选择排序】
- 简单排序算法:简单选择排序(选择排序)
- 简单的选择排序算法
- 排序算法---简单的选择排序
- 八大排序算法总结之二(简单选择算法,堆排序,归并排序,基数排序)
- 八大排序算法总结之二(简单选择算法,堆排序,归并排序,基数排序)
- 排序算法(二)选择类排序:简单选择排序,堆排序,锦标赛排序
- 排序问题二(简单选择排序)
- 排序算法(二):直接选择排序
- 排序算法(二)------选择排序
- 排序算法(二):选择排序法
- 排序算法(二)-- 选择排序
- C语言中变量定义的位置(C89和C99的区别)
- Linux IPC实践--System V消息队列(3)
- org.hibernate.exception.GenericJDBCException: Cannot open connection
- 天声人語 20151106 ベーコンに発がん性?
- linux/mac vi命令详解
- 神的规范:排序算法(二):简单选择排序
- 日经春秋 20151106
- hiho 7 完全背包
- leetcode123 Best Time to Buy and Sell Stock III
- 三星等低端手机OOM解决方法
- 图像梯度特征的常用边缘检测算子:Sobel、Prewitt、Roberts
- hdu 4655 Cut Pieces(贪心)
- linux和vxworks的实时性
- 没有BAT3级的应急响应中心,互联网公司该如何应对数据泄露事件?