Lists

当实现 list 的数据结构的时候Python 的设计者有很多的选择. 每一个选择都有可能影响着 list 操作执行的快慢. 当然他们也试图优化一些不常见的操作. 但是当权衡的时候,它们还是牺牲了不常用的操作的性能来成全常用功能.

本文地址:http://www.cnblogs.com/archimedes/p/python-datastruct-algorithm-list-dictionary.html,转载请注明源地址。

设计者有很多的选择,使他们实现list的数据结构。这些选择可能对如何快速列表操作的影响进行。帮助他们做出正确的选择,他们看着人们最常使用的列表数据结构的方式和他们优化列表的实现,导致最常见的操作速度非常快。当然他们也试图优化不常见的操作,但当一个权衡不得不作一个不太常见的操作的性能往往是牺牲在更常见的操作支持。

两种常见的操作的索引和分配给索引位置。不管列表多大这两个操作所需时间相同。称一个独立于list大小的操作时间复杂度为O(1).

另一个常见的编程操作是增长一个 list. 有两种方法来创建一个更长的list.你可以使用附加尾部的方法或串联运算符。附加的方法是O(1)。然而,连接操作是 O(k) 其中k是需要连接列表的尺寸。这对你很重要,因为它可以帮助你选择正确的工具的工作来使自己的节目更有效。

让我们看一下四种不同的方法构造一个包含 n 个数字起始为 0 的list. Listing 1 展示了list的四种不同的方法实现:

Listing 1

def test1():
l = []
for i in range(1000):
l = l + [i] def test2():
l = []
for i in range(1000):
l.append(i) def test3():
l = [i for i in range(1000)] def test4():
l = list(range(1000))

想要计算每个函数的执行时间, 我们可以使用Python 的 timeit 模块. timeit 模块设计的目的是允许程序员在一致的环境下跨平台的测量时间.

要使用 timeit 你必须先创建一个 Timer 对象,参数为两个Python声明. 第一个参数是你想计算时间是函数声明; 第二个参数是设置测试的次数. timeit 模块将计算执行时间. timeit 默认情况下执行声明参数代表的操作100万次. 当它完成时将返回一个浮点类型的秒数. 然而,因为它执行声明一百万次,你可以将结果理解为每执行一次花费多少毫秒. 你还可以传递给 timeit 函数一个名叫 number 的参数,它可以允许你指定多少次测试语句来执行. 下面显示运行每一个测试函数1000次需要多长时间.

t1 = Timer("test1()", "from __main__ import test1")
print("concat ",t1.timeit(number=1000), "milliseconds")
t2 = Timer("test2()", "from __main__ import test2")
print("append ",t2.timeit(number=1000), "milliseconds")
t3 = Timer("test3()", "from __main__ import test3")
print("comprehension ",t3.timeit(number=1000), "milliseconds")
t4 = Timer("test4()", "from __main__ import test4")
print("list range ",t4.timeit(number=1000), "milliseconds") concat 6.54352807999 milliseconds
append 0.306292057037 milliseconds
comprehension 0.147661924362 milliseconds
list range 0.0655000209808 milliseconds

上面是实验中,函数声明是 test1()test2(), 等等. 设置的声明会让你感觉很怪, 所以让我们来深入理解一下.你可能很熟悉 fromimport 语句, 但这通常是用在一个Python程序文件开始. 在这种情况下, from __main__ import test1 从 __main__命名空间将 test1 调入到 timeit 所在的命名空间.

关于这个小实验的最后提到的是, 你看到的关于调用也包含一定的开销时间, 但是我们可以假设, 函数调用的开销在所有四种情况下是相同的, 我们仍然可以得到比较有意义的操作比较结果. 所以不会说串联操作精确地需要6.54毫秒, 而说串联测试函数需要6.54毫秒.

从下表我们可以看到list中所有操作的 Big-O 效率。经过仔细观察,你可能想知道两个不同pop的执行时间的差异。当pop在list的尾部操作需要的时间复杂度为O(1), 当pop在list的头部操作需要的时间复杂度为O(n), 其原因在于Python选择如何实现列表。

