数据结构与算法
算法
概念
算法是独立存在的一种解决问题的方法和思想。
算法的五大特性
1.输入: 算法具有0个或多个输入
2.输出: 算法至少有1个或多个输出
3.有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
4.确定性:算法中的每一步都有确定的含义,不会出现二义性
5.可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成
时间复杂度
时间复杂度:假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为T(n)
最重要的是其数量级和趋势
主要关注算法的最坏情况,亦即最坏时间复杂度。
python内置timeit模块测代码执行速度
class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)
stmt参数是要测试的代码语句(statment);
setup参数是运行代码时需要的设置;
timer参数是一个定时器函数,与平台有关。
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法。
数据结构
概念
数据结构指数据对象中数据元素之间的关系
系统自己定义好的,不需要我们自己去定义的数据结构叫做Python的内置数据结构,比如列表、元组、字典
Python的扩展数据结构,比如栈,队列等。
抽象数据类型(Abstract Data Type)
把数据类型和数据类型上的运算捆在一起,进行封装。
最常用的数据运算有五种:
- 插入
- 删除
- 修改
- 查找
- 排序
线性表
一组序列元素的组织形式,我们可以将其抽象为线性表。
根据线性表的实际存储方式,分为两种实现模型:
- 顺序表,将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
- 链表,将元素存放在通过链接构造起来的一系列存储块中。
顺序表
顺序表的基本形式
图a表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc (e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得
访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)。
元素的大小不统一,则须采用图b的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。每个链接所需的存储量相同,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构。
顺序表的结构与实现
结构
完整信息包括两部分,元素集合,有关表的整体情况的信息,主要包括元素存储区的容量和当前表中已有的元素个数两项。
两种弄基本实现方式
一体式结构,存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象。顺序表创建后,元素存储区就固定了。
分离式结构,表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
元素存储区替换
一体式结构只能整体搬迁
分离式结构只需将表信息区中的数据区链接地址更新即可,而该顺序表对象不变。
元素存储区扩充
分离式结构可以在不改变表对象的前提下对其数据存储区进行了扩充,这种顺序表称为动态顺序表
策略:
线性增长:每次扩充增加固定数目的存储位置
每次扩充容量加倍:以空间换时间,推荐的方式。
顺序表的操作
增加元素
a. 尾端加入元素,时间复杂度为O(1)
b. 非保序的加入元素(不常见),时间复杂度为O(1)
c. 保序的元素加入,时间复杂度为O(n)
删除元素
a. 删除表尾元素,时间复杂度为O(1)
b. 非保序的元素删除(不常见),时间复杂度为O(1)
c. 保序的元素删除,时间复杂度为O(n)
Python中的顺序表
list就是一种采用分离式技术实现的动态顺序表。
list实现采用了如下的策略:在建立空表(或者很小的表)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区。但如果此时的表已经很大(目前的阀值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式,是为了避免出现过多空闲的存储位置。
链表
为什么?
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
定义
在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)
单向链表
也叫单链表,是链表中最简单的一种形式,每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
单链表的操作
- is_empty() 链表是否为空
- length() 链表长度
- travel() 遍历整个链表
- add(item) 链表头部添加元素,头插法
- append(item) 链表尾部添加元素,尾插法
- insert(pos, item) 指定位置添加元素
- remove(item) 删除节点
- search(item) 查找节点是否存在
链表与顺序表的对比
顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
栈
概念
栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。
后进先出(LIFO, Last In First Out)的原理运作。
栈结构实现
可以用顺序表实现,也可以用链表实现。
栈的操作
- Stack() 创建一个新的空栈
- push(item) 添加一个新的元素item到栈顶
- pop() 弹出栈顶元素
- peek() 返回栈顶元素
- is_empty() 判断栈是否为空
- size() 返回栈的元素个数
队列
概念
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的(First In First Out)的线性表,简称FIFO。
队列的实现
也可以用顺序表或者链表实现。
操作
- Queue() 创建一个空的队列
- enqueue(item) 往队列中添加一个item元素
- dequeue() 从队列头部删除一个元素
- is_empty() 判断一个队列是否为空
- size() 返回队列的大小
排序和搜索
排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法。
不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地实现为稳定。
冒泡排序
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
越小的元素会经由交换慢慢“浮”到数列的顶端。
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
插入排序
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前比较,找到相应位置并插入。
希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
常见排序算法效率比较
希尔排序虽然看似步骤复杂,但平均情况竟然可能比插入排序更好,最坏情况也就是插入排序的效率
归并排序
采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
搜索
搜索是在一个项目集合中找到一个特定项目的算法过程。搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找.
二分法查找
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。
树
概念
树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。
树的存储
链式存储
二叉树
二叉树是每个节点最多有两个子树的树结构。
二叉树的性质(特性)
性质1: 在二叉树的第i层上至多有2^(i-1)个结点(i>0)
性质2: 深度为k的二叉树至多有2^k - 1个结点(k>0)
性质3: 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
性质4:具有n个结点的完全二叉树的深度必为 log2(n+1)
性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)