C语言常用算法

2018-04-17作者:明日科技, 编著编辑:Solomon

一个程序主要由数据和操作两部分组成。数据作为程序操作的对象,而操作是对数据进行加工处理,加工处理的步骤就是算法。本章主要讲述程序的灵魂——算法,算法同人的灵魂一样,是一个很抽象的名词,人们不会将其与具体的物体建立联系。由算法被称作程序的灵魂,可见其重要性。那么算法到底为何物呢?为什么称算法为程序的灵魂呢?通过本章的学习您将会解决这些疑问。


第一章、程序之魂——算法


一个程序主要由数据和操作两部分组成。数据作为程序操作的对象,而操作是对数据进行加工处理,加工处理的步骤就是算法。本章主要讲述程序的灵魂——算法,算法同人的灵魂一样,是一个很抽象的名词,人们不会将其与具体的物体建立联系。由算法被称作程序的灵魂,可见其重要性。那么算法到底为何物呢?为什么称算法为程序的灵魂呢?通过本章的学习您将会解决这些疑问。


1.1    魂 之 说

很多人认为算法只存在于那些数学家或计算机专业人士的脑海中,其实不然,算法无 处不在,只是由于它不是看得见、摸得着的具体物体,所以人们常常忽略它的存在。

算法其实就是为解决一个问题而采取的方法和步骤。例如,洗脸可以简单地分成如下 几步:

    (1)将清水倒入盆中。 

    (2)挤上洗面奶,清洗脸部。 

    (3)用水洗净脸上的洗面奶。 

    (4)用毛巾擦干脸。

以上这4步就称为解决洗脸这个问题的算法。 著名科学家沃思提出一个公式:

数据结构+算法=程序 在计算机程序设计中,数据结构是操作的对象,算法是对对象进行加工处理,用以得到程序的运行结果,程序中的操作语句,实际上就是算法的体现。 如果将计算机程序比喻成有生命的人,那么数据结构是人的躯体,算法就是人的灵魂。只有躯体与灵魂相互结合,才能组成一个完整的有生命、有思想的人。因此,算法具有程序的灵魂之说。下面通过一个简单的C语言程序来体会一下什么是算法以及算法的重要性。


题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数, 请问该数是多少?

程序分析:

(1)随意拟定一个整数范围,对该范围内的整数判断是否满足题中所述条件,在此 例中所取范围是100000以内的整数。

(2)先将该数加上100后开方。 (3)再将加上100后的该数加上168后开方。 (4)若开方后满足条件,则输出结果。 源代码:

    #include "math.h" main()

   {

      long int i,x,y,z; for(i=1;i<100000;i++) {

      x=sqrt(i+100);

      y=sqrt(i+268);/*y为加上100再加上168开方后的结果*/

       if(x*x==i+100&&y*y==I+268)/*满足条件输出结果*/

           }

    }

运行结果:输出1~100000内所有满足条件的整数。


asdasd.jpg


在这个程序中,程序分析部分是对问题进行分析从而设计出解决这个问题采取的方 法,这就称之为算法,而源代码部分是通过代码对算法进行体现,最终得出正确的运行 结果。


1.2    算法的特性

算法是解决“做什么”和“怎么做”的问题,解决一个问题可能有不同的方法,但是 算法分析最为核心的是算法的速度。因此解决问题的步骤需要是在有限时间内能够完成 的,并且操作步骤中不可以有可能导致步骤无法继续进行下去的歧义性语句。通过对算法 概念的分析,可以总结出一个算法必须满足如下五个特性。


    1.有穷性

一个算法在执行有限步骤后在有限时间内能够实现的,就称该算法具有有穷性。例如, 在1.1节的学习园地中,若for循环中没有i++语句,就不会使i在有限步骤后不满足i小于 100000这个条件,而结束循环,否则会无休止地执行for循环中的语句,这样程序就进入了 死循环,不满足算法的有穷性。

有的算法在理论上满足有穷性的,在有限的步骤后能够完成,但是实际上计算机可能 会执行一天、一年、十年等,那么这个算法也就没有意义了,因为这样就忽视了一个概念, 即算法的核心是速度。总而言之,有穷性没有特定的限度,取决于实际需要。


    2.确定性

 一个算法中的每一个步骤的表述都应该是确定的、没有歧义的语句。在人们的日常生

