最全,最简明的图文算法总结

一.时间复杂度

时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。

  • 常数阶 O ( 1 ) O(1) O(1)
  • 线性阶 O ( n ) O(n) O(n)
  • 平方阶 O ( n 2 ) O(n^2) O(n2)
  • 立方阶 O ( n 3 ) O(n^3) O(n3)
  • 对数阶 O ( l o g n ) O(logn) O(logn)
  • 线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)
  • 指数阶 O ( 2 n ) O(2^n) O(2n)

常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)


二.空间复杂度

空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。

常数:o(1)

一维数组:o(n)

二维数组:o(n+m)


三.递归

递归,就是在运行的过程中调用自己。

构成递归需具备的条件:

  1. 子问题须与原始问题为同样的事,且更为简单;
  2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
def h(n):
    if n>0:
        print(n)
        h(n-1)

h(3)

结果:3,2,1
先打印后递归



def h(n):
    if n>0:
        h(n-1)
        print(n)

h(3)

结果:1,2,3
先递归后打印

四.汉诺塔

第一步:把a经过b移到c
第二步:把a移到c
第三步:把b经过a移到c
递推公式:h(x)=2h(x-1)+1

def h(n,a,b,c):
    if n>0:
        h(n-1,a,c,b)
        print('把%s移动到%s'% (a,c))
        h(n-1,b,a,c)
h(3,'A','B','C')


五.斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列

F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)

第一种:

def fib_recur(n):
   assert n >= 0, "n > 0"
 	if n <= 1:
   	return n
 	return fib_recur(n-1) + fib_recur(n-2)

for i in range(1, 20):
   print(fib_recur(i), end=' ')

第二种:

def fib_loop_for(n):
  	a, b = 0, 1
  	for _ in range(n):
    	a, b = b, a + b
    return a

def fib_loop_while(n):
  	a, b = 1, 1
  	while n > 0:
    	a, b = b, a + b
    	n -= 1
  	return a

for i in range(20):
  	print(fib_loop_for(i), end=' ')

斐波那契数列的应用题:

1 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。

提示:f(n) = f(n-1) + f(n-2)

扩展题:

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶…也可以跳上n级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。

提示:f(n) = 2n-1

2 如果我们用21的小矩形横着或者竖着去覆盖更大的矩形。请问8个21的小矩形无重叠地覆盖一个2*8的大矩形,总共有多少种方法?

提示:f(8) = f(7) + f(6)


线性查找

在常规无序数组中
,设数组项个数为N,则一个数组项平均查找长度为N/2。极端情况下,查找数据项在数组最后,则需要N步才能找到。

def line_search(li,val):
	for k,v in enumerate(li):
		if v ==val:
			return k
	else:
		return None		


二分查找

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

在这里插入图片描述

def bindary_search(li,val):
	left = 0
	right = len(li)-1
	while left <= right:
		mid = (left + right)//2
		if li[mid] ==val:
			return mid
		elif li[mid] > val:
			right = mid -1
		elif li[mid]<val:
			left = mid +1
	else:
		return None				
					



冒泡排序

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  • 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  • 针对所有的元素重复以上的步骤,除了最后一个。

  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

在这里插入图片描述

def bullue_sort(li):
	for i in range(len(li)-1):  #排序趟数
		for j in range(len(li)-i-1) #箭头位置
			if li[j] > li[j+1]:
				li[j],li[j+1] = li[j+1],li[j]

改进:

def bullue_sort(li):
	for i in range(len(li)-1):  #排序趟数
		for j in range(len(li)-i-1) #箭头位置
			if li[j] > li[j+1]:
				li[j],li[j+1] = li[j+1],li[j]
				exchange = True
			if not exchange:
				return

排序次数:n(n-1)/2


选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾

在这里插入图片描述


def select_sort_one(li):
	li_new=[]
	for i in range(len(li)): #第几躺
		min_num = min(li)
		li_new.append(min_num)
		li.remove(min_num)
	return li_new	

改进

def select_sort_advance(li):
	for i in range(len(li)-1):
		min_num = i
		for j in range(i+1,len(li)):
			if li[j] < li[min_num]:
				min_num = j
		if min_num != i:
			li[i],li[min_num] = li[min_num],li[i]
					

插入排序

插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,

现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数

的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的

过程,称为插入排序 。

插入排序的工作方式像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的

牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找

到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。拿在左手上的

