linux按函数数字大小排序,(第11章)LinuxC语言中插入排序、归并排序、快速排序、线性查找和折半查找...

文章目录

一、算法的概念

二、插入排序

1.用玩扑克的方式解释插入排序

2.插入排序的算法如下:

(2)如何严格证明这个算法是正确的?

3.算法的时间复杂度分析

(1)比较评价算法的好坏,一个重要的标准就是算法的时间复杂度

(2)在分析算法的时间复杂度时,我们更关心最坏情况而不是最好情况

(3)如何证明是2n^2+3lgn和 n ^2是同一量级的?

(4)几种常见的时间复杂度函数按数量级从小到大的顺序

(5)Small-O notation和Big-O notation

四、归并排序:分而治之

1.插入排序与归并排序的区别

2.归并排序的步骤

五、快速排序:分而治之

1.快排的核心思想

2.时间复杂度

六、线性查找

1.任意输入字符串中找出某个字母的位置并返回这个位置

2.习题

七、折半查找

1.在有序的序列里查找,可用折半查找

2.如何保证程序的正确性?

3.习题

一、算法的概念

(1)算法(Algorithm) 是将一组输入转化成一组输出的一系列计算步骤,其中每个步骤必须能在有限时间内完成。

(2)

442e0d84a2155693dc2e69ac0aefeb0e.png

二、插入排序

1.用玩扑克的方式解释插入排序

29027ffa8ac9de435dbce54056b86af7.png

要点在于:

(1)前提:每次插入新的值时,前面的序列都是有序的

(2)但和插入扑克牌有一点不同,不可能在两个相邻的存储单元之间再插入一个单元,因此要将插入点之后的数据依次往后移动一个单元。

2.插入排序的算法如下:

11ea69898b3f85a08e965a166ff23649.png

(1)运行结果如下:

6fcb18eb9ebe1904c089327f873badbc.png

(2)如何严格证明这个算法是正确的?

1cb2a3dd91ab2a820bb435231251efa5.png

5492a3efb7dc59d343cf8bba57427e38.png

总结:

这里的第一条就相当于递归的Base Case

第二条就相当于递归的递推关系。这再次说明了递归和循环是等价的

3.算法的时间复杂度分析

(1)比较评价算法的好坏,一个重要的标准就是算法的时间复杂度

ccc462834c8a986753265e4eb870047c.png

这里有一个问题,m不是个常数,也不取决于输入长度n,而是取决于具体的输入数据。

(a)在最好情况下

数组 a 的原始数据已经排好序了, while 循环一次也不执行,总的执行时间是(c1+c2+c5)*n-(c1+c2+c5),可以表示成an+b的形式,是n的线性函数(Linear Function)。

(b)在最坏情况(Worst Case) 下

所谓最坏情况是指数组 a 的原始数据正好是从大到小排好序的

(c)平均情况(Average Case)

72719168d282a0b1126fe51dee4536d8.png

(2)在分析算法的时间复杂度时,我们更关心最坏情况而不是最好情况

41cecb1ad1d92a640c20e1509115eea4.png

(3)如何证明是2n^2+3lgn和 n ^2是同一量级的?

12f744a7da876ebb8d6351d0a85934ff.png

44802d2083cbffc982b178a46caa0baf.png

(4)几种常见的时间复杂度函数按数量级从小到大的顺序

d510f96a622eaccb294fdd7c8d7646e2.png

(5)Small-O notation和Big-O notation

627d7380fd4150ba7d61d9feda8d7a1d.png

四、归并排序:分而治之

1.插入排序与归并排序的区别

41674b47ccb502a9f3e324869fa76213.png

2.归并排序的步骤

ccf59a7f90c529d422d17146f4cdf3b1.png

(1)代码如下

47ae9baca54bae19dafff87683cf1a70.png

(2)执行结果如下

fc3755a229b5a8253c02c0a859d181d2.png

(3)代码解释如下:

首先是sort函数:

sort 函数把a[start…end]平均分成两个子序列,分别是a[start…mid]和a[mid+1…end],对这两个子序列分别递归调用 sort 函数进行排序,然后调用 merge 函数将排好序的两个子序列合并起来。

接着是merge函数:

合并的过程很简单,每次循环取两个子序列中最小的元素进行比较,将较小的元素取出放到最终的排序序列中,如果其中一个子序列的元素已取完,就把另一个子序列剩下的元素都放到最终的排序序列中

(4)归并排序的调用过程如下:

1bfe2d7d49193aca8cd2d32d188713d6.png

(5)归并排序的复杂度

merge函数的时间复杂度为

3f4dd74e1f7b4c831764bda65be55fbb.png

sort函数的时间复杂度为

24f1eebad1872e9ca81e451cfeeee7c0.png

总的时间的复杂度为:

2bc7e347729f089a75d566357f3e17cb.png

6222d5711cf06256292c2fc025c2b112.png

0de79751a0cfe27a3b0a26f26b9fc1a0.png