Python List 操作的效率(Big-O)
操作           效率          
index [] O(1)
index assignment O(1)
append O(1)
pop() O(1)
pop(i) O(n)
insert(i,item) O(n)
del operator O(n)
iteration O(n)
contains (in) O(n)
get slice [x:y] O(k)
del slice O(n)
set slice O(n+k)
reverse O(n)
concatenate O(k)
sort O(n log n)
multiply O(nk)

为了演示性能上的不同,让我们使用 timeit模块做另一个实验. 我们的目的是能够证实在一个已知大小的list,从list的尾部和从list的头部上面 pop 操作, 我们还要测量不同list尺寸下的时间. 我们期望的是从list的尾部和从list的头部上面 pop 操作时间是保持常数,甚至当list的大小增加的时候, 然而运行时间随着list的大小的增大而增加.

下面的代码让我们可以区分两种pop操作的执行时间. 就像你看到的那样,在第一个例子中, 从尾部pop操作花费时间为0.0003 毫秒, 然而从首部pop操作花费时间为 4.82 毫秒.

Listing 2

popzero = timeit.Timer("x.pop(0)",
"from __main__ import x")
popend = timeit.Timer("x.pop()",
"from __main__ import x") x = list(range(2000000))
popzero.timeit(number=1000)
4.8213560581207275 x = list(range(2000000))
popend.timeit(number=1000)
0.0003161430358886719

上面的代码可以看到 pop(0)确实比 pop()效率低, 但没有验证 pop(0) 时间复杂度为 O(n) 然而 pop() 为 O(1). 要验证这个我们需要看一个例子同时调用一个list. 看下面的代码:

popzero = Timer("x.pop(0)",
"from __main__ import x")
popend = Timer("x.pop()",
"from __main__ import x")
print("pop(0) pop()")
for i in range(1000000,100000001,1000000):
x = list(range(i))
pt = popend.timeit(number=1000)
x = list(range(i))
pz = popzero.timeit(number=1000)
print("%15.5f, %15.5f" %(pz,pt))

Dictionaries

Python 第二个主要的数据结构是字典. 你可能记得, 词典不同于列表的是你可以通过关键字而不是位置访问字典中的项. 最重要的是注意获得键和值的操作的时间复杂度是O(1). 另一个重要的字典操作是包含操作. 查看键是否在字典中的操作也为 O(1). 所有的字典操作效率如下表所示:

 Dictionary操作的执行效率(Big-O ) 
操作             效率              
copy O(n)
get item O(1)
set item O(1)
delete item O(1)
contains (in) O(1)
iteration O(n)

我们最后的性能实验比较了包含了列表和字典之间的操作性能. 在这个过程中我们将证实, 列表包含操作是O(N)词典的是O(1).实验中我们将使用简单的比较. 我们会列出一包含一系列数据的list. 然后, 我们将随机选择数字并查看数据是否在 list中. 如果我们之前的结论正确, 随着list的容量的增大, 所需要的时间也增加.

我们将一个dictionary 包含相同的键做重复的实验. 在这个实验中,我们可以看到, 确定一个数是否在字典中不仅速度快得多, 而且检查的时间甚至不会随着字典容量的增加而改变.

下面的代码实现了这种比较. 注意我们执行相同非操作, number in container. 不同的是第7行 x 是一个list, 第9行 x 是一个dictionary.

import timeit
import random for i in range(10000,1000001,20000):
t = timeit.Timer("random.randrange(%d) in x"%i,
"from __main__ import random,x")
x = list(range(i))
lst_time = t.timeit(number=1000)
x = {j:None for j in range(i)}
d_time = t.timeit(number=1000)
print("%d,%10.3f,%10.3f" % (i, lst_time, d_time))

您还可能感兴趣:

Python数据结构与算法--算法分析

Python数据结构与算法--面向对象

Python数据结构与算法--数据类型

Python基础(10)--数字

Python基础(9)--正则表达式

Python基础(8)--文件

Python基础(7)--函数

Python基础(6)--条件、循环

Python基础(5)--字典

Python基础(4)--字符串

Python基础(3)--列表和元组

Python基础(2)--对象类型

Python基础(1)--Python编程习惯与特点

