LOGO
首页 网站广场 站长动态 活跃度榜 审核查询 逛逛好站 留言交流 提交申请 关于本站

站长动态

站长动态所展示的是已加入好站网成员站长文章
共同步 2455 篇博文
(每2小时更新一次)
Debug
入驻第1年
操作系统 进程线程模型 进程线程调度
调度是分层次的,在操作系统中,一般将调度分为高级调度、中级调度和低级调度。 高级调度也称作业调度,其主要任务是按一定的原则,对磁盘中的处于后备状态的作业进行选择并创建为进程。 中级调度的主要任务是按照给定的原则和策略,将处在磁盘对换区中切具备运行条件的就绪进程调入内存,或将处于内存就绪状态或内存阻塞状态的进程交换到对换区。 低级调度即进程(线程)调度,是决定就绪队列中哪个进程将获得处理机,并使即将处理及分配给该进程的操作。 进程(线程)调度即处理机调度。任务是控制、协调进程(线程)对CPU的竞争,按照一定的调度算法,使某一就绪进程获得CPU的控制权,转换成运行状态。 概述 进程(线程)调度的主要功能 记录系统中所有进程(线程)的执行状况,根据一定的调度算法,从就绪队列中选出一个进程(线程)来,准备把CPU分配给它,把CPU分配给进程(线程),即把选中进程(线程)的进程(线程)控制块内有关的现场信息,让它占用CPU运行。 进程(线程)调度的时机 正在执行的进程(线程)运行完毕。 调用阻塞原语将自己阻塞起来进入等待状态。 调用阻塞原语操作,并且因为资源不足而被阻塞;或调用唤醒原语操作激活了等待资源的进程(线程)。 时间片用完。 就绪队列中的某个就绪队列中一旦有优先级高于当前运行进程(线程)优先级时,引发进程(线程)调度。 抢占方式:就绪队列中一旦由优先级高于当前运行进程(线程)优先级的进程(线程)存在时,便立即进行调度,转让CPU。 不可抢占方式:一旦把CPU分配给一个进程(线程),它就一直占用CPU,直到该进程(线程)自己因调用原语操作或等待I/O而进入阻塞状态或时间片用完时才让出CPU,重新执行进程(线程)调度。 调度算法设计原则 进程行为 几乎所有的进程的(磁盘)I/O请求或计算都是交替完成的。某些I/O活动可以看作是计算,在CPU向视频RAM复制数据以更新屏幕时,因为使用了CPU,所以这是计算,而不是I/O,当一个进程等待外部设备完成工作而被阻塞的行为属于I/O。 计算密集型:进程花费了绝大多数时间在计算上。 I/O密集型:进程在等待I/O上花费了绝大多数时间。 系统分类 通常分为三类环境:批处理、交互式和实时系统。 批处理系统:减少了进程的切换从而改善了性能。 交互式:避免一个进程霸占CPU拒绝为其他进程服务,抢占是必需的。服务器也归于此类,因为通常他们要服务多个突发的(远程)用户。 实时限制:只运行那些用来推进现有应用的程序,而交互式系统是通用的,它可以运行任意的非协作甚至是有恶意程序。 调度算法的设计目标 运行大量批处理作业的大型计算中心的管理者们为了掌握其系统的工作状态,通常是检查各个指标:吞吐量、周转时间以及CPU利用率。 吞吐量:是系统每小时完成的作业数量。 周转时间:从一个批处理作业提交时间开始直到该作业完成时刻为止的统计平均时间。 CPU利用率:用于对批处理系统的度量,系统每小时可完成多少作业(吞吐量),以及完成作业需要多长时间(周转时间)。 进程(线程)调度算法 进程(线程)调度算法解决以何中次序对各就绪进程(线程)进程处理机的分配以及按何种时间比例让进程(线程)占用处理机。 先来先服务FCFS算法 进程按照他们请求CPU的顺序使用CPU。 最短作业优先SJF算法 当输入队列中有若干同等重要的作业被启动时,调度程序应使用最短作业优先算法。 有4个作业A、B、C、D,运行时间分别是8、4、4、4分钟。 先到先服务:若按图A,B,C,D 的次序运行,则A的周转时间为8分钟,B为12分钟,C为16分钟,D为20分钟,平均为14分钟。 最短作业优先:运行顺序为BCDA,则周转时间分别为4、8、12、20分钟,平均时间为11分钟。 最短剩余时间优先SRTN算法 调度程序总是选择其剩余运行时间最短的那个进程运行。 最高响应比优先HRRF算法 响应比Rp=(等待时间+预计运行时间)/预计运行时间=周期时间/预计运行时间。 轮转法RR算法 基本思想:将CPU的处理时间划分为一个个的时间片,就绪队列中的诸程序轮流运行一个时间片。当时间片结束时,就强迫运行的进程让出CPU,该进程机内就绪队列,等待下一次调度。同时,进程调度又去选择就绪队列中的一个进程,分配给它一个时间片,以投入运行。 影响时间片值设置的几个主要因素: 系统响应时间:当进程数目一定时,时间片Q值的大小占比于系统对响应时间的要求,例如进程数目为N,要求响应时间为T,则Q=T/N,Q值随T值的大或小而大或小。 就绪进程的数目:当系统响应时间T一定时,时间片Q值的大小反比于就绪进程数。 计算机的处理能力:计算机的处理能力直接决定了每道程序的处理时间,显然,处理速度越高,时间片值就可以越小。 结论:时间片设置的太短会导致过多的进程切换,降低了CPU效率;而设的太长有可能引起对短的交互请求的响应时间变长,将时间片设置为20~50ms通常是一个比较合理的折中。 最高优先级HPF算法 最高优先级调度每次将处理及分配给具有最高优先级的就绪进程(线程)。进程(线程)的优先级由进程(线程)优先数决定的。 进程(线程)优先数的设置可以是静态的也可以是动态的。 静态优先数是在进程(线程)创建时根据进程(线程)初始特性或用户要求而确定的,在进程(线程)运行期间不能再改变。 动态优先数是指在进程(线程)创建时先确定一个初始优先数,以后在进程(线程)运行中随着进程(线程)特性的改变(如等待时间增长),不断修改优先数。优先数小的进程(线程)优先级高。 如果不对优先级进行调整,则低优先级进程很有可能产生饥饿现象。 多级反馈队列算法 以最高优先级算法作为主要的调度模式,但对于具有相同优先数的进程(线程)按先进先出调度算法处理。 多级队列反馈法就是综合了先进先出调度算法、时间片轮转法和可抢占式最高优先级算法的一种进程(线程)调度算法。 被调度队列的设置:系统按优先级别设置若干个就绪队列,不同优先级别的队列有不同的时间片,对级别较高的队列分配较小的时间片Si(i=1,2…..,n)。 在同一个队列之间的调度原则:除了第n级队列是按照RR算法调度之外,其他各级队列均按照先进先出调度算法调度。 在不同队列之间的调度原则:西戎总是先调度级别比较高的队列,仅当级别较高的队列为空是才去调度次一级队列中的就绪队列。当等待进程(线程)被唤醒时,它进入与其优先级相同的就绪队列,若该进程(线程)优先级高于正在执行的的进程(线程),便抢占CPU。 最短进程优先 如何从当前可运行进程中找出最短的那一个进程。 根据进程过去的行为进行推测,并执行估计运行时间最短的哪一个。 老化:通过当前测量值和向前估计值进程加权平均而得到下一个估计值的技术。 实时系统中的调度算法 实时系统是一种时间起着主导作用的系统,即系统的正确性不及取决于计算的逻辑结果,而且还依赖于产生结果的时间。 实时系统应用的例子包括实验控制、过程控制设备、机器人、空中交通管制、电信、军事指挥与控制系统。 硬实时任务值必须满足最后期限的限制,否则会给系统带来不可接受的破坏或者致命的错误。 软实时任务也有一个与之关联的最后期限,并希望能满足这个期限的要求,但并不是强制的,即使超过了最后期限,调度和完成这个任务仍是有意义的。 速率单调调度算法:适用于可抢先的周期性进程的经典静态实时调度算法是速率单调调度RMS。 每个周期性进程必须在其周期内完成。 没有进程依赖于任何其他进程。 每一进程在一次有突发中需要相同的CPu时间量。 任何非周期性进程都没有最终时限。 进程抢先即刻发生而没有系统开销。 最早最终时限优先调度:EDF算法是一个动态算法,它不像速率单调算法那样要求进程是周期性的,他也不详RMS那样要求CPU突发有相同的运行时间。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
操作系统 进程线程模型 进程控制块及进程控制
进程控制块PCB 在操作系统中,为进程定义了一个专门的数据结构,称为进程控制块PCB。 PCB内容 PCB内容可以分为调度信息和现场信息两大部分。 调度信息供进程使用时使用,描述了进程当前所处的状况,他包括进程名、存储信息、进程号、优先级、当前状态、资源清单、“家族”关系、消息队列指针、进程队列指针和当前打开的文件等。 现场信息刻画了进程的运行情况,由于每个进程都有自己专用的工作存储区,其他进程运行时不会改变它的内容。 进程的组成 PCB组织 线性方式:将所有的PCB部分状态组织在一个连续表(称为PCB表)中。 优点:简单,且不需要额外的开销,适用于进程数且不多的系统。 缺点:需要扫描整个PCB表。 索引方式:对于具有相同状态的进程,分别设置各自的PCB索引表,表目为PCB在PCB表(线性表)中的地址。就构成了就绪索引表和等待索引表。 链接方式:对于具有相同状态的进程PCB,通过PCB中的链接字构成一个队列。按“先进先出”的原则出对,若队列指针为0,表示该队列为空。 进程的队列 就绪队列:进程入队和出队的次序与处理机调度算法有关。 等待队列:每一个等待事件一个队列。 运行队列:在单CPU系统中整个系统有一个运行队列。 进程控制 作用:就是对进程在这个生命周期中各种状态之间的转换进行有效的控制。 原语:通常由若干的指令组成,用来实现某个指定的操作。通过一段不可分割的或不可中断的程序实现其功能。原语的执行过程必须是连续的,一旦开始执行就不能间断,直到执行结束。原语是操作系统的可行,在管态下执行,并且常驻内存。 进程控制原语 用于进程控制的原语一般有:创建进程、撤销进程、挂起进程、激活进程、阻塞进程、唤醒进程以及改变进程优先级等。 创建原语:一个进程可以使用创建原语创建一个新的进程,前者称为父进程,后者称为子进程,子进程又可以创建新的子进程,构成新的子进程,构成新的父子关系。建立进程控制快PCB:先申请一个空闲的PCB区域,将有关信息填入PCB,置该进程为就绪状态,最后将它插入到就绪状态队列中去。 撤销原语:找到要被撤销的进程PCB,将它从所在队列中消去。 阻塞原语:把进程运行状态转换为阻塞状态。首先应中断CPU执行,把CPU的当前状态保存到PCB的现场信息中,把它插入到该事件的等待队列中去。 唤醒原语:京城因为等待时间的发生而处于等待状态,当等待事件完成后,就用唤醒原语将其转换为就绪状态。具体操作过程:在等待队列中找到该进程,置该进程的当前状态为就绪状态,然后将它从等待队列中撤去并插入到就绪队列中排队,等待调度执行。 UNIX类操作系统的进程控制操作 父进程调用fork()函数。 为子进程分配一个空闲的proc结构(进程描述符)。 赋予子进程唯一标识pid。 以一次一页的方式复制父进程用户地址空间。 获得子进程继承的共享资源的指针。 子进程就绪,加入调度队列。 对子进程返回标识符0;向父进程返回子进程的pid。 父进程和新建子进程的区别在于它们有着不同的pid。 fork()函数的执行的特点就像是只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Ajax 对缓存的处理
缓存 浏览器的一次请求需要从服务器获得许多css、img、js等相关的文件,如果每次请求都把相关资源文件加载一次,对带宽、服务器资源、用户等待时间都有严重的损耗,浏览器有做优化处理,就是把css、img、js等文件在第一次请求成功后就在本地保留一个缓存备份,后续的每次请辞u就在本身获得相关的缓存资源文件就可以了,可以明显地加快用户的访问速度。 css、img、js等文件可以缓存,但是动态程序文件例如PHP文件不能进行缓存,即使缓存我们也不要其缓存效果。浏览器对动态程序文件缓存的处理解决: 给请求的地址设置随机数【推荐】; 给动态程序设置header头信息,禁止浏览器对其缓存。 给请求的地址设置随机数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Ajax对缓存的处理</title> <script type="text/javascript"> function f1(){ var xhr = new XMLHttpRequest(); xhr.open('get', './server.php?'+Math.random()); xhr.send(null); } </script> </head> <body> <h2>Ajax对缓存的处理</h2> <input type="button" value="触发" onclick="f1()"> <div id="result"></div> </body> </html> 给动态程序设置header头信息 1 2 3 4 5 6 //设置header头禁止浏览器缓存当前页面 header("Cache-Control:no-cache"); header("Pragma:no-cache"); header("Expires:-1"); $fp = fopen("test.txt","a"); fwrite($fp, "php "); fclose($fp); 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
计算机网络 网络技术基础
计算机网络技术的形成与发展 主要知识点记录 “三网融合” :计算机网络、电信网与电视网之间的融合。 UNIX操作系统 集中式、分时、多用户的系统架构 制订了基于Unix的易移植操作系统环境(POSIX)标准 Linux操作系统 常见的发行版本:Rad Hat、Mandrake、Slackware、SUSE、TurbpLinux、Debian、Caldera、Ubuntu,国内的有蓝点、红旗等。 计算机网络的基本概念 计算机网络的定义 计算机网络 用相互共享资源的方式互联起来的自治计算机系统的集合。 特征: 主要目的是资源的共享 互联的计算机是分布在不同地理位置的多台独立的“自治计算机” 联网计算机之间的通讯需要遵循共同的网络协议。 常见计算机名词区分: 术语 英文名称 含义 计算机网络 computer network 用相互共享资源的方式互联起来的自治计算机系统的集合 网络互联 internet 将多个计算机网络互联成大型网络系统的技术 互联网 Internet 指目前广泛应用、覆盖了全世界的大型网络系统 内部的专用网络系统 Intranet 将分布在不同地理位置的部门局域网连接起来的企业内部专用网络。 计算机网络的分类 按照覆盖的地理范围划分为:广域网、城域网、局域网、个人区域网。 广域网技术 WAN又称为远程网,覆盖的地理位置从几十千米到几千千米。 城域网技术 IEEE802协会对其的定义与特征表述: 以光纤为传输介质,能够提供45-150Mbps的高传输速率,支持数据、语音和视频综合业务的数据传输,可以覆盖50~100km的城市范围,实现高速的数据传输。 局域网技术 局域网用于将有限范围的各种计算机,终端与外部设备互联成网。 特征如下: 局域网覆盖有限的地理范围 提供高传输速率(10Mbps~100Gbps)、低误码率的高质量数据传输环境 一般为一个单位所有,易于建立、维护与扩展 决定局域网的三个因素: 拓扑 传输介质 介质访问控制方法 从介质访问控制方法的角度,分为以下两方面: 共享介质式局域网 交换式局域网 个人局域网技术 自身附近范围的个人操作空间。 无线个人局域网络 用无线通信技术实现联网设备之间的通信。 计算机网络的拓扑结构 网络拓朴学知识补充: 拓扑学时间实体抽象成于其大小、形状无关的“点”,将连接实体的路线抽象成“线”。 计算机网络拓扑是通过网中节点与通信线路之间的几何关系表示的网络结构。 计算机网络拓扑是指通信子网的网络拓朴。 基本的网络拓朴有五种:星形、环形、总线型、树形与网形。 星形拓扑 通过点-点通信线路与中心节点连接。 任何两个节点之间的通信都要通过中心节点。 结构简单,易于实现,便于管理。 中心节点是全网性能与可靠性的预测,中心节点瘫痪会造成全网瘫痪。 环形拓扑 通过点-点通信线路连接成闭合的环路。 数据沿一个方向传送。 结构简单、传输延时稳定。 每个节点与连接节点之间的通信线路都会成为网络可靠性的瓶颈。 方便接点的加入和撤出、控制节点数据传输顺序。 总线型拓扑 所有的节点连接到一条作为公共传输介质的纵向,以广播的方式发送和接受数据。 但一个节点利用总线发送数据时,其他节点只能接受数据。 两个或两个以上的节点同时发送数据时,就会出现冲突,照成传输失败。 结构简单,但是得解决多节点访问总线的介质访问控制问题。 树形拓扑 按层次进行连接,信息交换主要在上下节点之间进行,相邻及同层节点之间通常不进行数据交换,或则交换量较小。 树形拓扑可以看成是星形拓扑的一种扩展,树形拓扑网络适用于汇集信息。 网状拓扑 节点之间的连接是任意的,没有规律。 系统可靠性高,广域网一般都采用网状拓扑。 必须采用路由算法、流量控制与拥塞控制方法。 描述计算机网络传输性能的参数 数据传输率 数据传输率是每秒钟传输构成数据的二进制比特数。 单位:比特/秒(bit/second)简称:bps 数据传输率公式: S=1/T(bps) T:发送1比特所需的时间。 常见速率换算公式: 奈奎斯特准则与香农定律 奈奎斯特准则 定义:具有理想低通矩形特性的信道在无噪声情况下的最高速率与带宽关系的公式。 奈奎斯特准则指出:如果间隔为π/w(w=2πf),通过理想通信信道传输窄脉冲信号,则前后码元之间不会产生相互串扰。 对于二进制数据信号的最大数据传输速率Rmax与通信信道带宽B(B=f,单位Hz)的关系可以转化为: $$ R_{\max }=2f\left( bps\right) $$ 此准则描述了有限带宽、五噪声信道的最大数据传输速率与信道带宽之间的关系。 香农定律 定义:在有随机热噪声信道上传输数据信号时,数据传输速率Rmax与信道带宽B,信号与噪声功率比S/N关系。 R max = 13 log 2 1 + S / N 此定律描述了有限带宽、有随机热噪声的最大传输速率与信道带宽、信号噪声功率比之间的关系。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 非线性结构
树 树的定义 专业定义: 有且只有一个根的节点 有若干的互不相交的子树,这些子树本身也是一棵树 通俗的定义: 树是由节点和边组成 每个节点只有一个父节点,但可以有多个子结点 但有一个节点例外,该节点没有父节点,此节点为根节点。 专业术语: 节点 父节点 子节点 子孙 祖先 堂兄弟 深度:从根节点到最底层节点的层数。(根节点是第一层) 叶子节点:没有子节点的节点 非终端节点:实际就是非叶子节点 度:子结点的个数 树的分类 一般树 任意一个节点的子节点的个数都不受限制 二叉树 任意一个节点的子节点的个数最多为两个,且子节点的位置不可变更 分类: 一般二叉树 满二叉树 在不增加树的层数的前提下,无法再多添加一个节点的二叉树。 完全二叉树 如果只是删除了满二叉树最底层最右边的连续若干个节点。 森林 n 个互不相交的树的集合 树的存储 二叉树的存储 连续存储[完全二叉树] 优点: 查找某个节点的父节点和子结点速度(也包括有没有子结点)很快. 缺点: 耗用的内存空间比较大. 链式存储 一般树的存储 双亲表示法 求父节点方便。 孩子表示法 求子节点方便。 双亲孩子表示法 求父节点和子结点都很方便。 二叉树表示法 把一个普通的树转换成二叉树来存储。 具体转换方法: 设法保证任意一个节点的左指针指向它的第一个孩子,右指针指向它的堂兄弟。 只要能满足此条件,就可以把一个普通的树转换成为二叉树。 一个普通树转换成的二叉树一定没有右子树。 森林的存储 先把森林转化成二叉树,再存储二叉树 树的操作 遍历 先序遍历 先访问根节点 再先序访问左子树 再先序访问右子树 先序遍历顺序:ABDCEFG 先序遍历顺序:ABCDEFLQMNS 中序遍历 中序遍历左子树 再访问根节点 再中序遍历右子树 中序遍历顺序:CDFELBAMSNQA 后序遍历 中序遍历左子树 中序遍历右子树 遍历根节点 后序遍顺序:FLEDCBSNMQA 已知两种遍历序列求原始二叉树 通过先序和中序 或者 中序和后序 可以还原出原始的二叉树 但是通过先序和后序是无法还原出原始的二叉树的 换种说法: 只有通过先序和中序,或通过中序与后序 我们才能可以唯一的确定一个二叉树 示例1(已知先序和中序求后序): 先序:ABCDEFGH 中序:BDCEAFHG 画出图如下: 求后序:DECBHGFA 示例2(已知中序和后序求先序): 中序:BDCEAFHG 后序:BECBHGFA 画出图如下: 求先序:ABCDEFGH 树的应用 树是数据库中数据组织的一种重要形式。 操作系统子父进程的关系本身就是一棵树。 面向对象中的类的继承关系 赫夫曼树 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 静态树表查找算法
算法思想 在使用查找表中有n个关键字,表中的每个关键字被查找的概率都是1/n。在等概率的情况下,使用折半查找算法最优。 然而在某些情况下,查找表中的个关键字被查找的概率都是不同的。例如在UI设计师设计图片的时候,不同的设计师和不同的项目经理需求不同,有些项目经理喜欢暖色调,那么暖色调就会应用的多一些,有的项目经理比较喜欢冷色调,之后你的设计采用冷色调的概率也是比较大的。 在查找表中的关键字不同的情况下,对应于折半查找算法,按照上面的情况并不是最优的查找算法。 静态最优查找二叉树 若在考虑查找成功的情况下,描述查找过程的判定树其带权路径之和(用PH表示)最小时,查找性能最优。 算法思想例子 在查找表中各关键字查找概率不相同的情况下,对于使用折半查找算法,按照之前的方式进行,其查找的效率并不一定是最优的。例如,某查找表中有 5 个关键字,各关键字被查找到的概率分别为:0.1,0.2,0.1,0.4,0.2(全部关键字被查找概率和为 1 ),则根据之前介绍的折半查找算法,建立相应的判定树为(树中各关键字用概率表示): 折半查找查找成功的平均查找长度计算方式: ASL = 判定树中的各节点的查找概率 * 所在层次 相对的平均查找长度为: ASL=0.41 + 0.22 + 0.22 + 0.13 + 0.1*3=1.8 带权路径之和的计算公式:PH = 所有结点所在的层次数 * 每个结点对应的概率值。 但是由于构造最优查找树花费的时间代价较高,而且有一种构造方式创建的判定树的查找性能同最优查找树仅差 1% – 2%,称这种极度接近于最优查找树的二叉树为次优查找树。 次优查找树的构建方法 构建二叉树方式 首先取出查找表中每个关键字及其对应的权值,采用如下公式计算出每个关键字对应的一个值: 其中 wj 表示每个关键字的权值(被查找到的概率),h 表示关键字的个数。 表中有多少关键字,就会有多少个 △Pi ,取其中最小的做为次优查找树的根结点,然后将表中关键字从第 i 个关键字的位置分成两部分,分别作为该根结点的左子树和右子树。同理,左子树和右子树也这么处理,直到最后构成次优查找树完成。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 typedef int KeyType;//定义关键字类型 typedef struct{ KeyType key; }ElemType;//定义元素类型 typedef struct BiTNode{ ElemType data; struct BiTNode *lchild, *rchild; }BiTNode, *BiTree; //定义变量 int i; int min; int dw; //创建次优查找树,R数组为查找表,sw数组为存储的各关键字的概率(权值),low和high表示的sw数组中的权值的范围 void SecondOptimal(BiTree T, ElemType R[], float sw[], int low, int high){ //由有序表R[low...high]及其累计权值表sw(其中sw[0]==0)递归构造次优查找树 i = low; min = abs(sw[high] - sw[low]); dw = sw[high] + sw[low - 1]; //选择最小的△Pi值 for (int j = low+1; j <=high; j++){ if (abs(dw-sw[j]-sw[j-1])<min){ i = j; min = abs(dw - sw[j] - sw[j - 1]); } } T = (BiTree)malloc(sizeof(BiTNode)); T->data = R[i];//生成结点(第一次生成根) if (i == low) T->lchild = NULL;//左子树空 else SecondOptimal(T->lchild, R, sw, low, i - 1);//构造左子树 if (i == high) T->rchild = NULL;//右子树空 else SecondOptimal(T->rchild, R, sw, i + 1, high);//构造右子树 } 时间复杂度 由于使用次优查找树和最优查找树的性能差距很小,构造次优查找树的算法的时间复杂度为 O(nlogn),因此可以使用次优查找树表示概率不等的查找表对应的静态查找表(又称为静态树表)。 完整实例演示 例如,一含有 9 个关键字的查找表及其相应权值如下表所示: 则构建次优查找树的过程如下: 首先求出查找表中所有的 △P 的值,找出整棵查找表的根结点: 例如,关键字 F 的 △P 的计算方式为:从 G 到 I 的权值和 – 从 A 到 E 的权值和 = 4+3+5-1-1-2-5-3 = 0。 通过上图左侧表格得知,根结点为 F,以 F 为分界线,左侧子表为 F 结点的左子树,右侧子表为 F 结点的右子树(如上图右侧所示),继续查找左右子树的根结点: 通过重新分别计算左右两查找子表的 △P 的值,得知左子树的根结点为 D,右子树的根结点为 H (如上图右侧所示),以两结点为分界线,继续判断两根结点的左右子树: 通过计算,构建的次优查找树如上图右侧二叉树所示。 后边还有一步,判断关键字 A 和 C 在树中的位置,最后一步两个关键字的权值为 0 ,分别作为结点 B 的左孩子和右孩子,这里不再用图表示。 注意:在建立次优查找树的过程中,由于只根据的各关键字的 P 的值进行构建,没有考虑单个关键字的相应权值的大小,有时会出现根结点的权值比孩子结点的权值还小,此时就需要适当调整两者的位置。 总结 在解决静态树表查找时,使用次优查找树的表示概率不等的查找表对应的静态查找表(又称静态树表)。 Reference 静态树表查找算法及C语言实现 严长生 数据结构 – 算法9.3-9.4 静态树表-构造次优查找树 最优二叉查找树详解(算法导论学习笔记) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 键树查找法
定义 键树查找法 又称数字查找树(根节点子树>=2个),键树节点存储的不是某个关键字,而是组成关键字的单个符号。 如果关键字本身是字符串,则键树中的一个结点只包含有一个字符;如果关键字本身是数字,则键树中的一个结点只包含一个数位。每个关键字都是从键树的根结点到叶子结点中经过的所有结点中存储的组合。 例如,当使用键树表示查找表 1 { CAI,CAO,CHEN,LI,LAN,ZHAO } 时,为了查找方便,首先对该查找表中关键字按照首字符进行分类(相同的在一起): 1 { { CAI,CAO,CHEN}, {LI,LAN} , { ZHAO}} 然后继续分割,按照第二个字符、第三个字符、…,最终得到的查找表为: 1 { {CAI,CAO},{ CHEN},{ LI,LAN},{ ZHAO}} 然后使用键树结构表示该查找表,如下图所示: 注意 :键树中叶子结点的特殊符号 $ 为结束符,表示字符串的结束。使用键树表示查找表时,为了方便后期的查找和插入操作,约定键树是有序树(兄弟结点之间自左至右有序),同时约定结束符 ‘$’ 小于任何字符。 键树的存储结构 键树的存储结构有两种,分别是: 双链树 :通过使用树的孩子兄弟表示法来表示键树。 字典树 :以树的多重链表表示键树。 双链树 当使用孩子兄弟表示法表示键树时,树的节点构成以下3部分: symobl域:存储关键字的一个字符。 first域:存储指向孩子节点的指针。 next域:存储指向兄弟节点的指针。 注意:对于叶子结点来说,由于其没有孩子结点,在构建叶子结点时,将 first 指针换成 infoptr 指针,用于指向该关键字。当叶子结点(结束符 ‘$’ 所在的结点)中使用 infoptr 域指向该自身的关键字时,此时的键树被称为双链树。 上图中键树用孩子兄弟表示法表示为双链树时,如下图所示: 提示:每个关键字的叶子结点 $ 的 infoptr 指针指向的是各自的关键字,通过该指针就可以找到各自的关键字的首地址。 双链树查找功能的具体实现 在使用孩子兄弟表示法表示的键树中做查找操作,从树的根结点出发,依次同被查找的关键字进行比对,如果比对成功,进行下一字符的比对;反之,如果比对失败,则跳转至该结点的兄弟结点中去继续比对,直至比对成功或者为找到该关键字。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> typedef enum{LEFT,BRANCH}NodeKind;//定义结点的类型,是叶子结点还是其他类型的结点 typedef struct { char a[20];//存储关键字的数组 int num;//关键字长度 }KeysType; //定时结点结构 typedef struct DLTNode{ char symbol;//结点中存储的数据 struct DLTNode *next;//指向兄弟结点的指针 NodeKind *kind;//结点类型 union{//其中两种指针类型每个结点二选一 struct DLTNode* first;//孩子结点 struct DLTNode* infoptr;//叶子结点特有的指针 }; }*DLTree; //查找函数,如果查找成功,返回该关键字的首地址,反则返回NULL。T 为用孩子兄弟表示法表示的键树,K为被查找的关键字。 DLTree SearchChar(DLTree T, KeysType k){ int i = 0; DLTree p = T->first;//首先令指针 P 指向根结点下的含有数据的孩子结点 //如果 p 指针存在,且关键字中比对的位数小于总位数时,就继续比对 while (p && i < k.num){ //如果比对成功,开始下一位的比对 if (k.a[i] == p->symbol){ i++; p = p->first; } //如果该位比对失败,则找该结点的兄弟结点继续比对 else{ p = p->next; } } //比对完成后,如果比对成功,最终 p 指针会指向该关键字的叶子结点 $,通过其自有的 infoptr 指针找到该关键字。 if ( i == k.num){ return p->infoptr; } else{ return NULL; } } Trie树(字典树) 若以树的多重链表表示键树,则树中如同双链树一样,会含有两种结点: 叶子结点:叶子结点中含有关键字域和指向该关键字的指针域; 除叶子结点之外的结点(分支结点):含有 d 个指针域和一个整数域(记录该结点中指针域的个数); d 表示每个结点中存储的关键字的所有可能情况,如果存储的关键字为数字,则 d= 11(0—9,以及 $),同理,如果存储的关键字为字母,则 d=27(26个字母加上结束符 $)。 开始的键树,采用字典树表示如下图所示: 注意:在 Trie 树中,如果从某个结点一直到叶子结点都只有一个孩子,这些结点可以用一个叶子结点来代替,例如 ZHAO 就可以直接作为叶子结点。 字典树查找功能的具体实现 使用 Trie 树进行查找时,从根结点出发,沿和对应关键字中的值相对应的指针逐层向下走,一直到叶子结点,如果全部对应相等,则查找成功;反之,则查找失败。 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 typedef enum{LEFT,BRANCH}NodeKind;//定义结点类型 typedef struct {//定义存储关键字的数组 char a[20]; int num; }KeysType; //定义结点结构 typedef struct TrieNode{ NodeKind kind;//结点类型 union{ struct { KeysType k; struct TrieNode *infoptr; }lf;//叶子结点 struct{ struct TrieNode *ptr[27]; int num; }bh;//分支结点 }; }*TrieTree; //求字符 a 在字母表中的位置 int ord(char a){ int b = a - 'A'+1; return b; } //查找函数 TrieTree SearchTrie(TrieTree T, KeysType K){ int i=0; TrieTree p = T; while (i < K.num){ if (p && p->kind==BRANCH && p->bh.ptr[ord(K.a[i])]){ i++; p = p->bh.ptr[ord(K.a[i])]; } else{ break; } } if (p){ return p->lf.infoptr; } return p; } 使用 Trie 树进行查找的过程实际上是走了一条从根结点到叶子结点的路径,所以使用 Trie 进行的查找效率取决于该树的深度 总结 双链树和字典树是键树的两种表示方法,各有各的特点,具体使用哪种方式表示键树,需要根据实际情况而定。例如,若键树中结点的孩子结点较多,则使用字典树较双链树更为合适。 本博文从 键树查找法(双链树和字典树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 哈夫曼树
与哈夫曼树相关名词 路径:在一棵树中,一个节点到另一个节点之间的通路。 路径长度:在一条路径中,每经过一个节点,路径都要加1. 节点的权:给每一个节点赋予一个新的数值。 节点的带全路径长度:从根节点到该节点之间的路径长度与该节点的乘积。 树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL” 。 此树的带全路径长度为: WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 哈夫曼树 当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。 遵循一个原则,那就是:权重越大的结点离树根越近。 构建哈夫曼树 对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法: 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和; 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推; 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。 (A)给定了四个结点a,b,c,d,权值分别为7,5,2,4; 第一步如(B)所示,找出现有权值中最小的两个,2 和 4 ,相应的结点 c 和 d 构建一个新的二叉树,树根的权值为 2 + 4 = 6,同时将原有权值中的 2 和 4 删掉,将新的权值 6 加入; 进入(C),重复之前的步骤。直到(D)中,所有的结点构建成了一个全新的二叉树,这就是哈夫曼树。 哈夫曼树的节点结构 构建哈夫曼树时,首先需要确定树中结点的构成。由于哈夫曼树的构建是从叶子结点开始,不断地构建新的父结点,直至树根,所以结点中应包含指向父结点的指针。但是在使用哈夫曼树时是从树根开始,根据需求遍历树中的结点,因此每个结点需要有指向其左孩子和右孩子的指针。 代码结构: 1 2 3 4 5 //哈夫曼树结点结构 typedef struct { int weight;//结点权重 int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标 }HTNode, *HuffmanTree; 哈夫曼树中的查找算法 构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。 查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑: 如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点; 如果介于两个结点权重值之间,替换原来较大的结点; 代码结构: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 //HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置 void Select(HuffmanTree HT, int end, int *s1, int *s2) { int min1, min2; //遍历数组初始下标为 1 int i = 1; //找到还没构建树的结点 while(HT[i].parent != 0 && i <= end){ i++; } min1 = HT[i].weight; *s1 = i; i++; while(HT[i].parent != 0 && i <= end){ i++; } //对找到的两个结点比较大小,min2为大的,min1为小的 if(HT[i].weight < min1){ min2 = min1; *s2 = *s1; min1 = HT[i].weight; *s1 = i; }else{ min2 = HT[i].weight; *s2 = i; } //两个结点和后续的所有未构建成树的结点做比较 for(int j=i+1; j <= end; j++) { //如果有父结点,直接跳过,进行下一个 if(HT[j].parent != 0){ continue; } //如果比最小的还小,将min2=min1,min1赋值新的结点的下标 if(HT[j].weight < min1){ min2 = min1; min1 = HT[j].weight; *s2 = *s1; *s1 = j; } //如果介于两者之间,min2赋值为新的结点的位置下标 else if(HT[j].weight >= min1 && HT[j].weight < min2){ min2 = HT[j].weight; *s2 = j; } } } 注意:s1和s2传入的是实参的地址,所以函数运行完成后,实参中存放的自然就是哈夫曼树中权重值最小的两个结点在数组中的位置。 哈夫曼编码 哈夫曼编码就是在哈夫曼树的基础上构建的,这种编码方式最大的优点就是用最少的字符包含最多的信息内容。 根据发送信息的内容,通过统计文本中相同字符的个数作为每个字符的权值,建立哈夫曼树。对于树中的每一个子树,统一规定其左孩子标记为 0 ,右孩子标记为 1 。这样,用到哪个字符时,从哈夫曼树的根结点开始,依次写出经过结点的标记,最终得到的就是该结点的哈夫曼编码。 文本中字符出现的次数越多,在哈夫曼树中的体现就是越接近树根。编码的长度越短。 如图所示,字符 a 用到的次数最多,其次是字符 b 。字符 a 在哈夫曼编码是 0 ,字符 b 编码为 10 ,字符 c 的编码为 110 ,字符 d 的编码为 111 。 使用程序求哈夫曼编码有两种方法: 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,图中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。 实现代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组 cd[n-1] = '\0';//字符串结束符 for(int i=1; i<=n; i++){ //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放 int start = n-1; //当前结点在数组中的位置 int c = i; //当前结点的父结点在数组中的位置 int j = HT[i].parent; // 一直寻找到根结点 while(j != 0){ // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1 if(HT[j].left == c) cd[--start] = '0'; else cd[--start] = '1'; //以父结点为孩子结点,继续朝树根的方向遍历 c = j; j = HT[j].parent; } //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码 (*HC)[i] = (char *)malloc((n-start)*sizeof(char)); strcpy((*HC)[i], &cd[start]); } //使用malloc申请的cd动态数组需要手动释放 free(cd); } 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,求图中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0。 实现代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); int m=2*n-1; int p=m; int cdlen=0; char *cd = (char *)malloc(n*sizeof(char)); //将各个结点的权重用于记录访问结点的次数,首先初始化为0 for (int i=1; i<=m; i++) { HT[i].weight=0; } //一开始 p 初始化为 m,也就是从树根开始。一直到p为0 while (p) { //如果当前结点一次没有访问,进入这个if语句 if (HT[p].weight==0) { HT[p].weight=1;//重置访问次数为1 //如果有左孩子,则访问左孩子,并且存储走过的标记为0 if (HT[p].left!=0) { p=HT[p].left; cd[cdlen++]='0'; } //当前结点没有左孩子,也没有右孩子,说明为叶子结点,直接记录哈夫曼编码 else if(HT[p].right==0){ (*HC)[p]=(char*)malloc((cdlen+1)*sizeof(char)); cd[cdlen]='\0'; strcpy((*HC)[p], cd); } } //如果weight为1,说明访问过一次,即是从其左孩子返回的 else if(HT[p].weight==1){ HT[p].weight=2;//设置访问次数为2 //如果有右孩子,遍历右孩子,记录标记值 1 if (HT[p].right!=0) { p=HT[p].right; cd[cdlen++]='1'; } } //如果访问次数为 2,说明左右孩子都遍历完了,返回父结点 else{ HT[p].weight=0; p=HT[p].parent; --cdlen; } } } 遍历哈夫曼树程序设计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <stdlib.h> #include <stdio.h> #include <string.h> //哈夫曼树结点结构 typedef struct { int weight;//结点权重 int parent, left, right;//父结点、左孩子、右孩子在数组中的位置下标 }HTNode, *HuffmanTree; //动态二维数组,存储哈夫曼编码 typedef char ** HuffmanCode; //HT数组中存放的哈夫曼树,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置 void Select(HuffmanTree HT, int end, int *s1, int *s2) { int min1, min2; //遍历数组初始下标为 1 int i = 1; //找到还没构建树的结点 while(HT[i].parent != 0 && i <= end){ i++; } min1 = HT[i].weight; *s1 = i; i++; while(HT[i].parent != 0 && i <= end){ i++; } //对找到的两个结点比较大小,min2为大的,min1为小的 if(HT[i].weight < min1){ min2 = min1; *s2 = *s1; min1 = HT[i].weight; *s1 = i; }else{ min2 = HT[i].weight; *s2 = i; } //两个结点和后续的所有未构建成树的结点做比较 for(int j=i+1; j <= end; j++) { //如果有父结点,直接跳过,进行下一个 if(HT[j].parent != 0){ continue; } //如果比最小的还小,将min2=min1,min1赋值新的结点的下标 if(HT[j].weight < min1){ min2 = min1; min1 = HT[j].weight; *s2 = *s1; *s1 = j; } //如果介于两者之间,min2赋值为新的结点的位置下标 else if(HT[j].weight >= min1 && HT[j].weight < min2){ min2 = HT[j].weight; *s2 = j; } } } //HT为地址传递的存储哈夫曼树的数组,w为存储结点权重值的数组,n为结点个数 void CreateHuffmanTree(HuffmanTree *HT, int *w, int n) { if(n<=1) return; // 如果只有一个编码就相当于0 int m = 2*n-1; // 哈夫曼树总节点数,n就是叶子结点 *HT = (HuffmanTree) malloc((m+1) * sizeof(HTNode)); // 0号位置不用 HuffmanTree p = *HT; // 初始化哈夫曼树中的所有结点 for(int i = 1; i <= n; i++) { (p+i)->weight = *(w+i-1); (p+i)->parent = 0; (p+i)->left = 0; (p+i)->right = 0; } //从树组的下标 n+1 开始初始化哈夫曼树中除叶子结点外的结点 for(int i = n+1; i <= m; i++) { (p+i)->weight = 0; (p+i)->parent = 0; (p+i)->left = 0; (p+i)->right = 0; } //构建哈夫曼树 for(int i = n+1; i <= m; i++) { int s1, s2; Select(*HT, i-1, &s1, &s2); (*HT)[s1].parent = (*HT)[s2].parent = i; (*HT)[i].left = s1; (*HT)[i].right = s2; (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight; } } //HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数 void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){ *HC = (HuffmanCode) malloc((n+1) * sizeof(char *)); char *cd = (char *)malloc(n*sizeof(char)); //存放结点哈夫曼编码的字符串数组 cd[n-1] = '\0';//字符串结束符 for(int i=1; i<=n; i++){ //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放 int start = n-1; //当前结点在数组中的位置 int c = i; //当前结点的父结点在数组中的位置 int j = HT[i].parent; // 一直寻找到根结点 while(j != 0){ // 如果该结点是父结点的左孩子则对应路径编码为0,否则为右孩子编码为1 if(HT[j].left == c) cd[--start] = '0'; else cd[--start] = '1'; //以父结点为孩子结点,继续朝树根的方向遍历 c = j; j = HT[j].parent; } //跳出循环后,cd数组中从下标 start 开始,存放的就是该结点的哈夫曼编码 (*HC)[i] = (char *)malloc((n-start)*sizeof(char)); strcpy((*HC)[i], &cd[start]); } //使用malloc申请的cd动态数组需要手动释放 free(cd); } //打印哈夫曼编码的函数 void PrintHuffmanCode(HuffmanCode htable,int *w,int n) { printf("Huffman code : \n"); for(int i = 1; i <= n; i++) printf("%d code = %s\n",w[i-1], htable[i]); } int main(void) { int w[5] = {2, 8, 7, 6, 5}; int n = 5; HuffmanTree htree; HuffmanCode htable; CreateHuffmanTree(&htree, w, n); HuffmanCoding(htree, &htable, n); PrintHuffmanCode(htable, n); return 0; } 本节中介绍了两种遍历哈夫曼树获得哈夫曼编码的方法,同时也给出了各自完整的实现代码的函数,在完整代码中使用的是第一种逆序遍历哈夫曼树的方法。 本博文从 哈夫曼树(赫夫曼树、最优树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 分块查找法
算法定义 分块查找,也叫索引顺序查找,算法实现除了需要查找表本身之外,还需要根据查找表建立一个索引表。 建立的索引表要求按照关键字进行升序排序,查找表要么整体有序,要么分块有序。 分块有序:指的是第二个子表中所有关键字都要大于第一个子表中的最大关键字,第三个子表的所有关键字都要大于第二个子表中的最大关键字,依次类推。 块(子表)中各关键字的具体顺序,根据各自可能会被查找到的概率而定。如果各关键字被查找到的概率是相等的,那么可以随机存放;否则可按照被查找概率进行降序排序,以提高算法运行效率。 算法原理 所有前期准备工作完成后,开始在此基础上进行分块查找。分块查找的过程分为两步进行: 确定要查找的关键字可能存在的具体块(子表); 在具体的块中进行顺序查找。 方法描述 将n个数据元素”按块有序”划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须”按块有序”;即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <stdlib.h> struct index { //定义块的结构 int key; int start; } newIndex[3]; //定义结构体数组 int search(int key, int a[]){ int i, startValue; i = 0; while (i<3 && key>newIndex[i].key) { //确定在哪个块中,遍历每个块,确定key在哪个块中 i++; } if (i>=3) { //大于分得的块数,则返回0 return -1; } startValue = newIndex[i].start; //startValue等于块范围的起始值 while (startValue <= startValue+5 && a[startValue]!=key) { startValue++; } if (startValue>startValue+5) { //如果大于块范围的结束值,则说明没有要查找的数 return -1; } return startValue; } int cmp(const void *a,const void* b){ return (*(struct index*)a).key>(*(struct index*)b).key?1:-1; } int main(){ int i, j=-1, k, key; int a[] = {33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53}; //确认模块的起始值和最大值 for (i=0; i<3; i++) { newIndex[i].start = j+1; //确定每个块范围的起始值 j += 6; for (int k=newIndex[i].start; k<=j; k++) { if (newIndex[i].key<a[k]) { newIndex[i].key=a[k]; } } } //对结构体按照 key 值进行排序 qsort(newIndex,3, sizeof(newIndex[0]), cmp); //输入要查询的数,并调用函数进行查找 printf("请输入您想要查找的数:\n"); scanf("%d", &key); k = search(key, a); //输出查找的结果 if (k>0) { printf("查找成功!您要找的数在数组中的位置是:%d\n",k+1); }else{ printf("查找失败!您要找的数不在数组中。\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 插入排序算法
插入排序算法介绍 插入排序算法是所有排序方法中最简单的一种算法,其主要的实现思想是将数据按照一定的顺序一个一个的插入到有序的表中,最终得到的序列就是已经排序好的数据。 直接插入排序是插入排序算法中的一种,采用的方法是:在添加新的记录时,使用顺序查找的方式找到其要插入的位置,然后将新记录插入。 例如采用直接插入排序算法将无序表 1 {3,1,7,5,2,4,9,6} 进行升序排序的过程为: 首先考虑记录 3 ,由于插入排序刚开始,有序表中没有任何记录,所以 3 可以直接添加到有序表中; 向有序表中插入记录 1 时,同有序表中记录 3 进行比较,1<3,所以插入到记录 3 的左侧; 向有序表插入记录 7 时,同有序表中记录 3 进行比较,3<7,所以插入到记录 3 的右侧; 向有序表中插入记录 5 时,同有序表中记录 7 进行比较,5<7,同时 5>3,所以插入到 3 和 7 中间; 向有序表插入记录 2 时,同有序表中记录 7进行比较,2<7,再同 5,3,1分别进行比较,最终确定 2 位于 1 和 3 中间; 照此规律,依次将无序表中的记录 4,9 和 6插入到有序表中。 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> //自定义的输出函数 void print(int a[], int n ,int i){ printf("%d:",i); for(int j=0; j<8; j++){ printf("%d",a[j]); } printf("\n"); } //直接插入排序函数 void InsertSort(int a[], int n) { for(int i= 1; i<n; i++){ if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。 int j= i-1; int x = a[i]; while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间 a[j+1] = a[j]; j--; } a[j+1] = x; //插入到正确位置 } print(a,n,i);//打印每次排序后的结果 } } int main(){ int a[8] = {3,1,7,5,2,4,9,6}; InsertSort(a,8); return 0; } 运行结果为: 1 2 3 4 5 6 7 1:13752496 2:13752496 3:13572496 4:12357496 5:12345796 6:12345796 7:12345679 时间复杂度 直接插入排序算法本身比较简洁,容易实现,该算法的时间复杂度为O(n2)。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 二叉排序树
定义 二叉排序树要么是空二叉树,要么具有如下特点: 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值; 二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值; 二叉排序树的左右子树也要求都是二叉排序树; 例如,下图就是一个二叉排序树: 二叉排序树关键字的操作 使用二叉排序树查找关键字 二叉排序树中查找某关键字时,查找过程类似于次优二叉树,在二叉排序树不为空树的前提下,首先将被查找值同树的根结点进行比较,会有 3 种不同的结果: 如果相等,查找成功; 如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中; 如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中; 实现函数为:(运用递归的方法) 1 2 3 4 5 6 7 8 9 10 11 12 BiTree SearchBST(BiTree T,KeyType key){ //如果递归过程中 T 为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针 if (!T || key==T->data) { return T; }else if(key<T->data){ //递归遍历其左孩子 return SearchBST(T->lchild, key); }else{ //递归遍历其右孩子 return SearchBST(T->rchild, key); } } 二叉排序树本身是动态查找表的一种表示形式,有时会在查找过程中插入或者删除表中元素,当因为查找失败而需要插入数据元素时,该数据元素的插入位置一定位于二叉排序树的叶子结点,并且一定是查找失败时访问的最后一个结点的左孩子或者右孩子。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 c++ //插入函数 BOOL InsertBST(BiTree T,ElemType e){ BiTree p=NULL; //如果查找不成功,需做插入操作 if (!SearchBST(T, e,NULL,&p)) { //初始化插入结点 BiTree s=(BiTree)malloc(sizeof(BiTree)); s->data=e; s->lchild=s->rchild=NULL; //如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点 if (!p) { T=s; } //如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子 else if(e<p->data){ p->lchild=s; }else{ p->rchild=s; } return true; } //如果查找成功,不需要做插入操作,插入失败 return false; } 例如,假设原二叉排序树为空树,在对动态查找表 {3,5,7,2,1} 做查找以及插入操作时,可以构建出一个含有表中所有关键字的二叉排序树,过程如图 所示: 通过不断的查找和插入操作,最终构建的二叉排序树如图 (5) 所示。当使用中序遍历算法遍历二叉排序树时,得到的序列为:1 2 3 5 7 ,为有序序列。 一个无序序列可以通过构建一棵二叉排序树,从而变成一个有序序列。 删除关键字 在查找过程中,如果在使用二叉排序树表示的动态查找表中删除某个数据元素时,需要在成功删除该结点的同时,依旧使这棵树为二叉排序树。 假设要删除的为结点 p,则对于二叉排序树来说,需要根据结点 p 所在不同的位置作不同的操作,有以下 3 种可能: 结点 p 为叶子结点,此时只需要删除该结点,并修改其双亲结点的指针即可; 结点 p 只有左子树或者只有右子树,此时只需要将其左子树或者右子树直接变为结点 p 双亲结点的左子树即可; 结点 p 左右子树都有,此时有两种处理方式: 令结点 p 的左子树为其双亲结点的左子树;结点 p 的右子树为其自身直接前驱结点的右子树,如图所示; 用结点 p 的直接前驱(或直接后继)来代替结点 p,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作。如图为使用直接前驱代替结点 p: 在对左图进行中序遍历时,得到的结点 p 的直接前驱结点为结点 s,所以直接用结点 s 覆盖结点 p,由于结点 s 还有左孩子,根据第 2 条规则,直接将其变为双亲结点的右孩子。 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include<stdio.h> #include<stdlib.h> #define TRUE 1 #define FALSE 0 #define ElemType int #define KeyType int /* 二叉排序树的节点结构定义 */ typedef struct BiTNode { int data; struct BiTNode *lchild, *rchild; } BiTNode, *BiTree; //二叉排序树查找算法 int SearchBST(BiTree T,KeyType key,BiTree f,BiTree *p){ //如果 T 指针为空,说明查找失败,令 p 指针指向查找过程中最后一个叶子结点,并返回查找失败的信息 if (!T){ *p=f; return FALSE; } //如果相等,令 p 指针指向该关键字,并返回查找成功信息 else if(key==T->data){ *p=T; return TRUE; } //如果 key 值比 T 根结点的值小,则查找其左子树;反之,查找其右子树 else if(key<T->data){ return SearchBST(T->lchild,key,T,p); }else{ return SearchBST(T->rchild,key,T,p); } } int InsertBST(BiTree *T,ElemType e){ BiTree p=NULL; //如果查找不成功,需做插入操作 if (!SearchBST((*T), e,NULL,&p)) { //初始化插入结点 BiTree s=(BiTree)malloc(sizeof(BiTree)); s->data=e; s->lchild=s->rchild=NULL; //如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点 if (!p) { *T=s; } //如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子 else if(e < p->data){ p->lchild=s; }else{ p->rchild=s; } return TRUE; } //如果查找成功,不需要做插入操作,插入失败 return FALSE; } //删除函数 int Delete(BiTree *p) { BiTree q, s; //情况 1,结点 p 本身为叶子结点,直接删除即可 if(!(*p)->lchild && !(*p)->rchild){ *p = NULL; } else if(!(*p)->lchild){ //左子树为空,只需用结点 p 的右子树根结点代替结点 p 即可; q = *p; *p = (*p)->rchild; free(q); } else if(!(*p)->rchild){//右子树为空,只需用结点 p 的左子树根结点代替结点 p 即可; q = *p; *p = (*p)->lchild;//这里不是指针 *p 指向左子树,而是将左子树存储的结点的地址赋值给指针变量 p free(q); } else{//左右子树均不为空,采用第 2 种方式 q = *p; s = (*p)->lchild; //遍历,找到结点 p 的直接前驱 while(s->rchild) { q = s; s = s->rchild; } //直接改变结点 p 的值 (*p)->data = s->data; //判断结点 p 的左子树 s 是否有右子树,分为两种情况讨论 if( q != *p ){ q->rchild = s->lchild;//若有,则在删除直接前驱结点的同时,令前驱的左孩子结点改为 q 指向结点的孩子结点 }else{ q->lchild = s->lchild;//否则,直接将左子树上移即可 } free(s); } return TRUE; } int DeleteBST(BiTree *T, int key) { if( !(*T)){//不存在关键字等于key的数据元素 return FALSE; } else { if( key == (*T)->data ){ Delete(T); return TRUE; } else if( key < (*T)->data){ //使用递归的方式 return DeleteBST(&(*T)->lchild, key); } else{ return DeleteBST(&(*T)->rchild, key); } } } void order(BiTree t)//中序输出 { if(t == NULL){ return ; } order(t->lchild); printf("%d ", t->data); order(t->rchild); } int main() { int i; int a[5] = {3,4,2,5,9}; BiTree T = NULL; for( i = 0; i < 5; i++ ){ InsertBST(&T, a[i]); } printf("中序遍历二叉排序树:\n"); order(T); printf("\n"); printf("删除3后,中序遍历二叉排序树:\n"); DeleteBST(&T,3); order(T); } 运行结果: 中序遍历二叉排序树: 2 3 4 5 9 删除3后,中序遍历二叉排序树: 2 4 5 9 总结 使用二叉排序树在查找表中做查找操作的时间复杂度同建立的二叉树本身的结构有关。即使查找表中各数据元素完全相同,但是不同的排列顺序,构建出的二叉排序树大不相同。 例如:查找表 {45,24,53,12,37,93} 和表 {12,24,37,45,53,93} 各自构建的二叉排序树图下图所示: 使用二叉排序树实现动态查找操作的过程,实际上就是从二叉排序树的根结点到查找元素结点的过程,所以时间复杂度同被查找元素所在的树的深度(层次数)有关。 本博文从 二叉排序树(二叉查找树)及C语言实现 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 B加树
B+树定义 一颗 m 阶的 B+树和 m 阶的 B-树的差异在于: 有 n 棵子树的结点中含有 n 个关键字; 在上一节中,在 B-树中的每个结点关键字个数 n 的取值范围为⌈m/2⌉ -1≤n≤m-1,而在 B+树中每个结点中关键字个数 n 的取值范围为:⌈m/2⌉≤n≤m。 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。 所有的非终端结点(非叶子结点)可以看成是索引部分,结点中仅含有其子树(根结点)中的最大(或最小)关键字。 例如,下图中所示的就是一棵深度为 4 的 3 阶 B+树: 如上图所示,B+树中含有两个头指针,一个指向整棵树的根结点,另一个指向关键字最小的叶子结点。同时所有的叶子结点依据其关键字的大小自小而大顺序链接,所有的叶子结点构成了一个 sqt 指针为头指针的链表。 所有,B+树可以进行两种查找运算:一种是利用 sqt 链表做顺序查找,另一种是从树的根结点开始,进行类似于二分查找的查找方式。 在 B+树中,所有非终端结点都相当于是终端结点的索引,而所有的关键字都存放在终端结点中,所有在从根结点出发做查找操作时,如果非终端结点上的关键字恰好等于给定值,此时并不算查找完成,而是要继续向下直到叶子结点。 B+树的查找操作,无论查找成功与否,每次查找操作都是走了一条从根结点到叶子结点的路径。 B+树中插入关键字 在B+树中插入关键字时,需要注意以下几点: 插入的操作全部都在叶子结点上进行,且不能破坏关键字自小而大的顺序; 由于 B+树中各结点中存储的关键字的个数有明确的范围,做插入操作可能会出现结点中关键字个数超过阶数的情况,此时需要将该结点进行“分裂”; B+树中做插入关键字的操作,有以下 3 种情况: 若被插入关键字所在的结点,其含有关键字数目小于阶数 M,则直接插入结束; 例如,在上图中插入关键字13,其结果如下图所示: 若被插入关键字所在的结点,其含有关键字数目等于阶数 M,则需要将该结点分裂为两个结点,一个结点包含⌊M/2⌋,另一个结点包含⌈M/2⌉。同时,将⌈M/2⌉的关键字上移至其双亲结点。假设其双亲结点中包含的关键字个数小于 M,则插入操作完成。 例如,在开始的图的基础上插入关键字 95,其插入后的 B+树如下图所示: 在第 2 情况中,如果上移操作导致其双亲结点中关键字个数大于 M,则应继续分裂其双亲结点。 例如,在开始的图的B+树中插入关键字 40,则插入后的 B+树如下图所示: 注意:如果插入的关键字比当前结点中的最大值还大,破坏了B+树中从根结点到当前结点的所有索引值,此时需要及时修正后,再做其他操作。例如,在图 1 的 B+树种插入关键字 100,由于其值比 97 还大,插入之后,从根结点到该结点经过的所有结点中的所有值都要由 97 改为 100。改完之后再做分裂操作。 B+树中删除关键字 在 B+树中删除关键字时,有以下几种情况: 找到存储有该关键字所在的结点时,由于该结点中关键字个数大于⌈M/2⌉,做删除操作不会破坏 B+树,则可以直接删除; 当删除某结点中最大或者最小的关键字,就会涉及到更改其双亲结点一直到根结点中所有索引值的更改; 当删除该关键字,导致当前结点中关键字个数小于⌈M/2⌉,若其兄弟结点中含有多余的关键字,可以从兄弟结点中借关键字完成删除操作; 第 3 种情况中,如果其兄弟结点没有多余的关键字,则需要同其兄弟结点进行合并; 当进行合并时,可能会产生因合并使其双亲结点破坏 B+树的结构,需要依照以上规律处理其双亲结点。 总之,在 B+树中做删除关键字的操作,采取如下的步骤: 删除该关键字,如果不破坏 B+树本身的性质,直接完成操作; 如果删除操作导致其该结点中最大(或最小)值改变,则应相应改动其父结点中的索引值; 在删除关键字后,如果导致其结点中关键字个数不足,有两种方法:一种是向兄弟结点去借,另外一种是同兄弟结点合并。(注意这两种方式有时需要更改其父结点中的索引值。) 本博文从 B+树 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 B-树
B-树定义 B-树,有时又写为B_树(其中的“-”或者“-_只是连字符,并不读作“B减树”),一颗 m 阶的 B-树,或者本身是空树,否则必须满足以下特性: 树中每个结点至多有 m 棵子树; 若根结点不是叶子结点,则至少有两棵子树; 除根之外的所有非终端结点至少有棵子树; 所有的非终端结点中包含下列信息数据:(n,A0,K1,A1,K2,A2,…,Kn,An); n 表示结点中包含的关键字的个数,取值范围是:⌈m/2⌉-1≤ n ≤m-1。Ki (i 从 1 到 n)为关键字,且 Ki < Ki+1 ;Ai 代表指向子树根结点的指针,且指针 Ai-1 所指的子树中所有结点的关键字都小于 Ki,An 所指子树中所有的结点的关键字都大于 Kn。 如图所示,当前结点中有 4 个关键字,之间的关系为:K1<K2<k3<K4。同时对于 A0 指针指向的子树中的所有关键字来说,其值都要比 K1 小;而 A1 指向的子树中的所有的关键字的值,都比 K1 大,但是都要比 K2 小。 所有的叶子结点都出现在同一层次,实际上这些结点都不存在,指向这些结点的指针都为 NULL; 例如图所示就是一棵 4 阶的 B-树,这棵树的深度为 4 : 在使用 B-树进行查找操作时,例如在如上图所示的 B-树中查找关键字 47 的过程为: 从整棵树的根结点开始,由于根结点只有一个关键字 35,且 35 < 47 ,所以如果 47 存在于这棵树中,肯定位于 A1 指针指向的右子树中; 然后顺着指针找到存有关键字 43 和 78 的结点,由于 43 < 47 < 78,所以如果 47 存在,肯定位于 A1 所指的子树中; 然后找到存有 47、53 和 64 三个关键字的结点,最终找到 47 ,查找操作结束; 以上图中的 B-树为例,若查找到深度为 3 的结点还没结束,则会进入叶子结点,但是由于叶子结点本身不存储任何信息,全部为 NULL,所以查找失败。 B-树插入关键字 B-树也是从空树开始,通过不断地插入新的数据元素构建的。B-树在插入新的数据元素时并不是每次都向树中插入新的结点。 因为对于 m 阶的 B-树来说,在定义中规定所有的非终端结点(终端结点即叶子结点,其关键字个数为 0)中包含关键字的个数的范围是[⌈m/2⌉-1,m-1],所以在插入新的数据元素时,首先向最底层的某个非终端结点中添加,如果该结点中的关键字个数没有超过 m-1,则直接插入成功,否则还需要继续对该结点进行处理。 假设现在下图的基础上插入 4 个关键字 30、26、85 和 7: 插入关键字 30 :从根结点开始,由于 30 < 45,所以要插入到以 b 结点为根结点的子树中,再由于 24 < 30,插入到以 d 结点为根结点的子树中,由于 d 结点中的关键字个数小于 m-1=2,所以可以将关键字 30 直接插入到 d 结点中。结果如下图所示: 插入关键字 26: 从根结点开始,经过逐个比较,最终判定 26 还是插入到 d 结点中,但是由于 d 结点中关键字的个数超过了 2,所以需要做如下操作: 关键字 37 及其左右两个指针存储到新的结点中,假设为 d’ 结点; 关键字 30 存储到其双亲结点 b 中,同时设置关键字 30 右侧的指针指向 d’; 经过以上操作后,插入 26 后的B-树为: 插入关键字 85: 从根结点开始,经过逐个比较,最终判定插入到 g 结点中,同样需要对 g 做分裂操作: 关键字 85 及其左右两个指针存储到新的结点中,假设为 g’ 结点; 关键字 70 存储到其双亲结点 e 中,同时设置 70 的右侧指针指向 g’ ; 经过以上操作后,插入 85 后的结果图为: 上图中,由于关键字 70 调整到其双亲结点中,使得其 e 结点中的关键字个数超过了 2,所以还需进一步调整: 将 90 及其左右指针存储到一个新的结点中,假设为 e’ 结点; 关键字 70 存储到其双亲结点 a 中,同时其右侧指针指向 e’ ; 最终插入关键字 85 后的 B-树为: 通过上边的例子,可以总结出一下结论:在构建 B-树的过程中,假设 p 结点中已经有 m-1 个关键字,当再插入一个关键字之后,此结点分裂为两个结点,如下图所示: 提示:如上图所示,结点分裂为两个结点的同时,还分裂出来一个关键字 K⌈m/2⌉,存储到其双亲结点中。 B-树删除关键字 在 B-树种删除关键字时,首先前提是找到该关键字所在结点,在做删除操作的时候分为两种情况,一种情况是删除结点为 B-树的非终端结点(不处在最后一层);另一种情况是删除结点为 B-树最后一层的非终端结点。 例如上边插入关键字的原始图来说,关键字 24、45、53、90属于不处在最后一层的非终端结点,关键字 3、12、37等同属于最后一层的非终端结点。 如果该结点为非终端结点且不处在最后一层,假设用 Ki 表示,则只需要找到指针 Ai 所指子树中最小的一个关键字代替 Ki,同时将该最小的关键字删除即可。 例如上边插入关键字的原始图中,如果要删除关键字 45 ,只需要使用关键字 50 代替 45 ,同时删除 f 结点中的 50 即可。 如果该结点为最后一层的非终端结点,有下列 3 种可能: 被删关键字所在结点中的关键字数目不小于⌈m/2⌉,则只需从该结点删除该关键字 Ki 以及相应的指针 Ai 。 例如,在上边插入关键字的原始图中,删除关键字 12 ,只需要删除该关键字 12以及右侧指向 NULL 指针即可。 被删关键字所在结点中的关键字数目等于⌈m/2⌉-1,而与该结点相邻的右兄弟结点(或者左兄弟)结点中的关键字数目大于⌈m/2⌉-1,只需将该兄弟结点中的最小(或者最大)的关键字上移到双亲结点中,然后将双亲结点中小于(或者大于)且紧靠该上移关键字的关键字移动到被删关键字所在的结点中。 例如在上边插入关键字的原始图中删除关键字 50,其右兄弟结点 g 中的关键字大于2,所以需要将结点 g 中最小的关键字 61 上移到其双亲结点 e 中(由此 e 中结点有:53,61,90),然后将小于 61 且紧靠 61 的关键字 53 下移到结点 f 中,最终删除后的 B-树如图所示。 上图删除结点50后的B-树 被删除关键字所在的结点如果和其相邻的兄弟结点中的关键字数目都正好等于⌈m/2⌉-1,假设其有右兄弟结点,且其右兄弟结点是由双亲结点中的指针 Ai所指,则需要在删除该关键字的同时,将剩余的关键字和指针连同双亲结点中的 Ki 一起合并到右兄弟结点中。 例如,在上图中 B-树中删除关键字 53,由于其有右兄弟,且右兄弟结点中只有 1 个关键字。在删除关键字 53 后,结点 f 中只剩指向叶子结点的空指针,连同双亲结点中的 61(因为 61 右侧指针指向的兄弟结点 g)一同合并到结点 g 中,最终删除 53 后的 B-树为: 上图删除结点53后的B-树。 在合并的同时,由于从双亲结点中删除一个关键字,若导致双亲结点中关键字数目小于⌈m/2⌉-1,则继续按照该规律进行合并。例如在上图中 B-树的情况下删除关键字 12 时,结点 c 中只有一个关键字,然后做删除关键字 37 的操作。此时在删除关键字 37 的同时,结点 d 中的剩余信息(空指针)同双亲结点中的关键字 24 一同合并到结点 c 中,效果图为: 上图删除结点 37后的效果图。 由于结点 b 中一个关键字也没有,所以破坏了B-树的结构,继续整合。在删除结点 b 的同时,由于 b 中仅剩指向结点 c 的指针,所以连同其双亲结点中的 45 一同合并到其兄弟结点 e 中,最终的B-树为: 上图删除37后的B-树。 总结 由于 B-树具有分支多层数少的特点,使得它更多的是应用在数据库系统中。 本博文从 B-树 严长生 转载而来,表示感谢。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
操作系统 运行机制
中央处理器CPU 单机系统: 一个计算机系统只有一个处理器。 多处理器系统: 一个计算机系统有多个处理器。 CPU的构成与基本工作方式 处理器一般由运算器、控制器、寄存器以及高速缓存构成。 运算器 实现任何指令中的算术和逻辑运算,是计算机的核心; 控制器 负责控制程序运行的流程,包括取指令、维护CPU状态、CPU与内存的交互等; 寄存器 是指令在CPU内部做处理的过程中能够张村数据、地址以及指令信息的存储设备; 高速缓存 处于CPU和物理内存之间,一般由控制器中的内存管理单元MMU管理; 处理器中的寄存器 用户可见存储器:对于高级语言来说,编译器通过一定的算法分配并使用这些寄存器,以最大限度地减少程序运行过程中的访问存储器的次数,这对程序运行速度的影响很大。 控制和状态存储器:用于控制处理器的操作。 数据寄存器:又称为通用寄存器,主要用于各种算术逻辑指令和访存指令,对具有浮点能力和多媒体能力处理能力的处理器来说,浮点处理过程的数据寄存器和整数处理时的数据寄存器一般是分离的。 地址寄存器:用于存储数据及指令的物理地址、线性地址、有效地址。 条形码寄存器:保存CPU操作结果的各种标记位。 作用:控制处理器的操作。 控制和状态寄存器包括了程序计数器、指令寄存器和程序状态字。 程序计数器(PC):记录了将要取出的指令的地址。 指令寄存器(IR):包含了最近取出的指令。 程序状态字(PSW):记录了处理器的运行模式信息。 指令执行的基本过程 处理指令的最简单的方式包括两种步骤:处理器先从存储器中每次读取一条指令,然后执行这条指令。 这些指令大致分为以下五类: 访问存储器指令:负责处理器和存储器之间的数据传送。 I/O指令:负责处理器与I/O模块之间的数据传输及命令发送。 算术逻辑指令:又称为数据处理指令,用以执行有关数据的算术与逻辑操作。 控制转移指令:可以指令一个新的指令的执行起点。 处理器控制指令:用于修改处理器状态,改变处理器工作方式。 例: 假设程序计数器PC正指向2000h地址处的指令,指令机器描述如下: 地址 指令 2000h MOVE [3340h], R1 2004h ADD R1, 1 2008h MOVE R1, [3340h] …… …… …… …… 指令MOVE被送入指令寄存器IR中,同时将自增一个指令的长度,(4个字节),取指之后PC为2004h。 这是一条访问内存的指令,树3340h所指定的双字地址单元中的数据取到通用寄存器R1中来。 CPU又从PC(地址为2004h)处取出指令ADD到IR中,PC变为2008h。 CPU根据指令将R1寄存器和立即数1相加。 访存指令MOVE被取到IR中,PC变为2004h。 特权指令与非特权指令 单用户单任务下使用计算机指令系统中的全部命令。 多用户多任务中,分为:特权模式和非特权模式。 特权指令 :是指指令系统中那些只能用操作系统使用的指令,这些特权指令是不允许一般的用户所使用的。 用户只能使用非特权指令,因为只有操作系统才能使用所有的指令。 处理器的状态 管态与目态 管态 :指操作系统管理程序运行的状态,具有较高的特权级别,又称特权态、系统态。 目态: 指用户程序运行时的状态,具有较低的特权级别,又称普通态、用户态。 例: 英特尔X86系列处理器特权级别 R0:运行操作系统的核心代码 R1:运行关键设备驱动程序和I/O处理例程 R2:运行其他受保护的贡献代码 R3:运行各种用户程序 R0到R3特权能力依次降低,R0相当于双状态系统的管态,R3相当于目态,而R1和R2则介于两者之间。 CPU 状态的转换 目态到管态的转换,权限提升 管态到目态的转换,可以通过设置PSW指令(修改程序状态字),实现从操作系统向用户程序的转换。 限制用户程序执行特权指令 程序状态字 PSW 程序状态字PSW:用程序计数器PC这个专门的寄存器来指示下一条要执行的指令。 PSW包括以下状态代码: CPU的工作状态码 条件码 中断屏蔽码 某些常见标志位: CF:进位标志位 ZF:结构为零标志位 SF:符号标志位 OF:溢出标志位 TF:陷阱标志位 IF:中断使能标志位 VIF:虚拟中断标志位 VIP:虚拟中断带决标志位 IOPL:IO特权级别 存储体系 一个作业必须把它的程序和数据存放在主存储器中才能运行。 存储器的层次结构 设计主要考虑三方面:容量、速度和成本。 容量是存储系统的基础。 速度存储系统的速度则要能匹配处理器的速度。 存储器的成本和其他部件相比应该在一个合适的范围内。 容量、速度和成本的匹配 存储速度越快,平均每比特价格越高,容量越小,平均每比特的价格越低,同时容量也增大。 存储访问局部性原理 存储保护 界地址寄存器(界限寄存器) 在CPU中设置一队界限寄存器来存放用户作业在主存中的下限和上限地址,分别称为下限寄存器和上限寄存器。指出程序在内存的存放位置。 越界中断又称存储保护中断,每当CPU要访问主存时,硬件自动被访问的主存地址与界限存储器的内容进行比较,以判断是否越界。如果未越界,则按此地址访问主存,否则将产生程序中断。 存储键 每个存储块都有一个与其相关的由二进位组成的存储保护键。 每当一个用户作业被允许进入主存时,操作系统分给他一个唯一的、不与其他作业相同的存储键号;并将分配该作业的各存储快的存储键,也设置成同样的键号。操作系统同时将该作业的存储键号存放到程序状态字PSW的存储键域中,这样,每当CPU访问主存时,都将对主存块的存储键与PSW中的钥匙进行比较。如果相比配,则允许访问;否则,拒绝并报警。 中断和异常机制 中断与异常 中断 指CPU对系统中或者系统外发生的异步事件的响应。 异步事件 是指无一定时间关系的随机发生的事件。 中断是所有要打断处理器的正常工作次序,并要求其去处理某一事件的一种手段。 中断事件:又称中断源,引起中断的事件。 中断请求:中断源向处理器发出的请求信号。 中断处理程序:处理中断事件的程序。 中断断点:处理器暂时当前程序转而处理中断的过程。 中断响应:处理器暂停当前程序转而处理中断的过程。 中断返回:中断处理结束后恢复原来程序的执行。 中断字一个计算机系统提供的中断源的有序集合。 中断向量表:中断处理程序入口地址映射表。 中断向量:表中的每一项,主要是由程序状态字PSW和指令计数器PC的值组成。 中断是由外部事件引发的。 异常则是由正在执行的指令引发的。 中断与异常的分类 中断 时钟中断:是由处理器内部的计时器产生。 输入输出(I/O)中断:正常完成或则发生的错误。 控制台中断:如系统操作员通过控制台发出的命令。 硬件故障中断:由掉电、存储器校验错等硬件故障引起的。 异常 程序性中断:由指令执行结果产生。 访问指令异常:要求操作系统提供系统服务。 中断系统 中断系统:是由硬件及软件相互配合、相互渗透而使得计算机系统得以充分发挥能力的计算机模式。 中斷系統的硬件中断装置和软件中断处理程序。硬件终端装置负责捕获中断源发出的中断请求,并以一定的方式响应中断源,然后将处理器的控制权移交给特定的中断处理程序.中断处理程序就是针对中断事件的性质而执行的一系列操作。 中断请求的接受 中断请求的接受是通过在计算机硬件上的终端逻辑线路和中断寄存器实现的。 触发器的值为1时,表示该触发器接收到了中断信号,为0时表示无中断信号。 中断响应 响应机制:处理器控制不见中的设置有中断信号扫描结构,它在每条指令执行周期内的最后时刻扫描出中断寄存器,查看是否有中断信号的到来。 有中断到来=》处理器结构硬件终端装置发来的中断向量代号。 无中断到来=》处理器就继续执行下一条指令。 中断请求响应的工作过程: 处理器接受中断信号 保护现场个,将中断断点的程序状态字PSW和程序计数器PC值存入系统堆栈。 分析中断向量,取得中断向量程序的入口程序。 将处理器的PC值置为中断处理程序的入口地址。 调解中断处理程序。 中断处理 接受和响应中断。 保护中断现场。 分析中断向量。 调用中断处理程序。 中断处理结束恢复现场。 原有程序继续执行。 几种典型的中断的处理 I/O中断:一般是由I/O设备的控制器或者通道发出。 时钟中断 维护软件时钟 处理器调度 控制系统定时任务 实时处理 硬件故障中断:一般是由硬件的问题引起的。例如复位硬件或者更换设备。 程序性中断:程序指令出错、指令越权或者指令寻址越界而引发的系统保护。 系统服务请求(访管中断):应用程序设计接口API。 中断优先级与终端屏蔽 多级中断与中断优先级 硬件上,多级中断系统表现为有多根中断请求线从不同设备连接到中断逻辑线路上。 各类中断信号依据其紧急程度和重要性划分级别。 解决如果有重要程度相当的多个中断信号同时到达时,如何选择首个被处理的中断信号的问题。 中断屏蔽 在整个中断系统中,可以允许或则禁止中断系统对某些类别中断的响应。 在程序状态字PSW中设计有中断屏蔽位,主机是否允许响应或禁止某些中断,则由PSW中的中断屏蔽位决定,这些屏蔽位标识了那些被屏蔽中断类或者中断。 例:在一个计算机系统中,CD-ROM到硬盘的数据传输的优先级低于硬盘内部的数据传输操作。 内存奇偶检验错,以及掉电等使得机器无法继续操作的一类故障。一旦发生这类不可屏蔽的中断,不管程序状态字的屏蔽位是否建立,处理器都要立即相应这类中断,并进行处理。 系统调用 系统调用 系统调用就是用户在程序中调用操作系统所提供的一系列子功能。 有特殊的机器指令实现的,由汇编语言直接访问。 系统调用与一般过程调用的区别 运行在不同的系统状态:调用程序运行在用户态,而被调用程序则运行在系统态。 状态的转换:通过软中断机制先由用户态转换为核心态,在操作系统核心分析之后,转向相应的系统调用处理子程序。 返回问题:让优先级最高的进程优先执行。 嵌套调用:在一个被调用的过程执行期间,还可在利用系统调用命令在去调用另一个系统调用。 系统调用的分类 进程控制类系统调用:对进程的控制,如创建和终止进程的系统调用。 文件操作类系统调用:对文件进行操作的系统调用,如创建、打开、关闭、读写文件等操作。 进程通信类系统调用:被用在进程之间传递信息和信号。 设备管理类系统调用:系统调用被用来请求和释放有关设备,以及启动设备操作。 信息维护类系统调用:有关信息维护的系统调用。 系统调用命令是作为扩充机器指令,增强系统的功能,方便用户使用而提供的。 “广义指令”:系统调用命令的过程。软件实现的 系统调用的处理过程 在系统中为控制系统调用服务的机构称为陷入(TRAP)或异常处理机构。 由于系统调用引起的处理机中断的指令称为陷入或异常指令(或称访管指令)。 一种是由陷入指令自带的参数。 另一种是通过有关通用寄存器来传递参数。 处理机在用户程序中执行称为用户态,而把处理机在系统程序中执行称为系统态(或管态)。 I/O技术 I/O结构 通道 通道是独立于中央处理器的,专门负责数据I/O传输工作的处理单元。 I/O处理机通道对外部设备实行统一的管理,代替CPU对I/O设备操作进行控制。 工作原理: 按程序规定的顺序执行一条条指令,按指令中给定的参数启动指定的设备。 控制权转移到通道 信息传送,由通道控制,而中央处理器则继续执行程序。 产生一个“输入输出操作结束”的I/O中断事件。 DMA 技术 直接存储器访问DMA技术通过系统总线中的一个独立的控制单元,自动的控制成块的数据在内存和I/O单元之间的传送, DMA控制单元命令包含了I/O设备的编址、开始读或写的主存编址、需要传送的数据长度、是否请求一次读和写等信息。 缓冲技术 缓冲技术实在外部设备于其他硬件之间的一种数据暂存技术,他利用存储器件在外部设备中设置了数据的一个存储区域,称为缓冲区。 两种用途: 在外部设备与外部设备之间的通信上。 在外部设备和处理器之间。 最根本原因:CPU处理数据的速度与设备传输数据速度不相匹配,需要缓冲区缓解其间的速度矛盾。 时钟 时钟的作用 在多道程序运行的环境中,防止时机的浪费。 在分时系统中,用时钟间隔来实现各个作业按时间片轮转运行。 在实时系统中,按要求的时间间隔输出争取的时间信号给相关的实时控制设备。 定时唤醒哪些要求按照事先给定的时间执行的各个外部事件。 记录用户使用各种设备的时间和记录某外部事件发生的时间间隔。 记录用户和系统所需要的绝对时间,即年月日。 时钟一般分为硬件时钟和软件时钟。 硬件时钟工作原理:在电路中的晶体震荡器,每个一定的时间间隔产生固定的脉冲频率,时钟电路中的时钟寄存器依据时钟电路所产生的脉冲数,对时钟寄存器进行加1操作。 软件时钟工作原理:主要是利用内存单元模拟时钟寄存器,并采用一段程序来计算相应的脉冲数,对内存时钟寄存器进行加1或减1操作。 时钟的用途可以分为绝对时钟和相对时钟。 绝对时钟是在计算机系统中不受外界干扰、独立运行的一种时钟。 相对时钟又称“间隔时钟”,它只计算从某一个时间初值开始的一段时间间隔。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
数据结构 折半查找法
算法原理 先确定待查记录所在的范围(区间),然后逐步缩小范围指导找到或找不到该记录为止。 算法性能 时间复杂度: log 2 n + 1 平均查找长度: log 2 n + 1 – 1 注意事项 折半查找法必须为有序数列。 可以是逆序的,但是必须得提前定义遍历对比对象。 算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 include "stdio.h" //折半查找函数 int binarySearch(int a[], int n, int key){ //定义数组的第一个数 int low = 0; //定义数组的最后一个数 int high = n-1; //定义中间的数值 int mid; //存放中间的数值的变量 int midVal; //当左边的值小于等于右边的值的时候 while(low <= high){ mid = (low + high)/2; midVal = a[mid]; //如果中间值小于用户查找到的数值,最低的数字到中间数值+1位置上 if(midVal < key){ low = mid + 1; }else if(midVal > key){ //如果中间值大于用户查找到的数值,最高的数字到中间数值-1位置上 high = mid - 1; } else return mid; } return -1; } int main(){ int i, val, ret; int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //打印数组 for(i=0; i<10; i++) printf("%d \t", a[i]); printf("\n请你输入你要进行查找的元素\n"); scanf("%d", &val); ret = binarySearch(a, 10, val); if(ret == -1){ printf("查找失败!\n"); } else{ printf("查找成功!\n"); } return 0; } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
操作系统 概论
操作系统的概念 计算机系统 计算机系统包括硬件子系统及软件子系统。 各种程序和数据组成了计算机的软件系统。 操作系统:在计算机系统中,集中了资源管理功能和控制程序执行功能的一种软件。 操作系统的定义 “有效”是指根据用户的不同的要求,在管理计算机资源时考虑到系统运行的效率及资源的利用率。 “合理”是指操作系统要“公平对待”不同的用户程序,保证系统不发生“死锁”及“饥饿”现象。 操作系统的特征 并发性 是指在计算机系统中同时存在若干个运行的程序。计算机的并发行体现在下面两个方面: 用户程序与用户程序之间并发执行 用户程序与操作系统之间并发执行 共享性 随机性 研究操作系统的观点 软件的观点 操作系统就是一种大型的软件系统,它是多种功能程序的集合。 外在特性 操作系统是一种软件,它的外部表现形式,即它的操作命令定义集和它的界面,完全确定了炒作系统这个软件的使用方式。 内在特性 操作系统既然是臃肿软件,他就具有一般软件的结构特点。 资源管理的观点操作系统就是负责记录谁在使用什么样的资源。 操作系统要提供一些机制去协调程序间的竞争与同步,提供机制对资源进行合理使用,对其保护,一机采取虚拟技术来“扩充”资源等。 进程的观点 操作系统就死看作是由多个可以独立运行的程序和一个对这些程序进行协调的核心所组成的。 虚拟机的观点 服务提供者的观点 操作系统的功能 进程管理 对中央处理器进行管理。 进程管理分为一下几个方面: 进程控制进程控制的主要任务就是创建进程、撤销结束的进程以及控制进程进行时候的各种状态的转换。 进程同步 互斥 :是指多个进程对临界资源访问时采用互斥的形式。 同步 :是在相互协作共同完成任务进程之间,用同步机制协调他们之间的执行顺序。 进程间通讯进程通讯主要发生在相互协作的进程之间。由操作系统提供给的进程间的通讯机制是协作的进程之间相互交换数据和消息的手段。 调度 调度又称处理器调度,通常包括进程调度、线程调度及作业调度。 进程调度 任务就是从进程(线程)的就绪队列中按照一定的算法挑选出一个,吧处理器资源分配给他,并准备好特定的执行上下文让他执行起来。 作业调度 依照作业说明书为他们分配一定的资源,把他们装进内存并未每个作业建立相应的进程。 存储管理存储管理的任务就是管理计算机内存的资源。 文件管理 文件管理的任务就是有效的支持文件的存储、检索及修改等操作,解决文件的共享、保密及保护问题,以使用户方便、安全的访问文件。 设备管理 用户接口 用户计算机系统之间的接口。 操作系统的发展 手工操作 通过在插板上的硬连接线来控制计算机的基本功能。 监控程序(早期批处理) 多道批处理 多道 是指允许多个程序同时存在于内存之中,由CPU以切换的方式为之服务,使得多个程序可以同时执行。 分时系统 分时系统 是指多个用户通过终端设备与计算机交互作用来运行自己的作业,并且共享一个计算机系统而互不干扰。 UNIX通用操作系统 C语言编写。 个人计算机操作系统 Android操作系统 操作系统分类 批处理操作系统 批处理操作系统特点就是成批处理。 作业吞吐率:在单位时间内计算机系统处理作业的个数。 设计思想 在监控程序启动之前,操作员有选择的把若干作业合并成一批作业,将这些作业安装在输入设备之上,然后自动监控程序,监控程序将自动控制这批作业执行。 一般指令与特权指令 运行模式通常分为用户模式和特权模式。 目态: 为用户服务的用户模式。 管态: 为系统专用的特权模式。 系统调用的过程 系统调用时,通常是中断或者异常处理,将处理器模式转变成特权模式。 由监控程序执行被请求的功能代码。 处理结束之后,监控程序恢复系统调用之前的现场;把运行模式从特权模式恢复成为用户方式;最后将控制权转移到原来的用户程序。 SPOOLing技术(假脱技术) 基本思想: 用磁盘设备作为主机的直接输入/输出设备,主机直接从磁盘上选取作业运行,作业的执行结果也存在磁盘上;相应的,通道则负责将用户作业从卡片机上动态写入磁盘,而这一操作与主机并行。 分时系统 基本工作方式 在分时系统中,一台计算机主机连接了若干个终端,每个终端可有一个用户使用。 设计思想 分时系统将CPU的时间划分成若干个小片段,称为时间片。操作系统以时间片为单位,轮流为每个终端用户服务。 特点 多路性: 只有多个用户在使用同一台计算机。 交互性: 指用户根据系统响应的结果提出下一个请求。 独占性: 指每个用户感觉不到计算机在为其他人服务。 及时性: 指系统能够对用户提出的请求及时给予响应。 分时操作系统追求的目标 :及时响应用户输入的交互命令。 分时与批处理的处理原则 :分时优先,批处理在后。 实时操作系统 实时操作系统(RTOS)是指计算机能在规定的时间内及时响应外部事件的请求,同时完成对该事件的处理,并能够控制所有实时设备和实时任务协调一致の工作的操作系统。 硬实时系统 对关键外部事件的响应和处理时间有着极为严格的要求,系统必须满足这种严格的时间要求,否则会产生严重的不良后果。 软实时系统 对事件的响应和处理时间有一定的时间范围要求,不能满足相关的要求会影响系统的服务质量,但是通常不会引发灾难性后果。 实时时钟管理 主要设计目标:对实时任务能够进行实时处理。 依据时间要求 定时任务: 依据用户定时启动并按照严格的时间间隔重复运行。 延时任务: 非周期运行,允许被延后执行,但往往有一个严格的时间线界限。 依据功能划分 主动式任务: 依据时间间隔主动运行,多用于实时监控。 从动式任务: 运行以来于外部时间的发生,但外部事件出现时,这种实时任务应尽可能地进行处理,并且保证不丢失现象。 过载防护 实时任务的启动时间和数量具有很大的随机性,突发的大量实时任务极有可能超出系统的处理能力,从而发生过载。 高可靠性 嵌入式操作系统EOS 嵌入式操作系统就是运行在嵌入式环境芯片中,对整个芯片以及它所操作的、控制的各种部件装置等资源进行统一协调、调度、指挥和控制的系统软件。 优点 具有高可靠性、实时性、占用资源少、智能化能源管理、易于连接、低成本等优点。 个人计算机操作系统PCOS 个人计算机操作系统是一种单用户多任务的操作系统。 网络操作系统NOS 网络操作系统:为计算机网络配置的操作系统。 分布式操作系统DOS 将大量计算机通过网络连接在一起,可以获得极高的运算能力及广泛的数据共享。 特征: 是一个统一的操作系统。 实现资源的深度共享。 透明性。 自治性。 集群 Cluster是分布式系统的一种,一个集群通常由一群处理器密集构成,集群操作系统专门服务于这样的集群。用低成本的微型计算机和以太网设备等产品,构造出性能相当于超级计算机运行性能的集群。 智能卡操作系统COS 四个基本功能:资源管理、通信管理、安全管理和应用管理。 操作系统结构 操作系统结构就是指操作系统各部分程序存在方式及相互关系。 模块结构: 以程序模块方式存在。 进程结构: 以进程的方式存在。 整体式结构 模块 将总功能分解成若干个子功能,实现每个子功能的程序。 优点:结构紧密,接口简单直接,系统效率较高。 模块组合法 (又称无需模块法,模块接口法),系统中的模块不是根据程序和数据本身的特性而是根据他们完成的功能来划分的,数据基本上作为全称量使用。 层次结构 层次结构就是把操作系统的所有功能模块,按照功能流程图的调用次序,分别将这些模块排列成若干层,各层之间的模块只能是单项依赖或则单先调用 。 全序的层次关系: 每一层中的同层模块之间不存在相互调用的关系。 优点: 整体问题局部化 各模块之间的组织架构和依赖关系清晰明了。 分层原则: 可适应性,方便于系统一直,可放在仅靠硬件的最底层。BIOS但硬件系统环境改变时只需要修改这一层模块就可以。 多种操作方式, 共同要使用的基本部分放在内层,而改变的部分放在外层。 微内核结构(C/S结构) 采用C/S结构的操作系统适宜于应用在网络环境下分布式处理的计算环境。 特点: 运行在核心态的内核:线程调度、虚拟内存、信息传递、设备驱动以及内核的原语操作集中中断处理等。 运行在用户态的并以C/S方式运行的进程层:除内核部分外,操作系统所有的其他部分被分成若干个相对独立的进程,每一个进程实现一组服务,称为服务进程。 这些服务进程可以提供各种系统功能、文件系统服务以及网络服务等。 好处: 可靠: 每一个分支是独立的,不会引起其他组成部分的损坏或崩溃。 灵活: 是自包含的,且接口规范,可维护性好。 分布式处理:具有分布式处理的能力。 缺点: 效率较低。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Sublime Text 崇高的文本编辑器
安装 Sublime text3 软件 官方网址:https://www.sublimetext.com/3 选择Windows - also available as a portable version一项,点击下载安装。 安装 packagecontrol 插件 官方网址:https://packagecontrol.io/ 选择 Installation 项 选择 SUBLIME TEXT3 代码进行复制 打开 sublime text3 软件,选择 View->Show Console 选项(或者按 Ctrl+~组合键),调出命令行,将代码粘贴至命令行,回车,进行安装 packagecontrol 插件; 安装好之后在菜单栏Preferences栏目中会有packagecontrol选项,即安装成功。 安装汉化插件 ChineseLocalization 安装 sublime 汉化插件 ChineseLocalization 在弹出的框中,由于网速原因,请耐心等待…… 输入插件名称ChineseLocalization,回车[enter]进行安装 各类插件安装推荐 其他插件均和 ChineseLocalization 插件安装过程一样,在此不再重复操作,只推荐几款插件。 必备的插件安装 (初学者推荐安装) Emmet Emmet 的前身是大名鼎鼎的 Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生。它使用仿 CSS 选择器的语法来生成代码,大大提高了 HTML/CSS 代码编写的速度。 PySide PySide 是跨平台的应用程序框架 Qt 的 Python 绑定版本。 AutoFileName 一款在 Sublime Text 中可以自动补全文件路径及名称的插件。 DocBlockr DocBlockr 是一款 Sublime Text 2 & 3 都可以使用的代码快注释插件。支持的语言有:JavaScript (including ES6), PHP, ActionScript, Haxe,CoffeeScript, TypeScript, Java, Groovy, Objective C, C, C++ and Rust. BracketHighlighter BracketHighlighter 是一款 Sublime 下匹配标签高亮的小插件,可以把匹配到的如 {}、()、”、””等对应的符号或者标签高亮显示。 Browser Refresh 通过一个快捷键可以实现保存文件,切换到浏览器并自动刷新浏览器来查看更改结果。 ConvertToUTF8 解决文档保存编码问题。 ColorPicker 一个多平台的颜色选择器插件。默认情况下,十六进制颜色代码使用大写字母插入。 进阶的程序猿推荐插件安装 a file icon 美化插件。可以更清楚了解每个文件的类型,一目了然。 git 版本控制仓库,推荐学习使用。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
小十
入驻第1年
火星旅行~~
工作逐渐进入正轨,刚入职到了市公司的人资部,后来分了工区就转战工区机关,再后来又被工区机关下放到运维班组。在运维班组也是待了两周多了~一级一级的放下来,也意味着离这几年的工作常态越来越接近。生活逐渐也平淡下来,但是还是不愿意接受一成不变的生活状态,想学一些新的东西,充实下自己。
小十
入驻第1年
AUTOCAD 安全系统软件锁许可管理器不起作用或未正确安装?解决办法在这里
如上图!安全系统(软件锁许可管理器)不起作用或未正确安装 1,打开任务管理器灭掉LUM.EXE进程; 2,删除 C:\ProgramData\FLEXnet 下的所有文件 3,启动服务管理,找到FlexNet Licensing Service 如果有X64也一起 启用!! 4,重新启动CAD并重新授权,这个时候已经OK了
小十
入驻第1年
工作狂魔
这周的周一到周五,是省公司组织的新员工培训,而上上周的周二到周四三天是市公司组织的新员工培训,也就是这两周都处在一个培训的过程中,也逐渐进入了工作状态。 自从7月14日正式入职以来也算是经历丰富,在供电服务指挥中心的客服岗干了一个月,接过各种各样的电话,对业务也有了一些了解,后来又去人资档案室帮忙,跟其他新入职的小伙伴都逐个见了面,熟悉了下。 市公司组织的培训有去看变电站,有正常讲课,也有去营销去参观,还有素质拓展,短时间的军训,还有可怕的按规考试……

© 2026 好站网HaoZhan.wang 1.7 版权所有

苏ICP备19065220号-4    萌ICP备20269980号    茶ICP备2026050346号
本站数据    2026年报    版本历史    关于本站