1、shell排序

Shell排序是这样来分组并排序的:将序列分成子序列,然后分别对子序列进行排序,最后将子序列组合起来。Shell排序将数组元素分成“虚拟”子序列,每个子序列用插入排序方法进行排序;另一组子序列也是如此选取,然后排序,依此类推。在执行每一次循环时, Shell排序把序列分为互不相连的子序列,并使各个子序列中的元素在整个数组中的间距相同。例如,为方便起见,我们设数组中元素的个数n是2的整数次幂。Shell排序的一种可能的实现是首先将它分成n/2个长度为2的子序列,每个子序列中两,个元素的下标相差n/2,如果有数组下标为0-15的16个记录,那么首先是将它分成8个各有两个记录的子序列,第一个序列元素的下标是0和8,第二个下标是1和9,依此类推。每一个两元素的序列都采用插入排序法来排序。,第二轮Shell排序将处理数量少一些的子序列,但每个子序列都更长了。对于上面的例子来说会有n/4个长度为4的子序列,序列中的元素相隔n/4,因此,第二次分割的第一个子序列中有位于0,4,8,12的4个元素,第二个子序列的元素位于1.5,9,13,依此类推。每一个四元素的子序列仍然用插入排序法进行排序。第三轮将对两个子序列进行排序,其中一个包含原数组中的奇数位上的元素,另一个包含偶数位上的元素。最后一轮将是一次“正常的”的插入排序。

Shell排序并不关心分割的子序列中元素的间隔(尽管最后的间隔为1,是一个常规的插,人排序),如果Shell排序总是以一个常规的插入排序结束,又怎么会比插入排序的效率更高呢?我们希望经过每次对子序列的处理可以使待排序的数组更加有序。这一情况不一定准确,但实际确实如此。当最后一轮调用插入排序时,数组已经是基本有序的了,并产生一个相对所花时间较少的最终插入排序。

void insert(int data[], int n, int incr)
{int i,j;for(i=incr; i<n; i += incr){for(j=i; (j>=incr)&&(data[j]<data[j-incr]); j-=incr){data[j]      = data[j]^data[j-incr];data[j-incr] = data[j]^data[j-incr];data[j]      = data[j]^data[j-incr];}}
}
//shell排序
void fun(int data[],int n)
{int i,j;for(i=n/2; i>2; i/=2){for(j=0; j<i; j++){insert(&data[j], n-j, i);}}insert(data,n,1);
}

2、快速排序

快速排序首先选择一个轴值(pivot),假设输入的数组中有k个小于轴值的结点,于是这些结点被放在数组最左边的k个位置上,而大于轴值的结点被放在数组最右边的n-k个位置上。这称为数组的一个分割(partition),在给定分割中的值不必被排序,只要求所有结点都放到了正确的分组位置中。而轴值的位置就是下标k。快速排序再对轴值的左右子数组分别进行类似的操作,其中一个子数组有k个元素,而另一个有n-k-1个元素。那么,这些值又是如何进行排序的呢?由于快速排序是一个好算法,可以对这两个子数组继续使用快速排序法。

//快速排序,第一次调用时输入fun1(data,0,n-1)
void fun1(int data[], int i, int j)
{int pivotindex,m,k;if(j <= i)return;pivotindex = findpiot(data, i, j);m = data[pivotindex];data[pivotindex] = data[j];data[j] = m;k = partition(data, i-1, j, data[j]);m = data[k];data[k] = data[j];data[j] = m;fun1(data, i, k-1);fun1(data, k+1, j);
}

选择轴值有多种方法。最简单的方法是使用第一个记录的关键码。但是,如果输入的数组是正序的或者是逆序的,就会将所有结点分到轴值的一边。较好的方法是随机选取轴值。这样可以减少由于较差的原始输入对排序造成的影响。遗憾的是,随机选取轴值的开销较大,,所以可用选取数组中间点的方法代替。下面是一个简单的findpivot函数:

int findpiot(int data[], int i, int j)
{return (i+j)/2;
}

函数partition将记录移动到合适的分组,然后返回值k,这是分割后的右半部分的起始位置。注意在调用partition之前,轴值已被放在数组的最后一个位置上了(位置i)。因此,函数分割一定不会影响到数组中;所指的记录。然后轴值被放到下标为k的位置上,这就是它在最终排好序的数组中的位置。要做到这一点,必须保证在递归调用qsort的过程中轴值不再移动。即使是在最差情况下选择了一个不好的轴值,导致轴的一边分割出了一个空子数组,而另一个子数组中起码有n-1个记录,也不能移动。

现在来看函数partition。如果事先知道有多少个结点比轴值小,则partition只需将关键码值比轴值小的结点放到数组的低端,关键码值比轴值大的元素放到高端。由于事先并不知道 , 有多少关键码比中心点(轴值)小,所以可用一种较为巧妙的方法来解决:从数组的两端移动下标,必要时交换记录,直到数组两端的下标相遇为止。

int partition(int data[], int l, int r, int pivot)
{int m;do{while(data[++l] > pivot);while((r != 0)&&(data[--r]<pivot));m = data[l];data[l] = data[r];data[r] = m;}while(l < r);m = data[l];data[l] = data[r];data[r] = m;return l;
}

