首页 > 编程语言 >【算法设计与分析】期末考试复习 - 基础知识(基础知识超详细)

【算法设计与分析】期末考试复习 - 基础知识(基础知识超详细)

时间:2024-07-19 10:28:44浏览次数:12  
标签:复习 复杂度 元素 期末考试 基础知识 算法 情况 排序 log

文章目录

前言

本系列课程总共详细讲解基础知识、分治、动态规划、贪心、回溯

本文为讲解基础知识

引言问题

问题描述

m m m万元钱,投资 n n n个项目,效益函数 f i ( x ) f_i(x) fi​(x)表示第 i i i个项目投 x x x元的效益, i = 1 , 2 , … , n i = 1,2, \ldots , n i=1,2,…,n。求如何分配每个项目的钱使得总效益最大?

实例

  • 投资金额:5万元
  • 投资项目数:4个项目

效益函数如下表所示:

x x x f 1 ( x ) f_1(x) f1​(x) f 2 ( x ) f_2(x) f2​(x) f 3 ( x ) f_3(x) f3​(x) f 4 ( x ) f_4(x) f4​(x)
00000
1110220
21251021
313103022
414153223
515204024

目标

找出分配给每个项目的钱,使得总效益最大。

数学表达

目标函数: max ⁡ ∑ i = 1 n f i ( x i ) \max \sum_{i=1}^{n} f_i(x_i) max∑i=1n​fi​(xi​)

约束条件:

  1. ∑ i = 1 n x i ≤ m \sum_{i=1}^{n} x_i \leq m ∑i=1n​xi​≤m
  2. x i ≥ 0 x_i \geq 0 xi​≥0

其中, x i x_i xi​表示分配给第 i i i个项目的资金。

思想:蛮力算法是一种通过穷举所有可能的方案,找到最佳解的方法。

对于这个问题,我们需要穷举所有可能的资金分配方式,然后计算每种分配方式下的总效益,选择总效益最大的分配方式。具体步骤如下:
在这里插入图片描述

步骤

  1. 枚举所有可能的资金分配方案:

    • 假设我们有 m m m万元钱和 n n n个项目,则我们需要枚举每个项目可以分配的资金 x i x_i xi​,满足约束条件 ∑ i = 1 n x i ≤ m \sum_{i=1}^{n} x_i \leq m ∑i=1n​xi​≤m。
    • 每个项目的资金可以从0到 m m m的整数值。
  2. 计算每种分配方案的总效益:

    • 对于每种分配方案 { x 1 , x 2 , … , x n } \{x_1, x_2, \ldots, x_n\} {x1​,x2​,…,xn​},计算其总效益 ∑ i = 1 n f i ( x i ) \sum_{i=1}^{n} f_i(x_i) ∑i=1n​fi​(xi​)。
  3. 选择总效益最大的分配方案:

    • 在所有枚举的分配方案中,选择总效益最大的那一个。

示例

以实例中的数据为例( m = 5 m = 5 m=5万元, n = 4 n = 4 n=4个项目),我们将枚举每个项目从0到5的资金分配方式。

伪代码
def brute_force_max_profit(m, n, profit_functions):
    max_profit = 0
    best_allocation = None
    
    # 枚举所有可能的分配方式(不优雅的写法)
    for x1 in range(m + 1):
        for x2 in range(m + 1):
            for x3 in range(m + 1):
                for x4 in range(m + 1):
                    if x1 + x2 + x3 + x4 <= m:  # 确保总投资不超过m万元
                        total_profit = profit_functions[0][x1] + profit_functions[1][x2] + profit_functions[2][x3] + profit_functions[3][x4]
                        if total_profit > max_profit:
                            max_profit = total_profit
                            best_allocation = (x1, x2, x3, x4)
    
    return max_profit, best_allocation


profit_functions = [
    [0, 11, 12, 13, 14, 15],  # f1(x)
    [0, 0, 5, 10, 15, 20],    # f2(x)
    [0, 2, 10, 30, 32, 40],   # f3(x)
    [0, 20, 21, 22, 23, 24]   # f4(x)
]

m = 5
n = 4

max_profit, best_allocation = brute_force_max_profit(m, n, profit_functions)
print(f"最大总效益: {max_profit}, 最佳分配方式: {best_allocation}")

这代码不够优雅

解释
  • 我们有四个嵌套的循环,每个循环从0到 m m m(即5)。
  • 通过嵌套循环枚举所有可能的分配方式,并检查总投资是否不超过 m m m万元。
  • 计算每种分配方式下的总效益,并记录最大效益及对应的分配方式。