活中,遇到歧义性语句,可以根据常识、语境等理解,然而还有可能理解错误。例如,将 1.1节学习园地中程序分析的第二步,描述成“该数加100开方”,要如何解释这个步骤呢? 是先将100开方,然后加上该数,还是加上100后再开方呢?计算机不比人脑,不会根据算 法的意义来揣测每个步骤的意思,所以算法的每一步都要有确定的含义。


    3.有零个或多个输入

一个程序中的算法和数据是相互联系的,算法中需要输入的是数据的量值。输入可以 是多个也可以是零个,零个输入并不是这个算法没有输入,而是这个输入没有直观地显现 出来,隐藏在算法本身当中。例如,1.1节中的例题没有明显的输入数据,真正的输入隐藏 在i=1中,输入的第一个值就是1,根据条件对输入的数值进行判断。


    4.有一个或多个输出 

输出就是算法实现所得到的结果,是算法经过数据加工处理后得到的结果。没有输出的算法是没有意义的。有的算法输出的是数值,有的是图形,有的输出并不是显而易见的。


    5.可行性 

算法的可行性就是指每一个步骤都能够有效地执行,并且得到确定的结果,同时能够

用来方便地解决一类问题。


1.3    算法的表示方式

一个算法有多种表述方式,常见的有自然语言、流程图、N-S图、伪代码、计算机语言等。


1.3.1     用自然语言描述算法

用自然语言表示算法就是用日常生活中使用的语言来描述算法的步骤。自然语言通俗 易懂,但是在描述上容易出现歧义。此外,用自然语言描述计算机程序中的分支和多重循 环等算法,容易出现错误,描述不清。因此,只有在较小的算法中应用自然语言描述,才 方便简单。


1.3.2    用流程图描述算法

简单的算法可以用自然语言来描述,但是较为复杂的算法要如何描述呢?在计算机程 序中经常会出现很多多分支选择结构的语句,这样的语句很容易产生歧义。而计算机程序 需要每一步都是确切的,因此,流程图成了描述算法最为常见的方法。


    1.流程图基本符号 

流程图是由一些简单的框图组成表示解题步骤及顺序的方法。美国国家标准化协会(ANSI)规定了一些常用的流程图符号。

(1)起止框:表示一个算法的开始和结束。

(2)处理框:将要进行的操作内容简洁明了地写到框中。

(3)判断框:在判断框中写入算法中需要判断的条件。满足条件,执行一条路径;

不满足条件则执行另一条路径。

(4)输入/输出框:记录从外部输入数据到计算机内部或者从计算机内部中输出数据

到计算机外部。

(5)流程线:指向算法即将运行的方向。


    2.种基本控制结构

在程序人员编写程序时,为了满足某些需求,会强制程序在某些地方跳转,即进行控 制转移,这样使得程序的可读性降低,使本身让人望而生畏的算法更加复杂、难于理解。 为了改善此问题,人们规定了3种基本控制结构,将这3种基本结构作为设计和理解算法的 基本单元(如同一栋大楼中的几个单元)。


(1)顺序结构

顺序结构是最为简单的一种基本结构,就是由上至下、按先后顺序依次执行程序语句。 顺序结构的流程。


(2)选择结构

选择结构也称为分支结构,是根据给定的条件进行判断的一种结构。此结构流程图中 必定包括一个判断框,满足条件执行一个处理框,不满足条件执行另一个处理框。


(3)循环结构

循环结构是一种重复某一部分的操作的结构,它可以简化程序的难度,将大工作量拆 分成小工作量,并对小工作量进行重复操作,这种方法充分利用了计算机运算速度快、自 动化的优点。有两种典型的循环结构:while型循环和do-while型循环。


while型循环采取先判断表达式,后执行语句的方式。当判断框中的表达式为非0值时, 执行while语句中的内嵌语句,如此往复,直到表达式为0值,结束循环。do-while型循环采用先执行循环体,再判断循环条件是否成立的方式。其执行过程为 先执行一次循环体语句,然后判断表达式,当表达式为非0值时,返回重新执行循环体语 句,如此循环,直到表达式为0值时跳出循环。


asda.jpg



1.3.3    用计算机语言描述算法

