zoukankan      html  css  js  c++  java
  • 数据结构与算法

    第一部分 算法简单概念

      算法概念

      复习:递归

      时间复杂度

      空间复杂度

    什么是算法?

      算法(Algrithm):一个计算过程,解决问题的方法

     复习:递归

    递归的两个特点:

        (1)、调用自身

         (2)、结束条件

    简单的几个函数:

    def func1(x):
        print(x)
        func1(x-1)
    
    def func2(x):
        if x>0:
            print(x)
            print(x+1)
    
    def func3(x):
        if x>0:
            print(x)
            func3(x-1)
    
    def func4(x):
           if x>0:
                func4(x-1)
                print(x)

    时间复杂度

      如下代码:判断其时间复杂度

    print("hello World")
    
    
    for i in range(n):
        print("hello world")
    
    for i in range(n):
        for j in range(n):
            print("hello world")
    
    for i in range(n):
        for j in rnage(n):
            for k in range(n):
                print("Hello World")

    以上四组代码,那组运行时间最短?

    用什么方式来体现代码(算法)运行的快慢?

    时间复杂度:用来评估算法运行效率的一个介质。

    上述的时间复杂度分别如下:一般肉眼观察,循环了几次,print了几次。

    再看以下代码例子:

     

     类比生活中的一些时间例子,估计时间:不是一个确切的数而是个约数

    继续推算时间复杂度的其他表示方法?

     综上,总结一下时间复杂度

    """
    
    时间复杂度是用来估计算法运行时间的一个式子(单位)。
    一般来说,时间复杂度高的算法比复杂度低的算法慢。
    常见的时间复杂度(按效率排序)
    O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
    不常见的时间复杂度(看看就好)
    O(n!) O(2n) O(nn) …
    如何一眼判断时间复杂度?
    循环减半的过程O(logn)
    几次循环就是n的几次方的复杂度
    
    """

    补充:为什么python中的b+树,二叉树查询速度灰快些?

      从时间复杂度的角度理解,因为它的时间复杂度仅次于O(1)

    数据库添加B-树索引,查询就快。那么为啥添加索引就快?

    B-树索引的内部结构如下:

    理解下图就明白了


    B-树索引有两种类型的块: 用于查找的分支块(Branch Blocks)和用于存储值的叶块(Leaf Blocks)。
    B-树索引的上层分支块包含指向下层索引块的索引数据。从结构上来说就像树一样,分支块就是树干树枝,而叶子就是叶块。
    但是和树有区别的是B-树索引是平衡的,所有叶块都自动处于相同的深度
    因此,在索引中从任意位置检索任意记录需要的时间基本上是相同的。可以理解为,如果走索引从中查询一条数据,那么我查找1W条数据和查询100w条数据的速度是一样的。
    最底层的叶块包含每个被索引的数据值,和一个相应的用来定位实际行的rowid。根据分支块,找到被索引的数据值,再找到对应的rowid,再从rowid取出对应的数据,就起到了快速查找数据的目的。

    空间复杂度 

    空间复杂度:用来评估算法内存占用大小的一个式子。
    “空间换时间”,更多的是涉及硬件性能有关,如内存,硬盘,cpu等。

    常见的算法

      排序算法是《数据结构与算法》中最基本的算法之一。

    排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:

    # 1.0 十大经典排序算法
    # 1.1 冒泡排序
    # 1.2 选择排序
    # 1.3 插入排序
    # 1.4 希尔排序
    # 1.5 归并排序
    # 1.6 快速排序
    # 1.7 堆排序
    # 1.8 计数排序
    # 1.9 桶排序
    # 1.10 基数排序

    关于时间复杂度

     平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。

    线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;

    O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序

    线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

    关于稳定性

    稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。

    不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

    名词解释:

    n:数据规模
    k:""的个数
    In-place:占用常数内存,不占用额外内存
    Out-place:占用额外内存
    稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同

    1、列表查找

    列表查找:从列表中查找指定元素
    输入:列表、待查找元素
    输出:元素下标或未查找到元素
    
    顺序查找
    从列表第一个元素开始,顺序进行搜索,直到找到为止。
    二分查找
    从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。

    列表查找

     递归版的二分法查找

     

     2、冒泡排序思路

      首先,列表每两个相邻的数,如果前边的比后边的大,那么交换这两个数……
    会发生什么?

    1. 算法步骤

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

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

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

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

    2. 动图演示

    python代码实现

    #### 冒泡排序  (************************)
    ### 时间复杂度:O(n^2)
    def Bubble_sort(li):
        for i in range(len(li)-1): # 第一趟是循环遍历
            for j in range(len(li)-1-i): # 内层循环是两两之间进行比较,取出较大的数
                if li[j] > li[j+1]:
                    li[j], li[j+1] = li[j+1], li[j]
    li=[1,2,3,5,8,4,9,10,12]
    Bubble_sort(li)
    print(li)

    3、选择排序思路 

    一趟遍历记录最小的数,放到第一个位置;
    再一趟遍历记录剩余列表中最小的数,继续放置;
    ……
    
    
    问题是:怎么选出最小的数?
    
    
    代码关键点:
    无序区
    最小数的位置

      选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。

    唯一的好处可能就是不占用额外的内存空间了吧。

    1. 算法步骤

    首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

    再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

    重复第二步,直到所有元素均排序完毕。

    2. 动图演示

    代码实现:

    #### 选择排序
    #### 时间复杂度:O(n^2)
    def select_sort(li):
        for i in range(len(li)):
            minLoc = i ###i = 0
            for j in range(i+1, len(li)):
                if li[j] < li[minLoc]:
                    li[j], li[minLoc] = li[minLoc], li[j],10
    li=[2,5,4,6,8,9,14,7]
    select_sort(li)
    print(li)

    4、插入排序思路

    列表被分为有序区和无序区两个部分。最初有序区只有一个元素。
    每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。

    插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。

    插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

    插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

    1. 算法步骤

    将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

    从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

    2. 动图演示

    代码实现:

    ##### 插入排序
    #### 时间复杂度: O(n^2)
    def insert_sort(li):
        for i in range(1, len(li)):
            tmp = li[i]
            j = i - 1
            while j >=0 and li[j] > tmp:
                li[j+1] = li[j]
                j = j - 1
            li[j+1] = tmp
    
    li = [1,2,5,7,12,23,15,56,21,45]
    insert_sort(li)
    print(li)

    5、 快速排序思路

    快速排序:快
    好写的排序算法里最快的
    快的排序算法里最好写的

    1. 算法步骤

    1、从数列中挑出一个元素,称为 "基准"(pivot);
    
    2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    
    3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

     2. 动图演示

     代码实现

    def partition(li, left, right):
        tmp = li[left]
        while left < right:
            while left < right and li[right] >= tmp:
                right = right - 1
            li[left] = li[right]
            while left < right and li[left] <= tmp:
                left = left + 1
            li[right] = li[left]
        li[left] = tmp
    
        return left
    
    #### 快速排序
    #### 时间复杂度:O(nlogn) 
    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)

     6、计数排序

     算法的步骤如下:

    (1)找出待排序的数组中最大和最小的元素
    (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
    (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
    (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

    2. 动图演示

    代码的实现:

    # 创建一个列表,用来统计每个数出现的次数
    
    #现在有一个列表,列表中的数范围都在0到100之间,
    # 列表长度大约为100万。设计算法在O(n)时间复杂度内将列表进行排序。
    
    def count_sort(li,max_num):
        count = [0 for i in range(max_num+1)]
        for num in li:
            count[num] += 1
        i = 0
        for num,m in enumerate(count):
            for j in range(m):
                li[i] = num
                i += 1
    li = [1,2,5,2,0,1,11,54,212,154,1,24,12,5,6,5,2,8,9,20,12,23,45,16]
    count_sort(li,1000)
    print(li)

     线性结构

      把所有的节点用一根线串起来

    数组和链表的区别

      数组需要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。 而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题。

    离散存储(链表)

    推荐的学习书籍

     

      

     

      

  • 相关阅读:
    二分图 洛谷P2055 [ZJOI2009]假期的宿舍
    并查集 洛谷P1640 [SCOI2010]连续攻击游戏
    贪心 洛谷P2870 Best Cow Line, Gold
    贪心 NOIP2013 积木大赛
    快速幂 NOIP2013 转圈游戏
    倍增LCA NOIP2013 货车运输
    树形DP 洛谷P2014 选课
    KMP UVA1328 Period
    动态规划入门 BZOJ 1270 雷涛的小猫
    KMP POJ 2752Seek the Name, Seek the Fame
  • 原文地址:https://www.cnblogs.com/Gaimo/p/12093452.html
Copyright © 2011-2022 走看看