显然这种思路比较好想,但是时间复杂度比较高,大规模不适用

拉莫有没有更好的方法呢,首先我们看一个刚才这里提到的概念 - 问题的复杂度

1. 问题的复杂度

算法类别算法最好时间复杂度最坏时间复杂度最好空间复杂度最坏空间复杂度
排序算法冒泡排序O(n)O(n^2)O(1)O(1)
排序算法选择排序O(n^2)O(n^2)O(1)O(1)
排序算法插入排序O(n)O(n^2)O(1)O(1)
排序算法归并排序O(n log n)O(n log n)O(n)O(n)
排序算法快速排序O(n log n)O(n^2)O(log n)O(log n)
排序算法堆排序O(n log n)O(n log n)O(1)O(1)
排序算法桶排序O(n)O(n^2)O(n)O(n)
排序算法计数排序O(n + k)O(n + k)O(k)O(k)
排序算法基数排序O(nk)O(nk)O(n + k)O(n + k)
搜索算法二分查找O(1)O(log n)O(1)O(1)
动态规划斐波那契数O(1)O(n)O(1)O(n)
图算法DijkstraO(E + V log V)O(E + V log V)O(V)O(V)
图算法Floyd-WarshallO(V^3)O(V^3)O(V^2)O(V^2)
图算法PrimO(E log V)O(E log V)O(V)O(V)
图算法KruskalO(E log E)O(E log E)O(V)O(V)
图算法Bellman-FordO(EV)O(EV)O(V)O(V)
查找算法哈希查找O(1)O(n)O(n)O(n)
字符串算法KMPO(m + n)O(m + n)O(m)O(m)
字符串算法Rabin-KarpO(m + n)O(mn)O(1)O(1)

注:n表示输入数据的规模,k表示数据范围,m表示模式串的长度,E表示边的数量,V表示顶点的数量。

首先来理解各个排序算法

冒泡排序笔记

核心原理:
冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表,比较相邻的两个元素,如果它们的顺序错误就交换它们的位置。这个过程重复进行,直到没有需要交换的元素为止。其名字来源于算法中较大的元素会逐渐“冒泡”到列表的顶端。

实际上也有点这样的意思,就是选定当前元素为某点,然后比较下一个元素和当前元素的大小。如果下一个元素比当前元素大的话。那么就把当前的某点放到这个比当前元素大的这个元素,那么某点到了下一个元素,然后继续比较。这是第1种情况,然后第2种情况的话就是,如果下一个元素比当前元素小的话,就把这两个替换。
在这里插入图片描述
总之每次都是使得较为大的元素向后

时间复杂度:

  • 最好情况: O(n) (扫描一遍过去即可)

    • 最好情况下,列表已经是有序的。在这种情况下,冒泡排序只需要进行一次遍历来确认列表已经有序,不需要进行任何交换。因此,时间复杂度为O(n)。
  • 平均情况: O(n^2)

    • 平均情况下,列表中的元素是随机排列的。冒泡排序需要进行n次遍历,每次遍历需要进行(n-1)次比较。平均情况下,每次比较可能需要进行交换,所以时间复杂度为O(n^2)。(这里实际上用趟来解释更好,每次操作一个元素冒泡,一个元素一趟,每趟需要比较大概n次,因为有N个元素,所以需要n趟)在这里插入图片描述
  • 最坏情况: O(n^2)

    • 最坏情况下,列表是逆序的。在这种情况下,冒泡排序需要进行n次遍历,每次遍历需要进行(n-1)次比较和交换。因此,时间复杂度为O(n^2)。

空间复杂度:
冒泡排序是一种原地排序算法,只需要常数级别的额外空间。除了用于交换的临时变量外,不需要额外的存储空间。因此,空间复杂度为O(1)。


选择排序笔记

核心原理:
选择排序是一种简单直观的排序算法。其基本思想是:首先从待排序的列表中找到最小(最大)元素,放到序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(最大)元素,放到已排序序列(注意是已经排序的序列)的末尾。如此循环,直到所有元素均排序完毕。
在这里插入图片描述
小的放到前面就是
在这里插入图片描述
这里注意,放到有序去里面采用的方式是无序区里面的(假设找小放前面为例子)最靠近有序的那个元素与当前选定的最小的元素交换

在这里插入图片描述
在这里插入图片描述