在对算法的一系列描述分析上,最终的目的就是对算法进行实现,得到算法的解。例 如,菜谱是讲述一个做菜的步骤,这是在用自然语言描述算法,但是人们学习菜谱的目的 并不仅仅是了解这个步骤,而是要根据描述的算法进行操作,做出一道实实在在的美味这 就是算法的实现,而那一道出锅的菜就是算法的解。

因此,程序员对算法的所有描述都是在为实现算法作出分析,最终应用计算机语言实 现算法。本书中要介绍的是如何应用C语言来实现算法。


    1.算法性能分析与度量

算法是解决问题的方法,但是解决一个问题的方法不止一个,方法多了,自然而然地 就有了优劣之分。例如,当一个人在扫地的时候,人们不会发现这个人扫的好与坏;然而, 若有两三个人同时做这个工作的时候,人们就有了比较,就可以根据不同的评定标准评价 出好坏,有人认为A好,因为他扫得快,有人认为B好,因为他扫得干净,等等。那么, 对于算法的优劣该如何来评定呢?同样有很多标准。然而,算法作为程序之魂,它的核心 是什么呢?这才是评定一个算法优劣的重要指标。在前面对算法的描述中,已经指出了算 法的核心就是速度。速度并不只是算法的核心,在计算机功能日益强大的时代,速度已经 成为一切事物的追求。


1.3.4    算法的性能指标

评定一个算法的优劣,主要有以下几个指标。


(1)正确性:

一个算法必须正确才有存在的意义,这是最重要的指标,要求编程人 员应用正确的计算机语言实现算法的功能。


(2)友好性:

算法实现的功能是给用户使用的,自然要具有良好的使用性,即用户 友好性。


(3)可读性:

算法的实现可能需要多次的修改,也可能被移植到其他的功能中,因 此算法应当是可读的、可以理解的,方便程序人员对其分析、修改移植到自己的程序中, 实现某些功能。


(4)健壮性:

在一个算法中,经常会出现不合理的数据或非法的操作,所以一个算 法必须具有健壮性,能够对这些问题进行检查、纠正。算法具有健壮性是一个升华,当用 户刚开始学习写算法时可以忽略它的存在,在逐渐的学习中要努力让算法更加完美。


(5)效率:

算法的效率主要是指执行算法时计算机资源的消耗,包括计算机内存的 消耗和计算机运行时间的消耗。这两个消耗可以统称为时空效率。一个算法只有正确性而 无效率是没有意义的,通常,效率也可以评定一个算法是否正确。如果一个算法需要执行 几年甚至几百年,那么无疑这个算法会被评为是错误的。


1.3.5    算法效率的度量

度量算法效率的方法有两种:

第一种是事后计算的方法,先实现算法,然后运行程序,测算其时间和空间的消耗。 这种度量方法有很多弊端,由于算法的运行与计算机的软硬件等环境因素有关,不容易发 现算法本身的优劣。同样的算法用不同的编译器编译出的目标代码数量不同,完成算法所 需的时间也不同;若计算机的存储空间较小,算法运行时间也就会延长。第二种是事前分析估算的方法,这种度量方法是通过比较算法的复杂性来评价算法的 优劣,算法的复杂性与计算机软硬件无关,仅与计算时间和存储需求有关。算法复杂性的度量可以分为空间复杂度度量和时间复杂度度量。


1.3.6    算法的时间复杂度

算法的时间复杂度度量主要是计算一个算法所用的时间,算法所用的时间主要包括程 序编译时间和运行时间。由于一个算法一旦编译成功可以多次运行,因此忽略编译时间, 在这里只讨论算法的运行时间。


算法的运行时间依赖于加减乘除等基本的运算以及参加运算的数据的大小和计算机 硬件和操作环境等。要想准确地计算时间是不可行的,而影响算法时间最为主要的因素是 问题的规模,即输入量的多少。同等条件下,问题的规模越大,运行的时间也就越长。例 如,求1+2+3+...+n的算法,即n个整数的累加求和,这个问题的规模为n。因此,运行算法 所需的时间T是问题规模n的函数,记作T(n)。


