MIT 6.001X 2016 (12)search and sort 查找和排序算法




为什么 retrieve(检索)element of list 所花的时间是不变的?

因为假如list 都是ints 的话 ,每一个element  存在内存里 需要4个字节,那么一个有着n个element的list在内存里储存的形式是这样的, 给他一个连续的 4n个字节的空间  存储 元素, 所以当要检索第i个元素的时候 我们直接去 base+4*i 个字节去找就好了

(base是第0个元素存的地方) 所以花费的时间是不变的


那么如果元素不全是 ints  是更复杂的情况的时候呢? 

其实我们用的是linked list 的方法存储列表的,对于n个元素的list 我们创建了一个n*一个固定长度的 的东东, 在这里存的是n个指针,分别指向自己对应的元素, 所以当我们要检索第i个元素 的时候,我们直接找第i个指针 然后通过指针 找到那个元素  所以时间还是固定的。





二分法:






龟龟,注意下面这个程序,他的复杂度不是O(logn)  

因为在递归的时候他还干了一件事 copy  而list的copy的复杂度是 O(n)

所以真正的复杂的是O(nlogn) 当然具体的应该是更小,因为这个copy的size每次都变小 但也应该大于等于O(logn) (下面黑图片,教授的话有解释)


那么怎么避免这个copy呢, 请看下面这个程序:


这里说的not constant  应该是说递归这个操作不是一个固定的时间 不是 O(1)  这个程序的复杂度其实是    O(logn)





对于没有sort 的list 来说  是直接linear search 好呢?还是先sort 再 二分查找 好呢?



通常来说  假如你只做 一次 操作  那应该是 linear 好, 但是假如你要干很多次操作呢? 那sort花费的时间 就能分摊到每次操作上了   amortize(分摊)



几种排序方法:

(1) monkey sort

就是拿到一个list 先看是不是 有序的,如果是 输出,如果不是 瞎鸡儿排 ,再看是不是有序的,一直重复直到找到有序的结果。

实现代码:



(2)bubble sort  冒泡排序

 冒泡排序的基本思想是,先比较第0个和第1个,如果小的在前 ,那么不交换,反之交换,然后比较第1个和第2个 同理,一直这么比较到最后第二个 和最后一个  那么这一趟下来 最后一个的元素就是最大的,

然后下一趟,这次不包含最后一个元素了,最后一个比较是最后第3个和最后第2个  

一直这么干下去,直到某一趟没有交换,或者最后只剩一个第一元素的时候  结束。



对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序

  


  代码实现

    在冒泡排序的过程中,如果某一趟执行完毕,没有做任何一次交换操作,比如数组[5,4,1,2,3],执行了两次冒泡,也就是两次外循环之后,分别将5和4调整到最终位置[1,2,3,4,5]。此时,再执行第三次循环后,一次交换都没有做,这就说明剩下的序列已经是有序的,排序操作也就可以完成了,来看下代码 

这里的代码不是很有效,因为这里每次都比了 n-1次 但实际可以每进行一次比较,下次都可以少一次,

具体修改:

def bubble(L):
    swap = False
    i = 0
    while not swap:
        swap = True
        for j in range(1,len(L)-i):
            if L[j-1] > L[j]:
                swap = False
                L[j],L[j-1] = L[j-1],L[j]           
        i += 1


  根据上面这种冒泡实现,若原数组本身就是有序的(这是最好情况),仅需n-1次比较就可完成;若是倒序,比较次数为 n-1+n-2+...+1=n(n-1)/2,交换次数和比较次数等值。所以,其时间复杂度依然为O(n2




(3) 选择排序

这个的思想就是先从  list 中选出最小的一个 把他和第0个元素交换, 然后再从除了第0个元素以外的元素 选出最小的 放在 第一个元素位置上,一直这么干下去


证明这个算法是对的:(其实就是数学归纳法了)





代码实现:


复杂度 仍是O(n^2)  不过 教授说这个比冒泡好一点  虽然不懂为什么)




(4) merge sort  归并排序


先讲归并是咋回事:  

归并 就是把两个已经sort 好了的list  弄成一个 list  而且这个list是  order 好了的(从小到大或者从大到小)

那咋归并呢 ?假设顺序是从小到大  方法是  弄一个 空list  叫result,然后 比较 order好了两个list(A,B)的 第一个元素 谁小谁  append 到 result   假如A 的第一个小,那接下来比较A的第二个元素和B的第一个元素……这么一直比较下去,知道有个list比完了,那么就把另一个list 剩下的元素 全部append 到result   就ok了  


下面就是归并的代码:


然后我们可以看他的复杂度 ,应该就是两个list的长度和    O(len(A)+len(B))




但问题是现实不可能给两个order 好了的list 给咱啊,一般就是一个无序的list  让咱排序,那咋办?

这就用到了 递归思想,啥叫递归?递归就是把一个大问题变成一些小问题加上一些简单操作。

到这个归并排序的问题的时候 我们就把对一个长度为n的无序的list A 排序的问题 变成  对A 前半部分排序和后半部分排序(一些小问题) 再归并成一个序列(一些简单操作)  的问题

那base 情况是啥呢? 就是如果A的length 小于2的时候 也就是1或者0 这时候直接返回A 就好了,因为已经排好序了

下面是代码:


那这个递归的次数是多少呢?  因为每个stage 都把list的size 变成一半了  所以应该是O(logn)

那每个stage的复杂度是啥呢? 

请看下图: 


之前咱知道  归并的复杂度是两个list 的size和,虽然每一个stage  list的size都变一半了 ,但是每个stage 要归并的变两倍了啊 ,所以每个stage 的复杂度还是O(n)


所以总的复杂度是O(nlogn)


总结:


教授说 O(nlogn) 是所有sort方法 可以达到的最快的了  ,不知道是指这节课的sort 方法  还是所有的sort方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值