时间复杂度:

  • 最好情况: O(n^2)

    • 在选择排序中,不论初始数组是否有序,每一轮都要扫描未排序的部分来找到最小(或最大)元素。因此,每一轮都需要进行n-i次比较(i是当前轮次)。即使数组已经有序,仍然需要进行这些比较。因此,最好情况下时间复杂度仍然是O(n^2)。
  • 平均情况: O(n^2)

    • 平均情况下,选择排序的操作步骤和最好情况完全相同,每轮都需要进行n-i次比较。因此,平均情况下时间复杂度为O(n^2)。
  • 最坏情况: O(n^2)

    • 在最坏情况下,选择排序同样需要进行固定次数的比较操作。因此,不论数组初始状态如何,选择排序每一轮都需要进行n-i次比较。因此,最坏情况下时间复杂度为O(n^2)。

空间复杂度:
选择排序是一种原地排序算法,只需要常数级别的额外空间。除了用于交换的临时变量外,不需要额外的存储空间。因此,空间复杂度为O(1)。


插入排序笔记

核心原理:
插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

时间复杂度:

  • 最好情况: O(n)

    • 最好情况下,列表已经是有序的。在这种情况下,每次插入操作只需要比较一次就可以确定元素的位置,不需要进行移动。因此,时间复杂度为O(n)。(就是每次都确定当前新拿到的元素就是已经放置好的位置,然后一遍扫描过去)
  • 平均情况: O(n^2)

    • 平均情况下,列表中的元素是随机排列的。插入排序需要对每个元素进行插入操作,插入操作平均需要比较和移动n/2次。由于有n个元素,因此时间复杂度为O(n^2)。
      在这里插入图片描述
  • 最坏情况: O(n^2)

    • 最坏情况下,列表是逆序的。在这种情况下,每次插入操作都需要比较和移动所有之前的元素。插入排序需要进行n次插入操作,每次插入操作需要比较和移动n-1次。因此,时间复杂度为O(n^2)。

空间复杂度:
插入排序是一种原地排序算法,只需要常数级别的额外空间。除了用于交换的临时变量外,不需要额外的存储空间。因此,空间复杂度为O(1)。


上面的都是时间复杂度比较大的,下面看几个优化时间复杂度的


归并排序笔记

核心原理:
归并排序是一种分治算法。其基本思想是将数组分成两半,分别对每一半进行排序,然后将两部分合并成一个有序数组。这一过程递归进行,直到每部分的规模为1(此时视为已排序),然后逐层向上合并,最终形成一个完整的有序数组。
在这里插入图片描述
在这里插入图片描述

时间复杂度:

  • 最好情况: O(n log n)

    • 在最好情况下,归并排序的每一步都需要进行对半拆分和合并操作。拆分的复杂度为O(log n),每一层的合并操作复杂度为O(n),因此总的时间复杂度为O(n log n)。
  • 平均情况: O(n log n)

    • 平均情况下,归并排序的操作步骤和最好情况完全相同,不受输入数据的初始排列状态影响。每一层都需要进行O(n)的合并操作,共有O(log n)层,因此时间复杂度为O(n log n)。
  • 最坏情况: O(n log n)

    • 在最坏情况下,归并排序仍然需要进行相同次数的拆分和合并操作,时间复杂度为O(n log n)。输入数据的初始状态不会影响归并排序的基本操作步骤。

空间复杂度:
归并排序不是原地排序算法,因为它需要额外的存储空间来保存拆分后的子数组和合并时的临时数组。空间复杂度为O(n),其中n是数组的大小。这是因为在合并过程中需要额外的O(n)空间来临时存储排序结果。


快速排序笔记

核心原理:
快速排序是一种分治算法,通过选择一个基准元素(通常是数组中的第一个元素),将数组分成两部分:小于基准元素的部分和大于基准元素的部分。然后递归地对这两部分进行排序,直到整个数组有序。

时间复杂度:

树的高度 logn 然后每一层由于两个指针相当于每个元素检查了一遍,n
所以最终nlogn

  • 最好情况: O(n log n)

    • 在最好情况下,每次划分都能平均地把数组分成大小大致相等的两部分。因此,快速排序的递归深度为O(log n),每层需要O(n)的时间来进行划分操作。因此,总的时间复杂度为O(n log n)。
  • 平均情况: O(n log n)

    • 平均情况下,快速排序能够保持每次划分都能将数组分成大小相近的两部分,递归深度为O(log n),每层需要O(n)的时间来进行划分操作。因此,平均时间复杂度为O(n log n)。
  • 最坏情况: O(n^2)

    • 在最坏情况下,每次划分只能将数组分成大小为n-1和0的两部分。这种情况通常发生在数组已经有序或者逆序的情况下。此时快速排序的递归深度为O(n),每层需要O(n)的时间来进行划分操作。因此,最坏情况下时间复杂度为O( n 2 n^2 n2)。
      (n*n)

