3 排序与搜索
排序与搜索
排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法。
排序算法的稳定性
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
冒泡排序
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(li): n = len(li) # 总共排序几趟 ,n - 1 目的是为了空间排序的次数 for j in range(n-1): # j = 0 1 2 3 count = 0 # 排序一趟,找到一个最大的数,排到最右边 for i in range(n-1-j): # 排序的次数 越来越小 # 比较相邻的两个数 if li[i] > li[i + 1]: li[i], li[i + 1] = li[i + 1], li[i] count += 1 if count == 0: break if __name__ == '__main__': li = [1,2,3,4,5] bubble_sort(li) print(li) # 最坏时间复杂度 O(n^2) # 最优时间复杂度 O(n) # 稳定性 稳定
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def select_sort(li): n = len (li) # 让temp 从 0 ~ n-2 每次循环,选择一个最小的数,替换到j位置 for j in range (n - 1 ): # 把0位置的数据,跟后面所有的数进行比较 # 比出来最小的数,放到0位置 # 把0位置角标记录,跟后面所有的数进行比较 temp = j for i in range (j + 1 , n): if li[i] < li[temp]: # 记录最小的数的角标 temp = i # for循环结束后,temp是最小的数的角标 li[temp], li[j] = li[j], li[temp] if __name__ = = '__main__' : li = [ 3 , 3 , 5 , 2 , 1 ] select_sort(li) print (li) # 最坏时间复杂度:O(n^2) # 最优时间复杂度:O(n^2) # 稳定性:不稳定 [3,3,5,2,1] |
插入排序
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
def insert_sort(li): n = len(li) for j in range(1,n): # 把1位置到n位置的数,依次进行插入排序 # 一次插入,把前面的数据认为有序,把当前位置的数据插入到前面的有序序列中 for i in range(j, 0 , -1): if li[i] < li[i - 1]: li[i], li[i - 1] = li[i - 1], li[i] else: break if __name__ == '__main__': li = [3,1,5,2,6] insert_sort(li) print(li) # 最坏时间复杂度:O(n^2) # 最优时间复杂度:O(n) # 稳定性:稳定
快速排序
快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
步骤为:
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
快速排序的分析
def quick_sort(alist, first, last): """快速排序""" if first >= last: return mid_value = alist[first] low = first high = last while low < high: # high 左移 while low < high and alist[high] >= mid_value: high -= 1 alist[low] = alist[high] while low <high and alist[low] < mid_value: low += 1 alist[high] = alist[low] # 从循环退出时,low==high alist[low] = mid_value # 对low左边的列表执行快速排序 quick_sort(alist, first, low-1) # 对low右边的列表排序 quick_sort(alist, low+1, last) if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) quick_sort(li, 0, len(li)-1) print(li)
二分查找

# 主要的思想是从中间开始,大的网右边查找,小的往左边查找,依次递归 def search(li,item): if len(li) == 0: return False mid = len(li)//2 if item == mid: return True if item > li[mid]: return search(li[mid+1:],item) else: return search(li[:mid,item]) if __name__ == '__main__': li = [1,2,3,4,5] print(search(li,2)) print(search(li,6))
队列

class Queue(object): def __init__(self): self.items = [] def enqueue(self, item): """往队列中添加一个item元素""" self.items.insert(0, item) def dequeue(self): """从队列头部删除一个元素""" return self.items.pop() def is_empty(self): """判断一个队列是否为空""" return self.items == [] def size(self): """返回队列的大小""" return len(self.items) if __name__ == '__main__': queue = Queue() queue.enqueue(1) queue.enqueue(2) queue.enqueue(3) print(queue.size()) print(queue.is_empty()) print(queue.dequeue()) print(queue.dequeue()) print(queue.dequeue())
双端队列