为了客观地反映一个算法的执行时间,通常用算法中基本语句的执行次数来度量算法 的工作量。而这种度量时间复杂度的方法得出的不是时间量,而是一种增长趋势的度量, 即当问题规模n增大时,T(n)也随之变大。换言之,当问题规模充分大时,算法中基本语句 的执行次数为在渐进意义下的阶,称为算法的渐进时间复杂度,简称时间复杂度,通常用 大O记号表示。用数学语言通常描述为:若当且仅当存在正整数O和n0,对于任意n≥n0, 都有T(n)≤c×f(n),则称该算法的渐进时间复杂度为T(n)=O(f(n))(或称算法在O(f(n))中)。


对于某些算法即使问题规模相同,如果输入数据不同,则算法运行时间也不同,因此 要全面分析一个算法,需要考虑算法在最好、最坏、平均情况下的时间消耗。由于最好情 况出现的概率太小,因此不具代表性,但是,当最好情况出现的概率大时,应该分析最好 情况;虽然最坏情况出现的概率也太小,不具代表性,但是分析最坏情况能够让人们知道 算法的运行时间最坏能到什么程度,这一点在实时系统中很重要;分析平均情况是比较普 遍的,特别是同一个算法要处理不同的输入时,通常假定输入的数据是等概率分布的。


通过对算法时间复杂度的分析,总结出这样一条结论,在计算任何算法的时间复杂度 时,可以忽略所有低次幂和最高次幂的系数,这样可以简化算法分析,并使注意力集中在 增长率上。


第二章、数据结构基础


2.1    数据结构概述

2.1.1    数据结构的发展

随着计算机科学技术的不断发展,计算机应用已经从最初的科学计算发展到控制、 管理等各处理领域。从而使计算机处理的数据从单纯的数值发展到字符、图像、声音、 文件等具有一定结构的数据,而且处理的数据量也不断增加。这就需要对计算机处理的 这些数据进行有效的管理,使其能够满足各项处理要求。这样便产生了“数据结构”这 门学科。1968年美国克努思教授开创了数据结构的最初体系,他的《计算机程序设计艺术》第 一卷《基本算法》是第一本比较系统地阐述数据逻辑结构和存储结构及其相关操作的著作。 20世纪70年代初,美国一些大学计算机系把“数据结构”规定为一门课程。


多学两招

克努思(Donald.E.Knuth,1938—)1963年在加利福尼亚理工学院任教,1968年成为 斯坦福大学教授;1992年荣誉退休,保留教授头衔,集中精力写作。他的《计算机程序设 计艺术》丛书对计算机科学的发展产生了深远的影响。“从某种意义上说,克努思就意味 着计算机程序设计艺术,也就意味着数据结构和算法这一类问题的答案。”到目前为止,数据结构的发展有3个阶段,即程序设计发展的3个阶段——无结构阶段、 结构化阶段和面向对象阶段。


2.1.2    数据结构的研究对象

数据结构在计算机科学领域是一门综合性学科,其研究涉及计算机硬件、软件的各 个方面。一般来说,计算机解决问题包括以下几个步骤:首先需要从具体问题抽象出一 个计算机理解的模型,然后设计算法解出该模型的解,最后编程得到答案。例如,线性 方程组数学模型、微分方程数学模型。但是更多的数据处理是非数值的,无法用数学方 程加以描述。


2.1.3    数据结构与算法的关系

“数据结构+算法=程序”这个公式在前面已经提到过,它表示了组成程序的两个基本 要素是数据结构和算法。在这个公式中没有涉及到代码,只是程序设计的思想。在计算机 程序设计中,数据结构是操作的对象,算法是对对象进行加工处理,用以得到程序的运行 结果。数据结构是躯体,算法是灵魂,二者组成程序,缺一不可。

算法是对一个程序的逻辑实现的描述,而数据结构是逻辑所依附的实体。只要程序员设计 出了程序的算法,确定了数据结构,那么这个程序就已经基本成型,剩下的就是填充代码了。


2.2    数据结构的基本概念

数据的主要功能就是传递信息,例如思想交流、商业交易、经营管理等,这些信息都

需要以数据的形式在计算机中进行处理。


 数据(data):信息的载体,是能够输入到计算机中并被计算机识别和处理的符 号的总称。

