Amdahl 定律

基本思想 当我们加速系统中某一部分时,整体性能的提升取决于可加速部分在原系统中的占比 $\alpha$ 和加速倍数 $k$。即被优化部分必须足够大且快,才能整体显著加速。 公式推导 程序总时间: $$ T_{old} $$ 可优化部分: $$ \alpha T_{old} $$ 优化后这部分时间: $$ \frac{\alpha T_{old}}{k} $$ 新的总时间: $$ T_{new} = (1-\alpha)T_{old} + \frac{\alpha T_{old}}{k} = T_{old}\big[(1-\alpha) + \frac{\alpha}{k}\big] $$ 加速比: $$ S = \frac{T_{old}}{T_{new}} = \frac{1}{(1-\alpha)+\alpha/k} $$ 如果 𝑘 趋向于 ∞ 时,这部分几乎不花费时间: $$ S_{\infty} = \frac{1}{1-\alpha} $$ 例 当 $\alpha = 0.6$,$k=3$ 时: $$ S = \frac{1}{0.4+0.6/3} = 1.67\times $$ 得出:即使当 60% 的部分提速三倍,整体只快了 1.67 倍。 所以,整体加速受限于未优化部分,优化要尽可能覆盖大比例的系统。 ...

八月 25, 2025 · 林墨瀚

并发和并行

背景知识:线程与进程 在计算机的发展中,有两个持续的追求:让计算机能做更多事情和让计算机运行的更快。这两个目标都可以通过让 CPU 同时执行多个任务来实现。于是衍生出了两个概念: 并发(Concurrency):系统中存在多个同时进行的活动。 并行(Parallelism):利用并发来提高系统运行速度。 并行可以在计算机的多个层次上体现,以下从高到低说明三种类型。 并行和并发的形式 线程级并发(Thread-Level Concurren) 线程级并发基于进程的概念,可以让多个控制流同时执行。 时间共享(time-sharing):20 世纪 60 年代,出现了时间共享系统,计算机通过快速上下文切换实现模拟并发执行。 Categorizing different processor configurations. 单处理器系统(Uniprocessor):通过时间共享实现模拟并发。 多处理器系统(Multiprocessor):多个 CPU(核心)由操作系统统一控制,可实现真正的并行。 多核处理器(Multi-core):在同一颗芯片上集成多个 CPU 核心,每个核心有独立的 L1、L2,共享 L3 和 RAM。 超线程技术 / 同时多线程(Hyperthreading/Simultaneous Multi-Threading):单个 CPU(核心)可以同时执行多个线程,利用空闲资源提高性能.如 AMD RYZEN 9 9950X 的 16 核 CPU 可以同时执行 32 个线程。 Multi-core processor organization. 指令级并行 了解指令级并行,需要先了解几个和 CPU 时钟有关的概念。 CPU 时钟(Clock):CPU时钟是中央处理器自带的计时单元,其基本工作单位为时钟周期,即处理器执行操作的最小时间单位。 时钟周期(Clock Cycle):时钟频率的倒数,是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU 仅完成一个最基本的动作。 时钟频率 / 时钟速度(Clock Frequency / Clock Rate):CPU 每秒能完成的时钟周期数,单位为 Hz、GHz。决定 CPU 执行指令的速度。时钟频率越高,理论上 CPU 执行指令越快。 时钟周期时间= 1 时钟频率 ...

八月 22, 2025 · 林墨瀚

线程与进程

注:本片文章讨论的“线程”,是软件线程,而不是硬件线程 进程 定义 关于进程,CSAPP 给出的定义是: A process is the operating system’s abstraction for a running program. It consists of the program code, its current activity (program counter and registers), and the process’s resources (memory, files, etc.). 这一段比较难懂,我们来剖析定义: A process is the operating system’s abstraction for a running program. 进程是操作系统对正在运行的程序的抽象。 理解这句话,我们先来理解什么是“正在运行的程序”。 程序只是在 ROM 里的一堆数据和指令。 而进程就是程序被操作系统加载到 ROM 中开始执行的状态。 It consists of the program code, its current activity, and the process’s resources. 进程包含程序代码、执行状态和资源。 执行状态包括 CPU 寄存器、PC、SP 等。 ...

八月 21, 2025 · 林墨瀚

【八月月报】暑假总结

暑假两个月的时间已经只剩下十天,回首这段时间,虽然感觉很快,但其实做了不少事情。下面是我的暑假总结。 一、时间轴回顾 7-19 组建计算机科学团队:TATEN 建站项目 TATEN(主要贡献者) taten_map 8-2 参与项目:NeoPage GitHub:NeoPage Demo:NeoPage 8-19 制作项目:AniDay GitHub:AniDay 网站:https://aniday.xyz/ Bilibili 宣传片:BV1m1euzGEGb 一个二次元番剧资讯与弹幕展示网站 二、生活与出行 出门 2025年7月27日 出游照片 2025年8月3日 出游照片 利用闲置笔记本 2025年8月14日,把闲置笔记本重新利用起来 三、总结 这个暑假,我主要完成了三个方向的事情: 团队与项目建设 —— 组建 TATEN 团队,完成多个网站与项目。 参与开源与协作 —— 在 NeoPage 项目中贡献代码。 个人实践与尝试 —— 制作 AniDay 项目,学习网站发布与宣传。 虽然时间飞快,但通过这些实践,我收获了不少经验,也为接下来的学期积累了资源。

八月 20, 2025 · 林墨瀚

关于我们的团队

在网络的另一端,我们相遇; 在代码与梦想之间,我们并肩前行。 我一直是个不善言辞的人,不太擅长在人群中表达自己,所以几乎没有什么朋友。直到五年级,我才遇到第一位真正意义上的朋友。过去很长一段时间,我都习惯了独处,习惯了一个人写代码、一个人思考问题、一个人面对电脑屏幕后面的世界。久而久之,我成了一个“死宅”,更喜欢待在卧室里写代码、看番、玩游戏。我的衣橱里除了校服几乎没有其他衣服,因为我很少外出。 今年三月,Susan 联系了我——他成了我第一个真正意义上的“灵魂上的朋友”。那时的我,其实并没有多大改变,只是多了一个可以说话的人。可慢慢地,和 Susan 的每一次聊天、每一次协作、每一次争论,像是一扇又一扇窗被打开。 到了五月,我在知乎上写的一篇回答意外走红,于是 Yaten 找到了我,他是我第一个“计算机方面的朋友”。于是很自然地,我们三个人建了一个群,这便是我们团队的雏形。半年之内,我的社交生活发生了巨大的变化。 后来我了解到,Yaten 是一个“现充”(中性用法)。而我心底一直渴望结识更多朋友,于是我们开始在B站寻找志同道合的人。我们遇见了其他几位伙伴,也让我第一次意识到:原来和人并肩做一件事,是这么让人安心、让人兴奋的事情。群里的人越来越多,我们在 GitHub 上建立了一个 organization,还一起搭建了团队网站,甚至做了成员分布图。 现实中,我依然是一个人,但我已不再孤单。能遇到这些朋友,我很高兴,也庆幸自己当初鼓起勇气,在B站评论区敲下那句——“你好,交个朋友吗……”。如今,我的生活很充实。有时候深夜看着团队群里的对话,我会觉得有点不可思议:那个曾经总是一个人坐在电脑前发呆的人,竟然有了可以并肩前行的伙伴。 有一位成员曾这样说: 在加这个群之前,我每天就是写完作业看B站。但进来之后,我发现遇到了一群有趣的人,每天都能学到很多东西,重新拿起了 VSCode,也真正意识到了人与人之间的差距。这是以前我不敢想的,也让我有了很多动力。在现实中,我的朋友用一只手都能数过来,所以能遇到你们,我真的很幸运……(后略) 看到这段话时,我几乎要落泪。那一刻我感到无比感动——他道出了我的心声。能帮到别人,让他们因为我们的存在而变得更积极,这让我觉得很幸福。 谢谢你们。 如果没有你们,我可能仍然只是个沉默的人;但因为有了你们,我开始愿意表达,愿意分享,愿意去承担和创造。这些成长,是我一个人永远不会经历的。 半年前的我,绝不会想到今天会有这么多并肩前行的伙伴。我依然是那个喜欢宅在房间里的人,但我知道,我的世界已经不一样了。也许未来我们会走向不同的方向,但我知道,在我心里,我们的小团队会一直在。

八月 10, 2025 · 林墨瀚

用 Hugo + Lotus Docs 重构我的个人笔记网站

我之前搭建的 个人笔记 使用的是 HonKit 的默认主题,界面难免有些简陋。某次在 GitHub 冲浪时偶然发现了这个主题:Lotus Docs,第一眼就被它吸引住了——颜值非常高,而且基于 Hugo 构建,demo 页面直接让我心动: 于是,我决定对笔记站进行一次重构。 安装 Hugo + Golang + Lotus Docs 首先使用 winget 安装 Hugo: winget install Hugo.Hugo.Extended 然后创建一个新站点: hugo new site mysite cd mysite 这将在 mysite 文件夹中生成 Hugo 项目的基础目录结构。 根据 GitHub 上的 README,这个主题依赖 Golang,因此需要先从 Golang 官网 下载并安装 Go。 此外,Lotus Docs 使用了 Hugo 的 Bootstrap 模块,需要将项目初始化为 Hugo 模块: hugo mod init github.com/linmoh/note 然后在项目根目录执行以下命令以添加主题: git init git submodule add https://github.com/colinwilson/lotusdocs themes/lotusdocs 接着,替换配置文件为以下内容(config.toml): baseURL = 'http://example.org/' languageCode = 'en-us' title = 'Mohan 的笔记本' contentDir = 'content' enableEmoji = true [module] [[module.imports]] path = "github.com/colinwilson/lotusdocs" disable = false [[module.imports]] path = "github.com/gohugoio/hugo-mod-bootstrap-scss/v5" disable = false [markup] [markup.tableOfContents] startLevel = 1 endLevel = 3 [markup.goldmark] [markup.goldmark.renderer] unsafe = true [markup.goldmark.parser] [markup.goldmark.parser.attribute] block = true 然后启动本地服务: ...

七月 26, 2025 · 林墨瀚

【七月月报】

一、学业 这个月期末考试,数学考得比想象中好一些,但整体成绩还是不太理想,在县里排第 111 名。 二、OI 方向 几乎没刷题,处于摆烂状态。 三、捡垃圾日常 捡了一台联想的老主机,配置如下: CPU Intel Celeron G1830 主板 不详 内存 DDR3 4GB ×1 显卡 NVIDIA GeForce GTX 720 硬盘 希捷 512GB 散热 不明 电源 振华(具体型号未知) 机箱 联想品牌机壳 检测了一下,主板坏了。换完主板就能用来当下载机了。 四、网站更新 本月主要更新了 电子书库,新加了二十多本书。 另外,我决定以后把所有知识类的文章都搬到 我的笔记本,整理起来会更清晰一些。 ...

七月 19, 2025 · 林墨瀚

Github 上的有趣项目

Logoly —— A P***hub Flavour Logo Generator 咳咳,一个很有趣的小项目,可以生成类似某知名sox网站风格的 Logo(懂的都懂)。 EveryoneNobel 很有意思,顾名思义,人人都有诺奖,无需解释。 一本有意思的书的关系网整理 这……谁扔我网站里的???不要害我写不了博客啊! 记得科学上网再看 Spotify Github Profile 可以生成一个这样的标签,在个人主页很有用。 Awesome GitHub Profile README 有很多漂亮的 GitHub 主页的模板,我的主页也是参考这里做的。 yaten.xyz 一个我非常喜欢的个人主页模板,其实是我写的,另外宣传一下他的博客。 The Economist 可以看《经济学人》,好耶! LXGW WenKai 霞鹜文楷 很常见的开源字体,但是我没用过(钟爱宋体)。 Misaka Mikoto 御坂美琴 「你指尖跃动的电光,是我此生不变的信仰,唯我超电磁炮永世长存」 ...

七月 9, 2025 · 林墨瀚

Ubuntu Server 24.04 + Nextcloud 安装食用指北