class Dequeue(object): def __init__(self): self.items = [] def add_front(self, item): """从队头加入一个item元素""" self.items.insert(0, item) def add_rear(self, item): """从队尾加入一个item元素""" self.items.append(item) def remove_front(self): """从队头删除一个item元素""" return self.items.pop(0) def remove_rear(self): """从队尾删除一个item元素""" return self.items.pop() def is_empty(self): """判断双端队列是否为空""" return self.items == [] def size(self): """返回队列的大小""" return len(self.items) if __name__ == '__main__': dequeue = Dequeue() # 当成栈 # dequeue.add_front(1) # dequeue.add_front(2) # print(dequeue.remove_front()) # print(dequeue.remove_front()) # dequeue.add_rear(3) # dequeue.add_rear(4) # print(dequeue.remove_rear()) # print(dequeue.remove_rear()) # 当成队列 # dequeue.add_front(1) # dequeue.add_front(2) # # print(dequeue.remove_rear()) # print(dequeue.remove_rear()) dequeue.add_rear(3) dequeue.add_rear(4) print(dequeue.remove_front()) print(dequeue.remove_front())
栈

class Stack(object): def __init__(self): # 初始化一个列表 self.items = [] def push(self, item): """添加一个新的元素item到栈顶""" # 从尾部添加 时间复杂度是O(1) self.items.append(item) def pop(self): """弹出栈顶元素""" # 从尾部弹出,时间复杂度是O(1) return self.items.pop() def peek(self): """返回栈顶元素""" # 列表的尾部是栈顶 return self.items[self.size()-1] def is_empty(self): """判断栈是否为空""" return self.items == [] def size(self): """返回栈的元素个数""" return len(self.items) if __name__ == '__main__': stack = Stack() print(stack.is_empty()) stack.push(1) stack.push(2) stack.push(3) print(stack.is_empty()) print(stack.size()) print(stack.pop()) print(stack.peek()) print(stack.pop()) print(stack.pop())
单向链表

class Node(object): """单链表的节点类""" # 初始化变量 def __init__(self, item): # 记录节点数据的变量 self.item = item # 记录下一个节点 self.next = None class SingleLinkList(object): """单链表类""" def __init__(self): # 所有的操作都需要首节点 self.__head = None def is_empty(self): """链表是否为空""" # 判断当前链表中的头节点是否为空 return self.__head is None def length(self): """链表长度""" # 定义变量记录节点个数 count = 0 # 创建游标变量,记录首节点 cur = self.__head # 循环让游标变量遍历节点 while cur is not None: # 游标指向了一个节点,节点个数+1 count += 1 # 让游标往后移动,指向下一个节点 cur = cur.next # 返回节点的总数 return count def travel(self): """遍历整个链表""" cur = self.__head while cur is not None: # 当前游标指向了一个节点,输出它的数据内容 print(cur.item,end=' ') cur = cur.next # 遍历完成,换行 print() def add(self, item): """链表头部添加元素""" # 创建节点 node = Node(item) # 让创建的节点记录之前的头结点 node.next = self.__head # 把创建的这个节点赋值给head变量 self.__head = node def append(self, item): """链表尾部添加元素""" # 判断链表是否为空 if self.is_empty(): # 调用头部添加节点 self.add(item) return # 1、遍历链表,找到尾节点 cur = self.__head # 循环完成后,cur指向尾节点 while cur.next is not None: cur = cur.next # 2、让尾节点的next指向新节点 node = Node(item) cur.next = node def insert(self, pos, item): """指定位置添加元素""" # 根据传入的位置判断 if pos <= 0: # 添加到首节点 self.add(item) elif pos >= self.length(): # 添加到尾部 self.append(item) else: # 1、需要先找到插入位置的前一个节点 # 2、把新节点的next指向之前位置的节点 # 3、让找到前一个节点指向新节点 cur = self.__head # 记录当前游标的索引 index = 0 # 遍历链表,结束时,游标指向指定位置的前一个节点 while index < pos -1: cur = cur.next index += 1 node = Node(item) node.next = cur.next cur.next = node def remove(self, item): """删除节点""" # 1、遍历找到要删除的元素 # 2、找到要删除节点的前一个节点 # 3、删除节点 cur = self.__head # pre记录cur游标的前一个节点 pre = None while cur is not None: # 判断当前游标指向的节点是否是要删除的节点 if cur.item == item: # 删除当前游标指向的节点 # 如果删除的是首节点,pre为None if pre is None: self.__head = cur.next else: pre.next = cur.next # 当cur游标往后面移动之前,先把cur指向的节点赋值给pre pre = cur cur = cur.next def search(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() print(sll.is_empty()) print(sll.length()) sll.add(5) sll.add(2) sll.add(1) print(sll.length()) sll.travel() sll.append(3) sll.travel() sll.insert(2,4) sll.travel() print(sll.search(3)) print(sll.search(6)) sll.remove(5) sll.travel()
双向链表

