数据结构(python版)—— 1前期知识和综述

Posted 重拾初心的青年人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构(python版)—— 1前期知识和综述相关的知识,希望对你有一定的参考价值。

前言

为了提高代码质量和后续处理需求的能力,有必要再复习下算法和数据结构,为后续ESP32项目和数据处理打下坚实基础。故根据所学整理此系列文章。文章分为:

1、概述:计算理论

2、算法分析

3、基本结构(线性表、链表、栈和队列)

4、递归(递归算法和分治策略)

5、排序与查找

6、树及其算法

7、图及其算法

概述:计算理论

把确定的事情交给机器去做 把不确定的事情交给人去做!

1、数据时代

我们已经进入大数据时代

关于数据的获取、组织、存储、处理成为至关重要的基础技术

数据结构与算法课程的主要目的

分析和抽象问题、数据结构描述问题、高性能算法处理数据、得到答案解决问题

Python语言的语法简洁、抽象层次高

具有丰富的数据类型和优秀的动态特性

很适合作为数据结构与算法的描述语言

信息时代就是数据的时代

人类在各个领域的生成生活

无时不刻在产生着巨量的数据

数据化的科学、技术、工程、商业

2、问题求解的计算之道


问题求解的计算之道
 
问题:未知的事物

人们在生活、生产、学习、探索、创造过程中会遇到各种未知的事物

What类型

云是什么?

这种草(虫子)可以吃吗?

什么是无理数?

什么是万物的起源?

Why类型

为什么会下雨?

为什么食物放久了会发霉?

为什么根号二是无理数?

生命的意义是什么?

How类型

怎么让粮食长得更多?

怎么将楼房建到101层?

怎么求最大公约数?

怎么维护公平与正义?

如何从未知到已知?

感觉、经验

占卜、求神

逻辑、数学、实验

工程、计算

模型、模拟、仿真

哲学?

有些问题已经解决

很多问题尚未解决

有些问题似乎无法完成解决

尚未解决和无法解决问题的共性

表述含混、标准不一、涉及主观、结果不确定

3、数学:解决问题的终极工具

在长期的发展过程中,人们把已经解决的问题逐渐表述为数学命题与模型

尚未解决的问题,人们试图通过数学建模,采用数据工具来解决;

无法解决的问题,人们试图转换表述、明晰问题来部分解决。

为什么是数学?

数学具有清晰明确的符号表述体系

严密确定的推理系统

但正如科学不是万能的,数学也不是万能的

有些问题天然无法明确表述(主观、价值观、意识形态、哲学问题等)

有些可明确表述的问题仍然无法解决(留后待述)

数学是上帝用来书写宇宙的语言——伽利略


4、问题解决的:“计算”之道

20世纪20年代,为了解决数学本身的可验证性问题,大数学家希尔伯特提出“能否找到一种基于有穷观点的能行方法,来判定任何一个数学命题的真假”

抽象的“计算”概念提出

基于有穷观点的能行方法

由有限数量的明确有限指令构成;

指令执行在有限步骤后终止;

指令每次执行都总能得到唯一结果;

原则上可以由人单独采用纸笔完成,而不依靠其它辅助;

每条指令可以机械地被准确执行,而不需要智慧和灵感。

20世纪30年代,几位逻辑学家各自独立提出了几个关于“计算”的数学模型

哥德尔和克莱尼的递归函数模型

丘奇的Lambda 演算模型

波斯特的Post 模型

图灵的图灵机模型

研究证明,这几个“基于有穷观点的能行方法”的计算模型,全都是等价的

虽然希尔伯特的计划最终被证明无法实现

不存在“能行方法”可判定所有数学命题的真假

总有数学命题,其真假是无法证明的

但“能行可计算”概念成为计算理论的基础

其中的一些数学模型(如图灵机)也成为现代计算机的理论基础

5、算法和计算复杂性

问题的分类

What:是什么?

面向判断与分类的问题;

Why:为什么?

面向求因与证明的问题;

How:怎么做?

面向过程与构建的问题。

可以通过“计算”解决的问题

用任何一个“有限能行方法”下的计算模型可以解决的问题,都算是“可计算”的

What:分类问题

可以通过树状的判定分支解决

Why:证明问题

可以通过有限的公式序列来解决

数学定理证明从不证自明的公理出发,一步步推理得出最后待证明的定理

我们在以往学习过的定理证明即为此类解决方法

How:过程问题

可以通过算法流程来解决

解决问题的过程:算法和相应数据结构的研究,即为本课主要内容

6、计算复杂性

“基于有穷观点的能行方法”的“可计算”概念

仅仅涉及到问题的解决是否能在有限资源内(时间/空间)完成

并不关心具体要花费多少计算步骤或多少存储空间

Yes or No

由于资源(时间/空间)相当有限,对于问题的解决需要考虑其可行性如何

人们发现各种不同的可计算问题,其难易程度是不一样的

