C语言中快速排序和插入排序优化的实现

   2015-11-26 0
核心提示:这篇文章主要介绍了C语言中快速排序和插入排序优化的实现,包括双向划分快速排序方法的介绍,需要的朋友可以参考下

快速排序
快速排序思想

    1962年,由C.A.R.Hoare创造出来。该算法核心思想就一句话:“排序数组时,将数组分成两个小部分,然后对它们递归排序”。然而采取什么样的策略将数组分成两个部分是关键,想想看,如果随便将数组A分成A1和A2两个小部分,即便分别将A1和A2排好序,那么A1和A2重新组合成A时仍然是无序的。所以,我们可以在数组中找一个值,称为key值,我们在把数组A分解为A1和A2前先对A做一些处理,让小于key值的元素都移到其左边,所有大于key值的元素都移到其右边。这样递归排序A1和A2,数组A就排好了。

举例

    我们要排序的数组如下:

55 41 59 26 53 58 97 93

    我们选取第一个元素作为key值,即55.(一般都是选取第一个元素)。假如我们有一种办法可以将数组做一步预处理,让小于key值的元素都位于其左边,大于key值的元素都位于其右边,预处理完数组如下:

41 26 53 55 59 58 97 93 

    这样数组就被key值划分成了两段,A[0...2]小于key,A[4...7]大于key,可见key值本身已排好序,接下来对A[0...2]和A[4...7]分别进行递归排序,那么整个数组就排好序了。预处理做的工作再次澄清下:找一个key值,把key位放到某位置A[p],使小于key值的元素都位于A[p]左边,大于key值的元素都位于A[p]的右边。到此,我们的快排模型就成型了。

/*l, u 代表待排序部分的下界和上界*/
void qsort(l, u)
{
  /*递归结束条件是待排序部分的元素个数小于2*/
  if(l >= u)
  {
    return;
  }
 
  /*此处进行预处理,预处理后key值位于位置p*/
 
  qsort(l, p-1);
  qsort(p+1, u);
}

     接下来看如何做预处理。我们选取A[0]做为key值, p作为key值的位置。我们从A[1]开始遍历后面的数组,用变量i指示目前的位置,每次找到小于key的值都与A[++p]交换。开始时p=0.

55 41 59 26 53 58 97 93 i = 1,A[i]位置为41, 即A[i] < key, swap(++p , i),即p = 1:
55 41 59 26 53 58 97 93 i = 2,A[i]位置为59,A[i] > key,不做任何改变。
i = 3,A[i]位置为26,A[i] < key,swap(++p, i), 即p = 2:
55 41 26 59 53 58 97 93 i = 4,A[i]位置为53,A[i] < key,swap(++p, i),p = 3:
55 41 26 53 59 58 97 93 i = 5,A[i]位置为58,A[i] > key,不做任何改变。
i = 6,A[i]位置为97,A[i] > key,不做任何改变.
i = 7,A[i]位置为93,A[i] > key,不做任何改变.结束循环。此时p为key的最终位置。还需一步把key值填入p位置。
最后swap(l, p)即把Key值放到最终位置上了。至于为什么要交换l,p的位置,可以另拿一组数据试一下:55,41,59,26,99,58,97,93。

完整的程序1

/*l, u 代表待排序部分的下界和上界*/

void qsort(int l, int u)
{
  /*递归结束条件是待排序部分的元素个数小于2*/
  if(l >= u)
  {
    return;
  }
 
  int p = l;
  for(int i = l+1; i <= u; i++)
  {
    if(A[i] < A[l])
    {
      swap(++p, i);
    }
  }
  swap(l, p);
 
  qsort(l, p-1);
  qsort(p+1, u);
}

这就是第一代快速排序算法,正常情况下其复杂度为nlogn,但在考虑一种极端情况:n个相同元素组成的数组。在n-1次划分中每次划分都需要O(n)的时间,所以总的时间为O(n^2)。使用双向划分就可以避免这个问题。

双向划分快速排序

/*l, u 代表待排序部分的下界和上界*/
void qsort(int l, int u)
{
  /*递归结束条件是待排序部分的元素个数小于2*/
  if(l >= u)
  {
    return;
  }
 
  key = A[l]
  for(int i = l, j = u+1; i <= j;)
  {
    do i++ while(i <= u && A[i] < key));
    do j-- while(A[j] > key);
    if(i > j)
    {
      break;
    }
    swap(i, j);
  }
  swap(l, j);
 
  qsort(l, j-1);
  qsort(j+1, u);
}

插入排序优化
插入排序的精髓就是首先将第一个元素视为有序子数组x[0...0],然后插入x[1]...x[n-1].思想很简单,代码也很简单,简单的代码有没有优化的空间呢?编程珠玑中提供了几个优化后的方案,效率提高了70%之多。

简单的实现(sort1)

void insertSort(int *array, size_t size)
{
  for(size_t i = 1; i < size; i++)
  {
    for(int j = i; j > 0 && array[j - 1] > array[j]; j--)
    {
      swap(array[j - 1], array[j]);
    }
  }
}