因为新电脑到手,陪伴了我五年的第一台个人电脑终于从一线退役了。不过退役不等于闲置,正好想起之前折腾过的 Nextcloud。虽然这台电脑性能有点差,但拿来做个私有云服务器还是绰绰有余的。 Step 1:安装 Ubuntu Server 24.04 因为熟悉 Ubuntu,就直接选它了。 首先从 Ubuntu 官网 下载服务器版本镜像。 (我这里用虚拟机重现安装过程) 语言选择英语,无需多言。 键盘布局默认即可。 如果没有特别需求,不建议选“最小安装”。推荐勾选安装第三方驱动,可以省去后续手动装驱动的麻烦。 实体机安装建议使用有线网,或手机通过 USB 共享网络。 代理地址一般不需要配置。 系统会自动查找最近的镜像源。 硬盘选择默认即可,如果有多块硬盘要注意选对目标盘。 建议给根分区(/)多分点空间,不然装东西容易捉襟见肘。 我另外还分了个 /home 分区。 (分区时,输入一个很大的数字即可分配剩余所有空间) 接着继续即可。 设置账户和密码。 这一步……广告时间? 我的 笔记本 服务器长期放角落,所以选上安装 SSH,方便远程登录。 没有特别需要的服务,直接跳过。 耐心等待安装过程结束。 看到提示重启就说明安装完成! Step 2:配置静态 IP 运行以下命令查看网卡配置文件名: ls /etc/netplan 比如我的文件是 50-cloud-init.yaml,接着编辑它: sudo vim /etc/netplan/50-cloud-init.yaml ...

七月 8, 2025 · 林墨瀚

电脑性能到底有多重要?

最近看到华为发布的 HUAWEI MateBook Fold 非凡大师 折叠屏电脑,第一反应就是:哇,好惊艳啊!(指价格) 网上一片“无脑吹”,这里就不挂链接了,免得拉低网站质量。 作为一个从 奔腾双核用到265K,从服务器用到家用机 的人,我觉得有必要对这款 笔记本 折叠电脑聊两句。 一、真便携? 官方数据显示,这款机器重 1.16kg,看上去似乎很轻。但你对比一下: MacBook Air 也才 1.24kg,而且它的性能、散热、续航和生态早已优化成熟。 所以,从重量和体积上说 MateBook Fold 没什么压倒性优势,“折叠”这个形式反而更像是噱头,而非实用功能。 二、性价比? 你知道它卖多少钱吗?23999 元。这价格能干嘛? 配一台台式高性能PC + RTX 5080 Ti 显卡。 或者直接上 MacBook Pro 16 英寸 M4 Pro:14核 CPU + 20核 GPU,48GB 内存,1TB SSD。 而 MateBook Fold: 性能比不上 PC 生产力比不过 MacBook Pro 便携性还不如 MacBook Air 怎么选不是一眼明了?所以它在“性能 vs 实用性”之间陷入了自相矛盾。 这更像是一款技术展示产品,而不是面向普通消费者的实用工具。 三、实际价值? 折叠屏能带来什么? 多任务?——Windows、macOS、Linux 哪个不是多任务? 新体验?——Surface 系列早就玩过“触控+平板形态”,也没掀起多大浪。 而折叠屏却带来了更多的物理厚度、软件适配、成本溢价,却没有带来对应的体验提升。说白了——你折叠个啥? 大多数办公场景真正看重的,是: 屏幕尺寸够不够 键盘手感好不好 接口丰富不丰富 你有见过哪个程序员在写代码时还在意能不能把屏幕折起来? ...

七月 3, 2025 · 林墨瀚

暑假安排

太好了,马上就要放暑假了,这宝贵的两个月时间可不能浪费了,来说说我的安排吧。 OI 刷题 & 看完 CSAPP 这是暑假的重头戏。目标是 J 组一等、S 组二等,同时啃完 CSAPP,打牢系统层的根基。 剪完我的漫剪 之前开了个头,趁假期给它一个完整的收尾。希望剪出一个能打动自己的成品。 持续写博文 把刷题思路、项目记录、生活反思都写下来。写,是一种整理,也是一种成长。 van 游戏 娱乐也是生活的一部分。偶尔放松,适度快乐。 预习功课 不求超前太多,只求打好基础。开学前,至少把新课本翻过一遍。 带上海来的表弟游泉城 他难得来一次,要好好安排泉城一日游: 趵突泉:毕竟上海没有泉; 大明湖:水滴湖就别拿出来说了; 千佛山:上海连山都没有; 山东省博物馆:毕竟上海没有历史。 总之,带他感受一下什么叫“四面荷花三面柳,一城山色半城湖”。 学英语:啃透《旋元佑文法》 系统化梳理语法框架,练习听说,争取让“看原版”成为常态。 学日语 让小李走出机场。 复习生物必修一 & 学习必修二 纯爱好,毕竟生物是为数不多 当然,计划是理想,现实可能会变。我给自己留了一些“缓冲时间”:偶尔发发呆、补补觉、听几首歌,或是在夜晚写点文章。这些看似“无用”的时间,有时才是最真实的成长。 希望这个暑假能充实又高效,每天都比昨天更接近目标。

六月 30, 2025 · 林墨瀚

十五岁,以及这一路走来的风景【补充】

过去是可以回忆的美梦,但未来才是活着的理由。 ——斯科特·菲茨杰拉德《了不起的盖茨比》 概述 时间过得真快,转眼马上就十五岁了,初二也快结束了。十五年的人生,不多也不少,算下来大概也就剩下五个十五年左右吧。 回头看这十五年,有开心的,也有难过的,但我不觉得后悔。那些经历都是自己的,都是成长的一部分。 性格 这里就不谈什么 MBTI 了。 我算是个完美主义者,有点强迫症,做事喜欢把细节做到最好,不太能忍受留下瑕疵。领导能力还可以,做事认真负责,习惯把事情安排得井井有条。 平时不太爱说话,不喜欢社交,和陌生人在一起会紧张,属于典型的宅男类型。这样的性格从小学开始就有了,到现在也没怎么变。 有时候会觉得,自己有点像 折木奉太郎——冷静、理智、沉默(当然,并不是刻意模仿他),但其实内心并不像表面看起来那么简单。我更喜欢安静的环境,也愿意花时间做自己感兴趣的事情,比如看书、写代码、或者玩游戏。 虽然如此,身边还是有几个关系不错的朋友。只是我更习惯独处,很多时候也觉得,保持距离其实是一种自我保护。 直到今年三月,在一次县里的活动中,我才第一次遇到一个真正意义上的计算机方面的知己,而前不久,在我发的一条知乎回答下,也有一位志同道合的朋友联系了我,我们相隔一千八百多公里,但那一刻我忽然明白了什么叫“千里共知音”。 世界上总有一些人,在某个你未曾注意的角落,和你一样热爱着某件事。 学业 总体来说,学习还算顺利。虽然我在一个不太出名的小县城,人才流失得厉害,但这里的成绩也不算差。小学时成绩不好,主要是因为学校条件不太好,老师也不够专业。刚上初中时,觉得学习压力挺大,尤其是遇到难题的时候,常常不知该怎么解决。 后来通过努力,考上了县里的直升班,成绩渐渐稳定下来。虽然还不算特别优秀,但至少比以前好多了。现在的目标很明确,就是考上211大学。觉得只要努力,梦想其实没那么遥远。 生活中也学会了给自己找乐子,比如读书、听音乐,玩游戏,都能缓解压力。虽然路还长,但我愿意一步步走下去。 OI(竞赛) 我接触电脑和编程算是比较早的,但真正接触 OI 却非常晚,直到今年二月份才开始了解。刚开始接触时觉得有些难,很多知识和思维方式都不熟悉。 不过我没有放弃,而是下定决心要赶上来。每天都会抽时间学习算法和练习题,慢慢理解其中的逻辑和技巧。虽然起步晚,但我相信只要坚持努力,还是有机会追上别人的。 学习 OI 理由其实很简单,就是为了混个强基计划。毕竟我也清楚,这项竞赛对将来就业或者实际应用来说帮助并不大,更多的是一种考验和挑战而已。 学了很多 这是我至今做过最有意义的一件事。 这里说的不是文化课,也不是OI,而是纯粹的计算机相关知识和技能。 从二年级开始,我接触并学习了很多编程语言,根据掌握程度大致分成几个层次: 十分熟悉的第一批: C++ HTML Markdown 比较熟悉的第二批: Python CSS 没有深入研究的第三批: JavaScript 除了编程语言,我还掌握了不少工具软件: 十分熟悉的第一批: Unreal Engine 5 Office 全家桶 VMware Workstation Pro 比较熟悉的第二批: DaVinci Resolve Adobe Photoshop Adobe Illustrator Adobe Premiere Blender 没有深究的第三批: FL Studio 2024 还有操作系统: ...

六月 17, 2025 · 林墨瀚

【六月月报】新机上阵,算法进阶与「第一桶金」

最近有些忙,一周都没能更新博客。今天来继续回顾一下这个月以来的点滴收获。 一、新电脑! 在我的不断请求 软磨硬泡 下,终于换了新电脑。以下是配置单: CPU Intel Core Ultra 7 265K 主板 ASUS TUF GAMING B860M-PLUS 内存 DDR5 6000MHz 64GB(16GB × 4) GPU NVIDIA GeForce RTX 5070 SSD1 Samsung SSD 990 1TB PRO SSD2 WD BLACK SN770 500GB(闲置) SSD3 SK hynix BC711 512GB(闲置) 散热 利民 PA140 BLAC 电源 海韵 FOCUS 850W ATX3.1 机箱 华硕 A21 追影 总之,这台机器在我的预算和需要之间找到了一个不错的平衡点。至少我库里的游戏都能稳 4K60Hz(显示器只有 4K60Hz),跑 UE5 也无压力了。 并且,我的主力操作系统又回到了 Windows 11。 ...

六月 14, 2025 · 林墨瀚

音乐

以下排名不分先后。 二次元名曲 打上花火(烟花) DAOKO x 米津玄师 我们二次元也有自己的难忘今宵! 伴随着你(天空之城) 久石让最好听的一首歌。 遠い空へ(缘の空) 央视都在用的音乐。 千本桜 初音万岁! 鸟之诗(AIR) 动漫届的国歌,未看其番,先闻其歌。再看其番,人过中年。 ...

六月 9, 2025 · 林墨瀚

感谢支持

就在昨天,我收到了自网站建立以来的第一笔赞助! 这份支持来自 *邦 大哥,对我来说意义非凡。 从搭建网站的第一行代码,到现在慢慢有人愿意浏览、愿意支持,这段路不算容易,但却无比值得。有你们在,我才更有动力继续创作、持续优化、不断前行。 感谢你们的认可和鼓励,未来我会继续更新内容,不负所托。

六月 2, 2025 · 林墨瀚

我掌握的技术栈

🖥️ 操作系统 Windows 系列:Windows XP、Windows 7、Windows 8、Windows 10、Windows 11 macOS Linux 发行版:Ubuntu、Arch Linux、Kali BSD 系列:OpenBSD 💻 编程语言 C++ Python JavaScript HTML / CSS 🛠️ 工具与框架 Adobe Photoshop Blender Adobe Illustrator Hexo Hugo Unreal Engine 4 / 5 ⚙️ 常用开发环境 主力系统:Arch Linux IDE:Visual Studio、VS Code 构建工具:g++ 版本控制:Git、GitHub 终端工具:zsh 🐧 Linux 运维经验 基础操作:系统安装、用户权限、挂载、软件包管理(pacman / apt) 服务管理:使用 systemd、配置 SSH、Nginx 等服务 熟悉基本的日志查看与问题排查方法 🎮 小插曲:做过一款“胎死腹中”的游戏 曾尝试使用 Unreal Engine 5 开发一款游戏,但因为队友太拉,项目草草结束。 但也因此接触到了虚幻引擎的蓝图系统和关卡搭建,对游戏开发产生了一定兴趣。

六月 2, 2025 · 林墨瀚

【MIT 6.006】Lecture 1 Notes