在实际应用中数据可以是数值,也可以是字符串、表、图、声音等,这些都是可 以通过编码方式使用计算机进行操作的对象。学生基本信息管理程序操作对象是 学生基本信息表;公司部门信息管理程序操作对象是部门信息表。


 数据元素(data element):数据的基本单位,构成数据元素的不可分割的最小单 位是数据项。例如,学生信息管理程序的每个学生信息就是一个数据元素,而表 中学生的学号、性别、姓名等就是数据项。讨论数据结构一般针对数据元素进行 讨论,数据项一般不予考虑。数据元素在计算机程序中通常作为一个整体进行操 作,其具有广泛的含义,一般来说,计算机中抽象出的,能够独立、完整地描述 问题世界的一切实体都是数据元素。例如学生的信息、部门信息、销售情况、城 市地图都是数据元素。


 数据对象(data object):具有相同性质的数据元素的集合,是数据的子集。数据 元素是数据对象的子集。在实际应用中把相同性质的数据元素集合到一起,就构 成了数据对象,例如每个学生信息是数据元素,而所有的学生信息就构成了一个 学生信息的对象,这里以表的形式表示。


 数据结构(data structure):相互之间存在一种或者多种数据关系的数据元素的 集合。这种数据元素之间的关系,通常被称为结构。例如,学生信息表中各学生 信息元素之间的关系是简单的线性结构;部门信息表中各数据之间的关系是树形 结构。根据各数据元素不同的关系类型,通常将结构分为4类:集合、线性结构、 树形结构和图结构(或者网状结构)。这4类关系的示意图如图2.2所示。


 集合:数据元素同属于一个范围,除此之外没有其他关系。例如,一个整数 的集合。


 线性结构:数据元素之间存在着一对一的线性关系。例如,学生信息的线性 关系。


 树结构:数据元素之间存在着一对多的层次关系。例如,部门信息的层次关系。


 图结构:数据元素之间存在着多对多的任意关系


数据存储在计算机中的存储结构又如何描述呢。如果直接以内存地址来描述存储结构 又会使操作很不方便。在高级语言中会提供“数据类型”来描述存储结构。例如,C语言 中的“数组”类型用以描述顺序存储结构,“指针”类型用以描述链式存储结构。

数据类型(date type)用以描述数据在计算机上的存储结构,通过数据类型可以显示 或者隐式地表示对象的取值范围,以及在这些对象上允许进行的操作。因此数据类型可以 定义为:一个值的集合和定义在这个值集上的一组操作的总称。


2.3    C语言常见数据结构

2.3.1    数组

在编写程序的过程中,经常会遇到使用大量数据的时候,处理一个数据就要有一个相 对应的变量,如果每一个变量都要单独进行定义就会很烦琐,使用数组就可以解决这种情 况。数组将一些有关联的相同的数据类型集合到一个数组变量中,方便数据的存储和使用。

数组是在程序开发中应用十分广泛的一种数据类型,属于线性结构,可以分为一维数 组和多维数组。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或 是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、 结构数组等各种类别,数组元素占用内存中一块连续的地址空间。


数组中的每一个元素都属于同一个数据类型,用一个统一的数组名和下标来确定数组 的元素。例如一个车库,车库里面存的都是车,就好像数组中存着同类型的数据,车库有 一个统一的名称,并且有车位编号。例如,一号车库二号车位,通过这个编号就能找到对 应的车。

  最常用的是一维数组和二维数组。下面介绍一维数组和二维数组的定义和引用。


1.一维数组

(1)一维数组的定义

一维数组是用以存储一维数列中数据的集合。其一般形式如下:

类型说明符 数组标识符[常量表达式];

 类型说明符表示数组中的所有元素类型。

 数组标识符就是这个数组型变量的名称,命名规则与变量名一致。

 常量表达式定义了数组中存放的数据元素的个数,即数组长度。例如iArray[5],5

表示数组中有5个元素,下标从0开始,到4结束。 


(2)一维数组的引用 数组定义完成后就要使用该数组,可以通过引用数组元素的方式,使用该数组中的元素。 表示数组元素的一般形式如下:数组标识符[下标];


2.二维数组 

(1)二维数组的定义

二维数组的声明和一维数组相同,其一般形式如下:

数据类型 数组名[常量表达式1][常量表达式2];


(2)二维数组的引用

二维数组元素的一般形式为

数组名[下标][下标];


2.3.2    结构体

