zoukankan      html  css  js  c++  java
  • 算法初识

    算法动画查看

    https://www.cnblogs.com/huang-yc/p/9774287.html

    或者

    https://visualgo.net/zh

    再讲递归

    递归的两个特点:1、调用自身;2、结束条件。

    # 下面那些函数是递归函数
    def func1(x):
        print(x)
        func1(x-1)
    
    def func2(x):
        if x > 0:
            print(x)
            func2(x+1)
    
    def func3(x):
        if x > 0:
            print(x)
            func3(x-1)
    
    def func4(x):
        if x>0:
            func4(x-1)
            print(x)
    
    #思考:func3和func4会打印什么效果?为什么?

    下图中:长条表示print打印,方框表示执行函数

    func3执行图示

    func4执行图示

    汉诺塔问题

    def hanoi(n, a, b, c):
        if n > 0:
            hanoi(n-1, a, c, b)
            print("moving from %s to %s" % (a, c))
            hanoi(n-1, b, a, c)

    查找

    # 线性查找
    def
    linear_search(li, val): for ind, v in enumerate(li): if v == val: return ind else: return None
    # 二分查找
    def binary_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: # 带查找的值在mid左侧 right = mid - 1 else: # li[mid] < val 带查找的值在mid右侧 left = mid + 1 else: return None

    LowB三人组

    一、冒泡

    """
    冒泡排序
    一共要走多少趟:
        列表长度-1趟(因为最后一个数就不用排了)
    无序区范围是多少:
    列表的长度-趟数-1 所以是两个循环 比较规律: 列表每相邻的数,如果前面比后面的大,则交换这两个数 一趟排序完成后,则无序区减少一个数,有序区增加一个数

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

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

    代码关键点:
    趟 和 无序区范围 两个循环 复杂度是O(n**2) 改进的地方 如果一趟没有发生位置的改变,就说明已经排好序
    """ def bubble_sort(li): for i in range(len(li) - 1): exchange = False 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-1趟
    无序区在是哪?
        第几趟到列表最后一个数
    怎么判断他是最小的数
        先指定无序区第一个就是最小的
        
    两个循环
        多少趟
        无序区
        
    一趟排序记录最小的数,放到第一个位置
    再一趟排序记录记录列表无序区最小的数,放到第二个位置
    。。。
    算法关键点:有序区和无序区,无序区最小数的位置
    O(n**2)
    """
    
    
    def select_sort(li):
        for i in range(len(li) - 1):  # 第i趟
            min_val = i  # 指定第一个为最小值
            for j in range(i + 1, len(li)):
                if li[j] < li[min_val]:
                    min_val = j
            li[i], li[min_val] = li[min_val], li[i]

    三、插入排序

    """
    初始:手里(有序区)只有一张牌
    每次(从无序区)摸一张牌,插入到手里的牌的正确位置
    
    算法复杂度O(n**2)
    """
    
    
    def insert_sort(li):
        for i in range(1, len(li)):  # 因为手上有一张牌,i表示摸到的牌的下标
            tmp = li[i]
            j = i - 1  # j指的是手里的牌的下标
            while j >= 0 and li[j] > tmp:  # 这个循环就是再找插入的位置
                li[j + 1] = li[j]
                j -= 1
            li[j + 1] = tmp

    NB三人组

    一、快排

    """
    要点;
        1/取一个元素P,使其归位
        2/列表分为两部分,左边都比P小,右边都比P大
        3/递归完成
    
    动画实例原理
        两个指针由两边向中间移动,分割成以P为边界的左右两边
            取出P的值存起来
            从右边找出一个比5小的数,放在P之前的位置
            从左边找出一个比5大的数,放在,右边空的位置
    
    partition代码是很对称的
    
    复杂度 O(nlogn)
    
    加装饰器,计算排序时间
        添加马甲,嵌套函数
    
    问题
        最坏情况:
          解决:随机找一个数,和第一个数交换位置
    
        递归
    """
    
    def partition_(li, left, right):
        # 先保存第一个数
        tmp = li[left]
        while left < right:
            while left < right and li[right] >= tmp:  # 先从右边开始的 left 小于 right 且right的值比tmp大
                right -= 1                             # 那就向左移动
            li[left] = li[right] # 直到指针碰到left,或者right的值比tmp小,就把right的值赋给left指针的位置
    
            while left < right and li[left] <= tmp:    # 然后是左边,找到比tmp小的值,放到右边指针处
                left += 1
            li[right] = li[left]
    
        li[left] = tmp   # 最后把tmp 放在 left和right交汇处
        return left   # 将这个中间值(即最终tmp所在位置的索引,并不是列表形式上的中间值)返回
    
    
    def quick_sort_(li, left, right):
        if left < right:
            mid = partition_(li, left, right)
            quick_sort_(li, left, mid - 1)
            quick_sort_(li, mid + 1, right)

    二、堆排序

    """
    堆排序前传-树与二叉树
        树是一种数据结构
        树是一种可以递归定义的数据结构
        树是由n个节点组成的集合:
            如果n=0,那这是一棵空树
            如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本省又是一棵树
    一些概念
        根节点,叶子节点(不能分叉的节点)
        树的深度(高度)
        树的度 (分了几个叉,树的度,就是分叉最多的那个度)
        孩子节点/父节点
        子树
    
    二叉树
        度不超过二的树
        每个节点最多有两个孩子
        两个孩子节点被区分为左孩子节点和右孩子节点
    满二叉树
        一个二叉树,如果每一层的节点数都达到最大值,则这个树就是满二叉树
    完全二叉树
        叶子节点只能出现在最下层和次下层,并且最下面的节点都集中在该层最左边的若干位置的二叉树
    二叉树的存储方式(表示方式)
        链式存储
        顺序存储(堆排序用)
            简单来说就是用列表来存
            父节点和左孩子节点的编号下标有什么关系
                i-->2i+1
            父节点和右孩子节点的编号下表有什么关系
                i-->2i+2
            孩子节点如何得到父节点
                i-->(i-1)//2
    堆--一种特殊的完全二叉树
        大根堆-一棵完全二叉树,满足任一节点都比孩子节点大
        小根堆-一棵完全二叉树,满足任一节点都比孩子节点小
    堆的向下调整
        假设:节点的左右子树都是堆,但自身不是堆
        当根节点的左右子树都是堆时,可以通过一次向下调整来将其变换成一个堆
    
    挨个出数
        排好序
    怎么构造一个堆
        农村包围城市,
        先看最后一个非叶子节点看起
        每次从小的调整,再调整大的
    
    时间复杂度
        nlogn
    
    堆排序-内置的模块
        import heapq
    topk问题
        现在有n个数,设计算法得到前K大的数
        解决思路:
            1/排序后切片   nlogn+k
            2/排序LowB三人组  kn
                维护k长度的列表,如果有新的值,就替换掉
            3/堆排序的思路   nlogk
                取列表前k个元素建立一个小根堆,堆顶就是目前第K大的数
                依次向后遍历原列表,对于列表中的元素,
                    如果小于堆顶,则忽略该元素
                    如果大于堆顶,则将堆顶更换为该元素,并对堆进行一次向下调整
                遍历列表所有元素后,倒序弹出堆顶
    
    """
    
    def sift(li, low, high, ):
        """
        向下调整函数
        :param li:
        :param low: 堆的根节点位置
        :param high: 堆的最后一个元素的位置,是为了保证向下调整的时候,不会超出堆
        :return:
        """
        i = low  # i指的的根节点
        j = 2 * i + 1  # j指的市左孩子
        tmp = li[low]  # 把堆顶存起来
        while j <= high:  # 只要j的位置有数,即没有越界
            if j + 1 <= high and li[j + 1] > li[j]:  # j+1 <= high 保证右孩子有,并且右孩子比较大
                j = j + 1  # 将j指向右孩子
            if li[j] > tmp:  # 如果孩子比堆顶大
                li[i] = li[j]  # 就交换孩子到堆顶
                i = j  # 往下调整一层,即跟新i和j的位置
                j = 2 * i + 1
            else:  # 表示tmp大,即本身的堆顶就是大的值,就不用更换了
                li[i] = tmp  # 把tmp放到某一级领导的位置
                break
        else:
            li[i] = tmp  # 循环结束,把tmp放到了叶子节点上
    
    
    # 使用sift 实现堆排序
    def heap_sort(li):
        """
        1/建堆,农村包围城市
        2/挨个出数,将堆顶的大数放到最后面high的位置,然后将high向前移一位
        :param li:
        :return:
        """
        # 最后一个非叶子节点的位置 最后一个元素的下标是n-1
        n = len(li)
        for i in range((n - 2) // 2, -1, -1):
            # i表示建堆的时候调整的部分的根的下标
            sift(li, i, n - 1)
            # 建堆完成了
        for i in range((n - 1), -1, -1):
            # i指向当前堆的最后一个元素
            li[0], li[i] = li[i], li[0]  # 将堆顶和最后一个数作交换
            sift(li, 0, i - 1)  # i-1 是新的high
    
    # 堆排序解决TopK问题
    def topK(li, k):
        heap = li[:k]
        for i in range((k - 2) // 2, -1, -1):
            sift(heap, i, k - 1)
        for i in range(k, len(li) - 1):
            if li[i] > heap[0]:
                heap[0] = li[i]
                sift(heap, 0, k - 1)
        for i in range(k-1,-1,-1):
            heap[0], heap[i] = heap[i], heap[0]
            sift(li, 0, i-1)
        return heap

    三、归并排序

    """
    假设现在的列表分两段有序,如何让将其合成为一个有序列表
        2 5 7 8 9   1 3 4 6
        两段都指定一个从左开始的指针,并对比大小,小的放下来
    
    这种操作称为一次归并
    
    """
    
    
    # 先假设两段已经有序,
    def merge(li, low, mid, high):
        """
        :param li:
        :param low:第一段的最左边
        :param mid:第一段的最右边
        :param high:第二段的最右边,即列表的末尾
        :return:
        """
        i = low
        j = mid + 1
        new_li = []
        while i <= mid and j <= high:  # 只要左右两边都有数
            if li[i] < li[j]:
                new_li.append(li[i])
                i += 1
            else:
                new_li.append(li[j])
                j += 1
        # while 执行完毕,肯定有一部分没数了
        while i <= mid:
            new_li.append(li[i])
            i += 1
        while j <= high:
            new_li.append(li[j])
            j += 1
        # 把有序的列表写回去
        li[low:high + 1] = new_li
    
    
    """
    归并排序的实现
        1/分解:把列表越分越小,直至分成一个元素
        2/终止条件:一个元素是有序的
        3/合并:将两个有序列表归并,列表越来越大
        
    """
    
    
    # 基于递归分解
    def merge_sort(li, low, high):
        if low < high:  # 至少有两个元素,递归
            mid = (low + high) // 2
            merge_sort(li, low, mid)
            merge_sort(li, mid + 1, high)
            merge(li, low, mid, high)
    """
    时间复杂度 nlogn
    空间复杂度 n 因为和之前的不一样,之前的都是原地排序
    """
  • 相关阅读:
    Python
    python参数传递方式
    python可变类型和不可变类型
    进程、线程和协程的理解
    cookie原理
    http报文格式
    Charles的HTTPS抓包方法及原理分析
    fiddler抓包https请求
    python正则表达式
    java的Junit单元测试
  • 原文地址:https://www.cnblogs.com/yxiaodao/p/10489768.html
Copyright © 2011-2022 走看看