2024-2025-1 20241328 《计算机基础与程序设计》第十周学习总结
作业信息
作业课程 | 2024-2025-1-计算机基础与程序设计 |
---|---|
作业要求 | 2024-2025-1计算机基础与程序设计第一周作业 |
作业目标 | 信息系统,数据库与SQL,人工智能与专家系统,人工神经网络,模拟与离散事件,排队系统,天气与地震模型,图形图像 |
教材学习内容总结
第12章:算法
12.1 算法概述
- 算法是解决问题的一组明确的指令或步骤。
- 特点:
- 有穷性:必须在有限步骤内完成。
- 确定性:每一步都有明确的操作。
- 输入:接受某些输入。
- 输出:产生某些输出。
- 可行性:每一步必须是可执行的。
12.2 算法效率
- 时间复杂度:衡量算法运行时间相对于输入规模的增长速度。
- 常见时间复杂度:O(1)、O(log n)、O(n)、O(n^2)、O(n log n)等。
- 空间复杂度:衡量算法使用内存空间的增长速度。
12.3 排序算法
- 冒泡排序:通过交换相邻元素实现排序。
- 选择排序:每次选择最小(大)元素放在排序的位置。
- 插入排序:逐个插入元素并维护一个已排序的序列。
- 归并排序:分治算法,将数据分为两部分排序后合并。
- 快速排序:分治算法,选择一个“基准”进行分区。
12.4 查找算法
- 线性查找:逐个比较元素,找到目标。
- 二分查找:通过每次将查找区间折半来提高效率,要求数据已排序。
第13章:计算机硬件
13.1 计算机硬件组成
- 输入设备:键盘、鼠标、扫描仪等。
- 输出设备:显示器、打印机等。
- 中央处理器(CPU):计算机的大脑,负责执行指令。
- 存储器:
- 主存储器:RAM、ROM。
- 辅助存储器:硬盘、固态硬盘等。
13.2 CPU和存储器
- CPU的组成:
- 运算器:执行算术运算。
- 控制器:控制各个部件的协同工作。
- 寄存器:存储临时数据。
- 存储器层次结构:
- 寄存器:最快但容量小。
- 缓存(Cache):中等速度和容量。
- 主存(RAM):大容量,速度较慢。
- 辅助存储(硬盘):大容量,速度最慢。
13.3 输入输出系统
- I/O设备:包括键盘、鼠标、显示器、打印机等。
- I/O接口:通过中断或轮询机制与CPU进行通信。
第14章:操作系统
14.1 操作系统概述
- 操作系统(OS)是管理计算机硬件和软件资源的程序集合。
- 功能:
- 进程管理:负责管理进程的创建、调度、终止。
- 内存管理:分配和回收内存。
- 文件管理:提供文件的存储、访问和管理。
- 设备管理:管理硬件设备的操作。
- 用户界面:提供与用户交互的界面。
14.2 进程管理
- 进程:正在执行的程序。
- 线程:轻量级进程,是程序执行的基本单位。
- 调度算法:如先来先服务(FCFS)、最短作业优先(SJF)等。
14.3 内存管理
- 内存分配:静态分配和动态分配。
- 分页系统:将内存分成固定大小的页面进行管理。
- 虚拟内存:通过硬盘上的交换空间扩展物理内存。
14.4 文件管理
- 文件系统:组织和管理文件的一种方法。
- 目录结构:用于组织文件的层次结构。
- 权限管理:控制文件的访问权限。
第9章:结构化程序设计
9.1 程序设计概述
- 结构化程序设计:强调将程序分为若干模块,每个模块完成一个具体功能。
- 三大基本控制结构:
- 顺序结构:程序按顺序执行。
- 选择结构:通过条件判断选择不同的执行路径。
- 循环结构:通过循环语句反复执行代码块。
9.2 C语言的基本语法
- 变量和数据类型:包括整数、浮点数、字符等。
- 运算符:算术运算符、关系运算符、逻辑运算符等。
- 控制语句:
- if语句:用于条件判断。
- for、while和do-while循环:用于重复执行代码块。
- switch语句:用于多重条件判断。
9.3 函数
- 函数定义:包括返回类型、函数名、参数列表和函数体。
- 递归函数:函数调用自身,需有终止条件。
- 函数的参数传递:
- 值传递:传递变量的值。
- 地址传递:传递变量的地址,可以修改原始变量。
9.4 数组和字符串
- 数组:固定大小、同类型的元素集合。
- 索引从0开始。
- 字符串:C语言中的字符数组,末尾有一个特殊的‘\0’字符标志结束。
9.5 指针
- 指针概念:存储变量地址的变量。
- 指针运算:可以通过指针访问数组元素、函数参数等。
- 指针与数组的关系:数组名作为指针使用。
9.6 结构体和文件操作
- 结构体:用户定义的复合数据类型。
- 文件操作:包括打开文件、读取写入数据、关闭文件。
教材学习中的问题和解决过程(先问 AI)
问题1:算法时间复杂度和空间复杂度的理解
我看了这一段文字
“时间复杂度是衡量算法运行时间相对于输入规模的增长速度,空间复杂度是衡量算法使用内存空间的增长速度。”
有这个问题
时间复杂度和空间复杂度的具体计算标准是什么?有时我看到不同的算法,对于相同输入规模的描述会有不同的复杂度表现,为什么会这样?
我查了资料,有这些说法
“算法的时间复杂度通常通过对算法的执行步骤进行计数来估算,空间复杂度通常通过估算数据结构的存储需求来决定。”
根据我的实践,我得到这些经验
在实际的编程中,时间复杂度和空间复杂度的计算往往是通过分析代码的循环、递归等结构来确定的。例如,在循环中,每次迭代都会增加一定的时间,递归的时间复杂度可以通过递归树来估算。然而,我仍然不太明白为什么在某些复杂的算法中,时间和空间的复杂度描述会有差异。
我困惑的点是
在一些优化算法中,时间复杂度和空间复杂度之间是否有直接的权衡关系?比如,有时为了减少时间复杂度,我们可能会牺牲空间复杂度,反之亦然。如何在实际应用中做出更合理的选择?
- 问题1:时间复杂度和空间复杂度如何权衡?
- 问题1解决方案:可以通过实验和分析实际需求来选择合适的算法。例如,内存充足时可以选择空间复杂度较高的算法,以减少运行时间;而在资源有限的情况下,则可能需要选择时间复杂度较低、空间占用小的算法。
问题2:操作系统中的进程调度算法
我看了这一段文字
“操作系统调度算法用于决定哪个进程应该在给定的时间运行,常见的调度算法包括先来先服务(FCFS)、最短作业优先(SJF)等。”
有这个问题
不同的调度算法有什么优缺点?实际中如何选择合适的调度算法?
我查了资料,有这些说法
“FCFS简单易懂,但可能导致长时间的等待;SJF在理论上可以减少平均等待时间,但它要求我们知道进程的执行时间。”
根据我的实践,我得到这些经验
在多任务环境下,FCFS可能会导致长时间的进程等待,这对实时系统尤其不利;而SJF虽然能提高效率,但实际操作中获取准确的执行时间是个难题。最近我尝试使用Round Robin算法,它在时间片的分配上相对公平,适用于负载平衡的场景。
我困惑的点是
虽然理论上知道每种调度算法的优缺点,但实际的系统中如何根据任务的特性来选择最合适的算法?如果无法准确估算进程的执行时间,是否有其他办法解决这个问题?
- 问题2:如何选择合适的调度算法?
- 问题2解决方案:可以根据系统的具体需求来选择。例如,对于实时系统,优先级调度可能更合适;而在多任务的桌面操作系统中,Round Robin更适用。如果无法准确估算进程执行时间,可以考虑基于历史执行时间的估算方法(如动态优先级调整)。
问题3:C语言中的指针和数组的关系
我看了这一段文字
“在C语言中,数组名实际上是指向数组第一个元素的指针。可以通过指针来访问数组的元素。”
有这个问题
数组名和指针的关系是否意味着数组实际上就是指针?在使用指针访问数组时,是否会影响数组本身的值?
我查了资料,有这些说法
“数组名是指向数组第一个元素的常量指针,但数组名和普通指针有本质区别,数组名无法被修改。”
根据我的实践,我得到这些经验
在C语言中,数组和指针之间的关系比较微妙,数组名作为常量指针,不允许修改它的值(即指向其他位置),而普通指针则可以修改指向的地址。我还尝试过用指针操作数组,发现使用指针可以直接通过索引访问数组元素。
我困惑的点是
虽然知道指针和数组是密切相关的,但当我通过指针修改数组元素时,是否有一些隐含的副作用?例如,指针会不会改变数组的大小或造成内存泄漏等问题?
- 问题3:指针操作数组时如何避免副作用?
- 问题3解决方案:可以通过谨慎管理指针的生命周期来避免副作用。例如,在指针访问数组时,避免超出数组边界,确保指针始终指向有效内存。同时,使用
malloc
和free
进行动态内存分配时,注意释放内存,以防止内存泄漏。
基于AI的学习
我在学算法的时间复杂度,请你以苏格拉底的方式对我进行提问,一次一个问题。
问题1
GPT: 你能告诉我,什么是“算法”吗?你理解算法的意思吗?
我: 算法是一个解决问题的步骤或者一系列指令。它通常是通过输入来产生输出,并且具有一定的规则。
问题2
GPT: 很好。那么,如果我给你一组数据,怎么才能判断一个算法是否好呢?
我: 一个好的算法应该能够快速地解决问题,尤其是面对大规模数据时,应该能够高效运行。
问题3
GPT: 你提到“快速”和“高效”运行,能否告诉我,在衡量算法好坏时,我们应该关注哪些具体的方面?
我: 我们应该关注算法的“时间复杂度”和“空间复杂度”。时间复杂度关注的是算法需要多少时间来完成任务,空间复杂度则是它需要多少内存。
问题4
GPT: 好的,那你能解释一下什么是“时间复杂度”吗?
我: 时间复杂度是用来衡量一个算法运行所需时间和输入规模之间关系的,它帮助我们知道随着数据量增加,算法的运行时间会怎样变化。
问题5
GPT: 当我们说时间复杂度是“线性”的时候,你明白这是什么意思吗?
我: 线性时间复杂度,通常用 O(n) 表示,意思是算法的运行时间会随着输入的大小 n 线性增长。也就是说,输入越多,算法需要的时间也会相应增加。
问题6
GPT: 很好。那么,除了 O(n),你能想到其他常见的时间复杂度吗?
我: 可能还会有 O(1)、O(n^2)、O(log n) 等。O(1)表示算法的执行时间是常数,不管输入有多大,O(n^2)表示算法的执行时间是随着输入的平方增长的,O(log n)则是对数时间复杂度,输入增加时增长的速度较慢。
问题7
GPT: 那么,假如你有一个算法,它的时间复杂度是 O(n^2),这意味着什么呢?
我: O(n^2)意味着,当输入数据的规模增加时,算法的运行时间会随着输入规模的平方增加。例如,输入规模从 10 增加到 20,算法的时间可能增加四倍。
问题8
GPT: 如果你的算法的时间复杂度是 O(n^2),你能想到可能有哪些优化的方式吗?
我: 我可以考虑减少不必要的计算,或者使用更高效的算法。比如,如果是排序算法,可能会选择快速排序(O(n log n))而不是冒泡排序(O(n^2))。
问题9
GPT: 你提到“更高效的算法”,能否解释一下“高效”是如何与“时间复杂度”相关联的呢?
我: 高效的算法应该是在最短时间内解决问题,也就是说,时间复杂度较低的算法会更高效。比如,O(log n) 的算法比 O(n) 更高效,O(n) 又比 O(n^2) 更高效。
问题10
GPT: 很好,那么如果你遇到一个算法,时间复杂度是 O(n^2),你会认为它适用于什么场景?是不是每个场景都适合?
我: 不一定。如果数据量很小,O(n^2) 的算法可能也能接受,但当数据量增大时,效率就会变得很低。在大数据量的场景下,我更倾向于使用时间复杂度较低的算法,比如 O(n log n) 或 O(n)。
问题11
GPT: 那么你觉得,时间复杂度和空间复杂度之间有关系吗?会不会有时候为了优化时间复杂度,我们需要牺牲空间复杂度?
我: 是的,有时候为了提高时间效率,我们可能需要牺牲空间,比如使用更多的内存来存储数据。反之,为了节省内存,我们可能需要选择时间复杂度更高的算法。
问题12
GPT: 如果你不得不选择在时间和空间之间做权衡,你会怎么做?
我: 我会根据具体的需求来决定。如果程序需要快速响应,比如实时系统,我可能会选择更低时间复杂度的算法,即使它需要更多的内存。如果内存资源有限,我可能会选择空间更小的算法,哪怕牺牲一点时间效率。
最终提问
GPT: 基于你对时间复杂度的理解,您认为它是如何影响我们选择算法的决策的?你现在理解了时间复杂度的核心概念吗?
我: 我理解了。时间复杂度帮助我们评估一个算法在面对大规模数据时的效率。时间复杂度低的算法通常更高效,但在某些情况下,可能需要牺牲一些空间来换取更高的速度。我已经理解了如何在实际应用中根据需求选择合适的算法。
结束提问
我: 我的回答结束了,请对我的回答进行评价总结。
GPT: 很好!你已经基本掌握了时间复杂度的核心概念,并且能够理解如何根据时间复杂度评估算法的效率。在选择算法时,你已经能够考虑不同复杂度之间的权衡,知道在不同场景下如何做出决策。你对时间复杂度的理解越来越深入,继续保持这样的思考方式,在未来学习更复杂的算法时,你会更加得心应手。
代码调试中的问题和解决过程
-
问题1:实现排序算法时,排序结果不正确,数据没有按照预期的顺序排序。
- 问题1解决方案:
- 检查算法逻辑:如果使用的是交换排序(如冒泡排序或选择排序),请确保交换逻辑正确。尤其是在冒泡排序中,要确保内外循环顺序正确,且边界条件设置得当。
- 调试打印:在排序过程中添加调试打印语句,逐步输出数组的状态,确认每次交换或调整后数组的变化。
- 检查数组越界:确保对数组的访问不超过数组的边界,尤其是在内外循环的条件判断时,避免访问未定义的数组元素。
- 问题1解决方案:
-
问题2:递归算法导致栈溢出,程序崩溃。
- 问题2解决方案:
- 检查递归终止条件:递归算法中,如果终止条件设置不当,递归可能会进入无限循环,导致栈溢出。确保递归函数有明确的基准条件并且会最终满足。
- 优化递归深度:对于递归深度较大的问题,如分治算法(例如归并排序),考虑使用迭代方式或者优化递归(如尾递归优化)来减少栈的使用。
- 使用调试工具:通过调试工具(如
gdb
)检查递归调用栈的深度,查看是否存在过深的递归调用或死循环。
- 问题2解决方案:
-
问题3:内存泄漏,程序长时间运行后占用的内存越来越多。
- 问题3解决方案:
- 确保每次
malloc
或calloc
都配对free
:动态分配的内存一定要在不再使用时及时释放。特别是在处理多次动态内存分配时,确保每次分配都与对应的free
配对。 - 使用
valgrind
等工具检测内存泄漏:通过valgrind
或类似工具检查程序的内存使用情况,定位未释放内存的地方。valgrind
能够给出具体的内存泄漏位置,帮助你快速定位问题。 - 避免重复释放内存:确认每块内存只释放一次,不要在释放后再访问已释放的指针。
- 确保每次
- 问题3解决方案:
-
问题4:数组越界,程序崩溃或者产生不可预测的输出。
- 问题4解决方案:
- 检查数组访问范围:确保数组的下标始终在有效范围内。例如,对于大小为
n
的数组,访问下标范围应为0
到n-1
。 - 使用
sizeof
确保正确的数组大小:通过sizeof
确保数组的大小符合预期,避免使用未定义的内存。 - 调试输出数组元素:在调试时,可以在访问数组之前输出数组的当前大小和下标,确保每次访问时没有越界。
- 注意传递数组的大小:在将数组传递给函数时,要确保函数能够接收并正确处理数组的大小,避免使用未经控制的数组长度。
- 检查数组访问范围:确保数组的下标始终在有效范围内。例如,对于大小为
- 问题4解决方案:
-
问题5:程序无法正确处理输入,导致未定义的行为或输出错误。
- 问题5解决方案:
- 检查输入格式:确认
scanf
使用的格式符是否正确。例如,读取整数时应使用%d
,读取字符串时应使用%s
,如果读取浮点数,应使用%f
。 - 防止缓冲区溢出:在读取字符串时,确保目标数组足够大,并使用
%ns
(n
为数组大小减1)来限制输入的长度,避免缓冲区溢出。 - 检查返回值:
scanf
函数的返回值表示成功读取的输入项数,应该检查返回值,确保所有输入都成功读取,并根据需要处理无效输入。 - 清理输入缓存:在读取多个输入时,确保清理输入缓存,避免上一输入影响下一输入,尤其是在读取字符时可能会遗留换行符或空格。
- 检查输入格式:确认
- 问题5解决方案:
其他(感悟、思考等,可选)
在学习计算机科学和编程的过程中,我深刻体会到理论与实践的紧密联系。特别是在学习《计算机科学概论》和《C语言程序设计》时,理论知识为我提供了一个清晰的框架,而实际编程则让我不断地面对各种具体的挑战,这种反复的实践与反馈让我更加深刻地理解了许多概念。
首先,理论知识的积累对我解决实际问题至关重要。例如,学习了算法分析的复杂度后,在编写排序算法时,我能够更加有意识地选择合适的算法,并考虑到时间和空间的开销。而通过C语言学习内存管理,我在调试过程中避免了很多内存泄漏和指针错误,尽管这些问题在初学时看似简单,却能够深刻影响程序的稳定性和性能。
然而,编程的过程中也让我意识到,理论知识虽然重要,但更多时候,它的价值在于能够有效地应用到实际的编码中。当代码出错时,调试和反复实践才是真正提升编程能力的关键。每一次错误的调试都让我更清楚地知道如何分析问题、定位问题,并一步步修正。通过解决这些问题,我不仅对语言本身有了更深的理解,也对程序设计的思维方式有了更加清晰的认识。
这让我明白,编程不仅仅是一个技术技能,更是一种解决问题的思维方式。每当面对一个新的问题,我会从理论出发,结合具体问题的实际需求,不断调整和优化我的解决方案。这种不断探索与总结的过程,不仅让我成为了更好的程序员,也让我在面对复杂问题时更加自信和从容。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 1/1 | 15/15 | |
第二周 | 200/400 | 2/2 | 15/30 | |
第三周 | 200/600 | 3/3 | 15/45 | |
第四周 | 200/800 | 4/4 | 15/60 | |
第五周 | 200/1000 | 5/5 | 15/75 | |
第六周 | 200/1200 | 6/6 | 15/90 | |
第七周 | 200/1400 | 7/7 | 15/105 | |
第八周 | 200/1600 | 8/8 | 15/120 | |
第九周 | 200/1800 | 9/9 | 15/135 | |
第十周 | 200/2000 | 10/10 | 15/150 |
-
计划学习时间: 15小时
-
实际学习时间: 15小时
-
改进情况:在不断增加的代码量和博客写作中,我对C语言的掌握越来越深入,逐步克服了代码调试中的难题。每周总结博客有助于我更加清晰地理顺学习的内容,并帮助巩固知识。