五、快速排序:分而治之

b4703d3986fcacab8d6d72d7625eb00f.png

define N 某个数;

int a[N];

int partition(int start,int end)

{

int i=start;

int j=end;

int base=a[i];

/*一定要先从右边开始找*/

while (i!=j){

while (a[j]>=base && istart){

mid=partition(start,end);

quicksort(start,mid-1);

quicksort(mid+1,end);

}

}

1.快排的核心思想

先找到一个基数(若是最左边的为基数,升序排列),从右边开始寻找小于基数的数字,然后从左边开始寻找大于基数的数,找到了就交换,直到这两端遍历的i和j碰头为止。

2.时间复杂度

N:需要排序的元素个数

最差时间复杂度O(N^2);

平均时间复杂度O(NlogN),这里的log是以2为底

六、线性查找

1.任意输入字符串中找出某个字母的位置并返回这个位置

(1)写一个 indexof 函数,从任意输入字符串中找出某个字母的位置并返回这个位置,如果找不到就返回-1:

代码如下:

d10a92fafded0b7b5bf3ea3c27abe595.png

(2)时间复杂度是:O(n)

2.习题

(1)

2ee04fdd177abed6ee0097520faf20c3.png

没有

(2)

5bcc4ec696ce7025c2bd7df639e0fb0b.png

/*在一组随机排列的数中找出第k小的*/

int partition(int start, int end)

{

int i=start,j=end;

int base=a[start];

while (i!=j)

{

while (i=base)

j--;

while (istart)

mid=partition(start,end);

int i=mid;

if (k==i)

return i;

else if (k>i)

return partition(mid+1,end,k)

else

return partition(start,mid-1,k)

}

七、折半查找

1.在有序的序列里查找,可用折半查找

(1)代码如下:

3927bdd3107a2c047c091be34cc34c63.png

(2)折半查找Binary Search的含义

00b12a575a32ee825f243517710b3453.png

2.如何保证程序的正确性?

(1)接着上面的二分查找的eg去讲,在这个a[start…end]范围之外的数组 a 的元素中一定不存在 number 这个元素。

以下为了书写方便,我们把这句话表示成 mustbe(start, end, number) 。

77b94ea39a0e385b5d2b43d66e5db511.png

解释:

(a)注意这个算法有一个非常重要的前提: a 是排好序的

(b)从更普遍的意义上说,函数的调用者(Caller) 和函数的实现者(Callee,被调用者) 之间订立了一个契约(Contract) ,在调用函数之前,Caller要为Callee提供某些条件,比如确保 a 是排好序的,确保a[start…end]都是有效的数组元素而没有

访问越界,这称为Precondition。然后Callee对一些Invariant进行维护(Maintenance) ,这些Invariant保证了Callee在函数返回时能够对Caller尽到某些义务,比如确保“如果 number 在数组 a 中存在,一定能找出来并返回它的位置,如果 number 在数组 a 中不存在,一定能返回-1”,这称为Postcondition。

(c)测试一个函数是否正确需要把Precondition、Maintenance和Postcondition这三方面都测试到,比如 binarysearch 这个函数,即使它写得非常正确,既维护了Invariant也保证了

Postcondition,如果调用它的Caller没有保证Precondition,最后的结果也还是错的。

(2)我们编写几个测试用的Predicate函数,然后把相关的测试插入到 binarysearch 函数中

下面的是带有测试代码的折半查找:

3fcc63b3c32208f4726459c53b7c0aa0.png

说明:

(a)断言Assertion的作用如下:

782922674b57b6361392239c196cfc27.png

1e97df87bc01610de0f2d635b2b0fdf5.png

(b)测试代码只在开发和调试时有用。

如果正式发布(Release) 的软件也要运行这些测试代码就会严重影响性能了,如果在包含 assert.h 之前定义一个 NDEBUG 宏(表示No Debug) ,就可以禁用 assert.h 中的 assert 宏定义,这样代码中的所有 assert 测试都不起作用了:

cd0a97bb1403f240dcd19e66f19dddd0.png

(c)总结下

在实际工作中我们要测试的代码绝不会像 binarysearch 这么简单,而我们编写的测试函数往往都很简单,比较容易保证正确性,也就是用简单的、不容易出错的代码去测试复杂的、容易出错的代码。

3.习题

9cd9424fa35cb2f1ffdb9161818b43f7.png

int binarysearch(int number)

{

int mid, start=0,end=N-1;

while (start<=end)

{

mid=(start+end)/2;

if(numbera[mid])

start=mid+1;

else

{

while (a[mid--]==number);

return mid;

}

}

}

6c23fabe1c97064a9b00972ca2c9a5e1.png

double mysqrt(double y)

{

double mid,start=0,end=y;

while (start<=end)

{

mid=(start+end)/2;

if ((abs(pow(mid,,2)-y)<0.001)

return mid;

else if ((pow(mid,,2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值