为什么 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方法。