Python数据结构与算法--List和Dictionaries的更多相关文章

  1. python数据结构与算法

    最近忙着准备各种笔试的东西,主要看什么数据结构啊,算法啦,balahbalah啊,以前一直就没看过这些,就挑了本简单的<啊哈算法>入门,不过里面的数据结构和算法都是用C语言写的,而自己对p ...

  2. Python数据结构与算法--算法分析

    在计算机科学中,算法分析(Analysis of algorithm)是分析执行一个给定算法需要消耗的计算资源数量(例如计算时间,存储器使用等)的过程.算法的效率或复杂度在理论上表示为一个函数.其定义 ...

  3. Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例

    本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个 ...

  4. Python数据结构与算法之图的广度优先与深度优先搜索算法示例

    本文实例讲述了Python数据结构与算法之图的广度优先与深度优先搜索算法.分享给大家供大家参考,具体如下: 根据维基百科的伪代码实现: 广度优先BFS: 使用队列,集合 标记初始结点已被发现,放入队列 ...

  5. python数据结构与算法之问题求解实例

    关于问题求解,书中有一个实际的案例. 上图是一个交叉路口的模型,现在问题是,怎么安排红绿灯才可以保证相应的行驶路线互不交错. 第一步,就是把问题弄清楚. 怎么能让每一条行驶路线不冲突呢? 其实,就是给 ...

  6. python数据结构与算法之问题求解

    懂得计算机的童鞋应该都知道,一条计算机程序由数据结构跟算法两大部分组成.所以,其实不管你使用哪种计算机语言编写程序,最终这两部分才是一个程序设计的核心.所以,一个不懂得数据结构与算法的程序员不是一个好 ...

  7. Python 数据结构和算法

    阅读目录 什么是算法 算法效率衡量 算法分析 常见时间复杂度 Python内置类型性能分析 数据结构 顺序表 链表 栈 队列 双端队列 排序与搜索 冒泡排序 选择排序 插入排序 希尔排序 快速排序 归 ...

  8. Python数据结构与算法(几种排序)

    数据结构与算法(Python) 冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是 ...

  9. Python - 数据结构与算法(Data Structure and Algorithms)

    入门 The Algorithms Python https://github.com/TheAlgorithms/Python 从基本原理到代码实现的Python算法入门,简洁地展示问题怎样解决,因 ...

随机推荐

  1. zepto - toggle

    <input type="text" value="123456789" /> <div id="too_long"> ...

  2. symfony安装使用

    symfony是一个强大的具有DI特性的框架,目前比较流行的php开发框架Drupal,Laravel底层都是使用了symfony. 想了解symfony更多内容,传送门 安装symfony很简单,一 ...

  3. android 隐藏标题栏的方法

    1:单个activity里 onCreate() { super.onCreate(); requestWindowFeature(Window.FEATURE_NO_TITLE); setConte ...

  4. 解决 -ERR Plaintext authentication disallowed on non-secure (SSL/TLS) connections 方案[sendmail, dovecot]

    在linux下安装sendmail比较容易, 但是在配置sendmail时却是比较麻烦的, 特别是对于一些新手来说, 配置过程必须十分小心谨慎, 要知道, 错误是千奇百怪, 但是成功的结果就只有一个, ...

  5. C# 5.0 新特性——Async和Await使异步编程更简单

    http://www.cnblogs.com/zhili/archive/2013/05/15/csharp5asyncandawait.html http://blog.zhaojie.me/201 ...

  6. node-gyp rebuild 卡住?

    最近 npm install 时候经常遇到在 node-gyp rebuild 那里卡很久的情况(大于十分钟),于是研究了一下输出的错误日志解决了这个问题,在这里分享一下. 首先,请检查 node-g ...

  7. Javascript之旅(一)

    Javascript之旅(一) 一.基础知识 基本语法 变量 数据类型 字符串 数组 对象 条件判断 循环 Map和Set iterable 为什么要学习JavaScript JavaScript 是 ...

  8. HT图形组件设计之道(三)

    上篇我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动,这类案例后续文章还会继续以便大家掌握更多的矢量应用场景,本篇我们先切换个话题, ...

  9. CentOS6.5菜鸟之旅:VIM插件NERDtree初探

    一.介绍 用于浏览目录结构的插件,功能和windows的资源管理器类似. 二.安装过程 1. 下载插件(https://github.com/scrooloose/nerdtree) 2. 将文件复制 ...

  10. C#一个方法返回多个值

    示例代码: static void Main(string[] args) { //声明 int value; string strOutValue; //调用函数 //函数的参数有两个返回的值 Re ...