空间复杂度:
快速排序通常是原地排序,只需要常数级别的额外空间用于递归调用时的栈空间。因此,空间复杂度为O(log n)。

在这里插入图片描述
注意一种特殊情况(每次都是这种也就是最坏情况,树结构直接退化为链表)
在这里插入图片描述
另外这个最小的叶子终止条件是,序列长度为0或者1,这种情况和归并一样的,一个元素自然有序,随后返回向上(向父节点)

举个例子
在这里插入图片描述


一些问题

哪个排序算法效率最高?

1. 常见排序算法及其效率:

  • 快速排序 (Quicksort):

    • 平均时间复杂度:(O(n \log n))
    • 最坏时间复杂度:(O(n^2))(可以通过随机化枢轴选择降低)
    • 空间复杂度:(O(\log n))
    • 通常是实际应用中最快的比较排序算法之一。
  • 归并排序 (Mergesort):

    • 时间复杂度:(O(n \log n))
    • 空间复杂度:(O(n))
    • 稳定排序,适用于大数据集。
  • 堆排序 (Heapsort):

    • 时间复杂度:(O(n \log n))
    • 空间复杂度:(O(1))
    • 不稳定排序,但在内存有限时表现出色。
  • 计数排序 (Counting Sort):

    • 时间复杂度:(O(n + k)),其中 (k) 是数据范围
    • 空间复杂度:(O(k))
    • 适用于数据范围较小的整数排序,且是稳定排序。
  • 桶排序 (Bucket Sort):

    • 时间复杂度:平均为 (O(n + k)),其中 (k) 是桶的数量
    • 空间复杂度:(O(n + k))
    • 在输入数据均匀分布时表现优异。
  • 基数排序 (Radix Sort):

    • 时间复杂度:(O(d(n + k))),其中 (d) 是数字的位数,(k) 是基数
    • 空间复杂度:(O(n + k))
    • 适用于整数或字符串的排序,且是稳定排序。

是否可以找到更好的排序算法?

比较排序算法的下界:
对于基于比较的排序算法,其最坏情况下的时间复杂度下界是 (O(n \log n))。这是通过比较树的高度得出的,因为对 (n) 个元素的排序可以看作是二叉决策树,每个内部节点是一次比较操作。

非比较排序算法:
在某些特定情况下,可以找到时间复杂度低于 (O(n \log n)) 的排序算法,例如:

  • 计数排序、桶排序和基数排序在特定情况下可以达到线性时间复杂度 (O(n))。

排序问题计算难度如何?

排序问题是计算机科学中的一个经典问题,通常被认为是“简单”的问题,因为有很多有效的算法来解决它。排序问题属于 P 类问题(可以在多项式时间内解决的问题)。

其他排序算法的复杂度

以下是一些其他常见排序算法及其复杂度:

  • 插入排序 (Insertion Sort):

    • 时间复杂度:(O(n^2))
    • 最好情况下:(O(n))(对于几乎有序的数组)
    • 空间复杂度:(O(1))
    • 稳定排序,适用于小规模数据集或几乎有序的数组。
  • 选择排序 (Selection Sort):

    • 时间复杂度:(O(n^2))
    • 空间复杂度:(O(1))
    • 不稳定排序,但实现简单。
  • 冒泡排序 (Bubble Sort):

    • 时间复杂度:(O(n^2))
    • 最好情况下:(O(n))(对于几乎有序的数组)
    • 空间复杂度:(O(1))
    • 稳定排序,但效率较低。

问题计算复杂度估计方法

计算复杂度估计方法主要有以下几种:

  1. 渐近分析 (Asymptotic Analysis):

    • 主要使用大 O 符号来表示时间和空间复杂度,描述输入规模趋向无穷时算法的增长速度。
  2. 最坏情况分析 (Worst-case Analysis):

    • 评估算法在最不利情况下的性能。
  3. 平均情况分析 (Average-case Analysis):

    • 评估算法在所有可能输入的平均性能。
  4. 最优情况分析 (Best-case Analysis):

    • 评估算法在最有利情况下的性能。
  5. 摊销分析 (Amortized Analysis):

    • 用于分析一系列操作的平均时间复杂度,特别是在某些操作很快而其他操作很慢的情况下。例如,动态数组扩展的摊销分析。