本文件内容源自麻省理工学院开放课程资料平台,根据官方英文讲义翻译而成,由 ChatGPT 初步翻译,并经人工精校润色。打印版参见:https://linmoh.github.io/mit6.006/l1.html 算法导论:6.006 麻省理工学院 讲师:Erik Demaine、Jason Ku 和 Justin Solomon 讲座 1:导论 本课程的目标是教会你如何解决计算问题,并说明你的解决方案是正确且高效的。 问题 问题输入与正确输出之间的二元关系 通常不会为所有输入指定每一个正确输出(太多了!) 提供一个可验证的谓词(属性),正确输出必须满足它 6.006 研究的是大规模、通用输入空间上的问题 非通用:小输入实例 例子:这个房间里是否有两位同生日的学生? 通用:任意大的输入 例子:给定任意 n 位学生,是否存在一对同生日的学生? 若生日只在 365 天中选,对于 n > 365,根据鸽巢原理,答案一定为真 假设生日的精度超过 n(包含年份、时间等) 算法 一个过程:将每个输入映射到一个输出(确定性) 若算法对每个问题输入都返回正确输出,则称其解决了该问题 例子:一个解决同生日问题的算法 保持一个姓名和生日的记录(初始为空) 按某个顺序询问每个学生 ∗ 若生日已在记录中,返回这对学生! ∗ 否则,将姓名和生日加入记录 若全部学生都询问完仍无结果,则返回 None 正确性 程序/算法是有限大小,如何证明其正确? 对于小输入,可使用情况分析 对于任意大的输入,算法必须以某种方式使用递归或循环 必须使用数学归纳法(递归在计算机科学中的关键原因) 例子:生日匹配算法的正确性证明 对 k 归纳:记录中已有的学生数量 假设:若前 k 位中有匹配,算法在询问第 k+1 位前就返回 基础情况:k = 0,前 k 位无匹配 假设归纳假设对 k = k₀ 成立,考虑 k = k₀ + 1 若前 k₀ 位已有匹配,算法已返回(由归纳) 否则,若前 k₀ + 1 位有匹配,匹配一定包含第 k₀ + 1 位 那么算法直接检查第 k₀ + 1 位学生的生日是否已在记录中 效率 算法生成正确输出的速度有多快? ...

六月 1, 2025 · 林墨瀚

【公告】关于以后的打算 & 网站的更新规划

以后的打算 以后我将学习以下几门科目(按优先度排序,不算课内内容) 计算机科学:这是为将来找工作打基础的必修课。 经济学:扩展知识面,也希望能培养点经济头脑。 英语:既是学业需求,也为就业做准备。 生物学(高中部分):主要为了应付现在的考试。 日语:算是兴趣加未来可能的国际发展准备。 总体上,我把学习内容分成两类: 就业导向:计算机科学、经济学、英语、日语,都是为了以后能顺利步入社会。 学业导向:英语和生物学,主要是为了应付眼下的课业和考试。 说实话,现在看来时间真的挺紧的,感觉总是不够用。以后得更加合理规划时间,娱乐时间肯定得压缩点,争取高效学习。 网站的更新规划 因为山东现在实行双休,时间上宽裕了不少,所以我打算给网站多花点时间。具体计划是: 题解:只写那些真有价值、有深度的题目,避免泛滥。 算法笔记 / 教学内容 / 数学相关:暂时先停一停,腾出精力做更重要的事情。 高质量文章:每个月争取写两篇,力求内容有深度、有用。 普通文章:保持频率,每周三篇,主要分享日常感想和一些技术内容,保持网站活跃。 总之,我想通过集中精力产出更优质的内容,让网站更有价值,同时也能兼顾学业和个人规划。希望以后大家多多支持!

五月 29, 2025 · 林墨瀚

【微观经济学】经济学十大原理

引言:我开始学习经济学 我有一个众所周知的小秘密:我希望我的下一代,甚至下下一代,能够进入上流社会,拥有更好的生活。 为了实现这个目标,我决定开始学习经济学,研究怎么搞钱 探索市场的规律和财富增长的奥秘。 经济学十大原理 原理一:人们面临权衡取舍 在资源有限的世界中,我们做每一个选择时,通常都意味着要放弃一些别的东西。这就是所谓的「权衡取舍」。简单说,就是:选择 A,就必须放弃 B。 举例解析 比方说,你是一个初中生,晚上有 2 小时自由时间,你面临三个选项: 做数学作业 看喜欢的动漫(比如《孤独摇滚》) 和朋友语音聊天打游戏 如果你决定用这两个小时做数学作业,那么你就放弃了看动漫和打游戏的机会,这就是一种权衡取舍。 原理二:某种东西的成本是为了得到它所放弃的东西 在经济学中,这个原理强调的是机会成本的概念。 当你做一个选择时,真正的成本不只是你花了多少钱或多少时间,而是你为做这个选择所放弃的「最有价值的其他选择」。 举例解析 比方说,假设你晚上可以: 花 2 小时看《孤独摇滚!》 或者用这 2 小时学习 C++ 和算法 你选择了看动漫,那么你为此所付出的「成本」,并不仅仅是2小时,而是你放弃了这2小时里可能提升编程技能、提高 OI 成绩的机会。所以你看动漫的「经济学成本」是这段时间本来可能带来的知识积累或竞赛得分。 原理三:理性人考虑边际量 在经济学中,「理性人」指的是那些有目的、有逻辑地做决策的人。他们不会轻易浪费资源,会在做决策时考虑「边际变化」。 边际量 「边际」意思是额外的一点点变化。也就是说,理性人不是考虑「全部」或「一刀切」的选择,而是问: 如果我再多做一点,会不会更好? 如果我再少做一点,是不是更节省? 边际分析就是衡量「多做一点」带来的额外收益和额外成本,然后做出最优选择。 举例解析 你明天要考 CSP-J,今天已经学习了 6 小时,考虑是否要再学 1 小时。 你就要考虑: 边际收益:再学 1 小时能不能再提升 5 分? 边际成本:这 1 小时可能让你太疲惫、没时间吃饭或影响睡眠 如果你判断这额外 1 小时学不到什么新东西,还可能影响明天状态,那理性选择就是不再继续学。 理性人的决策方式 if 边际收益 ≥ 边际成本: do it! else if 边际收益 < 边际成本: don't do it! 原理四:人们会对激励做出反应 在经济学中,「激励」指的是能影响人们行为的奖励或惩罚。 人们做出决策时,常常会根据激励来改变自己的选择。激励可以是:金钱、时间、成绩、情绪、声望等。人们不是凭空改变行为,而是因为有「改变行为的理由」,这就是激励。 举例解析 老师说:「谁今天晚上 8 点前交作业,就多加 2 分。」 ...

五月 29, 2025 · 林墨瀚

国产软件,越做越「重」

好的设计是看不见的服务,坏的设计是强迫性的对话。 ——唐纳德·诺曼《设计心理学》 自从几年前开始「科学上网」后,我的手机界面也悄然发生了变化:国内 App 和海外 App 仿佛成了两个不同世界的缩影。 图标设计:两个「审美」体系 最直观的区别,或许就是图标了。 海外 App 的图标普遍追求极简主义:形状规整、配色克制、更新缓慢。比如 Twitter(现𝕏)沿用蓝鸟符号十余年,Telegram的纸飞机、Spotify的声波图标,这些软件的图标几乎从未做过大幅修改。简洁、稳定,是它们传递出的第一印象。 反观国内 App,一年四季「百变造型」:618、双十一、春节、暑期档、开学季……各种节日与营销活动层出不穷,图标就像广告牌,忙得不亦乐乎。银行 App 图标还会直接加上版本号,给人一种“更新=进化”的错觉。美团、阿里、百度系的软件甚至会把品牌 LOGO 直接换成节日口号,一时间让人难以辨认。 ...

五月 25, 2025 · 林墨瀚

我开始记录代码之外的东西了

过去这个站主要是放技术内容,像博客、命令、折腾记录。现在也还是,但好像有点变了。准确说,我的关注点变了。 比如那天晚上写「焦虑的夜」,其实没想太多,就是单纯想把那种感觉写下来。写完后居然挺轻松的,比刷完一道算法题更像「做成了一件事」。 这两年我写了很多代码,建了几个站,刷了一些题,也搬了几次系统(是的,我真的折腾过头了)。但很少去想我自己在这些事中处于什么状态。也许这就是为什么最近我开始写这些「不那么技术」的东西。它们没有那么「有用」,但对我好像挺必要。 以后可能会在这里写点音乐、写作业的内容、写点聊天记录、做梦的内容。或者说,这个站会变得更「人」一点,而不是「作品集」那样的东西。

五月 25, 2025 · 林墨瀚

关于音乐

我一向对娱乐圈的“乱象”持反感态度,因此从不关注相关资讯。我与所谓的“饭圈文化”始终保持着清晰的界限。也正因如此,我更倾向于聆听那些真正触动人心的作品,比如米津玄师的歌。 其中,我最喜欢的一首歌之一,是《Lemon》,这是米津玄师为祖父的去世而作的,讲的是生死的理解。歌词如下: 日文 中文翻译 夢ならばどれほどよかったでしょう 如果这一切都是梦境该有多好 未だにあなたのことを夢にみる 至今还能在梦中寻到你的身影 忘れた物を取りに帰るように 就像归家取回遗忘之物 古びた思い出の埃を払う 打扫尘封的记忆 戻らない幸せがあることを 幸福无可再挽回 最後にあなたが教えてくれた 是你最后告诉了我 言えずに隠してた昏い過去も 那些未对他人提及过的晦暗往事 あなたがいなきゃ永遠に昏いまま 如果没有你它们将永远沉睡在黑暗中 きっともうこれ以上傷つくことなど 明白必定不会再有其他 ありはしないとわかっている 伤心胜过于此 あの日の悲しみさえ 甚至那日的悲伤 あの日の苦しみさえ 甚至那日的痛苦 そのすべてを愛してた あなたとともに 将所有一切,连同深爱的你一起 胸に残り離れない 都化作深深烙印在我心中 苦いレモンの匂い 苦涩柠檬的香气 雨が降り止むまでは帰れない 在雨过天晴前都无法归去 今でもあなたはわたしの光 时至今日 你仍是我的光芒 暗闇であなたの背をなぞった 在黑暗中追寻着你的身影 その輪郭を鮮明に覚えている 那轮廓至今仍鲜明地刻印于心 受け止めきれないものと出会うたび 每当遭遇无法承受的苦痛时 溢れてやまないのは涙だけ 汹涌不停的都只有泪水 何をしていたの 曾经历过什么 何を見ていたの 曾目睹过什么 わたしの知らない横顔で 脸上浮现着我不曾见过的神情 どこかであなたが今 如果你正在什么地方 わたしと同じ様な 与我一样 涙にくれ淋しさの中にいるなら 终日过着以泪洗面的寂寞生活 わたしのことなどどうか忘れてください 就请你将我的一切全部遗忘吧 そんなことを心から願うほどに 我从心底里祈愿 今でもあなたはわたしの光 时至今日 你仍是我的光芒 自分が思うより恋をしていたあなたに 我深深地恋慕着你 甚至超出自己的想象 あれから思うように息ができない 自那以后 再不能随心呼吸 あんなにそばにいたのにまるで嘘みたい 明明曾如此贴近 如今却恍如虚幻 とても忘れられないそれだけが確か 唯一能确定的是 对你难以遗忘 あの日の悲しみさえ 甚至那日的悲伤 あの日の苦しみさえ 甚至那日的痛苦 そのすべてを愛してた あなたとともに 将所有一切,连同深爱的你一起 胸に残り離れない 都化作深深烙印在我心中 苦いレモンの匂い 苦涩柠檬的香气 雨が降り止むまでは帰れない 在雨过天晴前都无法归去 切り分けた果実の片方の様に 如同被切开的半个柠檬一般 今でもあなたはわたしの光 时至今日 你仍是我的光芒 然而,最近在 B 站上偶然刷到一段视频,其中某些团体成员将《Lemon》的情绪深意“创意性”地嫁接到了咀嚼柠檬的体验之上,例如: ...

五月 24, 2025 · 林墨瀚

焦虑的夜。

