《算法图解》学习笔记习题和代码(第一、二章 二分法 选择排序)Python3

目录

一、二分法

练习1

1.3 大O表示法

练习2

1.3.5 旅行商 问题O(n!)

二、选择排序

2.1 内存

2.2 数组和链表  

2.2.2 数组的优势

练习 1

2.2.4 在中间插入 

2.2.5 删除 

练习 2

2.3 选择排序 

2.4 小结 



 

一、二分法

仅当列表是有序的时候,二分查找才管用。时间复杂度:\log_2n。(\log_ax:log以a为底,以x为真数。)

e.g. 从100个数中猜一个数,不超过7次就可以猜出来。2^7=128 

函数binary_search接受一个有序数组和一个元素。如果指定的元素包含在数组中,这个函数将返回其位置。

def binary_search(list,item):
    low = 0
    high = len(list)-1
    while low<=high:
        mid = (low+high) //2
        guess = list[mid]
        if guess == item:
            return mid
        elif guess > item:
            high = mid-1
        elif guess < item:
            low = mid+1
    return None
my_list = [1,3,5,7,9,11]
print(binary_search(my_list,7))
print(binary_search(my_list,1))

Output:
3
0

练习1

1.1 假设有一个包含128个名字的有序列表,你要使用二分查找在其中查找一个名字,请 问最多需要几步才能找到? 

2^7=128,7步
1.2 上面列表的长度翻倍后,最多需要几步?

2^8=256,8步

最多需要猜测的次数与列表长度相同,这被称为线性时间(linear time)。 O(n)

如果列表包含100个元素,最多要猜7次;如果列表包含40亿个数字,最多需猜32次。二分查找的运行时间为对数时间(或log时间)。O(\log n)

1.3 大O表示法

大O表示法让你能够比较操作数,它指出了算法运行时间的增速。 

大 O 表示法指出了最糟情况下的运行时间。 查找数用遍历的方法运行时间为O(n),用二分法则是O(\log n)

除最糟情况下的运行时间外,还应考虑平均情况的运行时间。在第四章讨论。

 

下面按从快到慢的顺序列出了经常会遇到的5种大O运行时间。 

O(log n),也叫对数时间,这样的算法包括二分查找。
O(n),也叫线性时间,这样的算法包括简单查找。
O(n * log n),这样的算法包括第4章将介绍的快速排序——一种速度较快的排序算法。
O(n2),这样的算法包括第2章将介绍的选择排序——一种速度较慢的排序算法。
O(n!),这样的算法包括接下来将介绍的旅行商问题的解决方案——一种非常慢的算法。

练习2

算法的速度指的并非时间,而是操作数的增速。算法的运行时间用大O表示法表示。

使用大O表示法给出下述各种情形的运行时间。 

1.3 在电话簿中根据名字查找电话号码。

O(logn)(名字是有序的?)

1.4 在电话簿中根据电话号码找人。(提示:你必须查找整个电话簿。)

O(n)

1.5 阅读电话簿中每个人的电话号码。

O(n)

1.6 阅读电话簿中姓名以A打头的人的电话号码。这个问题比较棘手,它涉及第4章的概念。答案可能让你感到惊讶!

O(n),大O表示法不考虑乘以、除以、加上或减去的数字

1.3.5 旅行商 问题O(n!)

 一个人前往这5个城市,同时要确保旅程最短。为此,可考虑前往这些城市的各种可能顺序。所以要计算5x4x3x2=120次,才能得出最短旅程是什么。涉及n个城市时,需要执行n!(n的阶乘)次操作才能计算出结果。因此运行时间为O(n!)。目前计算机领域认为没有更巧妙的算法。

1.4 小结 
二分查找的速度比简单查找快得多。
O(log n)比O(n)快。需要搜索的元素越多,前者比后者就快得越多。
算法运行时间并不以秒为单位。
算法运行时间是从其增速的角度度量的。
算法运行时间用大O表示法表示。 

二、选择排序

二分查找只能用于有序元素列表;选择排序是下一章将介绍的快速排序的基石。

2.1 内存

要把数据存储到内存时,需要向计算机请求内存空间,计算机会为你分配一个存储地址。需要存储多项数据时,有两种基本方式——数组和链表。下图中:fe0ffeeb是一个内存单元的地址。

 

2.2 数组和链表  

数组:

在数组中添加新元素也可能很麻烦。如果没有了空间,就得移到内存的其他地方,因此添加新元素的速度会很慢。 

链表:

而链表中的元素可存储在内存的任何地方。

 链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。 

 使用链表时,不需要移动元素。只要有足够的内存空间,就能为链表分配内存。 链表的优势在于插入元素。

2.2.2 数组的优势

数组也有优势。

链表在按顺序读取元素时,一个接一个跳向下一个地址(即使存储位置不挨着)效率很高。但是我想访问最后一个元素的时候,它的地址是存在上一个元素里的,上一个元素的地址。。。以此类推。需要跳跃的找到某个元素(不按顺序),链表的效率真的很低。 

数组则不是。假设假设有一个数组,它包含五个元素,起始地址为00,就很容易推断出第五个元素的地址是什么。

元素的位置称为索引。00,01,02,03,04成为索引。 