class Node(object): """双向链表的节点类""" # 初始化变量 def __init__(self, item): # 记录节点数据的变量 self.item = item # 记录上一个节点 self.pre = None # 记录下一个节点 self.next = None class DoubleLinkList(object): """双向链表类""" def __init__(self): # 所有的操作都需要首节点 self.__head = None def is_empty(self): """链表是否为空""" # 判断当前链表中的头节点是否为空 return self.__head is None def length(self): """链表长度""" # 定义变量记录节点个数 count = 0 # 创建游标变量,记录首节点 cur = self.__head # 循环让游标变量遍历节点 while cur is not None: # 游标指向了一个节点,节点个数+1 count += 1 # 让游标往后移动,指向下一个节点 cur = cur.next # 返回节点的总数 return count def travel(self): """遍历整个链表""" cur = self.__head while cur is not None: # 当前游标指向了一个节点,输出它的数据内容 print(cur.item,end=' ') cur = cur.next # 遍历完成,换行 print() def add(self, item): """链表头部添加元素""" # 创建节点 node = Node(item) # 判断链表是否为空 if self.is_empty(): self.__head = node return # 让老的头节点的pre记录新的节点 self.__head.pre = node # 让创建的节点记录之前的头结点 node.next = self.__head # 把创建的这个节点赋值给head变量 self.__head = node def append(self, item): """链表尾部添加元素""" # 判断链表是否为空 if self.is_empty(): # 调用头部添加节点 self.add(item) return # 1、遍历链表,找到尾节点 cur = self.__head # 循环完成后,cur指向尾节点 while cur.next is not None: cur = cur.next # 2、让尾节点的next指向新节点 node = Node(item) cur.next = node # 让新的尾节点的pre指向老的尾节点 node.pre = cur def insert(self, pos, item): """指定位置添加元素""" # 根据传入的位置判断 if pos <= 0: # 添加到首节点 self.add(item) elif pos >= self.length(): # 添加到尾部 self.append(item) else: # 1、需要先找到插入位置的前一个节点 # 2、把新节点的next指向之前位置的节点 # 3、让找到前一个节点指向新节点 cur = self.__head # 记录当前游标的索引 index = 0 # 遍历链表,结束时,游标指向指定位置的前一个节点 while index < pos -1: cur = cur.next index += 1 node = Node(item) node.next = cur.next # 老位置的节点的pre指向新节点 cur.next.pre = node cur.next = node # 新节点的pre指向它的前一个节点 node.pre = cur def remove(self, item): """删除节点""" # 1、遍历找到要删除的元素 # 2、找到要删除节点的前一个节点 # 3、删除节点 cur = self.__head # pre记录cur游标的前一个节点 while cur is not None: # 判断当前游标指向的节点是否是要删除的节点 if cur.item == item: # 删除当前游标指向的节点 # 如果删除的是首节点,pre为None if cur.pre is None: self.__head = cur.next else: cur.pre.next = cur.next # 如果删除的不是尾节点 if cur.next is not None: cur.next.pre = cur.pre # 当cur游标往后面移动之前,先把cur指向的节点赋值给pre cur = cur.next def search(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 = DoubleLinkList() print(sll.is_empty()) print(sll.length()) sll.add(5) sll.add(2) sll.add(1) print(sll.length()) sll.travel() sll.append(3) sll.travel() sll.insert(2,4) sll.travel() print(sll.search(3)) print(sll.search(6)) sll.remove(5) sll.travel()
单向循环链表

