算法图解笔记

一、二分查找

1. 仅当列表是有序的时候,二分查找才有用。

2. 对于包含n个元素的有序列表,用二分查找最多需要log2n步(运行时间为对数时间或log时间),而简单查找最多需要n步。

代码:

 1 def binary_search(inlist,item):
 2     low = 0 # python:from 0
 3     high = len(inlist)-1  
 4     while low<=high:
 5         mid = (low + high)/2
 6         guess = inlist[mid]
 7         if guess == item:
 8             return mid
 9         if guess <item:
10             low = mid + 1
11         if guess >item:
12             high = mid - 1
13     return None
14 
15 my_list = [1,3,5,7,9]
16 print(binary_search(my_list,3)) #output:1
17 print(binary_search(my_list,-1)) #output:None

二、大O表示法

1. 比较的是算法的操作数

2. 并为单位

3. 算法的速度指的并非是时间,而是操作数的的增速

4. 指出最糟糕情况下的运行时间

从快到慢的顺序

O(logn)<O(n)<O(n*logn)<O(n2)<O(n!)  对应于: 对数时间(二分查找)< 线性时间(简单查找)<(快速排序)<(选择排序)<(商旅问题:遍历n个城市,要求总旅程最小)

三、数组与链表

数组:随机访问、读取数据时有优势、

链表:顺序访问、插入与删除数据时有优势、读取数据时从头开始遍历(读取所有元素时)

  数组 链表
读取 O(1) O(n)
插入 O(n) O(1)
删除 O(n) O(1)

四、递归

1. 递归由两部分组成:基线条件(函数不再调用自己)和递归条件(函数调用自己)

循环的性能更好,递归让解决方案更清晰,并没有性能上的优势。

2. 栈:“后进先出”,始终在栈顶工作。

3. 调用栈(call stack)即:存储多个函数的变量,描述的是函数之间的调用关系,它由多个栈帧(stack frame)组成,每个栈帧对应着一个未运行完毕的函数,栈帧中保存了该函数的返回地址和局部变量,因而不仅能在执行完毕后找到正确的返回地址,还很自然地保存了不同函数间的局部变量互不相干——因为不同函数对应着不同的栈帧。

   调用另一个函数时,当前函数暂停并处于未完成的状态。

4. 使用栈很方便,但是由于其存储详尽的信息(大量的函数调用的信息)会占用大量的内存。解决:改用循环或者尾递归(并非所有语言都支持)。

5. 所有函数的调用都进入调用栈

五、几种排序算法

性能:直接插入排序 < 选择排序 < 冒泡排序 < 合并排序 < 快速排序

1. 直接插入排序

直接插入排序(Straight Insertion Sort)的基本思想是(两种不同的表述):

1)把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。

2)每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止。

 1 #按照定义
 2 #错误代码
 3 def Insertsort1(inlist):
 4     length = len(inlist)
 5     #遍历(1...n-1)
 6     for i in range(1,length):
 7         j=i-1
 8         while j>=0 :
 9             if inlist[j]<inlist[i]:
10                 break
11             j = j-1
12         #print(j)
13         if j!=i-1:
14             temp = inlist[i]
15             for k in range(i-1,j+1,-1): #error
16                 inlist[k+1] = inlist[k]
17             inlist[j+1] = temp
18     return inlist
19 #正确代码
20 def Insertsort2(inlist):
21     length = len(inlist)
22     #遍历(1...n-1)排序(0,1)(0..2)(0..n-1)
23     for i in range(1,length):
24         j=i-1
25         while j>=0 :
26             if inlist[j]<inlist[i]:
27                 break
28             j = j-1
29         #print(j)
30         if j!=i-1:
31             temp = inlist[i]
32             for k in range(i-1,j,-1): # right
33                 inlist[k+1] = inlist[k]
34             inlist[j+1] = temp
35     return inlist
36     
37 my_list = [1,3,2,1,0,7,4]
38     
39 res = Insertsort2(my_list)
40 print(res) # [0, 1, 1, 2, 3, 4, 7]
41 #!!!
42 for i in range(2,0,-1):
43     print(i)  # 2,1 
44 #0不会被输出

 

2. 冒泡排序

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

 1 def swap(arr,i,j):
 2     arr[i] = arr[i] + arr[j]
 3     arr[j] = arr[i] - arr[j]
 4     arr[i] = arr[i] - arr[j] 
 5     return arr
 6 #错误代码
 7 def selectionSort_3(inarr):
 8     for i in range(len(inarr)):
 9         for j in range(i,len(inarr)-1): # error
