《数据结构与算法 python语言描述》学习笔记(一)————绪论

第一部分:学习内容概要

  

  • 程序开发过程
  • 问题求解
  • 算法和算法分析
  • 数据结构

  

第二部分:学习笔记

  • 程序开发过程

  框架图

分析,严格化——设计——编码——检查,翻译——测试/调试

  牛顿迭代法
  
  0.对给定正实数x和允许误差e,令变量y取任意正实数值,如:y = x;
  1.如果y*y与x足够接近,即 | y * y - x | < e,计算结束并把y作为结果;
  2.取z =(y + x / y)/ 2;
  3.将z作为y的新值,回到步骤1.

(举个栗子:完成平方根计算的Python函数)

    def sqrt(x):
    		y = 1.0
    		while abs( y * y - x ) > 1e-6
    			y = ( y +x / y) / 2
    			return y
  • 问题求解
      交叉路口的红绿灯安排
      贪婪算法(贪心法):根据当时掌握的信息,尽可能地向得到解的方向前进,知道不能急需再换一个方向。这样做通常找不到最优解,但能找到可以接受的解。
      红绿灯问题求解过程:
    1.有关工作开始于交叉路口的抽象图示,首先美剧出所有允许通行方向;
    2.根据通行方向和有关不同方向冲突的定义,画出冲突图;
    3.把通行方向的分组问题归结为冲突图中不相邻顶点的划分问题,用求出不相邻顶点的分组作为交叉路口中可以同时通行的方向的分组。

    (具体实现)

    def coloring(G):
    	color = 0
    	groups = set()
    	verts = vertices(G)
    	while verts:
    		new_group = set()
    		for v in list(verts):
    		if not_adjacent_with_set(v, newgroup, G):
    			new_group.add(v)
    			verts.remove(v)
    		groups.add((color, new_group))
    		color += 1
    	return groups
    
  • 算法和算法分析

      

    • 三个基本概念

      问题:一个问题W是需要解决(需要用计算求解)的一个具体需求。(例如判断人一个正整数N是否为素数)

      问题实例:问题W的一个实例w是该问题的一个具体例子,通常可以通过一组具体的参数设定。(例如判断1013是否为素数)

      算法:解决问题W的一个算法A,是对一种计算过程的严格描述。对W的任何一个实例w,实施算法A描述的计算过程,就能得到w的解。(例如一个判断素数的算法应该能给出1013是否为素数的判断,也能满足其他正整数是否为素数的判断)

    • 算法的性质

        1.有穷性:一个算法的描述应该由有限多条指令或语言构成。

        2.能行性:算法中的指令的含义严格而且明确,所描述的操作过程可以完全机械的进行。

        3.确定性:作用于所求解问题的给定输入(以某种描述形式给出的要处理问题的具体实例),根据算法的描述将产生出唯一的确定的一个动作序列。

        4.终止性(行为的有穷性):对问题的任何实例,算法产生的动作序列都是有穷的,他或者终止并给出该问题实例的解;或者终止并指出给定的输入无解。

        5.输入/输出:有确定定义的输入和输出。

    • 算法和程序

        算法的实现:程序可以看作采用计算装置能够处理的语言描述的算法,由于它是算法的实际体现,又能在实际计算机上执行,因此被称为算法的实现

    • 算法设计与分析
        1.枚举法:根据具体问题枚举出各种可能,从中选出有用信息或者问题的解。

        2.贪心法:见上文

        3.分治法:把复杂问题分解为相对简单的子问题,分别求解,最后通过组合去子问题的接的方式得到原问题的解。

        4.回溯法(搜索法):专指通过探索的方式求解。分步骤进行,进行不下去了推翻原先的步骤,另行选择求解路径。

        5.动态规划法:在一些复杂情况下,问题求解很难直截了当地进行,因此需要在前面的步骤中积累信息,在后续步骤中根据已知信息,动态选择一致的最好求解路径。

        6.分支限界法:可以看作搜索方法的一种改良形式。

    • 常量因子和算法复杂度

        大O记法:对于单调的整数函数f,如果存在一个整数函数g和实常数c > 0,使得对于充分大的n总有f(n) >= c * g(n),就说函数g是f的一个渐进函数(忽略常量因子),记为f(n) = O(g(n)).

        假设存在函数g,使得算法A处理规模为n的问题实例所用的时间T(n) = O(g(n)),则成O(g(n))为算法A的渐进时间复杂度,简称时间复杂度。算法的空间复杂度S(n)的定义与此类似。

      算法复杂度函数增长情况
      《数据结构与算法 python语言描述》学习笔记(一)————绪论_第1张图片

    • 算法分析
        
      基本循环程序
         0.基本操作,认为期时间复杂度为O(1)。如果函数调用,应该讲其时间复杂度代入,参与整体时间复杂度的计算。

         1.加法规则。如果算法是两个部分的顺序复合,其复杂度是这两部分的复杂性之和。
         T(n) = T1(n) + T2(n) = O(T1(n)) + O(T2(n)) = O(max(T1(n), T2(n)))

         2.乘法规则。如果算法是一个循环,循环体将执行T1(n)次,每次执行需要T2(n)时间
         T(n) = T1(n) x T2(n) = O(T1(n)) x O(T2(n)) = O(T1(n) x T2(n))

         3.取最大规则。如果算法是条件分支,两个分支的时间复杂性分别为T1(n) , T2(n)
         T(n) = O(max(T1(n), T2(n))))

  • 数据结构

    • 信息、数据和数据结构

       数据就是计算机能够处理的符号形式的总和,或说是经过了编码的信息。

       数据元素指最基本的数据单位。

       数据结构是指研究据之间的关联和组合的形式,总结其中的规律性,发觉特别值得注意的有用结构,研究这些结构的性质,进而研究如何在计算机里实现这些有用的数据结构,以支持相应组合数据的高效使用,支持处理它们的高效算法。

    • 抽象定义与重要类别
        D = (E , R) 抽象地考虑数据结构的概念。从逻辑上看,一个数据结构包含一集数据元素,是一个有穷极,在这些元素之间有着某些特定的逻辑关系。其中E是数据结构D的元素集合,是某个数据集合 ε 的一个有穷子集,而R ∈ E x E 是D的元素之间的某种关系。

        典型的数据结构:

      集合结构、序列结构(线性结构、环形结构、p形结构)、层次结构、树形结构、图结构

    • 结构性功能性的数据结构

        0.上述的序列结构、树形结构、图结构等称之为结构性的数据结构

        1.支持元素存储和访问的数据结构被称为容器

        2.功能性数据结构包括栈、队列、优先队列、字典等

    • 计算机内存对象表示

        程序中直接使用的数据保存在计算机的内存储器(简称内存)。内存是CPU可以直接访问的数据存储设备。

        内存的基本结构是线性排列的一批存储单元。一个单元可以保存一个字节(8位二进制代码)的数据。

        内存单元具有唯一编号,成为单元地址,简称地址。64位计算机,一次可以存取8个字节的数据,也就是说依次操作访问8个单元的内容。O(1)操作。

  

