精华内容
下载资源
问答
  • RAID模式优缺点

    千次阅读 2018-09-10 14:57:27
    一、RAID模式优缺点的简要介绍 目前被运用较多的RAID模式其优缺点大致是这样的: 1、RAID0模式 优点:在RAID 0状态下,存储数据被分割成两部分,分别存储在两块硬盘上,此时移动硬盘的理论存储速度是单块硬盘...

    一、RAID模式优缺点的简要介绍
    目前被运用较多的RAID模式其优缺点大致是这样的:
    1、RAID0模式
    优点:在RAID 0状态下,存储数据被分割成两部分,分别存储在两块硬盘上,此时移动硬盘的理论存储速度是单块硬盘的2倍,实际容量等于两块硬盘中较小一块硬盘的容量的2倍。
    缺点:任何一块硬盘发生故障,整个RAID上的数据将不可恢复。
    备注:存储高清电影比较适合。
    2、RAID1模式
    优点:此模式下,两块硬盘互为镜像。当一个硬盘受损时,换上一块全新硬盘(大于或等于原硬盘容量)替代原硬盘即可自动恢复资料和继续使用,移动硬盘的实际容量等于较小一块硬盘的容量,存储速度与单块硬盘相同。RAID 1的优势在于任何一块硬盘出现故障是,所存储的数据都不会丢失。
    缺点:该模式可使用的硬盘实际容量比较小,仅仅为两颗硬盘中最小硬盘的容量。
    备注:非常重要的资料,如数据库,个人资料,是万无一失的存储方案。
    3、RAID 0+1模式
    RAID 0+1是磁盘分段及镜像的结合,采用2组RAID0的磁盘阵列互为镜像,它们之间又成为一个RAID1的阵列。硬盘使用率只有50%,但是提供最佳的速度及可靠度。
    4、RAID 3模式
    RAID3是把数据分成多个“块”,按照一定的容错算法,存放在N+1个硬盘上,实际数据占用的有效空间为N个硬盘的空间总和,而第N+1个硬盘存储的数据是校验容错信息,当这N+1个硬盘中的其中一个硬盘出现故障时,从其它N个硬盘中的数据也可以恢复原始数据。
    5、RAID 5模式
    RAID5不对存储的数据进行备份,而是把数据和相对应的奇偶校验信息存储到组成RAID5的各个磁盘上,并且奇偶校验信息和相对应的数据分别存储于不同的磁盘上。当RAID5的一个磁盘数据发生损坏后,利用剩下的数据和相应的奇偶校验信息去恢复被损坏的数据。
    6、RAID 10模式
    RAID10最少需要4块硬盘才能完成。把2块硬盘组成一个RAID1,然后两组RAID1组成一个RAID0。虽然RAID10方案造成了50%的磁盘浪费,但是它提供了200%的速度和单磁盘损坏的数据安全性。
    二、另外三种硬件快速硬件设置模式简介
    在收集资料时看到有的硬件设备提供快速磁盘模式设置,也很方便大家的使用,具体情况如下:
    1、Clone模式
    克隆模式,磁盘全部数据一样,以最小硬盘的为准。
    2、Large模式
    硬盘容量简单相加,将几个硬盘变成一个硬盘,容量为几个硬盘容量之和,此模式下可以获得最大的硬盘空间。
    3、Normal模式
    硬盘分别处于正常、独立的状态,可以分别独立的写入或读取资料,能使用的实际容量分别为4个硬盘的容量。如果其中一个硬盘受损,其他几个硬盘不会受影响。

     三、RAID使用简明注意事项
    ★使用前请先备份硬盘的资料,一旦进行RAID设定或是变更RAID模式,将会清除硬盘里的所有资料,以及无法恢复;
    ★建立RAID时,建议使用相同品牌、型号和容量的硬盘,以确保性能和稳定;
    ★请勿随意更换或取出硬盘,如果取出了硬盘,请记下硬盘放入两个仓位的顺序不得更改,以及请勿只插入某一块硬盘使用,以避免造成资料损坏或丢失;
    ★如果旧硬盘曾经在RAID模式下使用,请先进清除硬盘RAID信息,让硬盘回复至出厂状态,以免RAID建立失败;
    ★RAID0模式下,其中一个硬盘损坏时,其它硬盘所有资料都将丢失;
    ★RAID1模式下,如果某一块硬盘受损,可以用一块大于或等于受损硬盘容量的新硬盘替换坏硬盘然后开机即可自动恢复和修复资料以及RAID模式。此过程需要一定时间,请耐心等待
    四、细数RAID模式
    1、概念
    磁盘阵列(Redundant Arrays of Inexpensive Disks,RAID),有“价格便宜且多余的磁盘阵列”之意。原理是利用数组方式来作磁盘组,配合数据分散排列的设计,提升数据的安全性。磁盘阵列是由很多便宜、容量较小、稳定性较高、速度较慢磁盘,组合成一个大型的磁盘组,利用个别磁盘提供数据所产生加成效果提升整个磁盘系统效能。同时利用这项技术,将数据切割成许多区段,分别存放在各个硬盘上。磁盘阵列还能利用同位检查(Parity Check)的观念,在数组中任一颗硬盘故障时,仍可读出数据,在数据重构时,将数据经计算后重新置入新硬盘中。
    2、规范
    RAID技术主要包含RAID 0~RAID 50等数个规范,它们的侧重点各不相同,常见的规范有如下几种:
    

    RAID 0:RAID 0连续以位或字节为单位分割数据,并行读/写于多个磁盘上,因此具有很高的数据传输率,但它没有数据冗余,因此并不能算是真正的RAID结构。RAID 0只是单纯地提高性能,并没有为数据的可靠性提供保证,而且其中的一个磁盘失效将影响到所有数据。因此,RAID 0不能应用于数据安全性要求高的场合。

    RAID 1:它是通过磁盘数据镜像实现数据冗余,在成对的独立磁盘上产生互为备份的数据。当原始数据繁忙时,可直接从镜像拷贝中读取数据,因此RAID 1可以提高读取性能。RAID 1是磁盘阵列中单位成本最高的,但提供了很高的数据安全性和可用性。当一个磁盘失效时,系统可以自动切换到镜像磁盘上读写,而不需要重组失效的数据。

    RAID 0+1: 也被称为RAID 10标准,实际是将RAID 0和RAID 1标准结合的产物,在连续地以位或字节为单位分割数据并且并行读/写多个磁盘的同时,为每一块磁盘作磁盘镜像进行冗余。它的优点是同时拥有RAID 0的超凡速度和RAID 1的数据高可靠性,但是CPU占用率同样也更高,而且磁盘的利用率比较低。
    RAID 2:将数据条块化地分布于不同的硬盘上,条块单位为位或字节,并使用称为“加重平均纠错码(海明码)”的编码技术来提供错误检查及恢复。这种编码技术需要多个磁盘存放检查及恢复信息,使得RAID 2技术实施更复杂,因此在商业环境中很少使用。
    RAID 3:它同RAID 2非常类似,都是将数据条块化分布于不同的硬盘上,区别在于RAID 3使用简单的奇偶校验,并用单块磁盘存放奇偶校验信息。如果一块磁盘失效,奇偶盘及其他数据盘可以重

    新产生数据;如果奇偶盘失效则不影响数据使用。RAID 3对于大量的连续数据可提供很好的传输率,但对于随机数据来说,奇偶盘会成为写操作的瓶颈。

    RAID 4:RAID 4同样也将数据条块化并分布于不同的磁盘上,但条块单位为块或记录。RAID 4使用一块磁盘作为奇偶校验盘,每次写操作都需要访问奇偶盘,这时奇偶校验盘会成为写操作的瓶颈,因此RAID 4在商业环境中也很少使用。

    RAID 5:RAID 5不单独指定的奇偶盘,而是在所有磁盘上交叉地存取数据及奇偶校验信息。在RAID 5上,读/写指针可同时对阵列设备进行操作,提供了更高的数据流量。RAID 5更适合于小数据块和随机读写的数据。RAID 3与RAID 5相比,最主要的区别在于RAID 3每进行一次数据传输就需涉及到所有的阵列盘;而对于RAID 5来说,大部分数据传输只对一块磁盘操作,并可进行并行操作。在RAID 5中有“写损失”,即每一次写操作将产生四个实际的读/写操作,其中两次读旧的数据及奇偶信息,两次写新的数据及奇偶信息。
    RAID 6:与RAID 5相比,RAID 6增加了第二个独立的奇偶校验信息块。两个独立的奇偶系统使用不同的算法,数据的可靠性非常高,即使两块磁盘同时失效也不会影响数据的使用。但RAID 6需要分配给奇偶校验信息更大的磁盘空间,相对于RAID 5有更大的“写损失”,因此“写性能”非常差。较差的性能和复杂的实施方式使得RAID 6很少得到实际应用。
    RAID 7:这是一种新的RAID标准,其自身带有智能化实时操作系统和用于存储管理的软件工具,可完全独立于主机运行,不占用主机CPU资源。RAID 7可以看作是一种存储计算机(Storage Computer),它与其他RAID标准有明显区别。除了以上的各种标准(如表1),我们可以如RAID 0+1那样结合多种RAID规范来构筑所需的RAID阵列,例如RAID 5+3(RAID 53)就是一种应用较为广泛的阵列形式。用户一般可以通过灵活配置磁盘阵列来获得更加符合其要求的磁盘存储系统。
    RAID 5E(RAID 5 Enhencement): RAID 5E是在RAID 5级别基础上的改进,与RAID 5类似,数据的校验信息均匀分布在各硬盘上,但是,在每个硬盘上都保留了一部分未使用的空间,这部分空间没有进行条带化,最多允许两块物理硬盘出现故障。看起来,RAID 5E和RAID 5加一块热备盘好象差不多,其实由于RAID 5E是把数据分布在所有的硬盘上,性能会比RAID5 加一块热备盘要好。当一块硬盘出现故障时,有故障硬盘上的数据会被压缩到其它硬盘上未使用的空间,逻辑盘保持RAID 5级别。
    RAID 5EE: 与RAID 5E相比,RAID 5EE的数据分布更有效率,每个硬盘的一部分空间被用作分布的热备盘,它们是阵列的一部分,当阵列中一个物理硬盘出现故障时,数据重建的速度会更快。
    RAID 50:RAID50是RAID5与RAID0的结合。此配置在RAID5的子磁盘组的每个磁盘上进行包括奇偶信息在内的数据的剥离。每个RAID5子磁盘组要求三个硬盘。RAID50具备更高的容错能力,因为它允许某个组内有一个磁盘出现故障,而不会造成数据丢失。而且因为奇偶位分部于RAID5子磁盘组上,故重建速度有很大提高。优势:更高的容错能力,具备更快数据读取速率的潜力。需要注意的是:磁盘故障会影响吞吐量。故障后重建信息的时间比镜像配置情况下要长。
    3、优点
    提高传输速率。RAID通过在多个磁盘上同时存储和读取数据来大幅提高存储系统的数据吞吐量(Throughput)。在RAID中,可以让很多磁盘驱动器同时传输数据,而这些磁盘驱动器在逻辑上又是一个磁盘驱动器,所以使用RAID可以达到单个磁盘驱动器几倍、几十倍甚至上百倍的速率。这也是RAID最初想要解决的问题。因为当时CPU的速度增长很快,而磁盘驱动器的数据传输速率无法大幅提高,所以需要有一种方案解决二者之间的矛盾。RAID最后成功了。
    通过数据校验提供容错功能。普通磁盘驱动器无法提供容错功能,如果不包括写在磁盘上的CRC(循环冗余校验)码的话。RAID容错是建立在每个磁盘驱动器的硬件容错功能之上的,所以它提供更高的安全性。在很多RAID模式中都有较为完备的相互校验/恢复的措施,甚至是直接相互的镜像备份,从而大大提高了RAID系统的容错度,提高了系统的稳定冗余性。
    4、实现
    磁盘阵列有两种方式可以实现,那就是“软件阵列”与“硬件阵列”。
    软件阵列是指通过网络操作系统自身提供的磁盘管理功能将连接的普通SCSI卡上的多块硬盘配置成逻辑盘,组成阵列。软件阵列可以提供数据冗余功能,但是磁盘子系统的性能会有所降低,有的降低幅度还比较大,达30%左右。
    硬件阵列是使用专门的磁盘阵列卡来实现的。硬件阵列能够提供在线扩容、动态修改阵列级别、自动数据恢复、驱动器漫游、超高速缓冲等功能。它能提供性能、数据保护、可靠性、可用性和可管理性的解决方案。阵列卡专用的处理单元来进行操作,它的性能要远远高于常规非阵列硬盘,并且更安全更稳定。
    磁盘阵列其实也分为软阵列 (Software Raid)和硬阵列 (Hardware Raid) 两种. 软阵列即通过软件程序并由计算机的 CPU提供运行能力所成. 由于软件程式不是一个完整系统故只能提供最基本的 RAID容错功能. 其他如热备用硬盘的设置, 远程管理等功能均一一欠奉. 硬阵列是由独立操作的硬件提供整个磁盘阵列的控制和计算功能. 不依靠系统的CPU资源.
    由于硬阵列是一个完整的系统, 所有需要的功能均可以做进去. 所以硬阵列所提供的功能和性能均比软阵列好. 而且, 如果你想把系统也做到磁盘阵列中, 硬阵列是唯一的选择. 故我们可以看市场上 RAID 5 级的磁盘阵列均为硬阵列. 软 阵列只适用于 Raid 0 和 Raid 1. 对于我们做镜像用的镜像塔, 肯定不会用 Raid 0或 Raid 1。作为高性能的存储系统,已经得到了越来越广泛的应用。RAID的级别从RAID概念的提出到现在,已经发展了六个级别,其级别分别是0、1、2、3、4、5等。但是最常用的是0、1、3、5四个级别。
    五、个人用户该选那种RAID模式
    首先要分析清楚,我们需要存储的文件有什么样的属性。这其中需要大量存储的和占用存储量大的文件是两回事儿。
    从使用角度粗略分,个人需要存储的文件大致有文本文件、照片录像、影音文件、应用程序等。
    1、文本文件:大量长期存放,阶段性更新,但其占用空间小,安全性要求个别较高,大部分一般;
    2、照片录像:大量长期存放,永久性记录,占用空间大,安全性要求高,一旦损失很难弥补;
    3、影音文件:一部分大量长期存放,一部分大量短期存放,阶段性更新,占用空间大,安全性要求一般,即便损失了,也可以再从网络上下载恢复;
    4、应用程序:这其中包括一些软件和硬件的驱动等,对于软件,目前基本可以从网络上获得,驱动程序有时需要预先备份,安装设备时随时可用,属于量少但要长期存放的,阶段性更新,安全性要求一般。
    看看自己需要对哪种类型的文件进行存储,再选择自己需要的RAID模式即可。
    本人的照片和私人录影资料较多,平时喜欢收集APE等无损格式的音乐文件,对于个人来说这都是至宝,不可有所损失,再有就是一些硬件的驱动程序,相对比较重要,另外会编辑少量的个人文件,阶段性比较重要,最后是影片,看完也就删除了,不太重要。而照片录像和无损音乐占用的空间又是巨大的,安全性要求又很高,权衡后,在节约资金确保安全的前提下,准备购置五块大容量硬盘,组成NAS存储服务器,选择RAID5模式。
    顺便说,购置五快硬盘的原因还有一个,就是我使用的是老机箱改造NAS服务器,市面上有3转5的硬盘笼子可以简单将原有的3个光驱位变成5块硬盘的存储位,考虑到家用存储8T的容量已经足够了,10T基本上可以无忧了,所以选择了5块硬盘,每块2T容量。当然组成RAOD5后会少于10T,那也足够了!

    展开全文
  • 基于MATLAB通过对未编码,汉明码 ,循环码及卷积码的信噪比进行比较,比较它们之间的性能
  • NodeJS优缺点及适用场景讨论

    千次阅读 2016-10-17 16:14:32
    概述:NodeJS宣称其目标是“旨在提供一种简单的构建可伸缩网络程序的方法”,那么它的出现是为了解决什么问题呢,它有什么优缺点以及它适用于什么场景呢? 本文就个人使用经验对这些问题进行探讨。 一. NodeJS...

    概述:NodeJS宣称其目标是“旨在提供一种简单的构建可伸缩网络程序的方法”,那么它的出现是为了解决什么问题呢,它有什么优缺点以及它适用于什么场景呢?

    本文就个人使用经验对这些问题进行探讨。

    一. NodeJS的特点

    我们先来看看NodeJS官网上的介绍:

    Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

    其特点为:
    1. 它是一个Javascript运行环境

    2. 依赖于Chrome V8引擎进行代码解释

    3. 事件驱动

    4. 非阻塞I/O

    5. 轻量、可伸缩,适于实时数据交互应用

    6. 单进程,单线程

    二. NodeJS带来的对系统瓶颈的解决方案

    它的出现确实能为我们解决现实当中系统瓶颈提供了新的思路和方案,下面我们看看它能解决什么问题。

    1. 并发连接

    举个例子,想象一个场景,我们在银行排队办理业务,我们看看下面两个模型。

    (1)系统线程模型:

    系统线程模型

    这种模型的问题显而易见,服务端只有一个线程,并发请求(用户)到达只能处理一个,其余的要先等待,这就是阻塞,正在享受服务的请求阻塞后面的请求了。

    (2)多线程、线程池模型:

    多线程、线程池模型

    这个模型已经比上一个有所进步,它调节服务端线程的数量来提高对并发请求的接收和响应,但并发量高的时候,请求仍然需要等待,它有个更严重的问题。到代码层面上来讲,我们看看客户端请求与服务端通讯的过程:

    客户端请求与服务端通讯的过程

    服务端与客户端每建立一个连接,都要为这个连接分配一套配套的资源,主要体现为系统内存资源,以PHP为例,维护一个连接可能需要20M的内存。这就是为什么一般并发量一大,就需要多开服务器。

    那么NodeJS是怎么解决这个问题的呢?我们来看另外一个模型,想象一下我们在快餐店点餐吃饭的场景。

    (3)异步、事件驱动模型

    异步、事件驱动模型

    我们同样是要发起请求,等待服务器端响应;但是与银行例子不同的是,这次我们点完餐后拿到了一个号码,拿到号码,我们往往会在位置上等待,而在我们后面的请求会继续得到处理,同样是拿了一个号码然后到一旁等待,接待员能一直进行处理。

    等到饭菜做号了,会喊号码,我们拿到了自己的饭菜,进行后续的处理(吃饭)。这个喊号码的动作在NodeJS中叫做回调(Callback),能在事件(烧菜,I/O)处理完成后继续执行后面的逻辑(吃饭),这体现了NodeJS的显著特点,异步机制、事件驱动整个过程没有阻塞新用户的连接(点餐),也不需要维护已经点餐的用户与厨师的连接。

    基于这样的机制,理论上陆续有用户请求连接,NodeJS都可以进行响应,因此NodeJS能支持比Java、PHP程序更高的并发量虽然维护事件队列也需要成本,再由于NodeJS是单线程,事件队列越长,得到响应的时间就越长,并发量上去还是会力不从心。

    总结一下NodeJS是怎么解决并发连接这个问题的:更改连接到服务器的方式,每个连接发射(emit)一个在NodeJS引擎进程中运行的事件(Event),放进事件队列当中,而不是为每个连接生成一个新的OS线程(并为其分配一些配套内存)。

    2. I/O阻塞

    NodeJS解决的另外一个问题是I/O阻塞,看看这样的业务场景:需要从多个数据源拉取数据,然后进行处理。

    (1)串行获取数据,这是我们一般的解决方案,以PHP为例I/O阻塞-PHP为例

    假如获取profile和timeline操作各需要1S,那么串行获取就需要2S。

    (2)NodeJS非阻塞I/O,发射/监听事件来控制执行过程

    非I/O阻塞-PHP为例

    NodeJS遇到I/O事件会创建一个线程去执行,然后主线程会继续往下执行的,因此,拿profile的动作触发一个I/O事件,马上就会执行拿timeline的动作,两个动作并行执行,假如各需要1S,那么总的时间也就是1S。它们的I/O操作执行完成后,发射一个事件,profile和timeline,事件代理接收后继续往下执行后面的逻辑,这就是NodeJS非阻塞I/O的特点。

    总结一下:Java、PHP也有办法实现并行请求(子线程),但NodeJS通过回调函数(Callback)和异步机制会做得很自然。

    三. NodeJS的优缺点

    优点:1. 高并发(最重要的优点)

    2. 适合I/O密集型应用

    缺点:1. 不适合CPU密集型应用;CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;

    解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起;

    2. 只支持单核CPU,不能充分利用CPU

    3. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃

    原因:单进程,单线程

    解决方案:(1)Nnigx反向代理,负载均衡,开多个进程,绑定多个端口;

    (2)开多个进程监听同一个端口,使用cluster模块;

    4. 开源组件库质量参差不齐,更新快,向下不兼容

    5. Debug不方便,错误没有stack trace

    四. 适合NodeJS的场景

    1. RESTful API

    这是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的API需求。

    2. 统一Web应用的UI层

    目前MVC的架构,在某种意义上来说,Web开发有两个UI层,一个是在浏览器里面我们最终看到的,另一个在server端,负责生成和拼接页面。

    不讨论这种架构是好是坏,但是有另外一种实践,面向服务的架构,更好的做前后端的依赖分离。如果所有的关键业务逻辑都封装成REST调用,就意味着在上层只需要考虑如何用这些REST接口构建具体的应用。那些后端程序员们根本不操心具体数据是如何从一个页面传递到另一个页面的,他们也不用管用户数据更新是通过Ajax异步获取的还是通过刷新页面。

    3. 大量Ajax请求的应用

    例如个性化应用,每个用户看到的页面都不一样,缓存失效,需要在页面加载的时候发起Ajax请求,NodeJS能响应大量的并发请求。  总而言之,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景。

    五. 结尾

    其实NodeJS能实现几乎一切的应用,我们考虑的点只是适不适合用它来做。

    展开全文
  • MicroPython做嵌入式开发的优缺点

    千次阅读 2020-03-18 12:51:35
    让我们来看看使用 MicroPython 的一些优缺点: Python 编程语言具有浅薄的学习曲线,这使得开发人员可以非常轻松地开始使用它。事实上,我遇到过学习 Python 的小学生!Python 为开发人员提供了一种高级编程语言,可...

    让我们来看看使用 MicroPython 的一些优缺点:

    Python 编程语言具有浅薄的学习曲线,这使得开发人员可以非常轻松地开始使用它。事实上,我遇到过学习 Python 的小学生!Python 为开发人员提供了一种高级编程语言,可用于构建简单的脚本,或者可用于开发复杂的面向对象的体系结构,这些体系结构使用现代软件项目的所有最佳实践。与 C 相比,Python 还提供了内置机制,用于创建线程,处理错误并轻松集成到测试工具中。

    变革之风正在我们身上,MicroPython 可能是您用来构建下一个产品的编程语言。在本课程中,我们将研究如何使用 MicroPython 开发产品。与会者将远离本课程,详细了解他们需要做什么才能在下一个产品中使用 MicroPython。

    这些语言特征的有趣之处在于它们使开发团队的每个成员都可以成为程序员!MicroPython 提供了一系列库,可以控制低级微控制器功能,从而消除复杂性。例如,硬件工程师可以设计电路板,几乎不知道微控制器(或 C)的工作原理,开发可以通过控制 GPIO 测试电路板的高级脚本,甚至可以与 I2C 器件通信。在 C 中开发 I2C 驱动程序可能相当复杂,但使用 MicroPython 只需要以下代码行来创建 I2C 对象并将数据发送到从属设备而已!处理 I2C 的所有基础工作都由 MicroPython 库处理,这大大简化了开发。(看看我在 C 中的一个 I2C 驱动程序显示了几千行代码,授予它是一个非常强大的实现,并提供了一些功能,一旦奠定了基础工作,就可以与总线交互)。

    在考虑使用 MicroPython 进行产品开发时,开发人员需要考虑几个关键因素。首先,开发人员需要考虑如何保护他们的应用程序代码。MicroPython 允许开发人员加载基于代码文本的 Python 脚本或将这些脚本编译为字节码并将它们放入 a.mpy 模块中。这些解决方案的问题在于,任何能够访问 MicroPython 文件系统的人都可以轻松获得应用程序代码,这很容易实现。字节码确实使它更难一点,但将字节代码转换回可读代码并不困难。开发人员需要考虑他们需要系统的安全性,并可能采取额外措施来保护知识产权。

    接下来,开发人员需要考虑如果出现问题他们将如何恢复他们的系统。根据所选的微控制器,它们的应用程序代码可以在 MCU 内部,也可以在外部存储设备(如 SD 卡)上。我发现文件系统在电源循环或欠压条件下不是很强大。如果文件系统损坏,MicroPython 将通过将默认映像复制回文件系统来恢复它。开发人员需要确保将其默认代码集成到其内核版本中,以便在出现问题时,至少将其恢复为出厂默认设置,然后可以从设备上可能存在的其他内存位置恢复任何更新。

    最后,开发人员可能希望确保他们有多个内存设备可供选择。新的旗舰 MicroPython 开发板,pyboard D 系列,包括两个独立的 SPI 内存设备,每个 2 MB。一个用于存储应用程序代码,另一个用于存储数据或其他信息。生产系统应该做类似的事情,甚至保留固件备份副本,以便在出现问题时,可以恢复应用程序而不会给用户带来任何问题。

    使用 MicroPython 进行产品设计听起来很有趣,有没有人真正用它来开发商业产品?据我所知,MicroPython 已被用于多种产品中,毫无疑问,我不知道更多。例如,MicroPython 已被用于小型卫星中,用于电子电源和数据采集系统(其中一些我亲自参与过)。在欧洲航天局也已经在更大的卫星系统使用 MicroPython 调查。MicroPython 还用于流行的 OpenMV 模块,该模块允许开发人员创建机器视觉应用程序。该模块运行 MicroPython,允许开发人员通过交互式 IDE 创建脚本,然后将开发人员的脚本下载到模块。开发人员甚至可以训练机器学习模型并将其转换为在 OpenMV 模块上运行!这表明 MicroPython 可用于运行不仅仅是简单的算法或切换一些 I / O.

    使用 MicroPython 构建嵌入式产品无法满足每个开发团队的需求,如 C / C ++,但它为开发人员提供了一个有趣的快速原型设计或开发商业产品的解决方案。当然还存在一些挑战,例如保护基于 MicroPython 的系统并确保确定性行为。这些挑战可以通过适当的应用设计预先克服。MicroPython 的易用性和 Python 的普及使得使用 MicroPython 构建嵌入式系统成为一个有趣的机会。

    转自:MicroPython做嵌入式开发的优缺点

     

    展开全文
  • 各种数据结构优缺点分析

    千次阅读 2016-10-27 00:38:06
    缺点:查找慢,删除慢,大小固定 有序数组 优点:比无序数组查找快 缺点:删除和插入慢,大小固定 栈 优点:提供后进先出的存取方式 缺点:存取其他项很慢 队列 优点:提供先进先出的存取方式

    原文地址:http://www.xuebuyuan.com/2218977.html


    数组

    优点:插入块如果知道坐标可以快速去地存取

    缺点:查找慢,删除慢,大小固定

    有序数组

    优点:比无序数组查找快

    缺点:删除和插入慢,大小固定

    优点:提供后进先出的存取方式

    缺点:存取其他项很慢

    队列

    优点:提供先进先出的存取方式

    缺点:存取其他项都很慢

    链表

    优点:插入快,删除快

    缺点:查找慢

    二叉树

    优点:查找,插入,删除都快(如果数保持平衡)

    缺点:删除算法复杂

    红-黑树

    优点:查找,插入,删除都快,树总是平衡的

    缺点:算法复杂

     

    2-3-4树

    优点:查找,插入,删除都快,树总是平衡的。类似的树对磁盘存储有用

    缺点:算法复杂

     

    哈希表

    优点:如果关键字已知则存取速度极快,插入块

    缺点:删除慢,如果不知道关键则存取很慢,对存储空间使用不充分

    优点:插入,删除块,对最大数据的项存取很快

        缺点:对其他数据项存取很慢

    优点:对现实世界建模

    缺点:有些算法慢且复杂

     

     

    http://www.cppblog.com/cxiaojia/archive/2012/08/06/186432.html

    二叉树首先是一棵树,每个节点都不能有多于两个的儿子,也就是树的度不能超过2。二叉树的两个儿子分别称为“左儿子”和“右儿子”,次序不能颠倒。

    一种是满二叉树,除了最后一层的叶子节点外,每一层的节点都必须有两个儿子节点。

    另一种是完全二叉树,一棵二叉树去掉最后一层后剩下的节点组成的树为满二叉树,最后一层的节点从左到右连续,没有空出的节点,这样的树称为完全二叉树。

     

    http://blog.csdn.net/v_JULY_v/article/details/6105630

    二叉查找树Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树

    1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    2. 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    3. 任意节点的左、右子树也分别为二叉查找树。
    4. 没有键值相等的节点(no duplicate nodes)。

    平衡树计算机科学中的一类数据结构。
    平衡树是计算机科学中的一类改进的二叉查找树。一般的二叉查找树的查询复杂度是跟目标结点到树根的距离(即深度)有关,因此当结点的深度普遍较大时,查询的均摊复杂度会上升,为了更高效的查询,平衡树应运而生了。

    几乎所有平衡树的操作都基于树旋转操作,通过旋转操作可以使得树趋于平衡。 对一棵查找树(search tree)进行查询/新增/删除 等动作, 所花的时间与树的高度h 成比例, 并不与树的容量 n 成比例。如果可以让树维持矮矮胖胖的好身材, 也就是让h维持在O(lg n)左右, 完成上述工作就很省时间。能够一直维持好身材,
    不因新增删除而长歪的搜寻树, 叫做balanced search tree(平衡树)。

    红黑树,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)。

    ok,我们知道,红黑树上每个结点内含五个域,color,key,left,right,p。如果相应的指针域没有,则设为NIL。

    一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:

    1)每个结点要么是红的,要么是黑的。
    2)根结点是黑的。
    3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
    4)如果一个结点是红的,那么它的俩个儿子都是黑的。
    5)对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。

    :上述第3、5点性质中所说的NULL结点,包括wikipedia.算法导论上所认为的叶子结点即为树尾端的NIL指针,或者说NULL结点。然百度百科以及网上一些其它博文直接说的叶结点,则易引起误会,因,此叶结点非子结点

     

    http://blog.csdn.net/v_july_v/article/details/6531399

    2-3-4 树把数据存储在叫做元素的单独单元中。那么请问,到底什么是2-3-4树呢?顾名思义,就是有2个子女,3个子女,或4个子女的结点,这些含有2、3、或4个子女的结点就构成了我们的2-3-4树。所以,它们组合成结点,每个结点都是下列之一:
    2-节点,就是说,它包含 1 个元素和 2 个儿子,
    3-节点,就是说,它包含 2 个元素和 3 个儿子,
    4-节点,就是说,它包含 3 个元素和 4 个儿子 。

     

    http://blog.csdn.net/v_july_v/article/details/6530142

    树又叫平衡多路查找树。一棵m阶的B 树 (注:切勿简单的认为一棵m阶的B树是m叉树,虽然存在四叉树八叉树KD,及vp/R树/R*树/R+树/X树/M树/线段树/希尔伯特R树/优先R树等空间划分树,但与B树完全不等同)的特性如下

    1. 树中每个结点最多含有m个孩子(m>=2);
    2. 除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个取上限的函数);
    3. 若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
    4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null);(读者反馈@冷岳这里有错,叶子节点只是没有孩子和指向孩子的指针,这些节点也存在,也有元素。@研究者July:其实,关键是把什么当做叶子结点,因为如红黑树中,每一个NULL指针即当做叶子结点,只是没画出来而已)。
    5. 每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
             a)   Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。 
             b)   Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。 
             c)   关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1。
      如下图所示:

    一棵R树满足如下的性质:

    1.     除非它是根结点之外,所有叶子结点包含有m至M个记录索引(条目)。作为根结点的叶子结点所具有的记录个数可以少于m。通常,m=M/2。

    2.     对于所有在叶子中存储的记录(条目),I是最小的可以在空间中完全覆盖这些记录所代表的点的矩形(注意:此处所说的“矩形”是可以扩展到高维空间的)。

    3.     每一个非叶子结点拥有m至M个孩子结点,除非它是根结点。

    4.     对于在非叶子结点上的每一个条目,i是最小的可以在空间上完全覆盖这些条目所代表的店的矩形(同性质2)。

    5.     所有叶子结点都位于同一层,因此R树为平衡树。

    叶子结点的结构

    先来探究一下叶子结点的结构。叶子结点所保存的数据形式为:(I, tuple-identifier)。

          其中,tuple-identifier表示的是一个存放于数据库中的tuple,也就是一条记录,它是n维的。I是一个n维空间的矩形,并可以恰好框住这个叶子结点中所有记录代表的n维空间中的点。I=(I0,I1,…,In-1)。其结构如下图所示:

    下图描述的就是在二维空间中的叶子结点所要存储的信息。

     

    在这张图中,I所代表的就是图中的矩形,其范围是a<=I0<=b,c<=I1<=d。有两个tuple-identifier,在图中即表示为那两个点。这种形式完全可以推广到高维空间。大家简单想想三维空间中的样子就可以了。这样,叶子结点的结构就介绍完了。

     

    非叶子结点

          非叶子结点的结构其实与叶子结点非常类似。想象一下B树就知道了,B树的叶子结点存放的是真实存在的数据,而非叶子结点存放的是这些数据的“边界”,或者说也算是一种索引(有疑问的读者可以回顾一下上述第一节中讲解B树的部分

          同样道理,R树的非叶子结点存放的数据结构为:(I, child-pointer)。

          其中,child-pointer是指向孩子结点的指针,I是覆盖所有孩子结点对应矩形的矩形。这边有点拗口,但我想不是很难懂?给张图:

     

    D,E,F,G为孩子结点所对应的矩形。A为能够覆盖这些矩形的更大的矩形。这个A就是这个非叶子结点所对应的矩形。这时候你应该悟到了吧?无论是叶子结点还是非叶子结点,它们都对应着一个矩形。树形结构上层的结点所对应的矩形能够完全覆盖它的孩子结点所对应的矩形。根结点也唯一对应一个矩形,而这个矩形是可以覆盖所有我们拥有的数据信息在空间中代表的点的。

    我个人感觉这张图画的不那么精确,应该是矩形A要恰好覆盖D,E,F,G,而不应该再留出这么多没用的空间了。但为尊重原图的绘制者,特不作修改。

     

    http://zh.wikipedia.org/wiki/%E5%93%88%E5%B8%8C%E8%A1%A8

    散列表Hash table,也叫哈希表),是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,它通过把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表

    一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名x到首字母F(x)的一个函数关系),在首字母为W的表中查找“王”姓的电话号码,显然比直接查找就要快得多。这里使用人名作为关键字,“取首字母”是这个例子中散列函数的函数法则F(),存放首字母的表对应散列表。关键字和函数法则理论上可以任意确定。

     

     

    http://dongxicheng.org/structure/heap/

    http://zh.wikipedia.org/wiki/%E5%A0%86_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)

    http://www.cppblog.com/guogangj/archive/2009/10/29/99729.html

    (英语:heap)亦被称为:优先队列(英语:priority queue),是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

     

    首先说说数据结构概念——堆(Heap),其实也没什么大不了,简单地说就是一种有序队列而已,普通的队列是先入先出,而二叉堆是:最小先出。

    这不是很简单么?如果这个队列是用数组实现的话那用打擂台的方式从头到尾找一遍,把最小的拿出来不就行了?行啊,可是出队的操作是很频繁的,而每次都得打一遍擂台,那就低效了,打擂台的时间复杂度为Ο(n),那如何不用从头到尾fetch一遍就出队呢?二叉堆能比较好地解决这个问题,不过之前先介绍一些概念。

    完全树(Complete Tree):从下图中看出,在第n层深度被填满之前,不会开始填第n+1层深度,还有一定是从左往右填满。

    再来一棵完全三叉树:

    这样有什么好处呢?好处就是能方便地把指针省略掉,用一个简单的数组来表示一棵树,如图:

     

     

    http://blog.chinaunix.net/uid-21813514-id-3866951.html

    一、图的存储结构

    1.1 邻接矩阵

        图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。

        设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

        

        看一个实例,下图左就是一个无向图。

        

        从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij =
    a
    ji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

        从这个矩阵中,很容易知道图中的信息。

        (1)要判断任意两顶点是否有边无边就很容易了;

        (2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;

        (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;

        而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。

        若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

        

        这里的wij表示(vi,vj)上的权值。无穷大表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值。下面左图就是一个有向网图,右图就是它的邻接矩阵。

        

    那么邻接矩阵是如何实现图的创建的呢?代码如下。

    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
    #include <stdio.h>
    #include <stdlib.h>
    #include <curses.h>
     
    typedef char VertexType;                 //顶点类型应由用户定义
    typedef int EdgeType;                    //边上的权值类型应由用户定义
     
    #define MAXVEX  100             //最大顶点数,应由用户定义
    #define INFINITY    65535               //用65535来代表无穷大
    #define DEBUG
     
    typedef struct
    {
         VertexType vexs[MAXVEX];             //顶点表
         EdgeType   arc[MAXVEX][MAXVEX];          //邻接矩阵,可看作边
         int numVertexes, numEdges;      //图中当前的顶点数和边数
    }Graph;
     
    //定位
    int locates(Graph *g, char ch)
    {
         int i = 0;
         for (i = 0; i < g->numVertexes; i++)
         {
             if (g->vexs[i] == ch)
             {
                 break ;
             }
         }
         if (i >= g->numVertexes)
         {
             return -1;
         }
         
         return i;
    }
     
    //建立一个无向网图的邻接矩阵表示
    void CreateGraph(Graph *g)
    {
         int i, j, k, w;
         printf ( "输入顶点数和边数:\n" );
         scanf ( "%d,%d" , &(g->numVertexes), &(g->numEdges));
         
         #ifdef DEBUG
         printf ( "%d %d\n" , g->numVertexes, g->numEdges);
         #endif
     
         for (i = 0; i < g->numVertexes; i++)
         {
             g->vexs[i] = getchar ();
             while (g->vexs[i] == '\n' )
             {
                 g->vexs[i] = getchar ();
             }
         }
         
         #ifdef DEBUG
         for (i = 0; i < g->numVertexes; i++)
         {
             printf ( "%c " , g->vexs[i]);
         }
         printf ( "\n" );
         #endif
     
     
         for (i = 0; i < g->numEdges; i++)
         {
             for (j = 0; j < g->numEdges; j++)
             {
                 g->arc[i][j] = INFINITY; //邻接矩阵初始化
             }
         }
         for (k = 0; k < g->numEdges; k++)
         {
             char p, q;
             printf ( "输入边(vi,vj)上的下标i,下标j和权值:\n" );
             
             p = getchar ();
             while (p == '\n' )
             {
                 p = getchar ();
             }
             q = getchar ();
             while (q == '\n' )
             {
                 q = getchar ();
             }
             scanf ( "%d" , &w);   
             
             int m = -1;
             int n = -1;
             m = locates(g, p);
             n = locates(g, q);
             if (n == -1 || m == -1)
             {
                 fprintf (stderr, "there is no this vertex.\n" );
                 return ;
             }
             //getchar();
             g->arc[m][n] = w;
             g->arc[n][m] = g->arc[m][n];  //因为是无向图,矩阵对称
         }
    }
     
    //打印图
    void printGraph(Graph g)
    {
         int i, j;
         for (i = 0; i < g.numVertexes; i++)
         {
             for (j = 0; j < g.numVertexes; j++)
             {
                 printf ( "%d  " , g.arc[i][j]);
             }
             printf ( "\n" );
         }
    }
     
    int main( int argc, char **argv)
    {
         Graph g;
         
         //邻接矩阵创建图
         CreateGraph(&g);
         printGraph(g);
         return 0;
    }
    </curses.h></stdlib.h></stdio.h>
          从代码中可以得到,n个顶点和e条边的无向网图的创建,时间复杂度为O(n + n 2  + e),其中对邻接矩阵Grc的初始化耗费了O(n2)的时间。


    1.2 邻接表

        邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。

        邻接表的处理方法是这样的:

        (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。

        (2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。

        例如,下图就是一个无向图的邻接表的结构。

        

        从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

        对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。

        

        对于邻接表结构,图的建立代码如下。

    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
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    /* 邻接表表示的图结构 */
    #include <stdio.h>
    #include<stdlib.h>
     
    #define DEBUG
    #define MAXVEX 1000         //最大顶点数
    typedef char VertexType;        //顶点类型应由用户定义
    typedef int EdgeType;           //边上的权值类型应由用户定义
     
    typedef struct EdgeNode         //边表结点
    {
         int adjvex;         //邻接点域,存储该顶点对应的下标
         EdgeType weigth;        //用于存储权值,对于非网图可以不需要
         struct EdgeNode *next;      //链域,指向下一个邻接点
    }EdgeNode;
     
    typedef struct VertexNode       //顶点表结构
    {
         VertexType data;        //顶点域,存储顶点信息
         EdgeNode *firstedge;        //边表头指针
    }VertexNode, AdjList[MAXVEX];
     
    typedef struct
    {
         AdjList adjList;
         int numVertexes, numEdges;  //图中当前顶点数和边数
    }GraphList;
     
    int Locate(GraphList *g, char ch)
    {
         int i;
         for (i = 0; i < MAXVEX; i++)
         {
             if (ch == g->adjList[i].data)
             {
                 break ;
             }
         }
         if (i >= MAXVEX)
         {
             fprintf (stderr, "there is no vertex.\n" );
             return -1;
         }
         return i;
    }
     
    //建立图的邻接表结构
    void CreateGraph(GraphList *g)
    {
         int i, j, k;
         EdgeNode *e;
         EdgeNode *f;
         printf ( "输入顶点数和边数:\n" );
         scanf ( "%d,%d" , &g->numVertexes, &g->numEdges);
         
         #ifdef DEBUG
         printf ( "%d,%d\n" , g->numVertexes, g->numEdges);
         #endif
         
         for (i = 0; i < g->numVertexes; i++)
         {
             printf ( "请输入顶点%d:\n" , i);
             g->adjList[i].data = getchar ();          //输入顶点信息
             g->adjList[i].firstedge = NULL;          //将边表置为空表
             while (g->adjList[i].data == '\n' )
             {
                 g->adjList[i].data = getchar ();
             }
         }
         //建立边表
         for (k = 0; k < g->numEdges; k++)
         {
             printf ( "输入边(vi,vj)上的顶点序号:\n" );
             char p, q;
             p = getchar ();
             while (p == '\n' )
             {
                 p = getchar ();
             }
             q = getchar ();
             while (q == '\n' )
             {
                 q = getchar ();
             }
             int m, n;
             m = Locate(g, p);
             n = Locate(g, q);
             if (m == -1 || n == -1)
             {
                 return ;
             }
             #ifdef DEBUG
             printf ( "p = %c\n" , p);
             printf ( "q = %c\n" , q);
             printf ( "m = %d\n" , m);
             printf ( "n = %d\n" , n);
             #endif
         
             //向内存申请空间,生成边表结点
             e = (EdgeNode *) malloc ( sizeof (EdgeNode));
             if (e == NULL)
             {
                 fprintf (stderr, "malloc() error.\n" );
                 return ;
             }
             //邻接序号为j
             e->adjvex = n;
             //将e指针指向当前顶点指向的结构
             e->next = g->adjList[m].firstedge;
             //将当前顶点的指针指向e
             g->adjList[m].firstedge = e;
             
             f = (EdgeNode *) malloc ( sizeof (EdgeNode));
             if (f == NULL)
             {
                 fprintf (stderr, "malloc() error.\n" );
                 return ;
             }
             f->adjvex = m;
             f->next = g->adjList[n].firstedge;
             g->adjList[n].firstedge = f;
         }
    }
     
     
    void printGraph(GraphList *g)
    {
         int i = 0;
         #ifdef DEBUG
         printf ( "printGraph() start.\n" );
         #endif
         
         while (g->adjList[i].firstedge != NULL && i < MAXVEX)
         {
             printf ( "顶点:%c  " , g->adjList[i].data);
             EdgeNode *e = NULL;
             e = g->adjList[i].firstedge;
             while (e != NULL)
             {
                 printf ( "%d  " , e->adjvex);
                 e = e->next;
             }
             i++;
             printf ( "\n" );
         }
    }
     
    int main( int argc, char **argv)
    {
         GraphList g;
         CreateGraph(&g);
         printGraph(&g);
         return 0;
    }
    </stdlib.h></stdio.h>
         对于无向图,一条边对应都是两个顶点,所以,在循环中,一次就针对i和j分布进行插入。


        本算法的时间复杂度,对于n个顶点e条边来说,很容易得出是O(n+e)。

    1.3 十字链表

        对于有向图来说,邻接表是有缺陷的。关心了出度问题,想了解入度就必须要遍历整个图才知道,反之,逆邻接表解决了入度却不了解出度情况。下面介绍的这种有向图的存储方法:十字链表,就是把邻接表和逆邻接表结合起来的。

        重新定义顶点表结点结构,如下所示。

        

        其中firstin表示入边表头指针,指向该顶点的入边表中第一个结点,firstout表示出边表头指针,指向该顶点的出边表中的第一个结点。

        重新定义边表结构,如下所示。

        

        其中,tailvex是指弧起点在顶点表的下表,headvex是指弧终点在顶点表的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边。如果是网,还可以增加一个weight域来存储权值。

        比如下图,顶点依然是存入一个一维数组,实线箭头指针的图示完全与邻接表相同。就以顶点v0来说,firstout指向的是出边表中的第一个结点v3。所以,v0边表结点hearvex = 3,而tailvex其实就是当前顶点v0的下标0,由于v0只有一个出边顶点,所有headlink和taillink都是空的。

        

        重点需要解释虚线箭头的含义。它其实就是此图的逆邻接表的表示。对于v0来说,它有两个顶点v1v2的入边。因此的firstin指向顶点v1的边表结点中headvex为0的结点,如上图圆圈1。接着由入边结点的headlink指向下一个入边顶点v2,如上图圆圈2。对于顶点v1,它有一个入边顶点v2,所以它的firstin指向顶点v2的边表结点中headvex为1的结点,如上图圆圈3。

        十字链表的好处就是因为把邻接表和逆邻接表整合在一起,这样既容易找到以v为尾的弧,也容易找到以v为头的弧,因而比较容易求得顶点的出度和入度。

        而且除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图应用中,十字链表是非常好的数据结构模型。

        这里就介绍以上三种存储结构,除了第三种存储结构外,其他的两种存储结构比较简单。


    二、图的遍历

        图的遍历和树的遍历类似,希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫图的遍历。

        对于图的遍历来说,如何避免因回路陷入死循环,就需要科学地设计遍历方案,通过有两种遍历次序方案:深度优先遍历和广度优先遍历。

    2.1 深度优先遍历

        深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。

        它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。

        我们用邻接矩阵的方式,则代码如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14