有些问题非常容易解决,如基础数值计算;

有些问题的解决程度尚能令人满意,如表达式求值、排序等;

有些问题的解决会爆炸性地吞噬资源,虽有解法,但没什么可行性,如哈密顿回路、货郎担问题等

定义一些衡量指标,对问题的难易程度(所需的执行步骤数/存储空间大小)进行分类,是计算复杂性理论的研究范围

但对于同一个问题,也会有不同的解决方法,其解决效率上也是千差万别

如排序问题,以n张扑克牌作为排序对象

一般人们会想到的是“冒泡”排序,即每次从牌堆里选出一张最小的牌,这样全部排完大概会需要n²量级的比较次数

另一种有趣的“Bogo”排序方法

洗一次牌,看是否排好序,没有的话,接着洗牌,直到排序成功!

这样全部排完,平均需要n*n!量级的比较次数

计算复杂性理论研究问题的本质,将各种问题按照难易程度分类,研究各类问题的难度级别,并不关心解决问题的具体方案

而算法则研究问题在不同现实资源约束情况下的不同解决方案,致力于找到效率最高的方案

不同硬件配置(手持设备、PC设备、超级计算机)

不同运行环境(单机、多机环境、网络环境、小内存)

不同应用领域(消费、工业控制、医疗系统、航天领域)

甚至不同使用状况(正常状况、省电状况)

如何对具体的算法进行分析,并用衡量指标评价其复杂度,我们在后面的还会详细介绍

7、不可计算问题

有不少定义清晰,但无法解决的问题

并不是尚未找到解,而是在“基于有穷观点的能行方法”的条件下,已被证明并不存在解决方案

停机问题:判定任何一个程序在任何一个输入情况下是否能够停机

不可计算数:几乎所有的无理数,都无法通过算法来确定其任意一位是什么数字

可计算数很少:如圆周率Pi,自然对数的底e

似乎计算之道解决问题存在边界和极限?


怎么突破计算极限

超大规模分布式计算、社会公共也能通过贡献算力参与

新型计算技术:光子计算、DNA计算、量子计算

分布式智慧——众包

突破“基于有穷观点的能行方法”? 如果是具有智慧和直觉的众多人脑一起来共同解决问题呢?——游戏化众包学术研究

闲置计算力 闲置智力

8、什么是抽象和实现

计算机科学研究什么

计算机科学不仅仅是对计算的研究

虽然计算机是非常重要的计算工具

计算机科学主要研究的是问题、问题解决过程,以及问题的解决方案

包括了前述的计算复杂性理论

以及对算法的研究

