操作系统重点整理(全网搜集总结结尾附引用链接)

进程和线程区别

概述

看了一遍排在前面的答案,类似”进程是资源分配的最小单位,线程是CPU调度的最小单位“这样的回答感觉太抽象,都不太容易让人理解。

区别

做个简单的比喻:进程=火车,线程=车厢

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

百度

根本区别

根本区别:进程是资源分配最小单位,线程是程序执行的最小单位。 计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。

在这里插入图片描述

地址空间

地址空间:进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段;线程没有独立的地址空间,同一进程的线程共享本进程的地址空间。

资源拥有

资源拥有:进程之间的资源是独立的;同一进程内的线程共享本进程的资源。
在这里插入图片描述

执行过程

执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
在这里插入图片描述

单位

线程是处理机调度的基本单位,但是进程不是。由于程序执行的过程其实是执行具体的线程,那么处理机处理的也是程序相应的线程,所以处理机调度的基本单位是线程。
在这里插入图片描述

系统开销

系统开销:进程执行开销大,线程执行开销小。

死锁

条件

当且仅当以下四个条件同时成立时,死锁才会发生:

  • 互斥。同一个资源在同一时刻最多只能被一个进程占用。

  • 占有并等待。必然有一个进程至少占用了系统中的一个资源,同时在等待获取被其他进程占用的资源。

  • 不可剥夺。一个进程不能剥夺被其他进程占用的资源。

  • 循环等待。在等待图中有一个循环。

死锁的处理基本策略和常用方法。

解决死锁的基本方法如下:

预防死锁、避免死锁、检测死锁、解除死锁

预防死锁:

  • 资源一次性分配:(破坏请求和保持条件)

  • 可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)

  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁:

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。

检测死锁

首先为每个进程和每个资源指定一个唯一的号码;

然后建立资源分配表进程等待表

解除死锁:

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;

  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

解决四多的常用策略如下:

鸵鸟策略、预防策略、避免策略、检测与解除死锁

如果你对死锁的处理策略不是太熟悉,推荐阅读:产生死锁的原因和必要条件+解决死锁的基本方法https://blog.youkuaiyun.com/bxyill/article/details/8237339

进程(Linux)间的通信方式

1、管道

管道,英文为pipe。这是一个我们在学习Linux命令行的时候就会引入的一个很重要的概念。它的发明人是道格拉斯.麦克罗伊,这位也是UNIX上早期shell的发明人。他在发明了shell之后,发现系统操作执行命令的时候,经常有需求要将一个程序的输出交给另一个程序进行处理,也因此,管道应运而生了。
管道可以分为两类:匿名管道和命名管道。
常见的Linux命令 “|” 其实就是匿名管道,表示把一个进程的输出传输到另外一个进程

2、消息队列

注意,此消息队列不是我们常用的MQ,如kafka,rabbitmq,rocketmq等。
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
使用消息队列进行进程间通信,可能会收到数据块最大长度的限制约束等,这也是这种通信方式的缺点。如果频繁的发生进程间的通信行为,那么进程需要频繁地读取队列中的数据到内存,相当于间接地从一个进程拷贝到另一个进程,这需要花费时间。

3、共享内存

共享内存这个通信方式就可以很好着解决拷贝所消耗的时间了。系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块虚拟地址空间来,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但有一部分却是映射到相同的物理内存,这就完成了内存共享机制了。

4、信号量

共享内存最大的问题是什么?没错,就是多进程竞争内存的问题,就像类似于我们平时说的线程安全问题。如何解决这个问题?这个时候我们的信号量就上场了。
信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。

5、Socket

这个就是我们一直在用的进程间的通信方式了,如我们的微信APP跟微信服务器通信,其实就是使用的Socket套接字进行通信的。

https://www.cnblogs.com/happy4java/p/11343072.html

页式管理内存是指什么呢?

在分页系统下,一个程序发出的虚拟地址由两部分组成:页面号和页内偏移值。为了解决程序比内存大的问题,我们可以允许一个进程的部分虚拟页面存放在物理页面之外,也就是磁盘上。在需要访问这些外部虚拟页面时,再将其调入物理内存,需要腾出空间时,将暂时不同的内存swap到磁盘。

虚拟内存是指什么呢?

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换

虚拟内存到物理内存是怎么实现的呢?

总结: 对于一个内存地址转换,其实就是这样三个步骤:

  • 把虚拟内存地址,切分成页号和偏移量的组合;
  • 从页表里面,查询出虚拟页号,对应的物理页号;
  • 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