牌总是排序好的,原来这些牌是桌子上牌堆中顶部的牌 。

在这里插入图片描述

def insert_sort(li):
	for i in range(1,len(li)):
		tmp = li[i]
		j=i-1
		while j>=0 and tmp < li[j]:
			li[j+1] = li[j]
			j-=1
		li[j+1] = tmp	
			

快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。 快速排序由C. A. R.Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

在这里插入图片描述

在这里插入图片描述

def partation(li,left,right):
    tmp = li[left]
    while left < right:
        while left<right and li[right] >= tmp:
            right -=1
        li[left] = li[right]
        while left < right and li[left] <= tmp:
            left +=1
        li[right] = li[left]
    li[left] = tmp
    return left
		

def quick(li,left,right):
    if left < right:
        mid = partation(li,left,right)
        quick(li,left,mid-1)
        quick(li,mid+1,right)

lst=[3,6,2,4,8,5,3,5,0]
quick(lst,0,len(lst)-1)

堆排序

二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
在这里插入图片描述

  • 高度:有几层就是几
  • 度 :分支最大数

满二叉树:

国际标准定义是除了叶结点外每一个结点都有左右子结点的二叉树

在这里插入图片描述
完全二叉树:

若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。

一维数组可以作为完全二叉树的存储结构,堆排序使用的数据结构就是完全二叉树。

在这里插入图片描述
父亲节点和左孩子关系:2i+1

父亲节点和右孩子:2i+2

堆:一种特殊二叉树

大根堆,小根堆

堆的向下调整:

大的往上调,小的往下调

在这里插入图片描述

def build(arr:List[int], root, end):
    while True:
        child = 2 * root + 1 # 左子节点的位置
        if child > end: # 若左子节点超过了最后一个节点,则终止循环
            break
        if (child + 1 <= end) and (arr[child + 1] > arr[child]): # 若右子节点在最后一个节点之前,并且右子节点比左子节点大,则我们的孩子指针移到右子节点上
            child += 1
        if arr[child] > arr[root]: # 若最大的孩子节点大于根节点,则交换两者顺序,并且将根节点指针,移到这个孩子节点上
            arr[child], arr[root] = arr[root], arr[child]
            root = child
        else:
            break

def heap_sort(arr:List[int]):
    n = len(arr)
    first_root = n // 2 - 1 # 确认最深最后的那个根节点的位置
    for root in range(first_root, -1, -1): # 由后向前遍历所有的根节点,建堆并进行调整
        build(arr, root, n - 1)
        
    for end in range(n - 1, 0, -1): # 调整完成后,将堆顶的根节点与堆内最后一个元素调换位置,此时为数组中最大的元素,然后重新调整堆,将最大的元素冒到堆顶。依次重复上述操作
        arr[0], arr[end] = arr[end], arr[0]
        build(arr, 0, end - 1)

# 测试数据
if __name__ == '__main__':
    import random
    random.seed(54)
    arr = [random.randint(0,100) for _ in range(10)]
    print("原始数据:", arr)
    heap_sort(arr)
    print("堆排序结果:", arr)

归并排序

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

在这里插入图片描述


def merge(list_left, list_right):
    """
    入参数组都是有序的,此处将两个有序数组合并成一个大的有序数组
    """
    # 两个数组的起始下标
    l, r = 0, 0

    new_list = []
    while l < len(list_left) and r < len(list_right):
        if list_left[l] <= list_right[r]:
            new_list.append(list_left[l])
            l += 1
        else:
            new_list.append(list_right[r])
            r += 1
    new_list += list_left[l:]
    new_list += list_right[r:]
    return new_list


def merge_sort(mylist):
    if len(mylist) <= 1:
        return mylist
    mid = len(mylist) // 2
    list_left = merge_sort(mylist[:mid])
    list_right = merge_sort(mylist[mid:])
    return merge(list_left, list_right)


if __name__ == "__main__":
    mylist = [12, 33, 199, 0, 54, 33, 11]
    result = merge_sort(mylist)
    print(f'归并排序后:{result}')

					

总结

在这里插入图片描述

在这里插入图片描述

希尔排序

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。

希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。

在这里插入图片描述

def insert_sort_grap(li,gap):
	for i in range(gap,len(li)):
		tmp=li[i]
		j = i - gap
		while j>=0 and li[j]>tmp:
			li[j+gap] =li[j]
			j -= gap
		li[j+gap] = tmp	