我很焦虑。 看着一些小学生、初一的同学已经能做出比我还难的算法题,心里忍不住泛起一种深深的失落感。回头看看这几年走过的编程之路,满是绕远与反复——一直在努力,却始终觉得偏离了方向。 四年后就是高考,山东的竞争压力之大不言而喻。而我的成绩,却一次比一次差。无论是文化课还是编程,我都感到自己在一点点落后,仿佛看不清前方的路。 更让我无助的是,无论是在学习还是编程上,我似乎都没有一个真正的引路人。没有人告诉我该怎么学,往哪走;没有人指出我做错了什么,也没有人告诉我哪一步其实并不算失败。 于是我开始反复回顾自己的选择,怀疑自己是不是不够聪明、不够勤奋、甚至不够幸运。那些比我小的孩子,那些更早接触、更早起步的人,就像一道道亮眼的光,而我站在阴影中,被照得无处遁形。 我时常觉得,自己就像站在一条奔流不息的河边,看着别人奋力向前,而我却困在原地,寸步难行。不是不想追赶,而是脚下的土地似乎随时都可能塌陷,我根本不敢迈出下一步。 但夜深人静的时候,我也会逼自己冷静地想一想:我到底是在害怕落后,还是害怕自己根本无法赶上?我是真的走错了路,还是只是还没走到成果显现的那一步? 焦虑也许是成长的副产品。它提醒我:我在意,我不甘心,我还没放弃。 我不知道未来会变成什么样,也不知道自己还能坚持多久。但至少现在,我还想试一试——哪怕没人指引,我也想摸索出自己的路。 谁知道呢,也许某一天,当我再次回头望向此刻的自己,会想说一句:你已经做得很好了,真的。

五月 22, 2025 · 林墨瀚

【五月月报】 算法、建站、焦虑与成长

看了看网站,已经整整一个月没有更新了,连 SSL 证书都悄悄过期了。这段时间,我到底在忙些什么? 一、刷题进度与学习状态 算法类 最近在洛谷持续刷题,目前累计通过了 112 道题目。 其中: 入门:86 题 普及−:23 题 普及/提高−:3 题(包括第一次 AC 的 P1205) 刷题过程中,《算法竞赛入门经典》的一些题让我抓狂得快要怀疑人生,于是我转向口碑不错的《深入浅出程序设计竞赛》寻求“救赎”。目前刚学到第二部分算法——排序章节,讲得确实比我想象中更清晰易懂。 此外,我还参加了 【LGR-226-Div.4】洛谷入门赛 #35,最终排名 518 名,感觉还能接受,但也暴露了不少薄弱环节。 学业 地生一模,地理校第3,生物第一,总分校第一、县第五。 其他的没什么好说的。 二、技能提升与项目积累 除了算法,我也在慢慢完善一些个人积累: GitHub 整理刷题代码 我在 GitHub 上新建了一个代码库,用来记录我在洛谷刷过的题: 👉 algo_code 仓库 同时也开始打理 GitHub 主页,探索 Markdown 的高级用法: 👉 我的 GitHub 主页 三、建站:从兴趣出发的小尝试 我重新拾起了建站这个兴趣,做了一些看似“离谱”、实则有自我风格的项目: 波奇酱非官方同人页:boochi.fans GitHub 仓库地址:boochi.fans 仓库 同时,我还注册了一堆脑洞清奇的域名: bailan.top boochi.fans 玩原神玩的.xyz → 跳转到学校官网 no.neijuan.fun(来自域名 neijuan.fun) pingyinshigao.icu 这些可能现在还“没用”,但对我来说是一种“试错式探索” —— 在技术与兴趣之间寻找乐趣和平衡。 四、心态波动与自我调整 坦白讲,这段时间我有些焦虑。 看着一些小学、初一的同学在洛谷上做出比我更高难度的题,甚至已经在 CSP-J 拿奖,我不止一次怀疑自己是否起步太晚,是否天赋不够。 但转念一想:别人强,只能说明他们开始得早,而我也在进步。 焦虑说明我在乎,进步说明我没放弃。与其沉溺比较,不如专注成长。只要今天的我强于昨天,那就是值得骄傲的成绩。真正重要的是:走自己的路,走稳、走远。 毕竟凉先辈说过: 五、未来计划与行动方向 接下来,我打算: ...

五月 17, 2025 · 林墨瀚

排列组合

之前做 CSP-J 初赛试题总是不会做排列组合的题,现在就来补坑。 阶乘 n!(读作“n 的阶乘”)表示从1到n的所有整数的乘积,即: n ! = n × ( n − 1 ) × ( n − 2 ) × ⋯ × 2 × 1 并且规定: 0 ! = 1 排列公式 P ( n , k ) = n ! ( n − k ) ! 排列****强调顺序,表示从n个不同元素中取出k个元素并考虑顺序的排列方式总数,即两个排列如果顺序不同就视为不同的情况。 例如: P ( 5 , 3 ) = 5 ! ( 5 − 3 ) ! = 5 ! 2 ! = 5 × 4 × 3 × 2 × 1 2 × 1 = 120 2 = 60 ...

四月 2, 2025 · 林墨瀚

【补】搜索

在这里,汇集了搜索算法,包括线性查找、二分查找等。 线性查找(Linear Search) 代码实现 #include <iostream> using namespace std; int main() { int nums[10]; for (int i = 0; i < 10; i++) { nums[i] = i + 1; // 初始化数组,1~10 } int find; cin >> find; // 线性查找 for (int i = 0; i < 10; i++) { if (nums[i] == find) { cout << i; // 输出找到的索引 return 0; } } cout << "Not found"; // 没找到 } 算法解析 这是最基本的查找算法,也是最简单的一种。 遍历数组 for (int i = 0; i < 10; i++),依次检查 nums[i] 判断是否匹配 if (nums[i] == find),如果找到目标值,就输出索引 i 提前返回 return 0; 避免多余的遍历,提高效率 未找到时输出 “Not found” 遍历结束仍然没找到目标值,则打印 “Not found” 时间复杂度为 O(n),适用于无序数组,或小规模数据 二分查找(Binary Search) 代码实现 #include<iostream> using namespace std; int main() { int nums[10]; for (int i = 0; i <= 9; i++) { nums[i] = i + 1; } int find = 0; // 要查找的元素 int left = 0, right = 9; // 元素下标 int mid = 0; // 元素下标 cin >> find; while (left <= right) { mid = (left + right) / 2; if (nums[mid] > find) { // 要找的元素在中点左边 right = mid - 1; continue; } if (nums[mid] < find) { //要找的元素在中点左边 left = mid + 1; continue; } if (nums[mid] == find) { cout << mid; return 0; } } cout << "Not found"; } 算法解析 初始化 nums 数组被初始化为 {1, 2, 3, …, 10},即 升序排列。 left = 0, right = 9,分别表示搜索范围的 左端 和 右端。 mid = (left + right) / 2,计算当前范围的 中间元素。 查找过程 如果 nums[mid] > find,说明目标在 左半部分,所以更新 right = mid - 1。 如果 nums[mid] < find,说明目标在 右半部分,所以更新 left = mid + 1。 如果 nums[mid] == find,找到目标,输出索引并终止程序。 终止条件 找到目标时,输出其索引并返回。 若 left > right,表示搜索范围为空,目标元素不存在,输出 “Not found”。 时间复杂度 二分查找的时间复杂度是 O(log N),原因是: ...

三月 29, 2025 · 林墨瀚

给朋友的 CS 书单:从入门到放弃(bushi)

最近莫名其妙成了“CS懂王”,每天被问最多的除了“代码跑不通怎么办”,就是“该看什么书”。为了保住人设(和友谊),特此奉上我的私藏书单!声明:所有书都是我亲自读过且成功催眠的,质量有保障! (其实最主要的原因是为了@Susan_2333而写的) 一、新手村装备(看这些不会秃头) 📖《计算机科学概论》 看这本书的目的是对 CS 进行一个简单的了解。但可以当故事书看!很有意思,不用看的太深,厕所读物首选,看到睡着算我输。 📺计算机科学速成课 不可否认的一点是,这不是书。但是,这个系列的视频每集只有10多分钟,从晶体管讲到神经网络,用浅显的语言讲解 CS,非常有趣。我推荐大家先看这个视频,然后再看《计算机科学概论》。 (虽然我只看了前3集就去追番了) 三、Python修仙指南(头发可再生) 📖《Python编程:从入门到实践》 这本书是 Python 的入门书籍,内容非常丰富,适合初学者。这本书的内容比较陡峭,但是,这本书的内容非常实用,能够帮助大家提高编程水平。 看完就能用Python算命!我三年级就开始学了! 📖《笨方法学Python》 这本书我没看过,但是据说很棒(@Susan_2333也这么说)。 说实话,我还是更喜欢学术性强一点的书。 二、C++の试炼(秃头预警) 📖《C++ Primer》 这本书是 C++ 的入门书籍,内容非常丰富,适合初学者。但是,这本书的难度较大,建议有一定编程基础的同学阅读。 C++界的《新华字典》,买前雄心壮志,买后防身利器!建议和编译器拜把子再看。 📖《C++ Primer Plus》 看名字也知道,这本书是 C++ Primer 的补充。这本书的难度较小,适合初学者。并且,这本书内容又多又杂,面面俱到,适合想要具体学习 C++ 的同学。 比楼上那本多了个"Plus",价格也Plus!但确实适合萌新,当年我就是靠它写了第一个"Hello World" Effective C++ 这本书是 C++ 的进阶书籍,内容非常丰富,适合有一定编程基础的同学阅读。这本书的内容比较难,但是,这本书的内容非常实用,能够帮助大家提高编程水平。 More Effective C++ 在 Effective C++ 的基础上进一步补充。 四、算法の奥义(彻底秃头) 🎮《Hello 算法》 这本书不同于其他的所有书籍,这本书一开始是在 Github 上开源的、社区维护的书,这也是我极为推荐这本书的原因。 Github 项目地址 在线网页 📚《算法导论》(CLRS) ...

三月 28, 2025 · 林墨瀚

进制转换

日常生活中我们使用十进制(0-9),但在计算机领域中,二进制(0-1)、八进制(0-7)和十六进制(0-9, A-F)更为常见。进制转换的核心是通过数学运算在不同基数(Base)之间转换数的表示形式。理解这一原理不仅是计算机科学的基础,也是编程、网络通信甚至密码学的必备技能。 为什么学习进制转换? 考试需求 信息学竞赛(如CSP、NOI)中频繁出现进制转换题目。例如,2024年山东CSP-J第一轮测试中便有一道关于二进制与十六进制转换的单选题。 计算机原理基础 计算机硬件基于二进制逻辑运行,而八进制、十六进制常用于简化二进制的表示(如内存地址、机器指令)。 编程实践 编程中常需处理不同进制的数据(如Python的bin()、hex()函数,C/C++的格式化输出)。 实际应用场景 计算机系统:二进制用于硬件设计,十六进制简化调试(如内存地址显示为0x1A3F)。 网络与编码:IPv6地址使用十六进制(如2001:0db8::ff00),ASCII码用十进制或十六进制表示字符。 密码学:大数运算(如RSA密钥)常以十六进制存储。 装逼 进制转换的核心原理 一、进制的基本概念 基数(Base):进制使用的符号数量。 十进制(Base 10):0-9 二进制(Base 2):0-1 十六进制(Base 16):0-9 + A-F(A=10, B=11, …, F=15) 权值(Positional Value) 每一位数字的值由其位置决定,计算公式为: 数值 ...

三月 28, 2025 · 林墨瀚

数学专栏总序

当数学渣开始造轮子 “你要写数学专栏?!” ——来自三位好友的夺命连问 澄清一点,这个专栏的数学,是为了计算机科学服务的,而不是为了数学,毕竟我对数学可以说是到了“厌恶”的地步。所以,数学的难度,不会特别高,对于大佬们来说甚至是简单,但会尽量涵盖和 CS 有关的数学知识。 因此,我将题目改为: 为计算机科学而学习的数学知识 作为学校著名的数学渣渣,这个决定确实充满“魔幻现实主义色彩”。但当我第N次被《CLRS》和《CSAPP》中跃动的Σ符号击溃时,终于意识到:计算机科学大厦的裂缝,往往始于数学地基的沉降。 甚至复杂度也看不懂,我都不知道我是怎么学的算法。 本专栏绝非数学系的抽象漫游,而是程序员视角的生存指南: 撕开数学公式的裂口,直击计算机核心算法 毕竟,编写这个专栏的过程,本质上是一个数学渣渣的 debug 日志——那些让我头秃三天的数学路障,都将成为你前进路上的反光警示牌。 (注:本企划存活时长与读者催更力度呈正相关) 特别鸣谢 @Susan_2333 的数学外挂持续在线:他的B站主页(你敢信这只是一个初中生?)