加入冒泡排序进行比较,在Linux下编译执行:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>//冒泡排序:排序时间代价总是n^2;交换次数较多
void fun2(int data[], int n)
{int i,j;for(i=0; i<n-1; i++){for(j=n-1; j>1; j--){if(data[j] < data[j-1]){data[j]   = data[j]^data[j-1];data[j-1] = data[j]^data[j-1];data[j]   = data[j]^data[j-1];}}}
}int main()
{int i;int data[100000] = {0};struct timespec time;clock_gettime(CLOCK_REALTIME, &time);srand((int)(time.tv_sec));for(i=0; i<100000; i++){data[i] = rand()%1000;}printf("shell排序\n");clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);fun(data,100000);clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);printf("end\n");srand((int)(time.tv_sec));for(i=0; i<100000; i++){    data[i] = rand()%1000;}printf("快速排序\n");clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);fun1(data, 0, 100000-1);clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);printf("end\n");srand((int)(time.tv_sec));for(i=0; i<100000; i++){data[i] = rand()%1000;}printf("冒泡排序\n");clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);fun2(data,100000);clock_gettime(CLOCK_REALTIME, &time);printf("%ld\n",time.tv_sec);printf("end\n");}

可以得到结果:

在十万级的数据排序中可以看到shell排序和快速排序用时都不到1秒钟,而冒泡排序用了53秒,可见两种算法的差距是多大!

C--排序算法:shell排序、快速排序相关推荐

  1. 排序算法——shell排序(希尔排序)

    Shell排序 Shell排序,又称希尔排序,是我最喜欢的一种排序方式,也是我认为在简单的排序算法里效率最高.在效率高的排序算法里最简单的.(就像我,是成绩差的学生里代码最好的,在代码好的学生里成绩最 ...

  2. 排序算法——Shell排序

    Shell排序 Shell排序(又称希尔排序)也是一种插入排序,但它是插入排序的一个更高效的版本,也称为缩小增量排序. 基本思想 希尔排序是把记录按下标的一定量分组,对每组使用直接插入排序的算法排序: ...

  3. 算法之排序算法-shell排序(移位法)

    个人觉得移位法就是借希尔排序进行分组,插入排序进行排序 注释是上一篇的交换法. 而且这种移位排序的真的很快 package com.ebiz.sort;import java.text.SimpleD ...

  4. 算法之排序算法-shell排序(交换法)

    可以先看注释掉的分析,最后在看三层for循环 package com.ebiz.sort;import java.util.Arrays;/*** @author YHj* @create 2019- ...

  5. C语言排序算法 选择排序 插入排序 快速排序 qsort实现快排 堆排序

    常见排序算法 选择排序 选择排序(Selection sort)是一种简单直观的排序算法. 它的工作原理如下. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素 ...

  6. 经典排序算法 - 希尔排序Shell sort

    经典排序算法 - 希尔排序Shell sort 希尔排序Shell Sort是基于插入排序的一种改进,同样分成两部分, 第一部分,希尔排序介绍 第二部分,如何选取关键字,选取关键字是希尔排序的关键 第 ...

  7. js排序算法详解-快速排序

    全栈工程师开发手册 (作者:栾鹏) js系列教程5-数据结构和算法全解 js排序算法详解-快速排序 既然是快速排序,那顾名思义一定很快,快的连小编都被懵逼了好几圈!建议先不要看动图,先看第一种写法: ...

  8. 数据结构(七)高级排序算法——归并、快速排序

    一.归并排序 1.排序原理 归并排序算法是一种利用了分治算法思想实现的排序算法,这种排序算法是基于有序数组合并的归并排序算法的基本思想就是:判断一个待排序序列的长度,只要这个待排序序列的长度是超过1的 ...

  9. 十大经典排序算法-希尔排序算法详解

    十大经典排序算法 十大经典排序算法-冒泡排序算法详解 十大经典排序算法-选择排序算法详解 十大经典排序算法-插入排序算法详解 十大经典排序算法-希尔排序算法详解 十大经典排序算法-快速排序算法详解 十 ...

  10. JavaScript实现十种经典排序算法(js排序算法)

    冒泡排序算法 冒泡排序(Bubble Sort)是一种简单直观的排序算法.冒泡排序算法的步骤描述如下: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一 ...

最新文章

  1. 【Qt】Qt再学习(九):并发 QtConcurrent、QFuture、QFutureWatcher
  2. 令人头疼的字符编码的问题
  3. 【机器学习】情侣、基友、渣男和狗-基于时空关联规则的影子账户挖掘
  4. 【深度学习】短袖短裤识别算法冠军方案总结
  5. 启动DevStack的各项服务
  6. 李开复:21世纪7种人才最抢手
  7. [html] 移动端如何设置页面以全屏模式运行?
  8. C语言实现树,你一定看得懂
  9. 全球权威MLPerf基准测试再发榜,浪潮AI服务器创18项AI性能纪录
  10. [Usaco2009 Feb]Revamping Trails 堆优化 Dijkstra
  11. [转]Flex是什么?flex和flash是什么关系 ?
  12. CAD工程制图软件中洁具图库的使用
  13. 计算机技术和信息技术结合,浅谈通信技术与计算机技术融合发展
  14. “互联网“各职位的简称
  15. 【报告分享】2021年618全面复盘报告-魔镜市场情报(附下载)
  16. 用js代码简单的介绍自己
  17. 对建立奉贤区区级工程技术中心的企业给予30万元奖励
  18. IGF1重组人胰岛素样生长因子-1解决方案
  19. Python界面开发库大全
  20. 计算机视觉用于图像识别的难点在哪?

热门文章

  1. java自考一个专科证,为什么不建议自考大专?因为政策有这个漏洞!
  2. 图形打印。(20分)
  3. 第一章java入门:Markdown语法
  4. log日志打印封装,并保存到本地文件
  5. pyqt开发新浪微博粉丝聊天软件v1(图形界面)
  6. MongoDB复制集原理、搭建及复制集简单维护
  7. 每日学术速递1.27
  8. bzoj4179: B
  9. 关于coursera上Learning How to Learn课程的读书笔记2
  10. 详解高耦合低内聚,低耦合高内聚