def short_sort(li):
	d = len(li) // 2
	while d >= 1:
		insert_sort_grap(li,d)
		d //= 2
	

计数排序

计数排序是一个非基于比较的排序算法。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 [1] 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

在这里插入图片描述

def count_sort(li,max_count=100):
	count=[0 for_in range(max_count+1)]
	for val in li:
		count[val] +=1
	li.clear()
	for ind,val in enunerate(count):
		for i in range(val):
			li.append(ind)	

桶排序

桶排序是计数排序的扩展版本,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。

桶排序需要尽量保证元素分散均匀,否则当所有数据集中在同一个桶中时,桶排序失效。

在这里插入图片描述

def bukcer_sort(li,n=100,max_num=1000):
	buckets=[[] for _ in range(n)]
	for var in li:
		i = min(var //(max_num // n),n-1)
		buckets[i].append(var)
		for j in range(len(buckets[i])-1,0,-1):
			if buckets[i][j] < buckets[i][j-1]:
				buckets[i][j-1] = buckets[j-1][i]
			else:
				break
	sort_li =[]
	for buc in backets:
		sort_li.extend(buc)
	return sort_li					

基数排序

基数排序(Radix Sort)是在桶排序的基础上发展而来的,两种排序都是分配排序的高级实现。分配排序(Distributive Sort)的基本思想:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。

在这里插入图片描述

def radix_sort(li):
	max_num =max(li)
	it = 0
	while 10 ** it <= max_num:
		buckets=[[] for _ in range(10)]
		for var in li:
			digit = (var //10 ** it) % 10
			buckets[digit].append(var)
		li.clear()
		for buc in buckets:
			li.extend(buc)
		it +=1		
		

栈:

栈就是先进后出

class stack:
	def __init__(self):
		self.stack=[]
	def push(self,item):
		self.stack.append(item)
	def pop(self):
		return self.stack.pop()

队列

队列就是先进先出

class Queue():d
	def __init__(self):
		self.items=[]
	def enqueue(self,item):
		self.items.insert(0,item)
	def dequeue(self):
		self.items.pop()

两个栈实现一个队列

class StackToQueue:
    def __init__(self):
        self.stack1=[]
        self.stack2=[]


    def push(self,data):
        '''
        push数据到stack1
        append()数据是直接添加到列表的最后
        :return:
        '''
        self.stack1.append(data)
    def pop(self):
        if len(self.stack2)==0 and len(self.stack1)==0:
            return

        elif len(self.stack2)==0:
            while len(self.stack1)>1:
                self.stack2.append(self.stack1.pop())

        return self.stack2.pop()

s=StackToQueue()

两个队列实现一个栈

class QueueToStack:
    def __init__(self):
        self.queue1=[]
        self.queue2=[]
    # 元素push进入queue1
    def push(self,data):
        self.queue1.append(data)

    def pop(self):
        if len(self.queue1)==0:
            return None
        while len(self.queue1)!=1:
            # 则将queue1的数据的第一条数据append给queue2,这样queue2的最后一个数据一定是最后进入到列表中的
            self.queue2.append(self.queue1.pop(0))
        # 交换1和2 此时 ,queue1里面是有数据的,而queue2 是没有数据,那么下次pop的时候,还是会while 将queue1的数据全部添加到
        # queue2 中,那queue2 将保持最先进入的数据在列表的最后,最后进入的数据在列表的第0项  所以这里应该是这么理解的。
        self.queue1,self.queue2=self.queue2,self.queue1

        return self.queue2.pop()

输出/删除 单链表倒数第 K 个节点

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        second = fist = head
        for i in range(n):  # 第一个指针先走 n 步
            fist = fist.next
 
        if fist == None:  # 如果现在第一个指针已经到头了,那么第一个结点就是要删除的结点。
            return second.next
 
        while fist.next:  # 然后同时走,直到第一个指针走到头
            fist = fist.next
            second = second.next
        second.next = second.next.next  # 删除相应的结点
        return head

删除链表中节点,要求时间复杂度为O(1)

class Solution:
    def deleteNode(self, node):
        next_node=node.next
        next_nextnode=next_node.next
        node.val=next_node.val
        node.next=next_nextnode

反转链表

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre=None
        # 不断取出和向后移动头节点,并将头节点连接到新头节点后面
        while head:
            next_node=head.next
            head.next=pre
            pre=head
            head=next_node
        return pre
# 判断一棵二叉树是否为另一棵二叉树的子树

```python
class Solution(object):
    def isSubtree(self, s, t):  #用于判断的主函数,递归得遍历s的每一个结点,并将其作为新的根节点,再与t进行比较
        if not s:
            return False
            
        return self.isEqual(s,t) or self.isSubtree(s.left,t) or self.isSubtree(s.right,t)
        #使用or相连,即其中只要有一个s的子树与t相同,则返回True
        
        
    def isEqual(self,S,T):  #以S为根节点,判断S和T是否相等
        if not S and not T:
            return True
        if S and T:
            if S.val!=T.val:
                return False
            return self.isEqual(S.left,T.left) and self.isEqual(S.right,T.right)
        else:
            return False

二叉搜索树中第 K 小的元素

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        stack=[]
        res=[]
        while root or stack:
            if root:
                stack.append(root)
                root=root.left
            else:
                node=stack.pop()
                res.append(node.val)
                if len(res)==k:
                    break
                root=node.right
        return res.pop()

二叉树的深度优先和广度优先

深度优先

深度优先遍历即是先按深度来遍历二叉树,包括:

前序遍历
遍历顺序 --> 根节点 -> 左子树 -> 右子树

中序遍历
遍历顺序–> 左子树 -> 根节点 -> 右子树

后序遍历
遍历顺序–> 左子树 -> 右子树 -> 根节点

# 定义一个树节点
class TreeNode:
    def __init__(self, value=None, left=None, right=None):
        self.value = value
        self.left = left     # 左子树
        self.right = right   # 右子树

# 实例化一个树节点
node1 = TreeNode("A",
                 TreeNode("B",
                          TreeNode("D"),
                          TreeNode("E")
                          ),
                 TreeNode("C",
                          TreeNode("F"),
                          TreeNode("G")
                          )
                 )


# 前序遍历
def preTraverse(root):
    if root is None:
        return
    print(root.value)
    preTraverse(root.left)
    preTraverse(root.right)


# 中序遍历
def midTraverse(root):
    if root is None:
        return
    midTraverse(root.left)
    print(root.value)
    midTraverse(root.right)


# 后序遍历
def afterTraverse(root):
    if root is None:
        return
    afterTraverse(root.right)
    afterTraverse(root.left)
    print(root.value)


if __name__ == "__main__":
    preTraverse(node1)
    print("------------------------")
    midTraverse(node1)
    print("------------------------")
    afterTraverse(node1)

广度优先

广度优先遍历即是层次遍历,按一层一层地遍历。

def levelOrder(root):
    # 如果根节点为空,则返回空列表
    if root is None:
        return res
    # 模拟一个队列储存节点
    q = []
    # 首先将根节点入队
    q.append(root)
    # 列表为空时,循环终止
    while len(q) != 0:
        length = len(q)
        for i in range(length):
            # 将同层节点依次出队
            r = q.pop(0)
            if r.left is not None:
                # 非空左孩子入队
                q.append(r.left)
            if r.right is not None:
                # 非空右孩子入队
                q.append(r.right)
            print(r.value)
if __name__ == "__main__":
    levelOrder(node1)

python—重建二叉树

"""
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
"""

# 前序  {1,2,4,7,3,5,6,8}
# 中序  {4,7,2,1,5,3,8,6}


class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None


class Solution:
    # 返回构造的TreeNode根节点
    def reConstructBinaryTree(self, pre, tin):
        # 递归终止条件
        if not pre and not tin:
            return
        # 树的根结点
        root = TreeNode(pre[0])
        # 根结点在中序遍历中的位置
        i = tin.index(pre[0])
        root.left = self.reConstructBinaryTree(pre[1:i+1], tin[:i])
        root.right = self.reConstructBinaryTree(pre[i+1:], tin[i+1:])
        return root

python将列表list转换成链表

class Node:
    def __init__(self, elem, next_=None):
        self.elem = elem
        self.next = next_
def list2link(List):
    head = Node(List[0])#创建一个头节点并将list第一个值赋值给头结点
    p = head#创建头指针
    for i in range(1, len(List)):#list从第二位开始遍历
        p.next = Node(List[i])#下一个结点p.next指向list值
        p = p.next#指针往下移动
    return head#返回头结点

if __name__ == "__main__":
    old_list = [1, 2, 3, 4, 5]
    link = list2link(old_list)
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kuokay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值