三月 28, 2025 · 林墨瀚

逻辑谬误

班里杠精太多了!像我们这种嘴笨的内向小男生,哪怕讲得再有理,也常常被对方带偏节奏,吵不赢,怎么办? 没关系!今天我们就来学点“逻辑谬误”,让他们无话可说! 什么是逻辑谬误? 不依据逻辑的议论,尤其是指论证中不符合逻辑的推论。逻辑谬误分为形式逻辑谬误与非形式逻辑谬误。非形式逻辑谬误,实质上就是前提错误谬误。 逻辑谬误的类型 以下是针对形式逻辑谬误与非形式逻辑谬误的分类详解,包含定义、示例及反驳方法: 一、形式逻辑谬误 形式逻辑谬误是因推理结构错误导致的无效论证,常见于演绎推理(如三段论、假言推理等)。其核心问题在于逻辑形式不成立。 1. 否定前件谬误(Denying the Antecedent) 定义:在假言命题中,错误地通过否定前件推出否定后件。 逻辑形式:若 P ➛ Q ,则非 P ➛ 非 Q。 示例: 前提:“如果下雨(P),地面会湿(Q)。” 错误推理:“今天没下雨(非 P),所以地面不湿(非 Q)。” 反驳: 指出结构漏洞:“即使不下雨,地面也可能因洒水车或水管漏水而湿。” 2. 肯定后件谬误(Affirming the Consequent) 定义:在假言命题中,错误地通过肯定后件推出肯定前件。 逻辑形式:若 P ➛ Q ,则 Q ➛ P。 示例: 前提:“如果一个人中毒(P),他会呕吐(Q)。” 错误推理:“他呕吐了(Q),所以他中毒了(P)。” 反驳: 列举其他可能性:“呕吐可能由食物过敏、晕车或怀孕引起,未必是中毒。” 3. 假两难推理(False Dilemma) 定义:将复杂问题简化为非此即彼的二元选择,忽略中间选项。 示例: “要么完全禁止社交媒体,要么放任虚假信息泛滥!” 反驳: 提出中间方案:“可以加强内容审核算法或提升用户媒介素养,而非极端选择。” 二、非形式逻辑谬误 非形式逻辑谬误是因论证内容或语境缺陷导致的错误,涉及语义模糊、证据不足或情感操控等。 1. 诉诸无知(Appeal to Ignorance) 定义:以“无法证伪”作为“为真”的依据(或反之)。 示例: “科学无法解释意识起源,所以灵魂一定存在!” 反驳: 明确举证责任:“科学未解释 ≠ 你的假设成立。需提供灵魂存在的直接证据。” 2. 循环论证(Circular Reasoning) 定义:结论被隐含在前提中,实质是同义重复。 示例: ...

三月 17, 2025 · 林墨瀚

纠错日记

这几天忙着配置网站,总结了一下 debug 过程中遇到的问题和排查流程: 检查权限:确保权限设置为 775。 确认 Node.js 版本:确保版本匹配,避免兼容性问题。 清理浏览器缓存:防止缓存导致的页面异常。 检查域名部署:确保域名已正确解析并部署到服务器上。 每一步看似简单,但往往就是这些小细节决定了最终能否顺利运行。

三月 11, 2025 · 林墨瀚

参赛作品:算法与数据可视化工具集

说明 1. 累加计算器与可视化程序 功能描述: 该程序实现了一个简单的累加计算器,用户可以输入一个最小值和一个最大值,程序会计算从最小值到最大值的累加和并可视化累加过程。程序通过条形图展示每步累加的过程,实时显示累加结果。 主要流程: 用户输入最小值和最大值。 程序判断输入是否有效(最小值小于最大值)。 从最小值到最大值进行累加,并实时更新累加结果。 通过条形图可视化每一步的累加过程,展示当前最小值和当前累加和。 计算完成后,通过弹窗显示最终的累加结果。 使用说明: 在文本框中输入最小值和最大值。 点击“计算并可视化”按钮,程序会计算并显示累加结果。 程序会弹出对话框显示累加结果,并展示累加过程的条形图。 代码注释: entry_min 和 entry_max 是用户输入最小值和最大值的文本框。 plt.bar() 用于绘制条形图,显示累加过程。 messagebox.showinfo() 和 messagebox.showerror() 用于弹窗显示信息。 2. 选择排序与可视化程序 功能描述: 该程序实现了选择排序算法,并通过图形化界面展示每一步的排序过程。用户可以看到选择排序如何通过不断选择最小值并交换位置,逐步将数组排序完成。 主要流程: 随机生成一个包含 50 个元素的数组。 使用选择排序算法对数组进行排序,实时更新数组状态。 每次交换时,通过条形图展示当前数组状态,突出显示当前最小值、遍历元素以及已排序部分。 排序完成后,通过可视化展示排序结果。 使用说明: 程序会自动生成一个随机数组,并进行选择排序。 排序过程中会实时绘制条形图,显示每一步的排序情况。 排序完成后,会显示最终的排序结果。 代码注释: plt.bar() 用于绘制条形图,显示数组的排序过程。 plt.clf() 用于清除之前的绘图,准备更新图表。 plt.pause() 用于控制动画的更新速度,使排序过程可视化。 3. 插入排序与可视化程序 功能描述: 该程序实现了插入排序算法,并通过图形化界面展示每一步的排序过程。用户可以看到插入排序如何通过逐个插入元素到已排序部分,逐步将数组排序完成。 主要流程: 随机生成一个包含 50 个元素的数组。 使用插入排序算法对数组进行排序,实时更新数组状态。 每次插入元素时,通过条形图展示当前数组状态,突出显示当前插入元素及已排序部分。 排序完成后,通过可视化展示排序结果。 使用说明: 程序会自动生成一个随机数组,并进行插入排序。 排序过程中会实时绘制条形图,显示每一步的排序情况。 排序完成后,会显示最终的排序结果。 代码注释: plt.bar() 用于绘制条形图,显示数组的排序过程。 plt.clf() 用于清除之前的绘图,准备更新图表。 plt.pause() 用于控制动画的更新速度,使排序过程可视化。 4. 累加计算器(带最小值和最大值输入) 功能描述: ...

三月 5, 2025 · 林墨瀚

STL

STL(Standard Template Library,标准模板库)是 C++ 标准库的一部分,提供了一组通用的、可重用的模板类和算法,用于处理常见的数据结构和操作。STL 通过提供高效、抽象和灵活的代码,使开发者能够更快速地实现常见的数据操作,而无需从头开始编写底层实现。 STL 的核心包括三部分: 容器(Containers):用于存储和管理数据的对象。 算法(Algorithms):用于操作容器数据的函数。 迭代器(Iterators):用于遍历容器的对象,类似于指针。 函数对象(Function Objects):即可调用的对象,通常与算法结合使用。 STL 的主要组成部分: 1. 容器(Containers) 容器是用于存储数据的对象,它们通过提供不同的结构(如数组、链表、哈希表等)来管理数据。STL 提供了多种容器,主要分为以下几类: 顺序容器(Sequence Containers): 这些容器按顺序存储元素,元素的位置由其在容器中的位置决定。 常见的顺序容器包括: vector:动态数组,支持快速随机访问和尾部插入/删除。 deque:双端队列,支持在两端进行高效的插入和删除。 list:双向链表,支持高效的在两端进行插入和删除,但不支持快速随机访问。 array:固定大小的数组,大小在编译时确定,提供常数时间的元素访问。 关联容器(Associative Containers): 这些容器根据元素的键值对存储元素,并支持高效的键查找。 常见的关联容器包括: set:集合,不允许重复元素,自动排序。 map:映射,存储键值对(key-value),键不重复,自动排序。 multiset:多重集合,允许重复元素,自动排序。 multimap:多重映射,允许键重复,自动排序。 无序容器(Unordered Containers): 这些容器基于哈希表实现,提供常数时间的查找操作,但元素没有顺序。 常见的无序容器包括: unordered_set:无序集合,基于哈希表存储元素。 unordered_map:无序映射,基于哈希表存储键值对。 2. 算法(Algorithms) STL 提供了大量的算法,涵盖了排序、查找、修改等常见操作。STL 算法是泛型的,即它们可以与任何符合要求的容器配合使用。常见的算法包括: 排序算法:sort、stable_sort、partial_sort 等。 查找算法:find、binary_search、lower_bound、upper_bound 等。 修改算法:reverse、rotate、swap 等。 数值算法:accumulate、count、fill 等。 集合操作:set_union、set_intersection、set_difference 等。 这些算法通常都接受迭代器作为参数,使它们与各种容器兼容。 3. 迭代器(Iterators) 迭代器是 STL 中用于遍历容器的对象,它们可以看作是容器的指针。通过迭代器,算法可以访问容器中的元素,而不关心容器的具体实现。迭代器的类型有: 输入迭代器(Input Iterator):只能进行单向读取。 输出迭代器(Output Iterator):只能进行单向写入。 前向迭代器(Forward Iterator):可以进行多次读取和写入,支持双向遍历。 双向迭代器(Bidirectional Iterator):除了前向迭代,还可以进行反向迭代。 随机访问迭代器(Random Access Iterator):支持随机访问,可以直接跳转到容器中的任何位置。 4. 函数对象(Function Objects) 函数对象是实现了 operator() 的对象,它们可以像普通函数一样被调用。STL 算法广泛使用函数对象来指定排序规则、判断条件等。例如: ...

二月 18, 2025 · 林墨瀚

Vector 向量

Vector 是一种动态数组,属于 STL 中的容器类之一,它提供了可以自动扩展大小的数组功能。与传统的数组不同,Vector 在需要时会自动调整大小,且支持快速访问、插入、删除等操作。 Vector 的特点: 动态大小:Vector 的大小是动态变化的,可以根据需要自动增长或缩小,避免了固定大小数组的限制。 按索引访问:可以像数组一样使用索引来访问 Vector 中的元素。 存储效率:Vector 会根据需要自动调整内部存储的大小,通常会预留一些额外空间来减少频繁的内存重新分配。 随机访问:支持快速随机访问,时间复杂度为 O(1),就像数组一样。 支持常见操作:Vector 提供了许多常见的操作,如插入、删除、查找、排序等。 Vector 的优点: 灵活性:不需要事先知道容器的大小,可以动态调整。 效率:与数组相比,Vector 在元素插入和删除时表现更好,尤其是插入到末尾时。 内存管理:Vector 会根据需要自动调整内部容量,减少了内存浪费。 Vector 的缺点: 在中间插入和删除效率低:虽然插入和删除操作通常很高效,但在 Vector 中间插入或删除元素时,可能需要移动大量的元素,时间复杂度为 O(n)。 内存重新分配:当 Vector 需要扩展容量时,它会分配一个新的更大的数组,并将元素复制到新的位置,这个过程可能会影响性能,尤其是元素较多时。 Vector 的常见操作: 访问元素:使用索引或迭代器访问元素。 插入元素:可以在末尾插入元素,或者在指定位置插入。 删除元素:可以删除指定位置的元素,或者删除末尾的元素。 查找元素:可以使用 find 等函数查找元素。 示例(C++): #include <iostream> #include <vector> // 引入vector头文件 using namespace std; int main() { // 创建一个空的vector vector<int> vec; // 在末尾插入元素 vec.push_back(10); // 插入10 vec.push_back(20); // 插入20 vec.push_back(30); // 插入30 // 访问元素 cout << "第一个元素: " << vec[0] << endl; // 输出: 10 cout << "第二个元素: " << vec.at(1) << endl; // 输出: 20 // 删除末尾元素 vec.pop_back(); // 删除30 // 遍历vector cout << "Vector中的元素: "; for (int i = 0; i < vec.size(); i++) { cout << vec[i] << " "; // 输出: 10 20 } cout << endl; // 在指定位置插入元素 vec.insert(vec.begin() + 1, 15); // 在第二个位置插入15 cout << "插入15后的元素: "; for (int i = 0; i < vec.size(); i++) { cout << vec[i] << " "; // 输出: 10 15 20 } cout << endl; return 0; } 代码解析: push_back:将元素添加到 Vector 的末尾。 vec[0] 或 vec.at(1):通过索引或 at 方法访问元素,at 方法会进行越界检查。 pop_back:删除 Vector 末尾的元素。 insert:在指定位置插入元素,vec.begin() + 1 表示第二个位置。 Vector 的应用: 动态数组:Vector 主要用于处理不知道大小的动态数据集合,且需要快速随机访问和插入删除操作。 实现其他数据结构:Vector 可以用来实现栈、队列等数据结构。例:栈可以用 push_back 添加元素,使用 pop_back 删除元素。 数据存储:适用于存储需要动态增加或删除元素的场景,如图像数据、传感器数据等。 算法优化:由于 Vector 支持高效的随机访问,它常用于一些需要按位置访问元素的算法,如快速排序、查找算法等。 总结: Vector 是一个灵活且高效的动态数组容器,广泛应用于需要动态管理数据的场景。相比传统数组,它提供了更好的内存管理和扩展能力,适用于频繁添加元素的场合。 ...

