数据结构
概念:数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成
算法复杂度
时间复杂度
计算方法
空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量
对于一个算法,其 时间复杂度和空间复杂度往往是相互影响的。当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。算法的时间复杂度和空间复杂度合称为算法的复杂度
线性表
顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中
链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间。
而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)
相关术语:
前驱指针:指向上一个节点
后继指针:指向下一节点
单向链表: 一个节点,只包含一个指针,就是后继指针,用来指向下一节点。尾节点指向None
双向链表:一个节点,包含两个指针,前驱、后继指针。注意点 修改一个节点的时候,要记住前后节点的位置
单向循环:一个节点,只包含一个指针,用来指向下一节点,尾节点的下一节点就是头节点
# 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() """ True 0 False 1 9 8 1 2 3 4 5 6 9 8 1 100 2 3 4 5 6 9 8 1 100 2 3 4 5 6 200 9 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 """
# 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() """ True 0 False 1 9 8 1 2 3 4 5 6 9 8 1 100 2 3 4 5 6 9 8 1 100 2 3 4 5 6 200 9 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 """
# 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() """ True 0 False 1 9 8 1 2 3 4 5 6 9 8 1 100 2 3 4 5 6 9 8 1 100 2 3 4 5 6 200 9 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 200 8 1 2 3 4 5 6 """
排序
排序算法的稳定&不稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
冒泡排序
概念:冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 代码理解:就是借用两个for 循环(内循环 外循环)
# coding:utf-8 def bubble_sort(alist): """冒泡排序""" n = len(alist) for j in range(n-1): for i in range(0, n-1-j): # 从头走到尾 if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1], alist[i] if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) bubble_sort(li) print(li)
选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
def select_sort(alist): """选择排序""" n = len(alist) for j in range(n-1): # j: 0 ~ n-2 min_index = j for i in range(j+1, n): if alist[min_index] > alist[i]: min_index = i alist[j], alist[min_index] = alist[min_index], alist[j] if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) select_sort(li) print(li)
插入排序
插入排序算法有种递归的思想在里面,它由N-1趟排序组成。初始时,只考虑序列下标0处的元素,只有一个元素,显然是有序的。
然后第一趟 对下标 1 处的元素进行排序,保证序列[0,1]上的元素有序;
第二趟 对下标 2 处的元素进行排序,保证序列[0,2]上的元素有序;
.....
.....
第N-1趟对下标 N-1 处的元素进行排序,保证序列[0,N-1]上的元素有序,也就是整个数组有序了。
它的递归思想就体现在:当对位置 i 处的元素进行排序时,[0,i-1]上的元素一定是已经有序的了。
# coding:utf-8 def insert_sort(alist): """插入排序""" n = len(alist) # 从右边的无序序列中取出多少个元素执行这样的过程 for j in range(1, n): # j = [1, 2, 3, n-1] # i 代表内层循环起始值 i = j # 执行从右边的无序序列中取出第一个元素,即i位置的元素,然后将其插入到前面的正确位置中 while i > 0: if alist[i] < alist[i-1]: alist[i], alist[i-1] = alist[i-1], alist[i] i -= 1 else: break if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) insert_sort(li) print(li)
希尔排序
思想:将待排序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,然后再对整个序列进行插入排序。
# coding:utf-8 def shell_sort(alist): """希尔排序""" # n=9 n = len(alist) # gap =4 gap = n // 2 # // 整除取整数 # gap变化到0之前,插入算法执行的次数 while gap > 0: # 插入算法,与普通的插入算法的区别就是gap步长 for j in range(gap, n): # j = [gap, gap+1, gap+2, gap+3, ..., n-1] i = j while i > 0: if alist[i] < alist[i-gap]: alist[i], alist[i-gap] = alist[i-gap], alist[i] i -= gap else: break print('while:',alist) # 缩短gap步长 print('for:',alist) gap //= 2 if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) shell_sort(li) print(li) """ [54, 26, 93, 17, 77, 31, 44, 55, 20] while: [54, 26, 93, 17, 77, 31, 44, 55, 20] while: [54, 26, 93, 17, 77, 31, 44, 55, 20] while: [54, 26, 55, 17, 77, 31, 93, 44, 20] while: [54, 26, 55, 17, 77, 31, 93, 44, 20] while: [20, 26, 55, 17, 54, 31, 93, 44, 77] for: [20, 26, 55, 17, 54, 31, 93, 44, 77] while: [20, 26, 55, 17, 54, 31, 93, 44, 77] while: [20, 77, 55, 26, 54, 31, 93, 44, 17] while: [20, 77, 54, 26, 55, 31, 93, 44, 17] while: [20, 77, 54, 26, 55, 31, 93, 44, 17] while: [20, 77, 54, 26, 55, 31, 93, 44, 17] while: [20, 77, 54, 26, 55, 31, 93, 44, 17] while: [17, 77, 20, 26, 54, 31, 55, 44, 93] for: [17, 77, 20, 26, 54, 31, 55, 44, 93] while: [17, 77, 20, 26, 54, 31, 55, 44, 93] while: [17, 20, 77, 26, 54, 31, 55, 44, 93] while: [17, 20, 26, 77, 54, 31, 55, 44, 93] while: [17, 20, 26, 54, 77, 31, 55, 44, 93] while: [17, 20, 26, 31, 54, 77, 55, 44, 93] while: [17, 20, 26, 31, 54, 55, 77, 44, 93] while: [17, 20, 26, 31, 44, 54, 55, 77, 93] while: [17, 20, 26, 31, 44, 54, 55, 77, 93] for: [17, 20, 26, 31, 44, 54, 55, 77, 93] [17, 20, 26, 31, 44, 54, 55, 77, 93] """
快速排序
# coding:utf-8 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)
归并排序
概念:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
# coding:utf-8 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) """ [54, 26, 93, 17, 77, 31, 44, 55, 20] [54, 26, 93, 17, 77, 31, 44, 55, 20] [17, 20, 26, 31, 44, 54, 55, 77, 93] """
搜索
二分查找
说明:元素必须是有序的,如果是无序的则要先进行排序操作。
基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
复杂度分析:最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
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)) """ True False True False """
二叉树
相关术语:
二叉树性质
完全二叉树
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
满二叉树
# 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(" ") """ 0 1 2 3 4 5 6 7 8 9 0 1 3 7 8 4 9 2 5 6 7 3 8 1 9 4 0 5 2 6 7 8 3 9 4 1 5 6 2 0 """
先序遍历: 根 左 右
中序遍历: 左 根 右
后序遍历: 左 右 根
三种顺序之中:如果知道中序跟其中一个 ,就可以写出第三个顺序来