python数据结构
一、算法入门
程序设计 = 数据结构 + 算法
算法时为了解决实际问题而设计的,数据结构是算法需要处理的问题载体 数据结构只是静态的描述呢数据元素之间的关系 高效的程序需要再数据结构的基础上设计和选择算法
1. 数据结构
1.1 数据结构存在的意义
如果用Python的类型来保存一个班学生的信息,并如何通过学生姓名快速获取其信息呢?
Python中的列表和字典均可以来存储学生信息。
列表存储:获取一名学生的信息时,就要遍历这个列表,其时间复杂度为O(n); 字典存储:可以将学生姓名作为字典的键,学生信息作为值,查询直接使用键获取值,其时间复杂度为O(1)
明显的字典存储的数据结构数据的处理效率更高,数据存储方式(即数据结构)越优化,算法处理时效率越高。
1.2 数据结构概念
数据结构:数据元素相互之间存在的一种或多种特定关系的集合(数据结构指数据对象中数据元素之间的关系)。数据结构分为逻辑结构和物理结构。
逻辑结构:是指数据对象中数据元素之间的相互关系。 (1)集合结构:数据元素除了同属于一个集合外,没有什么其他关系 (2)线性结构:线性结构中的数据元素之间是一对一的关系 (3)树形结构:元素之间存在一种一对多的层次关系 (4)图形结构:元素是多对多的关系 物理结构:是指数据的逻辑结构在计算机中的存储形式。 (1)顺序存储:把数据元素存放在地址连续的存储单元你,其数据间的逻辑关系和物理关系是一致的 (2)链式存储:把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。链式存储结构的数据元素存储关系不能反映其逻辑关系,需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关数据元素的位置。
Python的数据结构分两种:
内置数据结构:列表、元组、字典等 扩展数据结构:Python系统里面没有直接定义,需要我们自己取定义实现这些数据的组织方式,如栈、队列等
1.3 抽象数据类型
抽象数据类型(Abstract Data Type):把数据类型和数据类型上的运算捆在一起,进行封装。
引入抽象数据类型目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中引用隔开,使他们相互独立。常见的物种数据运算:
插入 删除 修改 查找 排序
2. 算法
算法:是独立存在(不依赖特定的编程语言)的一种解决问题的方法和思想。算法有五大特性:
(1)输入:算法有0个或多个输入 (2)输出:算法至少有一个或多个输出 (3)有穷性:算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成 (4)确定性:算法中的每一步都有确定的含义,不会出现二义性 (5)可行性:算法的每一步都是能够执行有限次数完成的
程序在每台机器执行的总时间不同,但是执行基本运算数量大致相同。
2.1 最坏时间复杂度
时间复杂度:假设存在函数g,使得算法A处理规模为n的问题所用时间为T(n)=O(g(n)),则O(g(n))为算法A的奖金时间复杂度,简称时间复杂度,记为T(n)
最优时间复杂度:算法完成工作最少需要多少基本操作 --- 没有什么参考价值 * 最坏时间复杂度:算法完成工作做多需要多少基本操作 --- 基本操作都能完成 平均时间复杂度:算法完成工作平均需要多少基本操作
2.2 时间复杂度计算规则
1. 基本操作:只有常数项,认为其时间复杂度为O(1) 2. 顺序结构:时间复杂度按加画进行计算 3. 循环结构:时间复杂度按惩罚进行计算 4. 分支结构:时间复杂度取最大值 5. 判断一个算法的效率时,只需要关注操作数量的最高次项,其他次要项和参数项可以忽略 6. 在没有特殊说明时,我们所分析的时间复杂度指的是 最坏时间复杂度
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 01_abc.py @Time : 2019/10/22 12:18 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 如果 a + b +c = 1000 且 a**2 + b**2 = c**2(a, b, c均为自然数),求解a, b, c的所有组合 """ import time start_time = time.time() print("程序开始执行时间: %s" % start_time) # 算法一 # 时间复杂度: # T(n) = O(n*n*n) = O(n**3) for a in range(1001): for b in range(1001): for c in range(1001): if a + b + c == 1000 and a**2 + b**2 == c**2: print("a, b, c: %d, %d, %d" % (a, b, c)) # 算法二 for a in range(1001): for b in range(0, 1001): c = 1000 - a - b if a**2 + b**2 == c**2: print("a, b, c: %d, %d, %d" % (a, b, c)) # 算法三 # 时间复杂度: # T(n) = O(n*n(1+1))) = O(n**2) for a in range(1001): for b in range(0, 1001 - a): c = 1000 - a - b if a**2 + b**2 == c**2: print("a, b, c: %d, %d, %d" % (a, b, c)) end_time = time.time() print("程序执行结束时间: %s" % end_time) all_times = end_time - start_time print("总耗时all_times: %s" % all_times) print("执行完毕!")
2.3 常见时间复杂度
非正式术语 | 执行次函数 | 时间复杂度 |
---|---|---|
常数阶 | 12 | O(1) |
线性阶 | 2n + 3 | O(n) |
平方阶 | 3n**2+3n+2 | O(n**2) |
对数阶 | 5log2n+20 | O(logn) |
nlogn阶 | 2n+3nlog2n+19 | O(nlogn) |
立方阶 | 6n**3 + 2n **2+3n+4 | O(n**3) |
指数阶 | 2**n | O(2**n) |
所消耗的时间从小到大:
O(1) < O(logn) < O(n) < O(nlogn) < O(n **2) < O(n **3) < O(2 **n) < O(n!) < O(n **n)
2.4 Python内置类型性能分析
timeit模块可以用来测试一小段Python代码的执行速度。class timeit.Timer(stmt='pass', setup='pass', timer=
Timer是测量小段代码执行速度的类。
stmt参数是要测试的代码语句(statment); setup参数是运行代码时需要的设置; timer参数是一个定时器函数,与平台有关。 timeit.Timer.timeit(number=1000000) Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 202_timeit.py @Time : 2019/10/22 19:27 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ # class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>) # Timer是测量小段代码执行速度的类。 # stmt参数是要测试的代码语句(statment); # setup参数是运行代码时需要的设置; # timer参数是一个定时器函数,与平台有关 # timeit.Timer.timeit(number=1000000) # Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。 def test1(): l1 = [] for i in range(1000): l1 = l1 + [i] def test2(): l2 = [] for i in range(1000): l2.append(i) def test3(): l3 = [i for i in range(1000)] def test4(): l4 = list(range(1000)) from timeit import Timer t1 = Timer("test1()", "from __main__ import test1") print("concat ", t1.timeit(number=1000), "seconds") t2 = Timer("test2()", "from __main__ import test2") print("append ", t2.timeit(number=1000), "seconds") t3 = Timer("test3()", "from __main__ import test3") print("comprehension ", t3.timeit(number=1000), "seconds") t4 = Timer("test4()", "from __main__ import test4") print("list range ", t4.timeit(number=1000), "seconds") # pop内置函数的测试 # pop最后一个元素的效率远远高于pop第一个元素 x = list(range(2000000)) pop_zero = Timer("x.pop(0)", "from __main__ import x") print("pop_zero ", pop_zero.timeit(number=1000), "seconds") x = list(range(2000000)) pop_end = Timer("x.pop()", "from __main__ import x") print("pop_end ", pop_end.timeit(number=1000), "seconds")
二、顺序表
在程序中,将一组(通常同为某个类型)数据元素作为整体管理和使用,通常可以将这组数据看成一个序列,用元素在序列里的位置和顺序表示实际应用中的某种有意义的信息或者表示数据之间的某种关系。
这样一列序列元素的组织形式,我们可以抽象为线性表。一个线性表是某类元素的一个集合,还记录着元素之间的一种顺序关系。线性表是最基本的数据结构之一,它还经常被用作更复杂的数据结构的实现基础。
顺序表:将元素顺序滴存放在一块连续的存储区里,元素间的顺序关系由他们的存储顺序自然表示。
1. 顺序表的形式
图a)表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址。元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc (e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即:
Loc(ei) = Loc(e0) + c*i
所以访问指定元素时无需从头遍历,通过计算便可以获得对应地址,其时间复杂度为:O(1)
图b)的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。元素的大小不统一,由于每个链接所需的存储量相同,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。
2. 顺序表的结构和实现
2.1 基本概念
顺序表 = 元素存储区的容量 + 当前表中已有元素个数
顺序表的两种实现方式:
一体式结构:存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象。一体式结构整体性强,易于管理。但是由于数据元素存储区域是表对象的一部分,顺序表创建后,元素存储区就固定了。
分离式结构:表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
2.2 元素存储区替换
一体式结构: 由于顺序表信息区与数据区连续存储在一起,所以若想更换数据区,则只能整体搬迁,即整个顺序表对象(指存储顺序表的结构信息的区域)改变了。
分离式结构:只需将表信息区中的数据区链接地址更新即可,而该顺序表对象不变。
2.3 元素存储区扩充
采用分离式结构的顺序表,若将数据区更换为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行了扩充,所有使用这个表的地方都不必修改。
扩充策略:
(1)采用分离式结构的顺序表,若将数据区更换为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行了扩充,所有使用这个表的地方都不必修改。 特点:节省空间,但是扩充操作频繁,操作次数多。 (2)每次扩充容量加倍,如每次扩充增加一倍存储空间。 特点:减少了扩充操作的执行次数,但可能会浪费空间资源。以空间换时间,推荐的方式。
2.3 顺序表的操作
增加元素
a. 尾端加入元素,时间复杂度为O(1) b. 非保序的加入元素(不常见),时间复杂度为O(1) c. 保序的元素加入,时间复杂度为O(n)
删除元素
a. 删除表尾元素,时间复杂度为O(1) b. 非保序的元素删除(不常见),时间复杂度为O(1) c. 保序的元素删除,时间复杂度为O(n)
2.4 Python中顺序表
Python中的list和tuple两种类型采用了顺序表的实现技术
tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作。 list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序)。
list顺序表的特征:
(1)基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1),为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中。 (2)允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(函数id得到的值)不变,为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时list对象的标识id不变,只能采用分离式实现技术。
list就是一种采用分离式技术实现的动态顺序表。这就是为什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。
list实现采用了如下的策略:在建立空表(或者很小的表)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区。但如果此时的表已经很大(目前的阀值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式,是为了避免出现过多空闲的存储位置。
三、链表
链表存在的意义:
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,使用起来并不是很灵活 链表结构可以充分使用计算机内存空间,实现灵活的内存动态管理。它是一种线性表,在每个数据节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
3.1 单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
(1)表元素域elem用来存放具体的数据。 (2)链接域next用来存放下一个节点的位置(python中的标识) (3)变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点
单节点实现
class SingleNode(object): """单链表的结点""" def __init__(self,item): # _item存放数据元素 self.item = item # _next是下一个节点的标识 self.next = None
单链表的操作
is_empty() 链表是否为空 length() 链表长度 travel() 遍历整个链表 add(item) 链表头部添加元素 append(item) 链表尾部添加元素 insert(pos, item) 指定位置添加元素 remove(item) 删除节点 search(item) 查找节点是否存在
单链表的实现
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2031_singlelinkedList.py @Time : 2019/10/23 17:13 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ # 节点的实现 class SingleNode(object): """单链表的节点""" def __init__(self, item): # item 存放数据元素 self.item = item # next 是下一个节点的标识 self.next = None # 单链表的实现 class SingleLinkList(object): """单链表""" def __init__(self): self._head = None def is_empty(self): """判断链表是否为空""" return self._head is None def length(self): """链表长度""" cur = self._head count = 0 # 尾节点指向None,当为到达尾部时 while cur is not None: count = count + 1 # cur向后移动一个节点 cur = cur.next return count def travel(self): """遍历链表""" cur = self._head while cur is not None: print(cur.item) cur = cur.next print(" ") def add_item(self, item): """头部添加元素""" # 创建一个保存item值的节点 node = SingleNode(item) # 将新节点的链接域next指向头节点,即_head指向的位置 node.next = self._head # 将链表的头_head指向新节点 self._head = node def append_item(self, item): """尾部添加元素""" node = SingleNode(item) # 先判断链表是否为空,若是空链表,则将_head指向新节点 if self.is_empty(): self._head = node else: cur = self._head while cur.next is not None: cur = cur.next cur.next = node def insert_item(self, pos, item): """指定位置添加元素""" if pos <= 0: self.add_item(item) elif pos > (self.length() - 1): self.append_item(item) # 指定位置 else: node = SingleNode(item) count = 0 # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置 pre = self._head while count < (pos - 1): count += 1 pre = pre.next # 先将新节点node的next指向插入位置的节点 node.next = pre.next # 将插入位置的前一个节点的next指向新节点 pre.next = node def remove_item(self, item): """删除节点""" cur = self._head pre = None while cur is not None: if cur.item == item: # 如果第一个就是删除节点 if not pre: self._head = cur.next else: # 将删除位置前一个节点的next指向删除位置的最后一个节点 pre.next = cur.next break else: # 继续按链表后移节点 pre = cur cur = cur.next def search_item(self, item): """查找链接中元素是否存在""" cur = self._head while cur is not None: if cur.item == item: return True cur = cur.next return False if __name__ == "__main__": sll = SingleLinkList() sll.add_item(11) sll.add_item(12) sll.add_item(13) sll.travel() sll.insert_item(1, 14) sll.travel() print(sll.search_item(12))
链表与顺序表的对比
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(n) | O(1) |
在中间插入/删除 | O(n) | O(n) |
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。 顺序表查找很快,主要耗时的操作是拷贝覆盖。 因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
3.2 单向循环链表
单向循环链表:单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2032_sincycLinkedList.py @Time : 2019/10/23 20:53 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ class Node(object): """节点""" def __init__(self, item): self.item = item self.next = None class SinCycLinkedList(object): """单向循环链表""" def __init__(self): self._head = None """判断链表是否为空""" def is_empty(self): return self._head is None """返回链表的长度""" def length(self): if self.is_empty(): return 0 count = 1 cur = self._head while cur.next is not self._head: count += 1 cur = cur.next return count """遍历链表""" def travel(self): if self.is_empty(): return cur = self._head print(cur.item) while cur.item != self._head: cur = cur.next print(cur.item) print(" ") """添加节点""" def add_item(self, item): node = Node(item) if self.is_empty(): self._head = node node.next = self._head else: # 添加的节点时间指向_head node.next = self._head # 移到链表尾部,将尾部节点的next指向node cur = self._head while cur.next != self._head: cur = cur.next cur.next = node #_head指向添加node的 self._head = node def append_item(self, item): """尾部添加节点""" node = Node(item) if self.is_empty(): self._head = node node.next = self._head else: # 移到链表尾部 cur = self._head while cur.next != self._head: cur = cur.next # 将尾节点指向node cur.next = node # 将node指向头节点_head node.next = self._head def insert_item(self, pos, item): """在指定位置添加节点""" if pos <= 0: self.add(item) elif pos > (self.length()-1): self.append(item) else: node = Node(item) cur = self._head count = 0 # 移动到指定位置的前一个位置 while count < (pos-1): count += 1 cur = cur.next node.next = cur.next cur.next = node def remove_item(self, item): """删除一个节点""" # 若链表为空,则直接返回 if self.is_empty(): return # 将cur指向头节点 cur = self._head pre = None # 若头节点的元素就是要查找的元素item if cur.item == item: # 如果链表不止一个节点 if cur.next != self._head: # 先找到尾节点,将尾节点的next指向第二个节点 while cur.next != self._head: cur = cur.next # cur指向了尾节点 cur.next = self._head.next self._head = self._head.next else: # 链表只有一个节点 self._head = None else: pre = self._head # 第一个节点不是要删除的 while cur.next != self._head: # 找到了要删除的元素 if cur.item == item: # 删除 pre.next = cur.next return else: pre = cur cur = cur.next # cur 指向尾节点 if cur.item == item: # 尾部删除 pre.next = cur.next def search_item(self, item): """查找节点是否存在""" if self.is_empty(): return False cur = self._head if cur.item == item: return True while cur.next != self._head: cur = cur.next if cur.item == item: return True return False if __name__ == "__main__": scll = SinCycLinkedList() print(scll.is_empty()) print(scll.length())
3.3 双向链表
双向链表:每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
四、栈(LIFO)
栈(stack):也称为堆栈,四一种容器,可存放、删除、访问元素。只允许在容器的一端进行数据运算。栈的数据遵循:后进先出(LIFO, Last in First Out)的原理。
栈的实现(既可以用顺序表实现,也可以用链表实现)
栈的操作方法: Stack() 创建一个新的空栈 push(item) 添加一个新的元素item到栈顶 pop() 弹出栈顶元素 peek() 返回栈顶元素 is_empty() 判断栈是否为空 size() 返回栈的元素个数
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 204_stack.py @Time : 2019/10/23 21:20 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ """栈""" class Stack(object): def __init__(self): self.items = [] """判断栈是否为空""" def is_empty(self): return self.items == [] """加入元素""" def push_item(self, item): self.items.append(item) """弹出元素(删除元素)""" def pop_item(self): self.items.pop() """返回栈顶元素""" def peek_item(self): return self.items[len(self.items)-1] def size_item(self): return len(self.items) if __name__ == "__main__": s = Stack() print(s.is_empty()) s.push_item("123") s.push_item("456") s.push_item("789") s.push_item("234") s.push_item("567") print(s.size_item()) s.pop_item() print(s.peek_item())
五、队列
队列(queue):是一种先进先出(First in First Out,FIFO)的线性表。允许插入的一端为队尾,允许删除的一端为对头。队列不允许在中间部位进行操作。
1. 队列的实现
队列的实现(既可以用顺序表实现,也可以用链表实现)
队列的操作: Queue() 创建一个空的队列 enqueue(item) 往队列中添加一个item元素 dequeue() 从队列头部删除一个元素 is_empty() 判断一个队列是否为空 size() 返回队列的大小
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2051_queue.py @Time : 2019/10/23 21:43 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ """队列""" class Queue(object): def __init__(self): self.items = [] """判断队列是否为空""" def is_empty(self): return self.items == [] # 【["队尾", ..., "对头"]】 """进队列:只允许在队尾插入数据""" def enqueue(self, item): self.items.insert(0, item) """出队列:只允许在对头删除""" def dequeue(self): return self.items.pop() """返回队列的大小""" def size(self): return len(self.items) if __name__ == "__main__": q = Queue() print(q.is_empty()) q.enqueue("123") q.enqueue("456") q.enqueue("789") print(q.dequeue()) print(q.is_empty()) print(q.size())
2. 双端队列
双端队列:(deque,全名double-ended queue),是一种具有队列和栈的性质的数据结构。
双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。
操作方法: Deque() 创建一个空的双端队列 add_front(item) 从队头加入一个item元素 add_rear(item) 从队尾加入一个item元素 remove_front() 从队头删除一个item元素 remove_rear() 从队尾删除一个item元素 is_empty() 判断双端队列是否为空 size() 返回队列的大小
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2052_deque.py @Time : 2019/10/24 12:22 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ """双端队列""" class Deque(object): def __init__(self): self.items = [] """判断双端队列是否为空""" def is_empty(self): self.items == [] """在队头添加元素""" def add_front(self, item): self.items.insert(0, item) """在队尾添加元素""" def add_rear(self, item): self.items.append(item) """在对头移除元素""" def remove_front(self): return self.items.pop(0) """在队尾移除元素""" def remove_rear(self): return self.items.pop() """返回双端队列的大小""" def size(self): return len(self.items) if __name__ == "__main__": d = Deque() d.add_front(1) d.add_front(2) d.add_rear(3) d.add_rear(4) print(d.size()) print(d.remove_front()) print(d.remove_front()) print(d.remove_rear()) print(d.remove_rear()) print(d.size())
六、排序与搜索
1. 冒泡排序
冒泡排序:对于一序列,拿两个数据进行比较,如果左边的数大于右边的数,这两个数交换位置,继续交换后的第二坐的数据与下一个数进行比较交换,一直到没有交换为止(即排序完成)。
初始序列: 【54 26 93 17 77 31】 第一次交换:【26 54 17 77 31 93】 第一次比较次数:5 【n -1】 第二次交换:【26 17 54 31 77 93】 第二次比较次数:4 【n -2】 第三次交换:【17 26 31 54 77 93】 第三次比较次数:3 【n -3】
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2061_bubble_sort.py @Time : 2019/10/24 12:57 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ """冒泡排序 最优时间复杂度:O(n) 最坏时间复杂度:O(n**2) """ def bubble_sort(alist): for i in range(len(alist)-1, 0, -1): for j in range(i): if alist[j] > alist[j+1]: alist[j], alist[j+1] = alist[j+1], alist[j] if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] bubble_sort(li) print(li)
2. 快速排序
快速排序
(1)取出一个基准(pivot)元素 (2)分区(partition)操作:重新排序序列,比基准值小的放在基准值的前面,比基准值大的放在基准值后面,和基准值一样大的可以放在任意一边。分区结束后,基准值就在序列的中间位置。 (3)递归(recusive)地把小于基准元素的子序列和大于基准元素的子序列排序
【GitHub示例】
#!/usr/bin/env python # -*- encoding: utf-8 -*- """ @File : 2062_quick_sort.py @Time : 2019/10/24 15:49 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """ """快速排序 最优时间复杂度:O(nlogn) 最坏时间复杂度:O(n**2) """ def quick_sort(alist, start, end): # 递归的退出条件 if start >= end: return # 设置其实元素为基准元素 mid = alist[start] # low为序列左边的由左向右移动的游标 low = start # high为序列右边的由右向左移动的游标 high = end while low < high: # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动 while low < high and alist[high] >= mid: high -= 1 # 将high指向的元素放到low的位置上 alist[low] = alist[high] # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动 while low < high and alist[low] < mid: low += 1 # 将low指向的元素饭到high的位置上 alist[high] = alist[low] # 退出循环后,low与high重合,此时所指位置为基准位置元素的正确位置 # 将基准元素放到该位置 alist[low] = mid # 对基准元素左边的子序列进行快速排序 quick_sort(alist, start, low-1) # 对基准元素右边的子序列进行快速排序 quick_sort(alist, low+1, end) if __name__ == "__main__": alists = [54,26,93,17,77,31,44,55,20] quick_sort(alists, 0, len(alists)-1) print(alists)
3. 选择排序
4. 插入排序
5. 希尔排序
6. 归并排序
7. 常见排序算法效率比较
8. 搜索
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· C# 13 中的新增功能实操
· 万字长文详解Text-to-SQL
· Ollama本地部署大模型总结
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
· 卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!