第三部分:练习

.  

  • 一般练习

      
    1.设法证明求平方根的牛顿迭代法一定收敛。
      
    证明:
      由定理知:假设f(x)在x的某领域内具有连续的二阶导数,且设f(x) = 0, f′(x*) ≠ 0, 则对充分靠近x的初始值x0,牛顿迭代法产生的序列{xn}收敛于x
      求m的平方根等价于f(x) = x² - m = 0,f′(x) = 2x,应用与牛顿迭代法,得迭代计算格式为
          x(n+1) = (x(n) + m/x(n))/ 2,(n = 0, 1, 2,……)
       所以说求平方根的牛顿迭代法一定收敛
      
    2.修改红绿灯安排实例中的算法,使之最后给出的各个分组最大,可以考虑从本章算法给出的分组出发,给每个分组加入不冲突的所有行驶方向。
      
    解:得到{AB,ACAD,BA,DC,ED},{BC,BD,EA,BA,DC,ED},
        {DA,DB,BA,DC,ED,AD},{EA,EB,BA,DC,ED,EA}
      
    3.请完整写出采用高斯消元法求矩阵行列式的算法,并仔细分析其时间复杂度。
      
    解:

#设被求值矩阵为二维表A[0:N][0:N]
for i in range(n-1):
	#A[i][i] 将A[i +1:n][i]的值都变为0
	for j in range(i+1, n):
		A[j][i] == 0