二月 18, 2025 · 林墨瀚

队列 Queue

队列(Queue)是一种线性数据结构,它遵循“先进先出”(FIFO,First In First Out)原则,即第一个进入队列的元素将是第一个被移除的元素。队列的结构像排队的人群,先到的人先得到服务。 队列的特点: 先进先出(FIFO):队列的访问顺序遵循先进先出的原则,最先加入的元素最先被移除。 操作限制:队列只允许在一端进行插入操作(称为队尾),只允许在另一端进行删除操作(称为队头)。 存储结构:队列可以使用数组或链表来实现。 队列的常见操作: enqueue(入队):将元素加入到队列的尾部。 dequeue(出队):从队列的头部移除元素,并返回该元素的值。 peek(或 front):查看队列头部的元素,但不移除它。 isEmpty:检查队列是否为空。 size:返回队列中元素的个数。 队列的应用: 任务调度:操作系统使用队列来管理进程调度。队列用于存放等待执行的任务,系统会按照先进先出的顺序执行这些任务。 打印队列:在打印机中,打印任务通常以队列的形式管理。先发出的打印任务先被处理。 消息队列:在计算机网络和多线程编程中,消息队列常用来管理不同任务或进程间的通信。消息按照先进先出的顺序进行传递。 广度优先搜索(BFS):广度优先搜索算法(BFS)使用队列来遍历图或树的节点。 队列的示例(C++): #include <iostream> #include <queue> // 引入queue头文件 using namespace std; int main() { queue<int> q; // 创建一个空队列 // enqueue操作:入队 q.push(10); // 入队10 q.push(20); // 入队20 q.push(30); // 入队30 // peek操作:查看队头元素 cout << "队头元素: " << q.front() << endl; // 输出: 10 // dequeue操作:出队 q.pop(); // 出队10 // 打印队头元素 cout << "出队后, 队头元素: " << q.front() << endl; // 输出: 20 // isEmpty操作:检查队列是否为空 if (!q.empty()) { cout << "队列不是空的!" << endl; // 输出: 队列不是空的! } // size操作:获取队列的大小 cout << "队列的大小: " << q.size() << endl; // 输出: 2 return 0; } 代码解析: push:将元素添加到队列的尾部。 front:查看队列头部元素的值。 pop:从队列的头部移除元素。 empty:检查队列是否为空。 size:返回队列中的元素个数。 队列的优缺点: 优点: ...

二月 18, 2025 · 林墨瀚

二叉树 Binary Tree

二叉树是一种每个节点最多有两个子节点的树形数据结构。它是一种广泛应用于计算机科学中的数据结构,常用于表达层次结构、实现查找、排序、表达式求值等任务。 二叉树的基本定义: 节点:二叉树中的每个元素,通常包含数据和指向其子节点的指针。 根节点(Root):二叉树的第一个节点,是树的起点。 子节点(Children):每个节点可以有最多两个子节点,分别是左子节点和右子节点。 叶节点(Leaf):没有任何子节点的节点,也叫终端节点。 父节点(Parent):指向某一节点的上级节点。 深度(Depth):从根节点到该节点的路径长度。 高度(Height):从该节点到最远叶子节点的路径长度。 二叉树的基本类型 满二叉树(Full Binary Tree): 每个节点要么没有子节点,要么有两个 子节点。 即,除了叶节点外,其他每个节点都有两个子节点。 完全二叉树(Complete Binary Tree): 除了最后一层外,每一层的节点都尽可能多,且最后一层的节点都集中在左侧。 平衡二叉树(Balanced Binary Tree): 任意节点的左右子树的高度差不超过 1。 例如,AVL树和红黑树是常见的平衡二叉树。 二叉搜索树(Binary Search Tree, BST): 对于树中的每一个节点,左子树的所有节点的值小于该节点的值,右子树的所有节点的值大于该节点的值。 二叉搜索树提供了高效的查找、插入和删除操作。 赫夫曼树(Huffman Tree): 一种带权路径长度最短的二叉树,常用于数据压缩算法中。 二叉树的基本操作 二叉树的操作主要包括遍历、插入、删除、查找等。下面介绍常见的操作: 1. 遍历(Traversal) 遍历是指按照一定的顺序访问二叉树的每个节点。常见的遍历方式有: 前序遍历(Pre-order Traversal):访问根节点,然后递归遍历左子树和右子树。 顺序:根 → 左 → 右 示例:A, B, D, E, C, F 中序遍历(In-order Traversal):递归遍历左子树,然后访问根节点,最后递归遍历右子树。 顺序:左 → 根 → 右 示例:D, B, E, A, F, C 后 序遍历(Post-order Traversal):递归遍历左子树和右子树,然后访问根节点。 顺序:左 → 右 → 根 示例:D, E, B, F, C, A 层次遍历(Level-order Traversal):逐层遍历树的节点,通常使用队列实现。 顺序:从根节点开始,逐层访问每个节点。 示例:A, B, C, D, E, F 2. 插入(Insertion) 二叉搜索树的插入:在二叉搜索树中插入节点时,根据节点的值与当前节点的值进行比较,决定将新节点插入到左子树还是右子树,直到找到合适的位置。 3. 删除(Deletion) 删除节点:删除二叉树中的节点时,需要考虑三种情况: 没有子节点(叶节点):直接删除。 有一个子节点:用子节点替代被删除的节点。 有两个子节点:通常用右子树中的最小节点或左子树中的最大节点来替代被删除的节点,然后递归删除替代节点。 4. 查找(Search) 二叉搜索树的查找:从根节点开始,根据查找的值与当前节点的值比较,决定是向左子树还是右子树查找,直到找到该节点或遍历完整棵树。 二叉树的应用: * / \ + + / \ / \ a b c d 表达式树:用于表示数学表达式。在计算机科学中,表达式树是二叉树的一种应用,其中每个叶子节点代表操作数,每个非叶子节点代表操作符。 例如:(a + b) * (c + d) 可以表示为以上代码块。 二叉查找树的应用: 高效查找:在二叉查找树中,查找操作的平均时间复杂度是 O(log n),比线性查找 O(n) 更高效。 排序:通过中序遍历二叉搜索树可以得到一个有序序列,从而实现排序。 堆(Heap): 二叉堆是一种特殊的二叉树,常用于实现优先队列。二叉堆有两种:最大堆和最小堆。最大堆的父节点的值大于等于子节点的值,最小堆则相反。 二叉树的平衡:AVL 树、红黑树等自平衡二叉树可以用于实现高效的查找、插入和删除操作,广泛应用于数据库索引、内存管理等领域。 二叉树的优缺点: 优点: ...

二月 18, 2025 · 林墨瀚

哈希表 Hash Table

哈希表(Hash Table)是一种用于存储键值对(key-value pairs)的数据结构,它可以实现非常高效的查找、插入和删除操作。哈希表通过哈希函数将键映射到哈希表的索引位置,从而能在常数时间 O(1) 内完成查找、插入和删除操作。 哈希表的基本原理 哈希函数:哈希表通过哈希函数将键(key)映射到哈希表中的一个特定位置(即索引)。哈希函数的目标是尽可能将不同的键映射到不同的索引,以减少冲突。 哈希函数的一种常见实现方法是:hash(key) = key % table_size,这里的 table_size 是哈希表的大小。 冲突(Collision):不同的键可能通过哈希函数计算后得到相同的哈希值,从而映射到同一个位置,这时就发生了冲突。解决冲突的常见方法有: 链式地址法(Separate Chaining):每个哈希表的槽位(bucket)存储一个链表,冲突的元素存储在同一个链表中。 开放定址法(Open Addressing):当发生冲突时,哈希表会尝试在表中寻找其他空位,直到找到一个空槽。 负载因子(Load Factor):负载因子是哈希表中的元素数量与哈希表大小的比值,用来衡量哈希表的填充程度。负载因子过高可能导致哈希冲突增多,从而降低性能。通常当负载因子超过某个阈值时,会对哈希表进行扩容(rehash)。 扩容与再哈希(Rehashing):当哈希表的负载因子超过设定的阈值时,可以通过增加哈希表的大小并重新计算每个元素的哈希值来减少冲突,从而提高查找效率。 哈希表的基本操作 插入(Insert): 使用哈希函数计算键的哈希值,并将其映射到对应的槽位。 如果槽位已被占用,则根据解决冲突的方法(链式或开放定址法)处理冲突。 查找(Search): 使用哈希函数计算键的哈希值,然后检查该槽位是否存在对应的值。如果有冲突,则需要继续检查链表或其他空槽。 删除(Delete): 使用哈希函数找到元素的位置,并删除它。如果存在冲突(如链式法),需要相应地删除链表中的元素。 扩容(Rehashing): 当哈希表的负载因子过高时,哈希表的大小需要扩大,通常是扩展为原来大小的两倍,然后重新计算每个元素的哈希值并重新插入。 哈希表的优缺点 优点: 快速查找:在理想情况下,哈希表的查找、插入和删除操作的时间复杂度是 O(1),即常数时间操作。 高效存储:由于哈希表通过哈希函数直接映射到槽位,因此可以在大规模数据存储时提供高效的访问。 缺点: 哈希冲突:当多个键映射到同一个位置时,会发生哈希冲突。虽然可以通过链式或开放定址法解决冲突,但会影响操作效率。 不适合排序:哈希表中的元素没有顺序,不能直接支持按键排序。 空间开销:哈希表为了避免过多冲突,通常需要比实际存储元素更多的空间。这可能导致内存浪费。 哈希表的应用 快速查找:哈希表用于实现快速查找功能,广泛应用于数据库索引、缓存系统等。 去重:通过将元素作为键存储,哈希表可以方便地去除重复元素。例如,在处理大数据时,通过哈希表去除重复记录。 计数统计:哈希表可以用于统计每个元素出现的次数,广泛应用于词频统计、数据分析等。 实现集合操作:哈希表常常用于实现集合(set)的操作,如交集、并集、差集等。 哈希表的示例实现(C++) #include <iostream> #include <list> #include <vector> using namespace std; class HashTable { private: vector<list<int>> table; // 哈希表的桶(使用链表处理冲突) int size; // 哈希表的大小 int hashFunction(int key) { return key % size; // 哈希函数(取模) } public: HashTable(int s) { size = s; table.resize(s); // 初始化哈希表 } // 插入操作 void insert(int key) { int index = hashFunction(key); // 计算哈希值 table[index].push_back(key); // 在对应的槽位插入键 } // 查找操作 bool search(int key) { int index = hashFunction(key); for (int element : table[index]) { if (element == key) { return true; // 找到元素 } } return false; // 未找到元素 } // 删除操作 void remove(int key) { int index = hashFunction(key); table[index].remove(key); // 从链表中删除元素 } // 打印哈希表 void display() { for (int i = 0; i < size; i++) { cout << "Bucket " << i << ": "; for (int element : table[i]) { cout << element << " "; } cout << endl; } } }; int main() { HashTable ht(7); // 创建一个大小为7的哈希表 ht.insert(10); ht.insert(20); ht.insert(15); ht.insert(7); ht.insert(30); ht.display(); // 显示哈希表 cout << "Search 15: " << (ht.search(15) ? "Found" : "Not Found") << endl; cout << "Search 25: " << (ht.search(25) ? "Found" : "Not Found") << endl; ht.remove(15); cout << "After deleting 15:" << endl; ht.display(); // 删除元素后显示哈希表 return 0; } 代码解析 HashTable 类:包含了哈希表的基本操作,如插入、查找、删除和显示哈希表。 insert():通过哈希函数计算插入元素的槽位,然后将元素插入到该槽位的链表中。 search():通过哈希函数找到槽位后,遍历链表检查是否有该元素。 remove():通过哈希函数找到槽位,并从该槽位的链表中删除元素。 display():显示哈希表的所有槽位及其存储的元素。 总结 哈希表是一种高效的键值对存储数据结构,能够提供常数时间复杂度的查找、插入和删除操作。它广泛应用于需要高效查找、去重和计数的场景。虽然哈希表在实际应用中需要解决哈希冲突问题,但通过合适的冲突解决方法,可以保证其高效性。 ...