抽象(Abstraction

为了更好地处理机器相关性或独立性,引入了“抽象”的概念

用以从“逻辑Logical”或者“物理Physical”的不同层次上看待问题及解决方案

什么是抽象?例子:汽车

从司机观点看来,汽车是一台可以带人去往目的地的代步工具

司机上车、点火、换挡、踩油门加速、刹车

从抽象角度说,司机看到汽车的“逻辑”层次

司机可以通过操作各个机构来达到运输的目的

这些操作机构(方向盘、油门、档位)就称为“接口”Interface

而从汽车维修工的角度来看同一辆汽车,就会相当不同,他还需要清除每项功能是如何实现的

如发动机工作原理,档位操作的机械结构,发动机舱内各处温度如何测量和控制等等

这些内部构造构成了汽车的“物理”层次,工作过程就称为“实现”Implementaion

例子:计算机

从一般大众用户观点看来,计算机可以用来编辑文档、收发邮件、上网聊天、处理照片等等

并不需要具备计算机内部如何处理的知识

利用这些功能是计算机的“逻辑”层次

而对于计算机科学家、程序员、技术支持、系统管理员来说

就必须要了解从硬件结构、操作系统原理到网络协议等各方面的低层次细节

内部如何实现,是计算机的“物理”层次

抽象发生在各个不同层次上

即使对于程序员来说,使用编程语言进行编程,也会涉及到“抽象”

如计算一个数的平方根

程序员可以调用库函数math.sqrt,直接得到结果,而无需关心内部是如何实现

这种功能上的“黑盒子”称作“过程抽象”Procedural Abstraction

抽象与实现:编程

编程是通过一种程序设计语言,将抽象的算法实现为计算机可以执行的代码的过程

没有算法,编程无从谈起

图灵奖获得者Niklaus Wirth的著名公式:算法+数据结构=程序


程序设计语言实现算法的基本机制


程序设计语言需要为算法的实现提供实现“过程”和“数据”的机制

具体表现为“控制结构”和“数据类型”

程序设计语言均有语句对应控制结构

顺序处理、分支选择、循环迭代

程序设计语言也提供最基本的数据类型来表示数据,如整数、字符等

但对于复杂的问题而言,直接使用这些基本数据类型不利于算法的表达

9、为什么要研究数据结构与算法

清晰高效地表达算法

为了控制问题和问题解决过程的复杂度,利用抽象来保持问题的“整体感”

而不会陷入到过多的细节中去

这要求对现实问题进行建模的时候,对算法所要处理的数据,也要保持与问题本身的一致性,不要有太多与问题无关的细节

数据抽象:ADT 抽象数据类型


“过程抽象”启发我们进行“数据抽象”

相对于程序设计语言中基本数据类型,抽象数据类型(ADT:Abstract Data Type)

ADT是对数据进行处理的一种逻辑描述,并不涉及如何实现这些处理

抽象数据类型ADT建立了一种对数据的“封装”Encapsulate

封装技术将可能的处理实现细节隐蔽起来能有效控制算法的复杂度

数据结构是对ADT 的具体实现

同一ADT可以采用不同的数据结构来实现

采用程序设计语言的控制结构和基本数据类型来实现ADT所提供的逻辑接口

属于ADT的“物理”层次

ADT 实现:数据结构Data Structure


对数据实现“逻辑”层次和“物理”层次的分离,可以定义复杂的数据模型来解决问题,而不需要立即考虑次模型如何实现

接口的两端:抽象与实现

如电车与汽油车

底层动力、能源都不同

但是开车的操作接口(方向盘、油门、刹车、档位)基本都是相同的

司机无需重新考驾照,而车厂可以持续改进实现技术

由于对抽象数据类型可以有多种实现方案

独立于实现的数据模型

让底层开发程序员专注于实现和优化数据处理,又不改变数据的使用接口

让用户专注于用数据接口来进行问题的解决,而无需考虑如何具体实现这些接口

通过层层抽象,降低问题解决过程的复杂度

为什么要研究和学习算法


首先,学习各种不同问题的解决方案

有助于我们在面对未知问题的时候,能够根据类似问题的解决方案来更好解决

其次,各种算法通常有较大差异

我们可以通过算法分析技术来评判算法本身特性

而不仅仅根据实现算法的程序在特定机器和特定数据上运行的表现来评判它

即使同一个程序,在不同的运行环境和输入数据的情况下,其表现的差异可能也会很大

在某些情况下,当我们碰到棘手的难题

得到区分这种问题是根本不存在算法

还是能找到算法,但需要耗费大量的资源

某些问题解决需要一些折衷的处理方式

我们需要学会在不同算法使用之前进行选择,以适合当前条件的要求

数据结构:优先队列 基于堆实现(python版)

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 class PrioQueueError(ValueError):
 5     pass
 6 
 7 class PrioQueue(object):
 8     def __init__(self, elist = []):
 9         self._elems = list(elist)
10         if elist:
11             self.buildheap()
12 
13     def is_empty(self):
14         return not self._elems
15 
16     def peek(self):
17         if self.is_empty():
18             raise PrioQueueError("in peek")
19         return self._elems[0]
20 
21     def enqueue(self, e):
22         self._elems.append(None)
23         self.siftup(e, len(self._elems) - 1)
24 
25     def siftup(self, e, last):
26         elems ,i, j = self._elems, last, (last-1)//2 #父节点
27         while i > 0 and e < elems[j]:
28             elems[i] = elems[j]
29             i, j = j, (j-1)//2
30         elems[i] = e
31 
32     def dequeue(self):
33         if self.is_empty():
34             raise PrioQueueError("in dequeue")
35         elems = self._elems
36         e0 = elems[0]
37         e = elems.pop()
38         if len(elems) > 0:
39             self.siftdown(e, 0, len(elems))
40         return e0
41 
42     def siftdown(self, e, begin, end):
43         elems, i, j = self._elems, begin, begin*2+1
44         while j < end:
45             if j+1 < end and elems[j+1] < elems[j]:
46                 j += 1
47             if e < elems[j]:
48                 break
49             elems[i] = elems[j]
50             i, j = j, 2*j + 1
51         elems[i] = e
52 
53     def buildheap(self):
54         end = len(self._elems)
55         for i in range(end//2, -1, -1):
56             self.siftdown(self._elems[i], i, end)
57 
58 
59     def buildheap(self):
60         end = len(self._elems)
61         for i in range(end//2, -1, -1):
62             self.siftdown(self._elems[i], i, end)
63 
64 if __name__=="__main__":
65     pq = PrioQueue([3,5,2,7,6,9,8,1,0])
66     print(pq._elems)
67     for i in range(9):
68         print(pq.dequeue())

 

以上是关于数据结构(python版)—— 1前期知识和综述的主要内容,如果未能解决你的问题,请参考以下文章

机器学习与量化交易项目班 [从零搭建自动交易系统]

算法(第4版)-1.3.4 综述

综述 | 中科院计算所《事件知识图谱》

1.Python爬虫入门一之综述

深度学习驱动的知识追踪研究综述

图卷积神经网络(GCN)综述与实现(PyTorch版)