10             if inarr[j]>inarr[j+1]:
11                 inarr = swap(inarr,j,j+1)
12     return inarr
13 #正确代码
14 def selectionSort_4(inarr):
15     for i in range(len(inarr)):
16         for j in range(len(inarr)-1-i):
17             if inarr[j]>inarr[j+1]:
18                 inarr = swap(inarr,j,j+1)
19     return inarr    
20 arr_sort =  selectionSort_4([1,3,2,1,0,7,4])
21 print(arr_sort)  # [0, 1, 1, 2, 3, 4, 7]
22 #每一次比较,会将最大的值传到数组的后端!!!

 

3. 选择排序

 选择排序法 是对 定位比较交换法(也就是冒泡排序法) 的一种改进。每趟从待排序的记录中选出关键字最小(或最大)的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。依次挑选最小、第二小、第三小。。。 

 1 def findSmallest(inarr):
 2     smallest = inarr[0]
 3     smallest_index = 0
 4     for i in range(1,len(inarr)):
 5         if inarr[i] < smallest:
 6             smallest_index = i
 7             smallest = inarr[i]
 8     return smallest_index
 9 # 建立新数组    
10 def selectionSort(arr):
11     newArr = []
12     for i in range(len(arr)):
13         smallest_index = findSmallest(arr)
14         newArr.append(arr.pop(smallest_index))
15     return newArr
16 
17 arr_sort =  selectionSort([1,3,2,1,0,7,4])
18 print(arr_sort)  # [0, 1, 1, 2, 3, 4, 7]
 1 #交换元素
 2 #错误代码:
 3 def selectionSort_1(arr):
 4     for i in range(len(arr)):
 5         smallest_index = findSmallest(arr[i:len(arr)])
 6         if i!= smallest_index:
 7             arr[i] = arr[i] + arr[smallest_index]
 8             arr[smallest_index] = arr[i] - arr[smallest_index]
 9             arr[i] = arr[i] - arr[smallest_index]
10     return arr
11 # smallest_index = findSmallest(arr[i:len(arr)]) 返回的索引是子序列的索引,与arr索引不一样
12 #arr=[1,2,3,4,5],i=1时,findSmallest(arr[1:5])返回的索引为0(2对应的位置),但其在arr处的索引是1。
13 #修改代码
14 def selectionSort_1(arr):
15     for i in range(len(arr)):
16         smallest_index = findSmallest(arr[i:len(arr)])
17         locate = i + smallest_index #!!!
18         if i!= locate:
19             arr[i] = arr[i] + arr[locate]
20             arr[locate] = arr[i] - arr[locate]
21             arr[i] = arr[i] - arr[locate]
22     return arr
23     
24 arr_sort =  selectionSort_1([1,3,2,1,0,7,4])
25 print(arr_sort)  # [0, 1, 1, 2, 3, 4, 7]

 

4. 快速排序

 1)选择基准值

 2)将数组分成两个子数组:小于基准值的元素和大于基准值的元素

 3)对这两个数组进行快速排序(递归调用)

 1 def quickSort(inarr):
 2     if len(inarr)<2: # 空数组或只含有一个数组的元素
 3         return inarr
 4     pivot = inarr[0]
 5     less = [i for i in inarr[1:] if i <= pivot]
 6     greater = [i for i in inarr[1:] if i > pivot]
 7     return quickSort(less) + [pivot] + quickSort(greater)
 8 
 9 my_list = [1,3,2,1,0,7,4]
10 print(quickSort(my_list)) 

 

5.合并排序(merge sort)

 

 

算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

  直接插入排序 冒泡排序 选择排序 合并排序 快速排序
算法复杂度  O(n2)  O(n2)   O(n2) O(nlogn)  O(nlogn)
算法稳定性          

注:1. 在大O表示法中n是操作数,用c表示算法所需要的固定时间量,c*n表示算法时间。当两种算法的大O运行时间不同时,这种常量无关紧要。如直接插入排序与快速排序。但对合并排序与快速排序来说,常量的影响很大,由于快速查找的常量比合并查找的常量小,因此运行时间都为O(nlogn),快速查找的时间更快。

       2. 快速排序的最糟情况下,运行时间为O(n2)。最佳情况即平均情况,运行时间为O(nlogn)

六、分治算法

1. 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

2. 分治与递归像一对孪生兄弟,经常同时应用在算法设计之。

3. 分治法适用的情况

    分治法所能解决的问题一般具有以下几个特征:

    1) 该问题的规模缩小到一定的程度就可以容易地解决

    2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

    3) 利用该问题分解出的子问题的解可以合并为该问题的解;

    4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;

第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。

第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

4. 分治法在每一层递归上都有三个步骤:

    step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

    step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

    step3 合并:将各个子问题的解合并为原问题的解。

七、 

posted @ 2019-01-15 21:02  GuoXinxin  阅读(236)  评论(0编辑  收藏  举报