二月 18, 2025 · 林墨瀚

链表 Linked List

链表是一种线性数据结构,它由多个节点(Node)组成,每个节点包含两部分: 数据部分:存储实际的数据。 指针部分:存储指向下一个节点的地址。 链表的结构特点是节点在内存中不一定是连续存储的,而是通过指针将一个个节点连接起来。每个节点可以通过指针找到下一个节点,形成一个链式结构。 链表的类型: 单向链表(Singly Linked List):每个节点只包含指向下一个节点的指针,最后一个节点的指针指向 null。 双向链表(Doubly Linked List):每个节点包含两个指针,分别指向前一个节点和下一个节点,便于从两端遍历。 循环链表(Circular Linked List):链表的最后一个节点的指针指向第一个节点,形成一个环。 链表的特点: 动态大小:链表大小可以动态调整,元素可以随时添加或删除,且不需要提前指定大小。 插入和删除操作高效:链表在任何位置的插入和删除都比数组更高效,因为只需要改变指针的指向即可。 访问不方便:链表的元素不是连续存储的,访问某个特定元素时需要从头遍历链表,效率较低。 链表的缺点: 随机访问差:在链表中,访问某个元素需要从头节点开始逐个遍历,无法像数组那样通过索引直接访问,效率较低。 额外内存开销:每个节点都需要额外的指针空间,增加了内存的消耗。 复杂的实现和维护:链表的插入、删除、遍历等操作涉及到指针操作,相比数组实现较复杂。 链表的操作: 插入操作:在链表的任意位置插入一个新节点,需要更新指针的连接。 删除操作:删除链表中的某个节点时,更新指针,使其跳过被删除的节点。 遍历操作:从头节点开始,顺序访问链表中的所有节点。 查找操作:遍历链表查找特定元素(通常时间复杂度是 O(n))。 示例(C++): #include <iostream> using namespace std; // 定义链表节点结构 struct Node { int data; // 存储数据 Node* next; // 指向下一个节点的指针 }; // 插入节点 void insert(Node*& head, int value) { Node* newNode = new Node(); // 创建新节点 newNode->data = value; // 设置数据 newNode->next = head; // 新节点指向当前头节点 head = newNode; // 更新头节点 } // 打印链表 void printList(Node* head) { Node* temp = head; while (temp != nullptr) { cout << temp->data << " "; temp = temp->next; } cout << endl; } int main() { Node* head = nullptr; // 空链表 // 插入元素 insert(head, 10); insert(head, 20); insert(head, 30); // 打印链表 printList(head); // 输出: 30 20 10 return 0; } 在这个例子中,我们定义了一个链表节点 Node,每个节点包含一个 data 数据部分和一个 next 指针,指向下一个节点。通过 insert 函数将元素插入链表头部,并通过 printList 函数遍历链表输出元素。 ...

二月 18, 2025 · 林墨瀚

数组 array

数组的特点: 固定大小:数组在创建时必须指定大小,一旦定义,大小不能更改。这意味着,如果需要存储更多的数据,必须创建一个新的数组并复制数据。 元素类型相同:数组中的所有元素必须是相同类型的数据(如整数、字符、浮点数等)。 按索引访问:数组的每个元素都有一个唯一的索引,索引从0开始,允许直接访问任何元素。 连续内存:数组的元素在内存中是连续存储的,这使得数组具有较快的访问速度。 数组的缺点: 固定大小:数组一旦创建,其大小是固定的,无法动态扩展。如果数据量超出了原先设定的大小,必须重新分配更大的数组并复制数据,效率较低。 浪费内存:如果你事先定义的数组大小过大,可能会浪费内存空间;如果大小过小,可能会导致无法存储所有数据。 插入和删除效率低:数组中的元素是连续存储的,因此在数组中间插入或删除元素时,需要移动大量的数据,效率较低。 示例(C++): #include <iostream> using namespace std; int main() { // 声明并初始化一个包含5个整数的数组 int arr[5] = {1, 2, 3, 4, 5}; // 访问数组元素 cout << "数组的第一个元素: " << arr[0] << endl; // 输出 1 // 修改数组元素 arr[2] = 10; // 将数组中第三个元素修改为 10 // 输出修改后的数组元素 cout << "修改后的第三个元素: " << arr[2] << endl; // 输出 10 return 0; } 数组的操作: 访问元素:通过索引访问数组中的元素。例如,arr[3] 访问数组 arr 的第四个元素(索引从0开始)。 修改元素:通过指定索引修改数组中的元素。例如,arr[1] = 20 将数组中第二个元素的值改为20。 遍历数组:通过循环遍历数组中的每个元素。例如,使用 for 循环来访问所有元素。 for (int i = 0; i < 5; i++) { cout << arr[i] << " "; } 数组初始化:在声明时,你可以通过花括号 {} 初始化数组,或者使用循环动态赋值。 数组的应用: 存储一组相同类型的数据:最常见的应用是存储同类型的数据集合,例如学生的成绩、多个传感器的测量值等。 例:存储班级学生的数学成绩。 int scores[30]; // 存储30个学生的成绩 实现栈和队列:通过数组可以实现栈(后进先出)和队列(先进先出)的数据结构。 例:使用数组来实现一个队列: int queue[100]; int front = -1, rear = -1; 数据处理和分析:在数据科学和计算机科学中,数组广泛用于存储和处理数据。 例:用数组存储每日气温数据,进行温度变化分析。 排序和查找:数组是许多经典排序和查找算法(如冒泡排序、二分查找)的基础数据结构。 例:使用数组进行排序: for (int i = 0; i < 5; i++) { for (int j = 0; j < 5 - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); } } } 图像处理:在图像处理中,像素值通常以二维数组的形式存储,用于表示图像中的颜色和亮度。 例:一个图像的RGB像素值可以用二维数组表示。 int image[100][100][3]; // 100x100像素的图像,每个像素有RGB三个值

二月 18, 2025 · 林墨瀚

栈 Stack

栈(Stack)是一种线性数据结构,它遵循“后进先出”(LIFO,Last In First Out)原则,即最后加入的元素最先被取出。栈通常用于实现需要“反向”操作的算法和数据结构。 栈的特点: 后进先出(LIFO):栈的访问顺序遵循后进先出的原则,最后插入的元素最先被移除。 操作限制:栈只允许在一端进行插入和删除操作,这一端通常被称为栈顶(Top)。 动态增长:栈可以根据需要动态地扩展或收缩(对于动态数组实现的栈)。 存储结构:栈可以使用数组或链表来实现。 栈的常见操作: push:将一个元素添加到栈顶。 pop:移除栈顶元素,并返回该元素的值。 peek(或 top):查看栈顶元素的值,但不移除它。 isEmpty:检查栈是否为空。 size:返回栈中元素的个数。 栈的应用: 递归调用的实现:计算机的函数调用使用栈来管理函数的调用和返回。当一个函数调用时,它的返回地址和局部变量会被压入栈中,函数执行完毕后从栈中弹出返回地址。 表达式求值:栈常用于表达式的求值和解析,特别是在中缀表达式、后缀表达式和前缀表达式的转换和计算中。 深度优先搜索(DFS):栈用于深度优先搜索(DFS)算法,通过栈记录搜索路径,实现图的遍历。 回溯算法:在回溯算法中,栈用于记录当前的选择和路径,便于回退到上一步状态。 栈的示例(C++): #include <iostream> #include <stack> // 引入stack头文件 using namespace std; int main() { stack<int> st; // 创建一个空栈 // push操作:压栈 st.push(10); // 压入10 st.push(20); // 压入20 st.push(30); // 压入30 // peek操作:查看栈顶元素 cout << "栈顶元素: " << st.top() << endl; // 输出: 30 // pop操作:弹出栈顶元素 st.pop(); // 弹出30 // 打印栈顶元素 cout << "弹出栈顶元素后, 栈顶元素: " << st.top() << endl; // 输出: 20 // isEmpty操作:检查栈是否为空 if (!st.empty()) { cout << "栈不是空的!" << endl; // 输出: 栈不是空的! } // size操作:获取栈的大小 cout << "栈的大小: " << st.size() << endl; // 输出: 2 return 0; } 代码解析: push:将元素压入栈中。 top:查看栈顶元素的值。 pop:移除栈顶元素。 empty:检查栈是否为空。 size:返回栈的元素数量。 栈的优缺点: 优点: 时间效率高:栈的插入和删除操作通常为 O(1) 时间复杂度,因此在许多需要频繁插入和删除操作的场景中,栈非常高效。 简单的操作:栈的操作非常简单,只需要知道栈顶元素,并根据栈顶进行插入或删除操作。 缺点: 访问受限:栈只允许访问栈顶的元素,不能直接访问栈中的其他元素,因此它并不适用于需要随机访问元素的场景。 空间限制:栈的大小是有限的,如果栈中的元素太多,可能会发生栈溢出(尤其是递归调用时,栈空间可能会用尽)。 栈的应用实例: 括号匹配:栈可以用于检测表达式中的括号是否匹配,特别是在编译器中,栈用于解析代码中的括号、花括号、方括号等。 Undo(撤销)操作:在许多应用程序中,撤销功能通常是通过栈来实现的,每次执行操作时,将当前状态压入栈中,撤销时从栈中弹出之前的状态。 表达式求值:栈用于处理中缀、前缀、后缀表达式的转换和计算。 总结: 栈是一种常见的线性数据结构,具有后进先出的特点,广泛应用于各种计算机科学和算法问题中。它的简单性和高效性使其成为实现许多算法和数据结构的基础。在需要跟踪状态或执行回溯操作的场景中,栈是非常有用的工具。 ...

二月 18, 2025 · 林墨瀚

算法题类型