需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素的地址,以此类推,直到访问第五个元素。  

常见的数组和链表操作的运行时间。 

练习 1

2.1 假设你要编写一个记账的应用程序。 

你每天都将所有的支出记录下来,并在月底统计支出,算算当月花了多少钱。因此,你执行的插入操作很多,但读取操作很少。该使用数组还是链表呢? 

答:采用链表。因为链表(O(1))的插入操作时间少于数组(O(n))。

2.2.4 在中间插入 

新增待办事项要插入到原来的清单中间去,用链表比较合适。插入时只用修改这个元素之前的那个元素指向的地址即可。使用数组的话,则必须将后面的元素都向后移。,如果没有足够的存储空间,整个数组还要移动到别的地方去。

 

2.2.5 删除 

频繁删除元素,链表也具有优势,只用修改前一个元素的指向地址即可。而数组删除元素,必须把其后面的元素全部前移。

 数组和链表哪个更好用,要分情况,数组使用的更多。因为数组支持顺序访问和随机访问,链表只能顺序访问。数组读取的速度更快。链表擅长插入和删除,而数组擅长随机访问。数组和链表还被用来实现其他数据结构,后面会讲。

练习 2

2.2 假设你要为饭店创建一个接受顾客点菜单的应用程序。这个应用程序存储一系列点菜单。服务员添加点菜单,而厨师取出点菜单并制作菜肴。这是一个点菜单队列:服务员在队尾添加点菜单,厨师取出队列开头的点菜单并制作菜肴。 你使用数组还是链表来实现这个队列呢?(提示:链表擅长插入和删除,而数组擅长随机访问。在这个应用程序中,你要执行的是哪些操作呢?) 

答:使用链表,因为整个过程频繁执行插入操作,而且厨师是顺序读取,不需要随机访问。

2.3 我们来做一个思考实验。假设Facebook记录一系列用户名,每当有用户试图登录  Facebook时,都查找其用户名,如果找到就允许用户登录。由于经常有用户登录Facebook,因此需要执行大量的用户名查找操作。假设Facebook使用二分查找算法,而这种算法要求能够随机访问——立即获取中间的用户名。考虑到这一点,应使用数组还是链表来存储用户名呢?

答:用数组存储用户名。1.因为使用二分法要求列表是有序的 。2.数组支持随机访问。

2.4 经常有用户在Facebook注册。假设你已决定使用数组来存储用户名,在插入方面数组有何缺点呢?具体地说,在数组中添加新用户将出现什么情况? 

答:插入只能顺序插入,不然需要后面的元素都后移。而且需要足够的内存,顺序存储。在数组添加新用户时,顺序添加,如果内存不够,也不能存到别的地方,必须整体移动到别处。如果是中间插入,就需要把后面的元素全部后移,比较费时。

2.5 实际上,Facebook存储用户信息时使用的既不是数组也不是链表。假设Facebook使用的是一种混合数据:链表数组。这个数组包含26个元素,每个元素都指向一个链表例如,该数组的第一个元素指向的链表包含所有以A打头的用户名,第二个元素指向的链表包含所有以B打头的用户名,以此类推。 

假设Adit B在Facebook注册,而你需要将其加入前述数据结构中。因此,你访问数组的第一个元素,再访问该元素指向的链表,并将Adit B添加到这个链表末尾。现在假设你要查找Zakhir H。因此你访问第26个元素,再在它指向的链表(该链表包含所有以z打头的用户名)中查找Zakhir H。 

请问,相比于数组和链表,这种混合数据结构的查找和插入速度更慢还是更快?你不必给出大O运行时间,只需指出这种新数据结构的查找和插入速度更快还是更慢。 

答:融合了数组和链表的特点。查找时比数组慢,比链表快。插入时比链表慢,比数组快。(网上很多都是说于链表相当,可能时是前面26个字母的数组查找时相比于庞大的存储,相当于常数?)。

2.3 选择排序 

为喜欢的乐队按播放次数排序。遍历选择,第一次看完n个选出次数最多的。第二次看剩下n-1个,依次类推。

 

要找出播放次数最多的乐队,必须检查列表中的每个元素。

 需要的总时间为 O(n × n),即O(n^2)。 

选择排序速度不快,快速排序是一种更快的排序算法,其运行时间为O(n log n),下一章介绍。 

#用选择排序,将数组元素按从小到大的顺序排列。
#第一步找到最小的元素再返回索引
def findsmallest(arr):
    smallest = arr[0]
    for i in range(len(arr)):
        if arr[i]<=smallest:
            smallest = arr[i]
            small_index = i
        i+=1
    return small_index

a =[5,3,6,8,2,7,9] #待排序的数组
print(findsmallest(a))

def sortnumber(arr):
    new = []
    for i in range(len(arr)):
        index = findsmallest(arr)
        new.append(arr[index])
        del arr[index]
    return new
print(sortnumber(a))

Out:

4
[2, 3, 5, 6, 7, 8, 9]

2.4 小结 


计算机内存犹如一大堆抽屉。
需要存储多个元素时,可使用数组或链表。
数组的元素都在一起。
链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
数组的读取速度很快。
链表的插入和删除速度很快。
在同一个数组中,所有元素的类型都必须相同(都为int、double等)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值