优化思路
    内循环的swap函数可能不如内联函数快些,所以第一步优化将该swap函数展开,据作者说,展开后效率提高了60%。

优化代码(sort2)

void insertSort(int *array, size_t size)
{
  for(size_t i = 1; i < size; i++)
  {
    for(int j = i; j > 0 && array[j - 1] > array[j]; j--)
    {
      int t = array[j];
      array[j] = array[j - 1];
      array[j - 1] = t;
    }
  }
}

优化思路
    由于内循环中总是给变量t赋同样的值(x[i]的初始值),所以内循环关于t的两条赋值语句移出循环,据说这么做的效率又提高了15%。

优化代码(sort3)

void insertSort(int *array, size_t size)
{
  for(size_t i = 1; i < size; i++)
  {
    int j = i;
    int t = array[j];
    for(; j > 0 && array[j - 1] > array[j]; j--)
    {
      array[j] = array[j - 1];
    }
    array[j] = t;
  }
}

《编程珠玑》书中给出了三种排序的运行时间:

C语言中快速排序和插入排序优化的实现

插入排序的效率总是O(n2),效率差在比较的次数以及交换的频率,如果交换的频率减少的话就可以大大提高插入排序的效率,这也是为什么元素基本有序时插入排序效率高的原因。

个人观点

    代码调优以及性能优化都可能带来一系列的副作用,比如程序的正确性,可读性,可维护性等。是否需要调优要看问题性质,调优既是华而不实的“花活”,也是一把利刃,区别就在于使用的场合。

 
标签: C语言 排序
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Rust应用调用C语言动态库的操作方法
    目录外部功能接口FFIUDP套接字的读超时Rust调用C语言动态库中的函数避免重复造***,使用Rust官方C语言库外部功能接口FFI虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现。调用其他编程语
  • Delphi中获取Unix时间戳及注意事项(c语言中tim
    uses DateUtils;DateTimeToUnix(Now) 可以转换到unix时间,但是注意的是,它得到的时间比c语言中time()得到的时间大了8*60*60这是因为Now是当前时区的时间,c语言中time()是按格林威治时间计算的,北京时间比格林威治时间多了8小时DateTimeToUnix(Now)-8*60*
    02-09
  • Unicode与UTF-8互转(c语言和lua语言) python
    1. 基础1.1 ASCII码我们知道, 在计算机内部, 全部的信息终于都表示为一个二进制的字符串. 每个二进制位(bit)有0和1两种状态, 因此八个二进制位就能够组合出 256种状态, 这被称为一个字节(byte). 也就是说, 一个字节一共能够用来表示256种不同的状态, 每个状态
    02-09
  • R语言中cat函数 c语言cat命令
    R语言中cat函数 c语言cat命令
    R语言中cat函数。1、测试1cat("aa","bb")cat("aa","bb",sep = "_")  2、测试2a = 100b = 300c = "abcd"cat(a,b,c)cat(a,b,c,sep = "_") 3、测试3a = c("aaa", "bbb", "ccc")b = 1:4ca
    02-09
  • R语言之merge详解 c语言merge函数代码
    merge是R语言中用来合并数据框的函数merge函数的声明:?1234merge(x, y, by = intersect(names(x), names(y)),      by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all,      sort = TRUE, suffixes = c(".x"
    02-09
  • R语言调用的C语言源代码查询 R语言 c
    R语言使用时可以调用自己写的C代码,但是有些C函数是软件包自带的,怎么查询在使用软件包 kerfdr 时,涉及到一个函数y = .C("massdist", x = as.double(xtrunc), xmass = as.double(tau[trunc]/sum(tau[trunc])), nx = nx, xlo = as.double(lo), xhi = as.dou
    02-09
  • centos安装与配置R语言 centos配置c语言环境
    Linux下安装R语言一、编译安装      由于采用编译安装,所以需要用到gcc编译环境,在编译前check文件时还会用到libXt-devel和readline-devel两个依赖,所以在编译R语言源码时先将这些工具和依赖包准备好。readline-devel 也可以不安装,不安装此包R语言编
    02-09
  • C语言利用链表实现学生成绩管理系统
    链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示
  • C语言通过三种方法实现属于你的通讯录
    目录一、基础版本1.1 通讯录的个人信息(结构体来实现)1.2通讯录名单1.3人员初始化1.4菜单1.5主函数二、功能的实现2.1、增加人数2.2、删除人数2.3、查找2.4、展示2.5、排序(这里我是通过名字)三、通讯录进阶(设置动态存储)3.1通讯录从静态改为动态3.2通
  • C++集体数据交换实现示例讲解 c语言两个数据交
    目录一、说明二、示例和代码一、说明到目前为止介绍的功能共享一对一的关系:即一个进程发送和一个进程接收。链接是通过标签建立的。本节介绍在多个进程中调用相同参数但执行不同操作的函数。对于一个进程,函数可能会发送数据,对于另一个进程,它可能会接收
点击排行