原文链接

线程和进程的区别是什么?

https://www.zhihu.com/question/25532384/answer/411179772

进程间通信方式总结

https://www.cnblogs.com/happy4java/p/11343072.html

常见面试题整理–操作系统篇(每位开发者必备)

https://zhuanlan.zhihu.com/p/23755202?refer=passer

其他博客

一篇让你明白进程与线程之间的区别与联系

https://www.cnblogs.com/coder-programming/p/10595804.html

进程与线程的区别?

https://jingyan.baidu.com/article/11c17a2cf213eff446e39d1d.html

进程和线程的区别

https://www.cnblogs.com/zhuzhu2016/p/5804875.html

<think>好的,我现在需要帮用户整理关于前缀树的知识点,针对他的情况:大二下学期,学过数据结构但基础薄弱,目标是能够做练习题。首先,用户之前的对话已经给出了一些建议,我需要根据他的当前需求进一步细化。 用户现在明确说自己没有教材,需要整合全网的内容,针对之前提到的核心知识点,整理出一个适合他当前水平的知识点列表。他的目标是能够进行练习,所以知识点需要结构清晰,由浅入深,覆盖必要的基础和常见应用场景。 首先,我需要回顾之前提到的知识点,确保不遗漏关键内容。之前提到的包括基础概念、操作、变种和应用。现在需要将这些内容更系统地组织,分成不同的学习阶段,适合基础较弱的用户。 用户的基础是学过数据结构但几乎零基础,所以需要从最基础的部分开始,逐步推进。可能需要先解释前缀树是什么,为什么使用它,然后结构设计,基本操作,再到优化和变种,最后练习题。 需要考虑用户使用C++,所以在知识点中需要强调C++相关的实现细节,比如指针管理、内存泄漏等。同时,练习题部分需要推荐适合他水平的LeetCode题目,并给出一些实现技巧。 另外,用户可能需要一些学习资源,比如中文的博客、视频教程,以及具体的代码示例。之前提到的资源如GeeksforGeeks、知乎文章、B站视频都需要再次推荐,并确保链接有效。 需要注意避免过于理论化的内容,保持实用,侧重应用和代码实现。例如,删除操作和压缩Trie可能对初学者有一定难度,可以放在进阶部分,先掌握基础插入、搜索和前缀匹配。 还要提醒用户注意常见错误,比如指针错误、节点共享问题,以及如何调试。这些实际编码中的问题对于提升代码能力很重要。 最后,练习题需要按照难度排序,从易到难,逐步提升。推荐的题目包括LeetCode 208、211、212等,需要给出链接和简要说明,帮助用户选择。 总结下来,结构应该是:核心概念 -> 节点结构 -> 基本操作 -> C++实现要点 -> 常见问题 -> 练习题推荐 -> 学习资源。每个部分需要简明扼要,适合用户当前水平,并帮助他逐步提升到能做练习题的程度。</think>好的!根据你的背景(大二下学期,数据结构基础薄弱,目标是能上手做题),我会为你整理一份**精简、循序渐进的前缀树知识点清单**,覆盖核心概念、实现细节和必备题型,并上可直接参考的免费资源链接。 --- ### **一、必须掌握的核心知识点清单** #### **1. 基础概念(理解“是什么”和“为什么”)** - **定义**:前缀树(Trie)是一种**树形数据结构**,用于高效存储和检索字符串集合,核心利用字符串的**公共前缀**减少查询时间。 - **核心优势**: - **快速前缀匹配**:比哈希表更适合处理“查找是否有以某前缀开头的字符串”这类问题。 - **避免重复存储**:共享公共前缀的字符串不会重复存储前缀字符。 - **典型应用场景**: - 搜索引擎的输入提示(输入“app”自动补全“apple”)。 - 拼写检查、IP路由表的最长前缀匹配。 #### **2. 节点结构设计(理解“如何存数据”)** - **基本节点结构**(C++实现): ```cpp class TrieNode { public: bool isEnd; // 标记是否为单词结尾 TrieNode* children[26]; // 假设仅处理小写字母 TrieNode() : isEnd(false) { memset(children, 0, sizeof(children)); // 初始化子节点指针为nullptr } }; ``` - **关键点**: - **字符到子节点的映射**:通常用数组(如`children[26]`)或哈希表实现。 - **结束标记**:`isEnd`标记当前节点是否为一个单词的结尾(例如:插入“app”后,第二个‘p’节点的`isEnd`为true)。 #### **3. 基本操作(手写代码实现)** 1. **插入(Insert)**: - 从根节点开始,逐个字符向下创建节点。 - 时间复杂度:**O(L)**(L为单词长度)。 ```cpp void insert(TrieNode* root, string word) { TrieNode* node = root; for (char c : word) { int idx = c - 'a'; if (!node->children[idx]) { node->children[idx] = new TrieNode(); } node = node->children[idx]; } node->isEnd = true; } ``` 2. **搜索完整单词(Search)**: - 从根节点向下逐字符查找,且最后一个节点必须有`isEnd == true`。 ```cpp bool search(TrieNode* root, string word) { TrieNode* node = root; for (char c : word) { int idx = c - 'a'; if (!node->children[idx]) return false; node = node->children[idx]; } return node->isEnd; } ``` 3. **前缀匹配(StartsWith)**: - 只需检查是否存在该前缀路径,无需检查`isEnd`。 ```cpp bool startsWith(TrieNode* root, string prefix) { TrieNode* node = root; for (char c : prefix) { int idx = c - 'a'; if (!node->children[idx]) return false; node = node->children[idx]; } return true; } ``` #### **4. C++实现要点(避免踩坑)** - **内存管理**: - 插入时用`new`创建节点,需在析构函数中递归`delete`(否则内存泄漏)。 - 示例析构函数: ```cpp ~TrieNode() { for (int i = 0; i < 26; ++i) { if (children[i]) delete children[i]; } } ``` - **扩展字符集**:若要支持更多字符(如大写字母、数字),需调整`children`数组大小(例如ASCII码范围)。 #### **5. 常见问题与调试技巧** - **指针未初始化**:`children`数组必须初始化为`nullptr`。 - **节点共享问题**:删除操作时需确保其他单词不共享该节点(进阶内容,初期可不学)。 - **调试方法**: - 打印Trie树结构(递归输出节点层级)。 - 使用调试器观察`isEnd`和`children`数组状态。 --- ### **二、练习题清单(按难度排序)** 先从模板题开始,逐步过渡到变种题型: | 题目 | 链接 | 关键点 | 难度 | |------|------|--------|------| | 208. 实现 Trie | [LeetCode 208](https://leetcode.cn/problems/implement-trie-prefix-tree/) | 手写完整模板,理解基础操作 | 简单 | | 720. 词典中最长的单词 | [LeetCode 720](https://leetcode.cn/problems/longest-word-in-dictionary/) | 前缀存在性验证 + 排序技巧 | 中等 | | 211. 添加与搜索单词 | [LeetCode 211](https://leetcode.cn/problems/design-add-and-search-words-data-structure/) | 通配符`.`处理(DFS回溯) | 中等 | | 648. 单词替换 | [LeetCode 648](https://leetcode.cn/problems/replace-words/) | 前缀最短匹配 | 中等 | | 212. 单词搜索 II | [LeetCode 212](https://leetcode.cn/problems/word-search-ii/) | Trie + DFS回溯(二维网格) | 困难 | --- ### **三、学习资源推荐(全部免费)** #### **1. 图文教程** - [Trie树详解(含代码实现)](https://zhuanlan.zhihu.com/p/34747612)(知乎专栏,适合快速入门) - [GeeksforGeeks Trie Tutorial](https://www.geeksforgeeks.org/trie-insert-and-search/)(英文,代码详细) #### **2. 视频教程** - [B站:Trie树从原理到实现](https://www.bilibili.com/video/BV1yJ411s7dk)(15分钟快速掌握) - [LeetCode 208官方题解视频](https://leetcode.cn/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode-ti5o/)(代码逐行讲解) #### **3. 实战代码参考** - [C++ Trie模板代码](https://github.com/huihut/interview/blob/master/DataStructure/Trie.cpp)(GitHub开源项目) --- ### **四、学习计划建议** 1. **Day 1-2**:理解概念 + 手写基础插入、搜索、前缀匹配代码。 2. **Day 3**:实现LeetCode 208题,通过测试用例。 3. **Day 4-5**:尝试LeetCode 720和648,巩固前缀验证逻辑。 4. **Day 6-7**:挑战LeetCode 211(通配符匹配),学习DFS与Trie结合。 --- 通过以上知识点和练习,你可以在1-2周内掌握前缀树的核心内容,并能解决大部分中等难度题型。遇到卡点时,多画图理解节点关系,并善用调试工具!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值