“结构体”是一种构造类型,它是由若干“成员”组成的。其中的每一个成员可以是 一个基本数据类型或者又是一个构造类型。既然结构体是一种新的类型,那么就需要先对 其进行构造,这里称这种操作为声明一个结构体。声明结构体的过程就好比生产商品的过 程,只有商品生产出来才可以使用。假如在程序中就要使用“商品”这样一个类型。那么商品具有哪些特点呢?商品有形 状、颜色、功能、价格、产地和产品标号。

商品这种类型并不能使用之前学过的任何一种类型来表示,这个 时候就要自己定义一种新的类型,将这种自己指定的结构称为结构体。声明一种结构体的一般形式为:

     struct 结构体名 {

      成员表列

     };

关键字struct表示声明结构,其后的结构体名表示该结构的类型名。大括号中的变量构 成结构的成员,也就是声明结构体一般形式中的“成员列表”处。


2.3.3 链表

链表是一种常见的数据结构。之前介绍过使用数组存放数据,但是使用数组时要先指定 数组中包含元素的个数,即为数组的长度。但是如果要向这个数组中加入的元素个数超过了 数组的大小时,便不能将内容完全保存。例如在定义一个班级的人数时,如果小班是30人, 普通班级是50人,定义班级人数时若使用的是数组,那么就要定义数组的个数为最大,也就 是最少50个元素,否则就不满足最大时的情况。这样的存储方式非常浪费空间。


这个时候就希望有一种存储方式——其存储元素的个数是不受限定的,当添加元素时 存储的个数就会随之改变。这种存储方式就是链表。而且相对于数组来说,链表在插入和 删除结点时,比数组元素的插入和删除要简单而且开销更小。


在链表中有一个头指针变量,图中head表示的就是头指针,在这个指针变量保存一个 地址。从图2.5中的箭头可以看到该地址为一个变量的地址,也就是说头指针指向一个变量。 这个变量称为元素,在链表中每一个元素包括两个部分:数据部分和指针部分。数据部分 用来存放元素所包含的数据,而指针部分就用来指向下一个元素。最后一个元素的指针指 向NULL,表示指向的地址为空,链表到此结束。


从链表的示意图中可以看到head头结点指向第一个元素,第一个元素中的指针又指向 第二元素,而第二个元素的指针又指向第三个元素的地址,第三个元素的指针就指向为空。

根据对链表的描述,可以想象链表就像一个铁链一样一环扣一环,然后通过头指针寻 找链表中的元素。这就好比在幼儿园中,老师拉着第一个小朋友的手,第一个小朋友又拉 着第二个小朋友的手,这样下去在幼儿园中的小朋友就连成了一条线,最后一个小朋友没 有拉着任何人,他的手是空着的,这就好像是链表中的链尾,而老师就是头指针,通过老 师就可以找到这个队伍中的任何一个小朋友。


关注微信公号“书问”免费领取万本好书



内容来源:书问

作者明日科技
出版清华大学出版社
定价39.8元
书籍比价

分享到

扫描二维码 ×

参与讨论

电子纸书

C语言常用算法分析

明日科技, 编著
清华大学出版社[2012] ¥16

电脑常用工具软件标准教程(2018-2020版

冉洪艳, 编著
清华大学出版社[2018] ¥39

电脑常用工具软件标准教程

冉洪艳, 等编著
清华大学出版社[2014] ¥24

软件工程项目管理实用技术与常用模板

黎照, 黎连业, 王华, 李淑春, 编著
清华大学出版社[2012] ¥30

主板常用集成电路维修宝典

韩佶洋, 编著
清华大学出版社[2011] ¥40

计算机常用工具软件入门与应用

文杰书院, 编著
清华大学出版社[2017] ¥30

常用电子元器件使用指南

杜树春 著
清华大学出版社[2016] ¥32

流水车间与开放车间调度算法渐近分析

白丹宇 著
清华大学出版社[2015] ¥16

机器学习:从公理到算法

于剑, 著
清华大学出版社[2017] ¥48

离散制造业中生产批量计划问题的求解算法研究

韩毅, 著
清华大学出版社[2016] ¥21

出版业领先的TMT平台

使用社交账号直接登陆

Copyright © 2018 BookAsk 书问   |   京ICP证160134号


注册书问

一键登录

Copyright © 2018 BookAsk 书问   |   京ICP证160134号