class Node(object): """单循环链表的节点类""" # 初始化变量 def __init__(self, item): # 记录节点数据的变量 self.item = item # 记录下一个节点 self.next = None class SingleCycLinkList(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: # 游标指向了一个节点,节点个数+1 count += 1 # 让游标往后移动,指向下一个节点 cur = cur.next # 返回节点的总数 return count def travel(self): """遍历整个链表""" # 判断是否为空 if self.is_empty(): return cur = self.__head # 打印首节点 print(cur.item, end=' ') while cur.next is not self.__head: # 当前游标指向了一个节点,输出它的数据内容 cur = cur.next print(cur.item,end=' ') # 遍历完成,换行 print() def add(self, item): """链表头部添加元素""" # 创建节点 node = Node(item) # 判断是否为空 if self.is_empty(): self.__head = node # 自己的next指向自己 node.next = node return # 遍历找到尾节点,让尾节点指向新的头 cur = self.__head while cur.next is not self.__head: cur = cur.next # 当while循环结束,cur指向的就是尾节点 # 让创建的节点记录之前的头结点 node.next = self.__head # 把创建的这个节点赋值给head变量 self.__head = node # 让尾节点指向新的头节点 cur.next = self.__head def append(self, item): """链表尾部添加元素""" # 判断链表是否为空 if self.is_empty(): # 调用头部添加节点 self.add(item) return # 1、遍历链表,找到尾节点 cur = self.__head # 循环完成后,cur指向尾节点 while cur.next is not self.__head: cur = cur.next # 2、让尾节点的next指向新节点 node = Node(item) cur.next = node # 新节点指向头节点 node.next = self.__head def insert(self, pos, item): """指定位置添加元素""" # 根据传入的位置判断 if pos <= 0: # 添加到首节点 self.add(item) elif pos >= self.length(): # 添加到尾部 self.append(item) else: # 1、需要先找到插入位置的前一个节点 # 2、把新节点的next指向之前位置的节点 # 3、让找到前一个节点指向新节点 cur = self.__head # 记录当前游标的索引 index = 0 # 遍历链表,结束时,游标指向指定位置的前一个节点 while index < pos -1: cur = cur.next index += 1 node = Node(item) node.next = cur.next cur.next = node def remove(self, item): """删除节点""" # 判断为空 if self.is_empty(): return # 1、遍历找到要删除的元素 # 2、找到要删除节点的前一个节点 # 3、删除节点 cur = self.__head # pre记录cur游标的前一个节点 pre = None while cur.next is not self.__head: # 处理不了尾节点 # 判断当前游标指向的节点是否是要删除的节点 if cur.item == item: # 删除当前游标指向的节点 # 如果删除的是首节点,pre为None if pre is None: # 找到尾节点 temp = self.__head while temp.next is not self.__head: temp = temp.next self.__head = cur.next temp.next = self.__head else: pre.next = cur.next # 当cur游标往后面移动之前,先把cur指向的节点赋值给pre pre = cur cur = cur.next # 结束时判断尾节点是否是要删除的节点 if cur.item == item: # 当pre为空时,证明当前只有一个节点 if pre is None: self.__head = None else: pre.next = self.__head def search(self, item): """查找节点是否存在""" # 判断是否为空 if self.is_empty(): return False # 遍历链表 cur = self.__head # 判断首节点是否是要找的节点 if cur.item == item: return True while cur.next is not self.__head: # 判断当前节点的内容 cur = cur.next if cur.item == item: return True return False if __name__ == '__main__': sll = SingleCycLinkList() print(sll.is_empty()) print(sll.length()) sll.add(5) sll.add(2) sll.add(1) print(sll.length()) sll.travel() sll.append(3) sll.travel() sll.insert(2,4) sll.travel() print(sll.search(3)) print(sll.search(1)) print(sll.search(6)) sll.remove(5) sll.remove(3) sll.travel()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理