det = 0.0
for i in range(n):
	det += A[i][i]

  4.解决同一问题有两种算法,一个算法的时间开销约为100n³, 另一个约为0.5 x 2ⁿ。问题规模为多大的情况下后一算法更快?
  
  解:
    100n³ = 0.5 x 2ⁿ      n>20之后后者算法更快

  5.假设现在需要对全世界的人口做一些统计工作,希望在1天之内完成工作。如果采用的算法具有线性复杂度,多快的计算机就足以满足工作需求?如果所用算法具有平方复杂度呢?用天河二号超级计算机大约能处理什么复杂度的算法?
  
  解:
    1天 = 86400秒,截止至2018年3月当今人口约75.9亿。每秒878473次的计算机就能满足工作要求。平方复杂度的每秒275500次的计算机就能满足工作要求。天河二号超级计算机的平均速度为每秒3.39亿亿次,能处理指数及复杂度的算法。
  
  6.如果在宇宙大爆炸的那一刻启动了一台每秒十亿次的计算机执行斐波那契数列的递归算法,假设每条指令计算一次加法。到今天为止它可以算出第几个斐波那契数?这个数的数值应该是多少?
  
   解:
    递归算法难度级为O(n²),宇宙大爆炸至今为138.2亿年,约为4.3582752e+18秒,计算机计算至今已经计算了435827520次,因为难度级为n²,所以n约为20876。数值应该是。。。数值太恐怖了,还是不放了。。
  
  7.假设你从3岁开始手工操作计算器每秒可以完成3次加法或乘法。执行斐波那契数的递归算法,到今天为止你可能算出哪几个斐波那契数?假设执行本章求矩阵乘积的算法,到今天为止能做出两个多大的矩阵的乘积?
  
  解:
    我今年21岁,姑且算他18年,约为567648000秒,没日没夜,无时无刻的算斐波那契数列,应该算了1702944000次,递归算法难度级为O(n²),所以我应该算了约41266次。具体算了什么,我也不知道。矩阵乘积算法难度级为O(n³),我可能可以算出n = 1194大的矩阵的乘积。
  
  8.根据本章的讨论和自己的认识,设法对数据结构和算法的关系做一个综述和总结,并给出自己的认识,讨论其中的问题。
  

  • 编程练习
      
    1.请回忆(查阅)基础数学中求平方根的方法。请定义一个Python函数,其中采用基础数学中的方法求平方根。从各方面比较这个函数和基于牛顿迭代法的函数。
      
      解:
        基础数学(笔算)一个平方数如下操作,因个人能力有限还是得用到math模块的sqrt方法,所以没放上去。
        ①.将被开方数的整数部分从个位起向左每隔两位划为一段,用撇号分开(竖式中的11’56),分成几段,表示所求平方根是几位数;
        ②.根据左边第一段里的数,求得平方根的最高位上的数(竖式中的3);
        ③.从第一段的数减去最高位上数的平方,在它们的差的右边写上第二段数组成第一个余数(竖式中的256);
        ④.把求得的最高位数乘以20去试除第一个余数,所得的最大整数作为试商(3×20除 256,所得的最大整数是 4,即试商是4);
        ⑤.用商的最高位数的20倍加上这个试商再乘以试商.如果所得的积小于或等于余数,试商就是平方根的第二位数;如果所得的积大于余数,就把试商减小再试(竖式中(20×3+4)×4=256,说明试商4就是平方根的第二位数);
        ⑥.用同样的方法,继续求平方根的其他各位上的数.
        相比之下,更能感受到牛顿迭代法带来的简易性。
      
    2.基于1.3.4节中构造整数表的几个函数做一些试验,对一组参数值统计它们的执行时间。修改这些函数,重复试验对Python中构造表的各种方法做一个总结。
      
       代码如下:
import time

def test1(n):
    begin = time.time()
    lst = []
    for i in range(n*10000):
        lst = lst + [i]
    stop = time.time()
    print("test1消耗{}秒".format(stop-begin))



def test2(n):
    begin = time.time()
    lst = []
    for i in range(n*10000):
        lst.append(i)
    stop = time.time()
    print("test2消耗{}秒".format(stop - begin))


def test3(n):
    begin = time.time()
    lst = [i for i in range(n*10000)]
    stop = time.time()
    print("test3消耗{}秒".format(stop - begin))


def test4(n):
    begin = time.time()
    lst = list(range(n*10000))
    stop = time.time()
    print("test4消耗{}秒".format(stop - begin))


test1(10)
test2(10)
test3(10)
test4(10)

  显示结果为
  test1消耗41.73838710784912秒
  test2消耗0.011000633239746094秒
  test3消耗0.004000186920166016秒
  test4消耗0.002000093460083008秒
  因为test1的方法实在是不必要又耽误时间,所以后面几次我就没有继续使用test1方法。
  当n取10000时
  test2消耗62.904597759246826秒
  test3消耗30.589749813079834秒
  test4消耗16.711955785751343秒
  显然最后一个方法是最佳的,难度级也是最低的。test3花费的时间几乎是test4的两倍,test2约是test4的4倍。
  
  3.用Python字典表示冲突图结构,以关键码(A, B)关联的值为True表示冲突,没有值表示不冲突,用这个技术完成本章求无冲突分组的程序。
  
  这道题不知所云。。。是如关键码(AB, BC)的关联值还是什么。。
  
  4.基于前一题的类似设计,设法编写一个程序,使之能枚举出一个冲突图上的各种分组组合,从中找出最佳的无冲分组。
  
  不知所云+1
  

第四部分:总结

  以上记录的是自己学习的过程,课后练习也全是凭借个人理解来完成的,正确答案以及方式有待商榷,如果有大神偶然看见小弟的这篇笔记,有不对的地方也恳请斧正,谢谢。

你可能感兴趣的:(学习笔记)