1. 排序与查找 排序问题:将一组数据按照特定顺序(通常是升序或降序)排列。 基础排序:简单直观,适用于小规模数据。 冒泡排序:通过不断交换相邻元素进行排序。(稳定排序) 选择排序:每次选择未排序部分的最小元素放到已排序部分的末尾。(不稳定排序) 插入排序:将每个元素插入到已排序部分的正确位置。(稳定排序) 高级排序:效率较高,适用于大规模数据。 快速排序:通过分治法和基准元素进行排序。(不稳定排序) 归并排序:通过分治法将数据分成小份排序,然后合并。(稳定排序) 堆排序:利用堆这种数据结构进行排序。(不稳定排序) 线性排序:时间复杂度为O(n),适用于特定类型的数据。 计数排序:适用于数据范围较小的整数。(稳定排序) 桶排序:将数据分到不同的桶中,然后分别排序。(稳定排序) 基数排序:按位进行排序,适用于整数或字符串。(稳定排序) 特殊排序:针对特定场景的排序算法。 外部排序:用于处理无法一次性加载到内存的数据。 查找问题:在数据集中寻找特定元素。 线性查找:逐个遍历数据,直到找到目标元素。(时间复杂度O(n)) 二分查找:在已排序数据中,通过不断缩小搜索范围来查找目标元素。(时间复杂度O(log n)) 二分查找变种:查找第一个大于等于目标值的元素、查找最后一个小于等于目标值的元素等。 矩阵中的目标值查找:在二维数组中查找目标元素。 2. 数学与数论 基础数学: 素数判断:判断一个数是否为素数(只能被1和自身整除的数)。 试除法:通过尝试除以小于等于其平方根的所有整数来判断。(时间复杂度O(sqrt(n))) 埃拉托斯特尼筛法:一种高效的筛选素数的方法。(时间复杂度O(n log log n)) 最大公约数与最小公倍数: 欧几里得算法:用于计算两个整数的最大公约数。(时间复杂度O(log(a, b))) 快速幂:高效计算大整数的幂。(时间复杂度O(log n)) 二进制转换: 二进制转十进制 十进制转二进制 数论:研究整数性质的数学分支。 同余问题: 线性同余方程:求解形如ax ≡ b (mod m)的方程。 模运算:求一个数除以另一个数的余数。 数论函数:定义在整数集合上的函数。 欧拉函数:计算小于等于n且与n互质的正整数个数。 莫比乌斯函数:一个数论函数,用于解决一些组合数学问题。 中国剩余定理:求解同余方程组。 矩阵快速幂:用于快速计算矩阵的幂。 组合数学:研究组合问题的数学分支。 组合数与杨辉三角: 组合数:从n个元素中选择k个元素的方案数。 杨辉三角:一个数表,用于计算组合数。 卡特兰数:一个出现在各种组合问题中的数列。 3. 动态规划 (Dynamic Programming, DP) 经典DP问题: 斐波那契数列:一个递归定义的数列,每个数是前两个数之和。(时间复杂度O(n)) 背包问题: 0/1背包:每个物品只能选择一次。(时间复杂度O(n*capacity)) 完全背包:每个物品可以选择多次。(时间复杂度O(n*capacity)) 多重背包:每个物品有有限个。(时间复杂度O(n_capacity_count)) 最长公共子序列(LCS):两个序列的最长公共子序列。(时间复杂度O(m*n)) 最长递增子序列(LIS):一个序列的最长递增子序列。(时间复杂度O(n log n)) 硬币换零钱问题:用最少的硬币凑出指定金额。(时间复杂度O(amount*n)) 矩阵链乘法:找到矩阵相乘的最佳顺序,使得运算次数最少。(时间复杂度O(n^3)) 其他DP问题: 编辑距离问题:将一个字符串转换为另一个字符串所需的最少操作次数。(时间复杂度O(m*n)) 打家劫舍问题:在不能偷窃相邻房屋的情况下,偷窃最多金额。(时间复杂度O(n)) 状态压缩DP:使用位运算来表示状态的动态规划。 数位DP:用于解决与数字位数有关的动态规划问题。 4. 贪心算法 经典贪心问题: 活动选择问题:选择最多的不重叠活动。(时间复杂度O(n log n)) 区间调度问题:选择最多的非重叠时间区间。(时间复杂度O(n log n)) 零钱兑换问题:用最少的硬币凑出指定金额。(不一定能找到最优解) 其他贪心问题: 最小生成树:找到连接所有顶点的最小权值边的集合。 Prim算法(时间复杂度O(n^2)) Kruskal算法(时间复杂度O(m log n)) 霍夫曼编码:用于数据压缩的编码方式。(时间复杂度O(n log n)) 分数背包问题:允许选择物品的一部分。(时间复杂度O(n log n)) Huffman树:一种用于霍夫曼编码的树结构。 5. 回溯法 (Backtracking) 经典回溯问题: 八皇后问题:在棋盘上放置八个皇后,使其互不攻击。(时间复杂度较高) N皇后问题:八皇后问题的推广。(时间复杂度较高) 全排列与组合:生成集合的所有排列和组合。(时间复杂度较高) 数独问题:填充数独游戏。(时间复杂度较高) 其他回溯问题: 矩阵路径问题:在矩阵中寻找满足条件的路径。(时间复杂度较高) 子集和问题:找到一个集合的子集,其元素之和等于目标值。(时间复杂度较高) 6. 分治法 (Divide and Conquer) 经典分治问题: 排序问题: 归并排序(时间复杂度O(n log n)) 快速排序(平均时间复杂度O(n log n),最坏时间复杂度O(n^2)) 逆序对计数:计算数组中的逆序对数。(时间复杂度O(n log n)) 最大子数组和问题:找到数组中和最大的连续子数组。(时间复杂度O(n)) 其他分治问题: 矩阵乘法: Strassen算法(时间复杂度O(n^log2(7))) 7. 图论 (Graph Theory) 图的遍历: 深度优先搜索(DFS):沿着图的深度方向遍历。(时间复杂度O(V+E)) 广度优先搜索(BFS):沿着图的宽度方向遍历。(时间复杂度O(V+E)) 最短路径问题: Dijkstra算法:用于计算非负权图的单源最短路径。(时间复杂度O(E log V)) Bellman-Ford算法:用于计算可以包含负权边的图的单源最短路径。(时间复杂度O(V*E)) Floyd-Warshall 算法:用于计算任意两点之间的最短路径。(时间复杂度O(V^3)) 最小生成树问题: Prim 算法(时间复杂度O(E log V)) Kruskal 算法(时间复杂度O(E log V)) 其他图论问题: 拓扑排序:对有向无环图进行排序,使得所有边都指向后面的顶点。(时间复杂度O(V+E)) 强连通分量:找到图中互相可达的顶点集合。(时间复杂度O(V+E)) 图的二分性判断:判断一个图是否为二分图。(时间复杂度O(V+E)) 网络流:研究网络中流量的分配问题。 二分图匹配:找到二分图中最大的匹配数。 8. 位运算 位运算技巧:直接对二进制位进行操作,效率高。 判断是否为 2 的幂:n & (n - 1) == 0 统计二进制中 1 的个数:Hamming Weight 交换两个数的值:a ^= b; b ^= a; a ^= b; 找出只出现一次的数字:利用异或运算的性质 计算二进制补码 位运算应用: 状态压缩:用二进制位表示状态 快速幂:利用位运算优化幂运算 9. 树与二叉树 二叉树的遍历: 前序、中序、后序遍历:递归或迭代方式访问所有节点 层次遍历:逐层访问节点 二叉树的性质: 判断是否为二叉搜索树:中序遍历结果为升序 二叉树的最大深度:递归或迭代方式求解 路径和问题:寻找满足条件的路径 其他树结构: 平衡二叉搜索树: AVL 树:保持左右子树高度差不超过 1 红黑树:一种自平衡的二叉搜索树 线段树:用于处理区间查询问题 字典树:用于存储和查找字符串 10. 并行与分布式算法 并行算法:利用多核处理器并行计算,提高效率。 并行排序问题 并行搜索问题 分布式算法:在分布式系统中运行的算法,解决大规模数据处理和存储问题。 MapReduce 问题:一种分布式计算框架 分布式一致性问题:Paxos、Raft 等协议 11. 高级数据结构 平衡树: AVL 树 红黑树 其他数据结构: 跳表:一种有序链表,支持快速查找 并查集(Union-Find):用于处理集合的合并和查询操作 LRU 缓存机制:最近最少使用缓存 堆(优先队列):一种特殊的树形数据结构,用于快速访问最大/最小值

二月 5, 2025 · 林墨瀚

算法自学笔记专栏总序

欢迎来到我的《算法自学笔记》专栏!这是我在自学算法过程中的一个学习日志,旨在记录并分享我对常见算法和数据结构的理解、学习过程中的思考以及探索到的有用资源。我希望通过这个专栏,能帮助自己更好地总结学习成果,同时为其他对算法感兴趣的同学提供一些启发和参考。 专栏内容概览 本专栏将涵盖以下几个主要方面: 基本数据结构:包括数组、链表、栈、队列、哈希表、树、图等常见数据结构。 排序与查找算法:涉及快速排序、归并排序、二分查找、线性查找等常用算法。 动态规划:深入讲解背包问题、最短路径问题、最长公共子序列等经典问题的解法。 图算法:介绍深度优先搜索、广度优先搜索、最小生成树、最短路径等图相关算法。 其他算法:包括贪心算法、回溯算法、分治算法等常见的高级算法。 专栏目标 整理学习笔记:将学习过程中遇到的关键点、算法实现和常见问题进行总结,并加以归纳整理。 分享学习经验:记录并分享自己的自学心得,帮助更多对算法感兴趣的学习者更高效地入门和进阶。 持续更新:随着学习的深入,我将持续更新专栏内容,不断补充新的算法和数据结构,力求使本专栏内容更加丰富和完善。 专栏发展方向 目前,我已完成了常见数据结构和部分经典算法的学习笔记。未来,我会继续扩展更深入的算法和数据结构内容,并根据实践经验不断优化和完善笔记。 此外,专栏的配套网站也在开发中,未来将提供更加直观的学习资料,并结合在线编程和算法演示,进一步增强互动性和学习体验。 如何使用本专栏 仓库地址 浏览笔记:可以随时浏览并参考专栏中的各类学习笔记,帮助你理解算法的核心思想及其实现方式。 贡献代码:如果你在某个算法或数据结构上有新的见解或改进,欢迎通过提交 Pull Request 参与贡献。 参与讨论:如果你在学习过程中有问题或想法,可以在 Issues 区域提出,我们一起讨论、解决。 关于我 我的背景 我是一个初二学生,虽然算法学习起步较晚,但我一直对编程和算法充满浓厚兴趣。如今,我正通过自学不断提升自己的算法水平,并希望能通过实践提升自己的编程能力。 为什么专注算法学习 在我的学习经历中,我曾对编程语言产生浓厚兴趣,并开始尝试学习一些编程语言。然而,老师建议我先专注于算法学习,因为掌握算法的核心思维将帮助我更好地理解编程语言的底层原理。因此,我决定将语言学习暂时搁置,集中精力攻克算法,培养扎实的逻辑思维能力。这一决定,也为我后续的编程之路打下了坚实的基础。 我的目标 我的目标是通过不断学习算法和数据结构,在信息奥赛(NOI)中争取获得省赛一等奖。我相信,通过扎实的算法基础,不仅能提升我的编程水平,也能帮助我在将来的编程竞赛中脱颖而出。 学习资源与参考资料 在这条自学算法的道路上,我参考了以下书籍和资料: 《hello 算法》:为初学者提供了很多实用的算法学习资料和代码示例,帮助我从零基础入门。 CS自学指南:为自学计算机科学的学生提供了清晰的学习路径,帮助我规划和组织学习内容。 HackWay技术学习路线:提供了从入门到进阶的技术学习路线,帮助我更系统地提升技能。 《算法基础:打开算法之门》:为算法入门提供了详细的讲解,帮助我理解基础概念。 《算法导论》:经典教材,深入探讨了算法设计和分析,是我的重要参考书。 《数据结构、算法与应用:C++语言描述》:结合C++语言讲解数据结构和算法的实现,帮助我理解实际应用。 许可证 此专栏内容采用 CC BY-NC-SA 4.0 许可证,您可以在非商业用途下使用和分享本专栏的内容,但需要注明出处,并且分享的内容须遵循相同的许可协议。 致谢 特别感谢所有在算法和编程领域做出贡献的前辈,正是因为有了他们的知识和经验,我们这些自学者才能更容易获得进步。希望通过我的努力,这些宝贵的知识能帮助更多的学习者。 这是我自学算法过程中的一个小小总结和记录,希望你在这个专栏中能收获知识与灵感。欢迎你在学习中提出建议,或者与我共同探讨算法的奥妙!

一月 21, 2025 · 林墨瀚

技研录,开站!

我创建这个网站的初衷源于对编程、语言学习和历史的浓厚兴趣。作为一名热衷于探索技术与人文领域的学生,我希望在这片数字空间里记录自己的成长与探索,并通过这个平台展示自己的编程项目和学习成果,同时与志同道合的人分享思考与见解。我的网站将展示我在编程、英语学习、日语以及历史研究等方面的积累与思考。从简单的代码到深刻的思辨,每一项成果都是我成长的一部分。希望你能在这里找到与你志同道合的灵感与启发。 人生是一段不断挑战自我、追求卓越的旅程。无论是编程的代码,语言的表达,还是历史的学习,每一步都需要耐心与坚持。通过这个平台,我希望与大家共同进步、互相激励。未来的路上,让我们继续学习、探索、不懈追求,成为更好的自己。 我的未来仍然很长,充满了无限的可能与机会。在这段不断学习与成长的旅程中,我相信每一次努力和突破,都会带来更多选择与机遇。无论是技术创新,还是人生的其他领域,我都愿意保持积极向上的心态,勇敢追逐梦想,迎接新的挑战。未来,我将继续积累经验、拓宽视野,把握每一个机会,成就更好的自己。

一月 20, 2025 · 林墨瀚