简介
数据结构:线性结构和非线性结构,用来存储数据的方式
算法:就是解决问题的思路和方法,要有设计出好的数据结构才能更好用算法
特性:有输入、有输出、有穷性、确定性、可行性
时间复杂度
表示随着问题规模不断变化的主要趋势,衡量算法的优劣
大O标记法:将次要条件省掉,最终形成一个表达式
比如说:嵌套的3次for循环,最里面是if什么的,再打印,只看最外的3次循环,都是
1 2 3 for i in range(0,5): O(1) 如果是0,n O(n) for j in range(0,n): O(n) print(i) O(1)* O(n)= O(n) 最终就 O(n^2)
时间复杂度取最高次项 ,即最坏的复杂度
最优时间复杂度指的算法完成工作最少需要多少基本操作
1 2 3 4 5 6 7 | 执行次数函数举例 | 阶 | 非正式术语 | |------------------|----------|------------| | 12 | O(1) | 常数阶 | | 2n+3 | O(n) | 线性阶 | | 3n²+2n+1 | O(n²) | 平方阶 | | 5log₂n+20 | O(logn) | 对数阶 | | 6n³+2n²+3n+4 | O(n³) | 立方阶 |
所消耗的时间从小到大:
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³)
时间复杂度越低,效率越高 O(1) 最高
备注:
O(logn):二分法
O(nlogn):一个for循环是n 另外一个for循环是二分法,组合在一起
空间复杂度
空间复杂度(S(n))对算法在运行过程中临时占用存储空间大小,也可以用大O记法
O(1) < O(logn) < O(n) < O(n²) < O(n³)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for i in range (0 , n): a = i print (a) list =[]for i in range (1 ,n): list .append(i) list_1 = [] for i in range (0 , n): list_2 = [] for j in range (0 , n): list_2.append(j) list_1.append(list_2)
存储结构
内存以字节为基本存储单位,每个存储空间有自己的地址,一个地址代表1个字节8bit
整形 4个字节 如10 占用4个字节 字符串1个
数据结构分类:
(1)线性结构 一个父节点 ,1个子节点
顺序表 (栈, 队列) 适合查(时间复杂度O(1))和改
一站式存储(列表同一类型) 数据区、信息区
分离式存储(列表不同类型)
链表 适合 增和删 没有索引 由节点组成,其中节点由 元素域(数值域) 和 地址域组成
单向链表:节点由1个数值域和1个地址域组成,前边节点的地址域存储后续节点的地址,最后一个地址域为None
自定义代码模拟链表
(2)非线性结构
1. 顺序表中如何增加元素?
a. 尾端加入元素,时间复杂度为 O(1)
b. 非保序的加入元素(不常见),时间复杂度为 O(1)
c. 保序的元素加入,时间复杂度为 O(n)
2. 顺序表中如何删除元素?
a. 删除表尾元素,时间复杂度为 O(1)
b. 非保序的元素删除(不常见),时间复杂度为 O(1)
c. 保序的元素删除,时间复杂度为 O(n)
自定义代码模拟链表,思路分析:
自定义 SingleNode 类,表示 节点类 。
属性:
item:数值域(元素域)
next:地址域(链接域)
自定义 SingleLinkedList 类,表示:链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 class SingleNode : """单链表的节点类""" def __init__ (self, item ): self.item = item self.next = None class SingleLinkedList : """单向链表类""" def __init__ (self,node=None ): self.head = node def is_empty (self ): """判断头节点是否为空""" return self.head is None def length (self ): """获取链表长度""" cur = self.head count = 0 while cur is not None : 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 = SingleNode(item) node.next = self.head self.head = node def append (self, item ): """在链表尾部添加元素 (尾插法)""" node = SingleNode(item) 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 (self, pos, item ): """在指定位置 pos 添加元素""" if pos <= 0 : self.add(item) elif pos > (self.length() - 1 ): self.append(item) else : node = SingleNode(item) count = 0 pre = self.head while count < (pos - 1 ): count += 1 pre = pre.next node.next = pre.next pre.next = node def remove (self, item ): """删除节点""" cur = self.head pre = None while cur is not None : if cur.item == 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 ): """查找节点是否存在,存在返回 True,否则返回 False""" cur = self.head while cur is not None : if cur.item == item: return True cur = cur.next return False if __name__ == "__main__" : node1 = SingleNode(10 ) print (f'节点1的数值域: {node1.item} ' ) print (f'节点1的地址域: {node1.next } ' ) print (f'node1对象: {node1} ' ) print (f'node1类型: {type (node1)} ' ) my_linkedlist = SingleLinkedList(node1) print (f'头节点为: {my_linkedlist.head} ' ) print (f'头节点元素域: {my_linkedlist.head.item} ' ) print (f'头节点地址域: {my_linkedlist.head.next } ' ) node2 = SingleNode('赵云' ) single_linkedlist = SingleLinkedList(node2) print (single_linkedlist.is_empty()) print (single_linkedlist.length()) single_linkedlist.travel()
递归
即函数自己调用自己
核心要点:
1、必须有出口,否则造成死递归
2、调用次数不能过多,否则容易死递归
3、递归必须有规律
1 2 3 4 5 6 7 8 def show (n ): if n == 1 : return 1 return n * show(n-1 ) print (show(5 ))
算法
排序算法:按照其中某个关键字的大小,递增或递减的排列起来操作
稳定性算法:相同元素的 相对位置 不改变
不稳定的排序算法: 选择排序、快速排序、希尔排序、堆排序
稳定的排序算法: 冒泡排序、插入排序、归并排序和基数排序
冒泡排序
相邻两两比较,前面的比后面大就交换
比如:5个人身高排序,第一轮两两比较就能知道最高的,第二轮,第二高,4轮就能排完
名字由来:最小元素会慢慢交换浮到数列的顶端。如二氧化碳的气泡
[5,3,4,7,2]
1、比较的总轮数 2、每轮比较的总次数 3、谁和谁比较
原理
相邻元素两两比较,大的往后走,这样第一轮比较完毕后,最大值就在最大索引处。重复此动作,直至排序完成。
流程(假设共 5 个元素)
第几轮(索引)
该轮比较的总次数
公式
第1轮(0)
4次
5−1−0=4
第2轮(1)
3次
5−1−1=3
第3轮(2)
2次
5−1−2=2
第4轮(3)
1次
5−1−3=1
要点
比较的总轮数 :列表长度 - 1
每轮比较的总次数 :列表长度 - 1 - 轮数的索引(从0开始)
谁和谁比较 :索引 j 和 j + 1 位置的元素比较
时间复杂度
扩展
外循环-1:减少比较次数、提高效率
内循环-1:为了防止索引越界 内循环 -i:减少每轮的比较次数,提高效率
冒泡排序 = 稳定 排序算法 比如[3,2,7,6,6] 6 7 6 | 6 6 7 发现 6 6 的相对位置没有变化就是稳定的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def dubble_sort (list ): """冒泡排序""" for i in range (len (list ) - 1 ): for j in range (len (list ) - 1 - i): if list [j] > list [j + 1 ]: list [j], list [j + 1 ] = list [j + 1 ], list [j] def dubble_sort_optimize (list ): """冒泡排序""" count=0 for i in range (len (list ) - 1 ): for j in range (len (list ) - 1 - i): if list [j] > list [j + 1 ]: count+=1 list [j], list [j + 1 ] = list [j + 1 ], list [j] print (f"第{i+1 } 轮数,交换次数为{count} " ) if count==0 : print ("已经有序" ) break if __name__ == "__main__" : list = [5 , 3 , 4 , 7 , 2 ] list1=[1 ,2 ,3 ,4 ,5 ] dubble_sort_optimize(list1) print (list )
选择排序
不稳定 排序算法 5 3 5 7 2 第一轮交换2 3 5 7 5 本来是前5,交换后变后5,位置变化了,所以不稳定
待排序的数据元素中选最小或最大的一个元素,放序列的起始位置
要点:
比较的总轮数。n - 1
每轮比较的总次数。i+1, n
谁和谁比较(交换)。i 和 min_index
第1轮(索引:0) 0 和 1,2,3,4比较 共4次
第2轮(索引:1) 1 和 2,3,4比较 共3次
第3轮(索引:2) 2 和 3,4比较 共2次
第4轮(索引:3) 3 和 4比较 共1次
[5, 3, 4, 7, 2]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def selection_sort (arr ): n = len (arr) for i in range (0 ,n-1 ): min_idx = i for j in range (i+1 ,n): if arr[j] < arr[min_idx]: min_idx = j if min_idx != i: arr[i], arr[min_idx] = arr[min_idx], arr[i] return arr if __name__ == '__main__' : arr = [5 , 3 , 4 , 7 , 2 ] print (selection_sort(arr))
插入排序
将一个数据插入到已经排好序的有序数据
默认第一个是有序的,其他默认无序
稳定排序
第几轮(索引)
该轮比较的总次数
公式(具体的谁和谁比较)
第1轮(1):
1次
索引1和 0比较
第2轮(2):
2次
索引2和1, 2和0比较
第3轮(3):
3次
索引3和2, 3和1, 3和0比较
第4轮(4):
4次
索引4和3, 4和2, 4和1, 4和0比较
时间复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def insertion_sort (arr ): n = len (arr) for i in range (1 ,n): for j in range (i,0 ,-1 ): if arr[j] < arr[j-1 ]: arr[j], arr[j-1 ] = arr[j-1 ], arr[j] else : break return arr if __name__ == '__main__' : arr = [5 , 3 , 4 , 7 , 2 ] print (insertion_sort(arr))
快速排序
首先设置一个分界值,将数组分为左右两边
大于或等于的放分界值右边,小于的放左边
时间复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 def quick_sort (arr, start, end ): """ 快速排序 :param arr: 待排序数组 :param start: 起始索引 0 :param end: 结束索引 len(arr)-1 :return: 排序后的数组 """ if start >= end: return left = start right = end mid = arr[start] while left < right: while left < right and arr[right] >= mid: right -= 1 arr[left]=arr[right] while left < right and arr[left] < mid: left += 1 arr[right]=arr[left] arr[left]=mid quick_sort(arr, start, left-1 ) quick_sort(arr, left+1 , end) if __name__ == '__main__' : my_list = [3 , 2 , 4 , 1 ,5 , 6 , 7 , 9 , 8 ] quick_sort(my_list, 0 , len (my_list)-1 ) print (my_list)
二分查找
属于查找类算法,相对效率比较高,时间复杂度 O(logn)
原理: 假设列表是升序 0 1 3 5 7 9 10
1、比较要查找的元素和列表的中值,如果一样就返回True,程序结束
2、如果要查找的元素比中值小,去前半段(中值前)查找
3、如果 要查找的元素 比中值大,去后半段(中值后)查找
4、重复上面操作,直到找完,如果找完了还是没有,就返回False
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 def binary_search_recursive (my_list, target ): """ 二分查找递归版 :param my_list: 有序列表 :param target: 目标值 :return: True在列表中/False不在 """ n = len (my_list) if n == 0 : return False mid = n // 2 if my_list[mid] == target: return True elif my_list[mid] > target: return binary_search_recursive(my_list[:mid], target) else : return binary_search_recursive(my_list[mid+1 :], target) def binary_search (my_list, target ): """ 二分查找非递归版 定义start,end指针分别指向列表首尾索引 :param my_list: 有序列表 :param target: 目标值 :return: True在列表中/False不在 """ start = 0 end = len (my_list) - 1 while start <= end: mid = (start + end) // 2 if my_list[mid] == target: return True elif my_list[mid] > target: end = mid - 1 else : start = mid + 1 return False if __name__ == '__main__' : my_list = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] print (binary_search(my_list, 6 ))
树
非线性结构
节点的度 :一个节点含有的子节点的个数称为该节点的度
树的度 :一棵树中,最大的节点的度称为树的度
叶节点或终端节点 :度为零的节点
父亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点
兄弟节点 :具有相同父节点的节点互称为兄弟节点
节点的层次 :从根开始定义起,根为第1层,根的子节点为第2层,以此类推
树的高度或深度 :树中节点的最大层次
堂兄弟节点 :父节点在同一层的节点互为堂兄弟
节点的祖先 :从根到该节点所经分支上的所有节点
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙
森林 :由m(m>=0)棵互不相交的树的集合称为森林
分类:
有序树:
无序树:
二叉树:
完全二叉树:最后一层不满,其他都是满
非完全二叉树:中间有断的
满二叉树:全满
平衡二叉树:任意节点的两个子树高度不超过1
广度优先遍历算法:一层一层按顺序
深度优先遍历:中序(中序遍历 左根右)、先序(前序遍历 根左右)、后序(后序遍历 左右根)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 class Node : def __init__ (self, item ): self.item = item self.left = None self.right = None class BinaryTree : def __init__ (self,node=None ): self.root = node def add (self,item ): new_node = Node(item) if self.root is None : self.root = new_node return queue=[] queue.append(self.root) while True : node=queue.pop(0 ) if node.left is None : node.left=new_node return else : queue.append(node.left) if node.right is None : node.right=new_node return else : queue.append(node.right) def breadth_travel (self ): if self.root is None : return queue=[] queue.append(self.root) while len (queue)>0 : node=queue.pop(0 ) print (node.item,end=' ' ) if node.left is not None : queue.append(node.left) if node.right is not None : queue.append(node.right) def preorder (self,root ): if root is None : return print (root.item,end=' ' ) self.preorder(root.left) self.preorder(root.right) def inorder (self,root ): if root is None : return self.inorder(root.left) print (root.item,end=' ' ) self.inorder(root.right) def postorder (self,root ): if root is None : return self.postorder(root.left) self.postorder(root.right) print (root.item,end=' ' ) def demo_binary_tree (): node1 = Node("A" ) print (node1.item) print (node1.left) print (node1.right) print ('-' *20 ) bt=BinaryTree(node1) print (bt.root.item) def demo_binary_tree2 (): bt=BinaryTree() bt.add("A" ) bt.add("B" ) bt.add("C" ) bt.add("D" ) bt.add("E" ) bt.add("F" ) bt.add("G" ) bt.add("H" ) bt.add("I" ) bt.add("J" ) bt.breadth_travel() def demo_binary_tree3 (): bt=BinaryTree() bt.add(0 ) bt.add(1 ) bt.add(2 ) bt.add(3 ) bt.add(4 ) bt.add(5 ) bt.add(6 ) bt.add(7 ) bt.add(8 ) bt.add(9 ) print ('先序 根左右:' ,end=' ' ) bt.preorder(bt.root) print ('-' *20 ) print ('中序 左根右:' ,end=' ' ) bt.inorder(bt.root) print ('-' *20 ) print ('后序 左右根:' ,end=' ' ) bt.postorder(bt.root) print () if __name__ == '__main__' : demo_binary_tree3()
二叉树遍历结果反推 二叉树结构
先序遍历结果:0 1 3 7 8 4 9 2 5 6 根左右
中序遍历结果:7 3 8 1 9 4 0 5 2 6 左根右
通过先序,确定根是0,再根据中序,0的位置,切分为左右树