2. 计算复杂性理论

这里由于CSDN一篇文章字数有限制,这几个留坑的问题后续应该会出文章详细讲解,后续可以到我的主页搜索这些标题继续学习

货郎问题(留坑待解)

01背包问题(留坑待解)

双机调度问题(留坑待解)

NP-Hard(留坑待解)

一个好的算法,不仅要提高求解问题的效率,还要节省存储空间。
我们分析的时候,从时间复杂度和空间复杂度进行。

要学会设计算法,分析算法、对于问题的复杂度分析


3.算法以及时间复杂度

平均情况下的时间复杂度A(n)
最坏情况下的时间复杂度 W(n)

实例

标签:复习,复杂度,元素,期末考试,基础知识,算法,情况,排序,log
From: https://blog.csdn.net/weixin_73002968/article/details/140069958

相关文章

  • java八股复习指南-计网篇
    网络分层模型osi七层模型tcp-ip四层模型应用层传输层网络层网络接口层与osi七层模型对应为:应用层主要提供两个终端设备上应用之间的消息交换的服务。它定义了消息交换的格式。常见协议有:结合常见的协议,可以这样理解应用层:应用层就是专门为特定的应用之间的通信提......
  • java基础知识(3)—关键字
    在Java编程的广阔领域中,关键字宛如一把把精确的工具,赋予开发者准确表达意图和实现复杂逻辑的能力。访问控制关键字:private:确保变量、方法或内部类仅在所属的类内部可访问,为数据提供了最高级别的隐私保护。protected:在继承关系中,允许子类和同一包中的类访问特定的成员。pu......
  • 代码随想录二刷复习(二分法)
    二分法模板:1:左闭右闭区间写法第一种写法,我们定义target是在一个在左闭右闭的区间里,也就是[left,right](这个很重要非常重要)。区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left,right]区间,所以有如下两点:while(left<=right)要使用<=,因为left==rig......
  • JS基础知识总结(3)
    一、让我们的字符串反转:思路就是我们先把它分割成一个个数组,然后我们使用数组的反转,然后我们再把这个数组连接再一起。合起来,样子就是这样的:document.getElementById("result").innerHTML=end_Num.split("").reverse().join("");二、整除有两种方法,一个是parseInt(),还有一......
  • 从零开始学Python第一天:基础知识
    前言在这个信息爆炸的时代,编程技能已经成为我们生活和工作中不可或缺的一部分。而Python,作为一门简洁易读、功能强大的编程语言,正逐渐受到越来越多人的青睐。作为初学者,你可能会对编程充满好奇与期待,同时也有一些担忧和困惑。但是请相信,只要你愿意付出努力和时间,Python的......
  • 基础知识(JAVA入门)
    常用的CMD命令盘符名称+冒号说明:盘符切换dir说明:查看当前路径下的内容cd说明:进入单级目录cd..说明:回退到上一级目录cd目录1\目录2....说明:进入多级目录cls说明:清屏exit说明:退出命令提示符窗口环境变量我们想要在任意的目录下都可以打开指定的软件。就可以把软件的路......
  • 代码随想录算法训练营第14天 | 复习二叉树翻转
    2024年7月17日递归法翻转二叉树易错:要考虑节点为空的情况,以及写好边界条件。/***Definitionforabinarytreenode.*publicclassTreeNode{*intval;*TreeNodeleft;*TreeNoderight;*TreeNode(){}*TreeNode(intval){this.va......
  • java八股复习指南-多线程篇
    多线程线程的实现在Java中,实现多线程的主要有以下四种继承Thread类,重写run()方法;实现Runnable接口,实现run()方法,并将Runnable实现类的实例作为Thread构造函数的参数target;实现Callable接口,实现call()方法,然后通过FutureTask包装器来创建Thread线程;......
  • c++零基础知识要点整理(2)
    基本数据类型1.整数类型(1)short(短整型):占2个字节:00;         取值范围:-2^15~2^15-1(2)int(基本整数型) :占4个字节:0000;       取值范围:-2^31~2^31-1(3)long(长整型):占4个字节:0000;          取值范围:-2^31~2^31-1(4)long......
  • java八股复习指南
    spring全家桶理解Spring框架核心:ioc和aop1.ioc:控制反转是指把对象的创建和配置的控制权从调用方转移给spring容器,我们可以将对象交给容器管理,即bean,这样不需要自己去new对象,只需要获取bean就可以使用。好比在家自己做菜,菜的味道全部由自己控制;去餐馆吃饭,菜的味道则是交由餐馆......