python【第十一篇】 数据结构与算法
why
1.知名开源项目如Apache Nginx Kafka Django等背后含有大量的数据结构和算法。
2.如果你不追求极致的性能,数据结构与算法基本用不到。
大家都在学编程语言本身,学框架,学习一些 IT 工具的使用,至于背后的架构、原理、技术实现,没有人去关心。虽然有些接口,类库,框架不需要你自己实现,但并不代表什么都不需要了解,在计算机领域,只能你理解了的东西,你才能更好地使用它,才能让它达到最佳的性能和最小的资源消耗。掌握了数据结构与算法,不管对于阅读源码,还是理解背后的设计思想,都是非常有用的。
3.从更深的角度解决性能问题
我们都知道,程序 = 数据结构 + 算法。
我们为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越快越好,于是我们就需要考虑数据究竟如何保存的问题,这就是数据结构。就是说,如果解决一个问题,数据结构确定了,那只能考虑优化算法;如果数据结构可以选择,那么就可以从优化数据结构来提高效率而不只是算法角度。
时间复杂度和空间复杂度
算法的两个主要问题:时间复杂度、空间复杂度
空间复杂度
算法的存储量包括: 1、程序本身所占空间; 2、输入数据所占空间; 3、辅助变量所占空间; 若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的辅助变量所占额外空间。 若所需额外空间相对于输入数据量来说是常数,则称此算法为 原地工作。 若所需存储量依赖于特定输入,则通常按最坏情况考虑。
时间复杂度
四种基本时间复杂度
for (i=1; i<=n; i++) x++; for (i=1; i<=n; i++) for (j=1; j<=n; j++) x++;
第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n2),则整个算法的时间复杂度为Ο(n+n2)=Ο(n2)。
注意:经典的for循环的时间复杂度为Ο(n),但并不是所有的for循环的时间复杂度都为Ο(n),如for(i=1;i<=n,i+=i)就是logN
常数时间
若对于一个算法,的上界与输入大小无关,则称其具有常数时间,记作时间。一个例子是访问数组中的单个元素,因为访问它只需要一条指令。但是,找到无序数组中的最小元素则不是,因为这需要遍历所有元素来找出最小值。这是一项线性时间的操作,或称时间。但如果预先知道元素的数量并假设数量保持不变,则该操作也可被称为具有常数时间。
对数时间
随着数据量N的增加,该算法的时间复杂度效率最高
若算法的T(n) = O(log n),则称其具有对数时间
常见的具有对数时间的算法有二叉树的相关操作和二分搜索。
对数时间的算法是非常有效的,因为每增加一个输入,其所需要的额外计算时间会变小。
递归地将字符串砍半并且输出是这个类别函数的一个简单例子。它需要O(log n)的时间因为每次输出之前我们都将字符串砍半。 这意味着,如果我们想增加输出的次数,我们需要将字符串长度加倍。
注意:经典的递归的时间复杂度为Ο(logn),但并不是所有的递归的时间复杂度都为Ο(logn)
推导过程:
log(n*N)/logN=(logn + logN)/logN=1 + logn/logN=1(n为常数,远小于N) 也就说当数据量N足够大时,logN算法效率很高,数据量翻n倍后,时间复杂度基本不变
线性时间
如果一个算法的时间复杂度为O(n),则称这个算法具有线性时间,或O(n)时间。非正式地说,这意味着对于足够大的输入,运行时间增加的大小与输入成线性关系。例如,一个计算列表所有元素的和的程序,需要的时间与列表的长度成正比。
常见时间复杂度之间的关系
执行次数函数举例 | 阶 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n2+2n+1 | O(n2) | 平方阶 |
5log2n+20 | O(logn) | 对数阶 |
2n+3nlog2n+19 | O(nlogn) | nlogn阶 |
6n3+2n2+3n+4 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
![](https://img2018.cnblogs.com/blog/795445/201904/795445-20190402204804268-1536085791.png)
所消耗的时间从小到大
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
Python内置类型性能分析
timeit模块
timeit模块可以用来测试一小段Python代码的执行速度。
class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)
Timer是测量小段代码执行速度的类。 stmt参数是要测试的代码语句(statment); setup参数是运行代码时需要的设置; timer参数是一个定时器函数,与平台有关。
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
list的操作测试
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)) 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") # ('concat ', 1.7890608310699463, 'seconds') # ('append ', 0.13796091079711914, 'seconds') # ('comprehension ', 0.05671119689941406, 'seconds') # ('list range ', 0.014147043228149414, 'seconds')
pop操作测试
x = range(2000000) pop_zero = Timer("x.pop(0)","from __main__ import x") print("pop_zero ",pop_zero.timeit(number=1000), "seconds") x = range(2000000) pop_end = Timer("x.pop()","from __main__ import x") print("pop_end ",pop_end.timeit(number=1000), "seconds") # ('pop_zero ', 1.9101738929748535, 'seconds') # ('pop_end ', 0.00023603439331054688, 'seconds')
list内置操作的时间复杂度
dict内置操作的时间复杂度
数据结构
顺序表/线性表
顺序表的基本形式
图a表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc (e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即:
Loc(ei) = Loc(e0) + c*i
故,访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)。
如果元素的大小不统一,则须采用图b的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。由于每个链接所需的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。注意,图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量通常很小。
图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构。
链表
单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
表元素域elem用来存放具体的数据。 链接域next用来存放下一个节点的位置(python中的标识) 变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Node(object): """节点""" def __init__(self, elem): self.elem = elem self.next = None class SingleLinkList(object): """单链表""" def __init__(self, node=None): self.__head = node def is_empty(self): """链表是否为空""" return self.__head == None def length(self): """链表长度""" # cur游标,用来移动遍历节点 cur = self.__head # count记录数量 count = 0 while cur != None: count += 1 cur = cur.next return count def travel(self): """遍历整个链表""" cur = self.__head while cur != None: print(cur.elem, end=" ") cur = cur.next print("") def add(self, item): """链表头部添加元素,头插法""" node = Node(item) node.next = self.__head self.__head = node def append(self, item): """链表尾部添加元素, 尾插法""" node = Node(item) if self.is_empty(): self.__head = node else: cur = self.__head while cur.next != None: cur = cur.next cur.next = node def insert(self, pos, item): """指定位置添加元素 :param pos 从0开始 """ if pos <= 0: self.add(item) elif pos > (self.length() - 1): self.append(item) else: pre = self.__head count = 0 while count < (pos - 1): count += 1 pre = pre.next # 当循环退出后,pre指向pos-1位置 node = Node(item) node.next = pre.next pre.next = node def remove(self, item): """删除节点""" cur = self.__head pre = None while cur != None: if cur.elem == item: # 先判断此结点是否是头节点 # 头节点 if cur == self.__head: self.__head = cur.next else: pre.next = cur.next break else: pre = cur cur = cur.next def search(self, item): """查找节点是否存在""" cur = self.__head while cur != None: if cur.elem == item: return True else: cur = cur.next return False if __name__ == "__main__": ll = SingleLinkList() print(ll.is_empty()) print(ll.length()) ll.append(1) print(ll.is_empty()) print(ll.length()) ll.append(2) ll.add(8) ll.append(3) ll.append(4) ll.append(5) ll.append(6) # 8 1 2 3 4 5 6 ll.insert(-1, 9) # 9 8 1 23456 ll.travel() ll.insert(3, 100) # 9 8 1 100 2 3456 ll.travel() ll.insert(10, 200) # 9 8 1 100 23456 200 ll.travel() ll.remove(100) ll.travel() ll.remove(9) ll.travel() ll.remove(200) ll.travel()
链表与顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
链表与顺序表的各种操作复杂度如下所示:
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
在头部插入/删除 | O(1) | O(n) |
在尾部插入/删除 | O(n) | O(1) |
在中间插入/删除 | O(n) | O(n) |
注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作。链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
单向循环链表
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Node(object): """节点""" def __init__(self, elem): self.elem = elem self.next = None class SingleCycleLinkList(object): """单向循环链表""" def __init__(self, node=None): self.__head = node if node: node.next = node def is_empty(self): """链表是否为空""" return self.__head == None def length(self): """链表长度""" if self.is_empty(): return 0 # cur游标,用来移动遍历节点 cur = self.__head # count记录数量 count = 1 while cur.next != self.__head: count += 1 cur = cur.next return count def travel(self): """遍历整个链表""" if self.is_empty(): return cur = self.__head while cur.next != self.__head: print(cur.elem, end=" ") cur = cur.next # 退出循环,cur指向尾节点,但尾节点的元素未打印 print(cur.elem) def add(self, item): """链表头部添加元素,头插法""" node = Node(item) if self.is_empty(): self.__head = node node.next = node else: cur = self.__head while cur.next != self.__head: cur = cur.next # 退出循环,cur指向尾节点 node.next = self.__head self.__head = node # cur.next = node cur.next = self.__head def append(self, item): """链表尾部添加元素, 尾插法""" node = Node(item) if self.is_empty(): self.__head = node node.next = node else: cur = self.__head while cur.next != self.__head: cur = cur.next # node.next = cur.next node.next = self.__head cur.next = node def insert(self, pos, item): """指定位置添加元素 :param pos 从0开始 """ if pos <= 0: self.add(item) elif pos > (self.length()-1): self.append(item) else: pre = self.__head count = 0 while count < (pos-1): count += 1 pre = pre.next # 当循环退出后,pre指向pos-1位置 node = Node(item) node.next = pre.next pre.next = node def remove(self, item): """删除节点""" if self.is_empty(): return cur = self.__head pre = None while cur.next != self.__head: if cur.elem == item: # 先判断此结点是否是头节点 if cur == self.__head: # 头节点的情况 # 找尾节点 rear = self.__head while rear.next != self.__head: rear = rear.next self.__head = cur.next rear.next = self.__head else: # 中间节点 pre.next = cur.next return else: pre = cur cur = cur.next # 退出循环,cur指向尾节点 if cur.elem == item: if cur == self.__head: # 链表只有一个节点 self.__head = None else: # pre.next = cur.next pre.next = self.__head def search(self, item): """查找节点是否存在""" if self.is_empty(): return False cur = self.__head while cur.next != self.__head: if cur.elem == item: return True else: cur = cur.next # 退出循环,cur指向尾节点 if cur.elem == item: return True return False if __name__ == "__main__": ll = SingleCycleLinkList() print(ll.is_empty()) print(ll.length()) ll.append(1) print(ll.is_empty()) print(ll.length()) ll.append(2) ll.add(8) ll.append(3) ll.append(4) ll.append(5) ll.append(6) # 8 1 2 3 4 5 6 ll.insert(-1, 9) # 9 8 1 23456 ll.travel() ll.insert(3, 100) # 9 8 1 100 2 3456 ll.travel() ll.insert(10, 200) # 9 8 1 100 23456 200 ll.travel() ll.remove(100) ll.travel() ll.remove(9) ll.travel() ll.remove(200) ll.travel()
双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Node(object): """结点""" def __init__(self, item): self.elem = item self.next = None self.prev = None class DoubleLinkList(object): """双链表""" def __init__(self, node=None): self.__head = node def is_empty(self): """链表是否为空""" return self.__head == None def length(self): """链表长度""" # cur游标,用来移动遍历节点 cur = self.__head # count记录数量 count = 0 while cur != None: count += 1 cur = cur.next return count def travel(self): """遍历整个链表""" cur = self.__head while cur != None: print(cur.elem, end=" ") cur = cur.next print("") def add(self, item): """链表头部添加元素,头插法""" node = Node(item) node.next = self.__head self.__head = node node.next.prev = node def append(self, item): """链表尾部添加元素, 尾插法""" node = Node(item) if self.is_empty(): self.__head = node else: cur = self.__head while cur.next != None: cur = cur.next cur.next = node node.prev = cur def insert(self, pos, item): """指定位置添加元素 :param pos 从0开始 """ if pos <= 0: self.add(item) elif pos > (self.length()-1): self.append(item) else: cur = self.__head count = 0 while count < pos: count += 1 cur = cur.next # 当循环退出后,cur指向pos位置 node = Node(item) node.next = cur node.prev = cur.prev cur.prev.next = node cur.prev = node def remove(self, item): """删除节点""" cur = self.__head while cur != None: if cur.elem == item: # 先判断此结点是否是头节点 # 头节点 if cur == self.__head: self.__head = cur.next if cur.next: # 判断链表是否只有一个结点 cur.next.prev = None else: cur.prev.next = cur.next if cur.next: cur.next.prev = cur.prev break else: cur = cur.next def search(self, item): """查找节点是否存在""" cur = self.__head while cur != None: if cur.elem == item: return True else: cur = cur.next return False if __name__ == "__main__": ll = DoubleLinkList() print(ll.is_empty()) print(ll.length()) ll.append(1) print(ll.is_empty()) print(ll.length()) ll.append(2) ll.add(8) ll.append(3) ll.append(4) ll.append(5) ll.append(6) # 8 1 2 3 4 5 6 ll.insert(-1, 9) # 9 8 1 23456 ll.travel() ll.insert(3, 100) # 9 8 1 100 2 3456 ll.travel() ll.insert(10, 200) # 9 8 1 100 23456 200 ll.travel() ll.remove(100) ll.travel() ll.remove(9) ll.travel() ll.remove(200) ll.travel()
栈
栈可以用顺序表实现,也可以用链表实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Stack(object): """栈""" def __init__(self): self.__list = [] def push(self, item): """添加一个新的元素item到栈顶""" self.__list.append(item) def pop(self): """弹出栈顶元素""" return self.__list.pop() def peek(self): """返回栈顶元素""" if self.__list: return self.__list[-1] else: return None def is_empty(self): """判断栈是否为空""" return self.__list == [] # return not self.__list def size(self): """返回栈的元素个数""" return len(self.__list) if __name__ == "__main__": s = Stack() s.push(1) s.push(2) s.push(3) s.push(4) print(s.pop()) print(s.pop()) print(s.pop()) print(s.pop())
队列
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Queue(object): """队列""" def __init__(self): self.__list = [] def enqueue(self, item): """往队列中添加一个item元素""" self.__list.append(item) def dequeue(self): """从队列头部删除一个元素""" return self.__list.pop(0) def is_empty(self): """判断一个队列是否为空""" return self.__list == [] def size(self): """返回队列的大小""" return len(self.__list) if __name__ == "__main__": s = Queue() s.enqueue(1) s.enqueue(2) s.enqueue(3) s.enqueue(4) print(s.dequeue()) print(s.dequeue()) print(s.dequeue()) print(s.dequeue())
双端队列(deque,全名double-ended queue),是一种具有队列和栈的性质的数据结构。
双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。
树
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 class Node(object): """""" def __init__(self, item): self.elem = item self.lchild = None self.rchild = None class Tree(object): """二叉树""" def __init__(self): self.root = None def add(self, item): node = Node(item) if self.root is None: self.root = node return queue = [self.root] while queue: cur_node = queue.pop(0) if cur_node.lchild is None: cur_node.lchild = node return else: queue.append(cur_node.lchild) if cur_node.rchild is None: cur_node.rchild = node return else: queue.append(cur_node.rchild) def breadth_travel(self): """广度遍历""" if self.root is None: return queue = [self.root] while queue: cur_node = queue.pop(0) print(cur_node.elem, end=" ") if cur_node.lchild is not None: queue.append(cur_node.lchild) if cur_node.rchild is not None: queue.append(cur_node.rchild) def preorder(self, node): """先序遍历""" if node is None: return print(node.elem, end=" ") self.preorder(node.lchild) self.preorder(node.rchild) def inorder(self, node): """中序遍历""" if node is None: return self.inorder(node.lchild) print(node.elem, end=" ") self.inorder(node.rchild) def postorder(self, node): """后序遍历""" if node is None: return self.postorder(node.lchild) self.postorder(node.rchild) print(node.elem, end=" ") if __name__ == "__main__": tree = Tree() tree.add(0) tree.add(1) tree.add(2) tree.add(3) tree.add(4) tree.add(5) tree.add(6) tree.add(7) tree.add(8) tree.add(9) tree.breadth_travel() print(" ") tree.preorder(tree.root) print(" ") tree.inorder(tree.root) print(" ") tree.postorder(tree.root) print(" ")
常用算法
排序
排序算法的稳定性
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
不稳定排序算法有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有
(4, 1) (3, 1) (3, 7)(5, 6) (3, 1) (3, 7) (4, 1) (5, 6) (维持次序) (3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 冒泡是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。 ''' '''时间复杂度 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。) 最坏时间复杂度:O(n2) 稳定性:稳定 ''' # 从大到小 # 方法一 array = range(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for i in range(len(array)): for j in range(i + 1, len(array)): if array[i] < array[j]: array[i], array[j] = array[j], array[i] print(array) ''' [9, 0, 1, 2, 3, 4, 5, 6, 7, 8] [9, 8, 0, 1, 2, 3, 4, 5, 6, 7] [9, 8, 7, 0, 1, 2, 3, 4, 5, 6] [9, 8, 7, 6, 0, 1, 2, 3, 4, 5] [9, 8, 7, 6, 5, 0, 1, 2, 3, 4] [9, 8, 7, 6, 5, 4, 0, 1, 2, 3] [9, 8, 7, 6, 5, 4, 3, 0, 1, 2] [9, 8, 7, 6, 5, 4, 3, 2, 0, 1] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] '''
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。 选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。 在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。 对比冒泡排序,将需要交换的操作换成了下标赋值操作 ''' '''时间复杂度 最优时间复杂度:O(n2) 最坏时间复杂度:O(n2) 稳定性:不稳定(考虑升序每次选择最大的情况) ''' array = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for i in range(len(array)): smallest_index = i for j in range(i + 1, len(array)): if array[i] < array[j]: smallest_index = j array[i], array[smallest_index] = array[smallest_index], array[i] print(array)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列, 对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 相比选择排序,由于采用的是”临界值“比较,而不需要和前面的每个元素比较,因此更加高效 ''' '''时间复杂度 最优时间复杂度:O(n) 最坏时间复杂度:O(n2) 稳定性:稳定 ''' def insert_sort(alist): # 从第二个位置,即下标为1的元素开始向前插入 for i in range(1, len(alist)): # 从第i个元素开始向前比较,如果小于前一个元素,交换位置 for j in range(i, 0, -1): if alist[j] < alist[j - 1]: alist[j], alist[j - 1] = alist[j - 1], alist[j] print(alist) alist = [54, 26, 93, 17, 77, 31, 44, 55, 20] insert_sort(alist) ''' [26, 54, 93, 17, 77, 31, 44, 55, 20] [26, 54, 93, 17, 77, 31, 44, 55, 20] [17, 26, 54, 93, 77, 31, 44, 55, 20] [17, 26, 54, 77, 93, 31, 44, 55, 20] [17, 26, 31, 54, 77, 93, 44, 55, 20] [17, 26, 31, 44, 54, 77, 93, 55, 20] [17, 26, 31, 44, 54, 55, 77, 93, 20] [17, 20, 26, 31, 44, 54, 55, 77, 93] '''
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。 最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。 希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。 ''' '''时间复杂度 最优时间复杂度:根据步长序列的不同而不同 最坏时间复杂度:O(n2) 稳定想:不稳定 ''' def shell_sort(alist): n = len(alist) # 初始步长 gap = n / 2 while gap > 0: # 按步长进行插入排序 for i in range(gap, n): j = i # 插入排序 while j >= gap and alist[j - gap] > alist[j]: alist[j - gap], alist[j] = alist[j], alist[j - gap] j -= gap # 得到新的步长 gap = gap / 2 alist = [54, 26, 93, 17, 77, 31, 44, 55, 20] shell_sort(alist) print(alist) 希尔排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分, 其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 相比插入排序,由于使用了递归,因此更加高效 ''' '''时间复杂度 最优时间复杂度:O(nlogn) 最坏时间复杂度:O(n2) 稳定性:不稳定 ''' 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) alist = [54, 26, 93, 17, 77, 31, 44, 55, 20] quick_sort(alist, 0, len(alist) - 1) print(alist) 快速排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
'''原理 归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。 然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。 ''' '''时间复杂度 最优时间复杂度:O(nlogn) 最坏时间复杂度:O(nlogn) 稳定性:稳定 ''' def merge_sort(alist): """归并排序""" n = len(alist) if n <= 1: return alist mid = n // 2 # left 采用归并排序后形成的有序的新的列表 left_li = merge_sort(alist[:mid]) # right 采用归并排序后形成的有序的新的列表 right_li = merge_sort(alist[mid:]) # 将两个有序的子序列合并为一个新的整体 # merge(left, right) left_pointer, right_pointer = 0, 0 result = [] while left_pointer < len(left_li) and right_pointer < len(right_li): if left_li[left_pointer] <= right_li[right_pointer]: result.append(left_li[left_pointer]) left_pointer += 1 else: result.append(right_li[right_pointer]) right_pointer += 1 result += left_li[left_pointer:] result += right_li[right_pointer:] return result if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) sorted_li = merge_sort(li) print(li) print(sorted_li)
常见排序算法效率比较
所以,择中选择是归并排序
搜索
搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。 搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找
二分法查找
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# coding:utf-8 def binary_search(alist, item): """二分查找,递归""" n = len(alist) if n > 0: mid = n // 2 if alist[mid] == item: return True elif item < alist[mid]: return binary_search(alist[:mid], item) else: return binary_search(alist[mid + 1:], item) return False def binary_search_2(alist, item): """二分查找, 非递归""" n = len(alist) first = 0 last = n - 1 while first <= last: mid = (first + last) // 2 if alist[mid] == item: return True elif item < alist[mid]: last = mid - 1 else: first = mid + 1 return False if __name__ == "__main__": li = [17, 20, 26, 31, 44, 54, 55, 77, 93] print(binary_search(li, 55)) print(binary_search(li, 100)) print(binary_search_2(li, 55)) print(binary_search_2(li, 100))