前言

随着社会的发展,人们生活水平的提高,欣赏电影逐渐成为人们闲暇时的主要娱乐方式之一。随着电影在众人的娱乐生活中占据越来越重要的地位,传统手动售票方式繁琐,统计帐户的时候一张一张的记录进入到账户薄里面,容易出现错误,所以研究一个电影售票系统已经非常的重要了。设计电影院售票系统,能方便的订票,极大的提高了了工作效率。传统的电影售票都是人工服务,观看座位都是人工安排,无法体现人性化选择,加上现在人们的生活节奏越来越快,购票时间需要相应缩短以及方便电影院工作人员的管理,本系统就是为了解决这一系列问题提出的。

电影成为现今社会人们娱乐的重要项目,因此一个完善的影院售票系统为我们的出行和观影提供了方便,避免迟到错过影片和排队拥挤。人工售票的手续繁琐、效率低下给具有强烈时间观念的管理人员带来了诸多不便,影院缺少一套完善的售票系统软件,为了对售票的管理方便,因此必须开发影院售票系统。随着计算机技术的不断应用和提高,计算机已经深入到社会生活的各个角落。而采用手工售票的方法,不仅效率低、易出错、手续繁琐,而且耗费大量的人力。为了满足售票人员对售票进行高效的管理,在工作人员具备一定的计算机操作能力的前提下,特编此影院售票系统软件以提高影院的管理效率。根据对周边电影院售票系统的调查和了解,通过系统的设计,实现电影购票系统。

1、设计任务

利用计算机进行电影院座位管理系统设计,能够通过数据库获得每个放映厅的售票情况,而且还可以利用计算机进行购票,已经售出的座位将不能再选。

1.1任务设计要求

设计一个电影院座位管理系统。一个电影院有多个放映厅,每个放映厅的座位数量大于100且分多行,根据电影票的不同选择不同的放映厅,然后在相应的放映厅中选择座位,座位示意图应该与实际的方位和数量相同,已经选过的座位不能再选。

1.2系统功能需求分析

流程图:

图1.21

 

电影院座位管理系统应包含以下几个功能:

图1.22

2、总体设计

2.1开发环境

操作系统:Microsoft Windows 10 64位

IDE:CodeBlocks 32位

编译器:GNU GCC Compiler

数据库:Sqlite3

编程语言:C/C++

图形库:ACLLib

2.2 SQLite

SQLite是一款轻型的本地文件数据库,是遵守ACID的关联式数据库管理系统。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它的功能强、速度快,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等主流的操作系统,同时能够跟很多程序语言相结合。

2.21 SQLite的数据类型

在进行数据库操作之前,有个问题需要说明,就是SQLite的数据类型,和其他的数据库不同,Sqlite支持的数据类型有他自己的特色:Typelessness(无类型)。 SQLite是无类型的,这意味着你可以保存任何类型的数据到你所想要保存的任何表的任何列中, 无论这列声明的数据类型是什么。

而大多数的数据库在数据类型上都有严格的限制,在建立表的时候,每一列都必须制定一个数据类型,只有符合该数据类型的数据可以被保存在这一列当中。而在SQLite 2.X中,数据类型这个属性只属于数据本生,而不和数据被存在哪一列有关,也就是说数据的类型并不受数据列限制(有一个例外:INTEGER PRIMARY KEY,该列只能存整型数据)。

但是当SQLite进入到3.0版本的时候,这个问题似乎又有了新的答案,SQLite的开发者开始限制这种无类型的使用,在3.0版本当中,每一列开始拥有自己的类型,并且在数据存入该列的时候,数据库会试图把数据的类型向该类型转换,然后以转换之后的类型存储。当然,如果转换被认为是不可行的,SQLite仍然会存储这个数据,就像他的前任版本一样。

举个例子,如果你企图向一个INTEGER类型的列中插入一个字符串,SQLite会检查这个字符串是否有整型数据的特征, 如果有而且可以被数据库所识别,那么该字符串会被转换成整型再保存,如果不行,则还是作为字符串存储。
诚然SQLite允许忽略数据类型, 但是仍然建议在你的Create Table语句中指定数据类型. 因为数据类型对于你和其他的程序员交流, 或者你准备换掉你的数据库引擎时能起到一个提示或帮助的作用. SQLite支持常见的数据类型, 如:

1.NULL,值是NULL
2.INTEGER,值是有符号整形,根据值的大小以1,2,3,4,6或8字节存放
3.REAL,值是浮点型值,以8字节IEEE浮点数存放
4.TEXT,值是文本字符串,使用数据库编码(UTF-8,UTF-16BE或者UTF-16LE)
5.BLOB,只是一个数据块,完全按照输入存放(即没有准换)

2.22 SQLite的5个主要的函数:

sqlite3_open(), 打开数据库
sqlite3_exec(),执行非查询的sql语句
sqlite3_prepare(),准备sql语句,执行select语句或者要使用parameter bind时,用这个函数(封装了sqlite3_exec).
sqlite3_step(),在调用sqlite3_prepare后,使用这个函数在记录集中移动。
sqlite3_close(),关闭数据库文件

2.3 SQLite Studio

SQLiteStudio是一个基于QT写的SQLite数据的可视化编辑和查看的开源软件软件,使用非常简单。进入sqlitestudio官网,下载它的已经编译可用的软件包,找到跟你系统匹配的软件包,下载到自己的电脑的指定的目录中,第一次打开,需要设置默认的软件语言,这个可视化操作软件是支持多语言的,如下图所示操作

图2.31

可以可视化操作编辑我们的数据库进行增删改查操作,十分方便。

2.4建立数据库

首先创建一个数据库,在数据库中建立四张表,分别名为t1,t2,t3,t4,分别代表放映厅1,放映厅2,放映厅3,放映厅4。

图2.41

在每个放映厅的表中,只有一条string类型的数据,售票情况用0和1表示,0表示该位置已售出,1表示该位置尚未售出,如下图所示。

图2.42

2.5 数据库的操作

2.51 数据库的查询

数据库的查询SQL语句为(以放映厅1为例):SELECT * from t1,就可以查到表t1中的所有数据。

数据库的连接只需通过Sqlite3提供的C/C++接口,即可实现用户操作与数据资源的连接,并可对相关的数据库信息进行操作。

sqlite3 *db;//定义一个数据库指针

rc = sqlite3_open(“camera.db”, &db);//链接数据库

sql = “SELECT * from t1”;//把我们要执行的数据库查询SQL语句存入sql变量中

rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);//执行SQL语句并调用回调函数

sqlite3_close(db);//断开与数据库的链接,释放相应的资源

2.42数据库的更新

数据库的更新SQL语句为(以放映厅1为例):UPDATE t1 set zuowei =1,就可以将表t1中的zuowei的值更新为1。

数据库的修改只需通过Sqlite3提供的C/C++接口,编辑好相应的SQL语句,即可实现用户操作与数据资源的连接,并可对相关的数据库信息进行操作。

rc = sqlite3_open(“1.db”, &db);//链接同目录下名为1.db的数据库

sql = “UPDATE t1 set zuowei = ‘123’”;//将我们的数据库更新语句赋值给变量sql1

const char* p1 = sql.data();//将string类型的sql变量赋值给const char*类型的p1

rc = sqlite3_exec(db, p1, callback0, (void*)data, &zErrMsg);//执行数据库更新语句

sqlite3_close(db);//断开与数据库的链接闭关释放相应资源。

2.5 ACLLib图形库介绍

  1. Acllib是一个基于Win32API的函数库,提供了相对较为简单的方式来做Windows程序。
  2. 实际提供了⼀个.c和两个.h,可以在MSVC和Dev C++( MinGW)等环境下中使用。
  3. 纯教学用途,但是编程模型和思想可以借鉴。
  4. 在使用ACLLib创建窗口时,我们只需要写一个Setup()函数,调用initWindow()初始化窗口,这样用户就可以定义自己所规划的窗口。同时,通过initConsole()函数,程序也打开了console,方便程序读入和反馈信息等。对于具体的程序界面设计及相应事件响应,调用相应的函数即可。

3、代码执行效果

  • 编译并运行我们的程序之后,首先会显示一个黑色的命令行,接着出现我们的图形界面,有最近上映的电影可以选择,并且列出了该电影的综合评分,电影类型,电影产地以及电影时长,用户可以根据个人喜好从中选取一个电影查看座位,当用户选择任意一个电影时,会查询数据库并显示该电影的放映厅的售票情况,如下图所示:

图3.1

  • 选取任意一个放映厅后,会刷新界面,显示该放映厅当前的售票情况,白色代表该位置已售出,棕色代表该位置已经售出,当我们选择一个可选的位置时,该位置会变成红色,底部有两个按钮,分别为确认选座和返回大厅,如下图所示:

图3.2

  • 当用户选择好一个可选的位置之后,点击确认选座时,如果购票成功则会弹出提示告知用户购票成功,且该位置会被锁定成棕色,且不可再被选用,同时会调用相关的代码将信息更新给数据库,以便下次再打开时能够从数据库中读取到正确的信息。

图3.3

(4)当用户点击返回大厅时,将返回上一界面,用户可以重新选择放映厅。

源代码:maplefan/cinema

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

1 绪论

1.1 项目开发的背景

我们每个活在世上的人,为了能够参与各种社会活动,都需要一个用于识别自己的标志。也许你觉得名字或是身份证就足以代表你这个人,但是这种代表性非常脆弱,因为重名的人很多,身份证也可以伪造。最可靠的办法是把一个人的所有基因序列记录下来用来代表这个人,但显然,这样做并不实际。而指纹看上去是一种不错的选择,虽然一些专业组织仍然可以模拟某个人的指纹,但这种代价实在太高了。

而对于在互联网世界里传送的文件来说,如何标志一个文件的身份同样重要。比如说我们下载一个文件,文件的下载过程中会经过很多网络服务器、路由器的中转,如何保证这个文件就是我们所需要的呢?我们不可能去一一检测这个文件的每个字节,也不能简单地利用文件名、文件大小这些极容易伪装的信息,这时候,我们就需要一种指纹一样的标志来检查文件的可靠性,这种指纹就是我们现在所用的Hash算法(也叫散列算法)。

1.2 项目开发的目的

散列算法(Hash Algorithm),又称哈希算法,杂凑算法,是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样,散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原有文件发生改变时,其标志值也会发生改变,从而告诉文件使用者当前的文件已经不是你所需求的文件。

这种标志有何意义呢?之前文件下载过程就是一个很好的例子,事实上,现在大部分的网络部署和版本控制工具都在使用散列算法来保证文件可靠性。而另一方面,我们在进行文件系统同步、备份等工具时,使用散列算法来标志文件唯一性能帮助我们减少系统开销,这一点在很多云存储服务器中都有应用。

当然,作为一种指纹,散列算法最重要的用途在于给证书、文档、密码等高安全系数的内容添加加密保护。这一方面的用途主要是得益于散列算法的不可逆性,这种不可逆性体现在,你不仅不可能根据一段通过散列算法得到的指纹来获得原有的文件,也不可能简单地创造一个文件并让它的指纹与一段目标指纹相一致。散列算法的这种不可逆性维持着很多安全框架的运营,而这也将是本次课程设计讨论的重点。

2 相关技术介绍

2.1散列表介绍

使用Hash的数据结构叫做散列表,主要是为了提高查询的效率。也有直接译作哈希表,也叫Hash表,

Hash表是一种特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。这个源于Hash表设计的特殊性,它采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。

2.2 Hash算法特点

一个优秀的 hash 算法,将能实现:

(1)正向快速:给定明文和 hash 算法,在有限时间和有限资源内能计算出 hash 值。

(2)逆向困难:给定(若干) hash 值,在有限时间内很难(基本不可能)逆推出明文。

(3)输入敏感:原始输入信息修改一点信息,产生的 hash 值看起来应该都有很大不同。

(4)冲突避免:很难找到两段内容不同的明文,使得它们的 hash 值一致(发生冲突)。即对于任意两个不同的数据块,其hash值相同的可能性极小;对于一个给定的数据块,找到和它hash值相同的数据块极为困难。

2.3 常见的Hash算法

(1)数字分析法

(2)直接定址法

(3)除留余数法

2.4 常见的解决冲突方法

(1)线性探测法

(2)平方取中法

(3)拉链法

3 需求分析

Hash主要应用于数据结构中和密码学中。

用于数据结构时,主要是为了提高查询的效率,这就对速度比较重视,对抗碰撞不太看中,只要保证hash均匀分布就可以。

在密码学中,hash算法的作用主要是用于消息摘要和签名,换句话说,它主要用于对整个消息的完整性进行校验。

我们这里主要研究Hash用于数据结构中的作用。

  1. 问题描述

(1)首先需要随机生成中国人姓名的汉字拼音形式。

(2)随机生成3000个人名列表,并保存在文本文件中,在构建哈希表时读入。

(3)实现3种不同的哈希函数,并在建表的时候提供对应的冲突处理方案。

(4)比较不同方法的平均查找长度

2.功能要求

(1)随机姓名生成。

名字的长度不少于3个字符,不多于10个字符,为了符合要求,我们随机选用单姓的拼音加上一个随机的文字拼音组成随机姓名的拼音形式,并保存在文本文件中。

(2)建立Hash表功能。

分别通过直接取址法,平方取中法和除留余数法对姓名拼音的第一个字母,倒数第二个字母,倒数第一个字母的组合公式进行哈希,建立哈希表。

(3)实现Hash查找函数。

实现Hash查找函数并返回查找需要的次数,如果查找失败,返回0。

3.1可行性研究

开发环境

IDE:DevC++

编译器:GNU GCC Compiler

操作系统:Windows 10 64位

3.2需求概述

该程序可以分为以下几个模块:

1.生成随机名字拼音模块:随机生成名字拼音并保存在文本文件中。

2.建立Hash表模块:通过3种不同的算法建立不同的Hash表。

3.查找Hash表模块:通过3种不同的算法对应的查找函数查找Hash表中是否存在我们的名字,并返回查找次数,如果不存在该名字,则返回0。

4.平均查找长度比较模块:通过返回的查找次数求和并求平均数,比较不同的Hash表的查找效率。

4 详细设计

本章是根据软件工程知识,对概要设计的具体实现。通过对每个模块的功能进行描述,绘出功能流程图,编写代码,最终展示出相应的页面。使得整个设计变成一个可运行物理实体,从而达到本次设计的最终目的。

4.1 生成随机姓名拼音

首先,我建立了两个字符串数组name0和name1,分别保存我们所需要用到的姓氏和名字的拼音,一共存储了444个姓氏的拼音和305个名字的拼音。

然后实现了makeName()这个函数,函数通过调用头文件time.h中的随机函数rand()生成随机数,并对随机数分别mod 444和mod 305,获得我们要的数组范围内索引,从两个数组中分别取得姓和名,拼接成我们所要的随机姓名。

同时,我们通过C语言的文件操作,以只写模式打开同目录下的name.txt,并将我们生成的随机姓名存入该文本文件中,一个姓名占据一行,最后关闭文件操作。

流程图如下:

模块函数实现如下:

 

void makeName()
{
    srand((int)time(0));//让我们的随机数根据系统时间变化
    FILE *fp;//建立一个文件操作指针
    if ( (fp=fopen("name.txt","w+"))==NULL)     // 以追加只写模式打开文件操作
    {
        printf("File open error!\n");
    }
    for(int i = 0; i<3000; i++)
    {
        int a = rand()%444;//用来作为姓氏的数组索引
        int b = rand()%305;//用来作为名字的数组索引
        char tempName[12];
        strcpy(tempName,name0[a]);//将随机的姓复制到tempName中
        strcat(tempName,name1[b]);//将随机的名复制到tempName中
        fprintf( fp, "%s\n", tempName  );//将tempName写入文件
    }
    fclose(fp);//关闭文件操作
}

 

 

4.2 建立Hash表模块

我们分别通过不同的方法建立了3个不同的Hash表,分别名为hashmap1,hashmap2,hashmap3,hashmap4,其中hashmap4是hashmap3的补充,即当hashmap3发生冲突时我们的数据会存入hashmap4。

建立三个不同的Hash表我们使用了3个不同的函数,分别名为myHash1(),myHash2(),myHash3(),分别采用了直接取址法、平方取中法、除留余数法进行建表,并在函数中提供了相应的冲突解决方案。

由于我们的名字是由字符串组成,我们希望生成相应的数字用来当哈希表的索引,所以我们在myHash1()中使用公式int k = (a[0]-‘a’)*26*26 + (a[strlen(a)-2]-‘a’)*26 + (a[strlen(a)-1]-‘a’)生成索引。如果发生冲突我们就一直往后找直到找到空位置为止,把数据存到该空位置中。

在myHash2()中使用公式long long int k = (a[0]-‘a’)*26*26 + (a[strlen(a)-2]-‘a’)*26 + (a[strlen(a)-1]-‘a’)生成一个大数k,如果k小于或等于4位数,则直接用k作为索引,如果k大于4位数,则使用k的中间4位作为我们的索引。如果发生冲突我们就一直往后找直到找到空位置为止,把数据存到该空位置中。

在myHash3()中使用公式int k = (a[0]-‘a’)*26*26 + (a[strlen(a)-2]-‘a’)*26 + (a[strlen(a)-1]-‘a’)来生成一个数,并对这个数取mod 2777,其中2777是一个接近我们的数组大小的素数,如果发生冲突我们把冲突的数据按顺序放到hashmap4中。

流程图如下:

模块函数实现如下:

 

void myHash1()
{
    memset(hashmap1,0,sizeof(hashmap1));//将hashmap1字符串数组中所有的字符串置为空
    FILE *fpRead;
    if ( (fpRead=fopen("name.txt","r"))==NULL)     // 打开文件
    {
        printf("File open error!\n");
    }
    char a[12];
    for(int i = 0; i<3000; i++)
    {
        fscanf(fpRead,"%s",&a);//读取name.txt文件的一行给字符串数组a
        int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
        int index = k;
        while(hashmap1[index][0]!='\0')
        {
            index++;
        }
        strcpy(hashmap1[index],a);//将读取到的a存入hashmap1的该位置
    }
}

void myHash2()
{
    memset(hashmap2,0,sizeof(hashmap2));//将hashmap2字符串数组中所有的字符串置为空
    FILE *fpRead;
    if ( (fpRead=fopen("name.txt","r"))==NULL)     // 打开文件
    {
        printf("File open error!\n");
    }
    char a[12];
    int index = 0;
    for(int i = 0; i<3000; i++)
    {
        fscanf(fpRead,"%s",&a);//读取name.txt文件的一行给字符串数组a
        long long int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
        k = k*k;//平方
        if(k<10000)
        {
            index = k;
        }
        else//取中
        {
            int dight = getDigit(k);
            for(int i = 0; i<(dight-4)/2 ; i++)
            {
                k = k/10;
            }
            index = k%10000;
        }
        while(hashmap2[index][0]!='\0')
        {
            index++;
        }
        strcpy(hashmap2[index],a);//将a存到对应的位置
    }
}

void myHash3()
{
    int count = 0;
    memset(hashmap3,0,sizeof(hashmap3));//将hashmap3字符串数组中所有的字符串置为空
    memset(hashmap4,0,sizeof(hashmap4));//将hashmap4字符串数组中所有的字符串置为空
    FILE *fpRead;
    if ( (fpRead=fopen("name.txt","r"))==NULL)     // 打开文件
    {
        printf("File open error!\n");
    }
    char a[12];
    int index = 0;
    for(int i = 0; i<3000; i++)
    {
        fscanf(fpRead,"%s",&a);//读取name.txt文件的一行给字符串数组a
        int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
        index = k%2777;
        if(strlen(hashmap3[index])==0)
        {
            strcpy(hashmap3[index],a);//如果不存在字符串,放进hashmap3对应位置
        }
        else
        {
            strcpy(hashmap4[count],a);//否则放到hashmap4字符串数组中
            count++;
        }
    }
}

 

 

4.3 查找Hash表模块

查找Hash表模块分别用myHash1Find(),myHash2Find(),myHash3Find()三个函数实现,根据建表时的方法和冲突解决方案对应的去查找我们要的数据是否存在,同时都设置了计数器,当找到了我们要的数据时,返回查找次数,否则返回0表示没有找到我们要的数据。

流程图如下:

具体实现如下:

int myHash1Find(char* a) //存在返回查找次数,不存在返回0
{
    int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
    int temp = k;
    while(1)
    {
        if(strcmp(hashmap1[temp],a)==0)
        {
            return temp-k+1;
        }
        else if(strlen(hashmap1[temp])!=0)
        {
            temp++;
        }
        else
        {
            return 0;
        }
    }
    return temp-k+1;
}

int myHash2Find(char* a) //存在返回查找此时,不存在返回0
{
    long long int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
    int temp ;
    k = k*k;//平方
    if(k<10000)
    {
        temp = k;
    }
    else//取中
    {
        int dight = getDigit(k);//计算大数k的位数,根据位数来取中间4位数
        for(int i = 0; i<(dight-4)/2 ; i++)
        {
            k = k/10;
        }
        temp = k%10000;
    }
    int num = temp;
    while(1)
    {
        if(strcmp(hashmap2[temp],a)==0)
        {
            return temp-num+1;
        }
        else if(strlen(hashmap2[temp])!=0)
        {
            temp++;
        }
        else
        {
            return 0;
        }
        k++;
    }
}

int myHash3Find(char* a) //存在返回查找此时,不存在返回0
{
    int k = (a[0]-'a')*26*26 + (a[strlen(a)-2]-'a')*26 + (a[strlen(a)-1]-'a');
    int temp = k%2777;
    if(strcmp(hashmap3[temp],a)==0)
    {
        return 1;
    }
    else
    {
        int count = 0;
        while(strlen(hashmap4[count])!=0)
        {
            if(strcmp(hashmap4[count],a)==0)
            {
                return count+1;
            }
            count++;
        }
        return 0;
    }
}

 

 

4.4 平均查找长度模块

平均查找长度模块由函数compareHash实现,分别通过文件操作读入name.txt中的3000个姓名并查找该姓名,将查找次数求和,最后输入查找总次数除以3000即可。

流程图如下:

实现函数如下:

void compareHash() //比较平均查找长度
{
    double hash1Count = 0;//用来存储使用hash1查找函数时用的次数和
    double hash2Count = 0;//用来存储使用hash2查找函数时用的次数和
    double hash3Count = 0;//用来存储使用hash3查找函数时用的次数和
    FILE *fpRead;
    if ( (fpRead=fopen("name.txt","r"))==NULL)     // 打开文件
    {
        printf("File open error!\n");
    }
    char a[12];
    for(int i = 0; i<3000; i++)
    {
        fscanf(fpRead,"%s",&a);//读取name.txt文件的一行给字符串数组a
        hash1Count = hash1Count + myHash1Find(a);
        hash2Count = hash2Count + myHash2Find(a);
        hash3Count = hash3Count + myHash3Find(a);
    }
    printf("hash1算法查找3000个名字的总查找次数为:%.2f\n",hash1Count/3000.00);
    printf("hash2算法查找3000个名字的总查找次数为:%.2f\n",hash2Count/3000.00);
    printf("hash3算法查找3000个名字的总查找次数为:%.2f\n",hash3Count/3000.00);
}

 

5 软件测试与分析

软件测试是软件开发中必不可少的阶段。本章中,通过各种测试方法和多个测试用例,对应用进行测试,以期找出系统中可能导致系统出现问题的地方,使得该应用成为一个稳定的,高效的,能够达到用户标准的应用。

经过多次测试调试,我们的程序可以正常生成随机姓名拼音,hash函数也正常运行。

生成的3000个姓名拼音如下:

效率比较如下:

最终我们发现我们的hash1算法查找效率最高,但使用的空间最多,hash3算法查找效率极低,但使用的空间较少,而hash2算法使用空间较少,效率也比较高。

源代码:maplefan/hashHomeWork

 

设计一个日期类,要求重载其输入输出运算符,方便输入输出(输入和输出的格式为1970.09.25),并要求实现日期加和减运算(即(1)一个日期减另一个日期返回值为这两个日期之间相隔的天数;(2)一个日期减一个整数8则返回比这个日期早8天的日期;(3)一个日期加一个整数8则返回比这个日期晚8天的日期),并能比较两个日期的先后顺序,在main函数中首先要求用户输入两个日期,然后首先输出第一个日期加100天和减100天是哪一天?再输出第一个日期与第二个日期之间相隔多少天?最后将两个日期按先后次序输出。(考虑闰年,考虑月份不同)

#include<iostream>
#include<algorithm>
#include <ctime>
using namespace std;

class Date
{
private:
    int year;
    int month;
    int day;

public:
    Date()
    {
        year = 0;
        month = 0;
        day = 0;
    }

    Date(int a,int b,int c)
    {
        year = a;
        month = b;
        day = c;
    }

    ~Date()
    {

    }

    int getYear()
    {
        return this->year;
    }

    void setYear(int year)
    {
        this->year = year;
    }

    int getMonth()
    {
        return this->month;
    }

    void setMonth(int month)
    {
        this->month = month;
    }

    int getDay(int day)
    {
        return this->day;
    }

    void setDay()
    {
        this->day =day;
    }
    bool IsLeapYear(int year)
    {
        if(year % 400 || (year % 4 && year % 100))
            return true;
        return false;
    }
    int YearsOfMonth(int year, int month)
    {
        int day;
        int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        day = days[month];
        if (month == 2)
            day += IsLeapYear(year);
        return day;
    }

    Date operator+(int a)
    {
        Date b;
        b.year = this->year;
        b.month = this->month;
        b.day = this->day + a;
        while(b.day > YearsOfMonth(b.year,b.month))
        {
            if(b.month == 12)
            {
                b.month = 1;
                b.year++;
            }
            else
            {
                b.month++;
            }
            b.day = b.day - YearsOfMonth(b.year,b.month);
        }
        return b;
    }

    Date operator-(int a)
    {
        Date b;
        b.year = this->year;
        b.month = this->month;
        b.day = this->day - a;
        while(b.day <= 0)
        {
            if(b.month == 1)
            {
                b.month = 12;
                b.year--;
            }
            else
            {
                b.month--;
            }
            b.day = b.day + YearsOfMonth(b.year,b.month);
        }
        return b;
    }

    int daysOfDate(Date d)//计算一共的天数
    {
        int days=d.day;
        for(int y=1; y<d.year; y++) //计算年
            days= days + 365+IsLeapYear(d.year);
        for(int m=1; m<d.month; m++) //计算月
            days= days + YearsOfMonth(d.year,d.month);
        return days;
    }

    int operator-(Date a)
    {
        Date b;
        b.year = this->year;
        b.month = this->month;
        b.day = this->day ;
        int days1=daysOfDate(b);
        int days2=daysOfDate(a);
        return days1 - days2;
    }

    bool operator>(Date a)
    {
        Date b;
        b.year = this->year;
        b.month = this->month;
        b.day = this->day ;
        int days1=daysOfDate(b);
        int days2=daysOfDate(a);
        if(days1 >= days2)
        {
            return true;
        }
        return false;
    }

    friend    ostream& operator<<(ostream& out, const Date &a)
    {
        out<<a.year<<"."<<a.month<<"."<<a.day;
        return out;
    }


    friend    istream& operator>>(istream& in, Date &a)
    {
        in >>a.year;
        cin.get();
        in>>a.month;
        cin.get();
        in>>a.day;
        return in;
    }
};

int main()
{
    Date a;
    Date b;
    cin>>a;
    cin>>b;
    cout<<a+100<<endl;
    cout<<a-100<<endl;
    cout<<a-b<<endl;
    if(a>b)
    {
        cout<<b<<endl;
        cout<<a<<endl;
    }
    else
    {
        cout<<a<<endl;
        cout<<b<<endl;
    }
}

 

描述

米兔爸爸为了让小米兔好好锻炼身体,便给小米兔设置了一个挑战——跳格子。

要吃到自己心爱的胡萝卜,小米兔需要跳过面前一些格子。现有 N 个格子,每个格子内都写上了一个非负数,表示当前最多可以往前跳多少格,胡萝卜就放在最后一个格子上。米兔开始站在第 1 个格子,试判断米兔能不能跳到最后一个格子吃到胡萝卜呢?

输入

输入为 N 个数字 (N<10),用空格隔开,第 i个数字 si(100si<10) 表示米兔站在第 i个格子上时,最多能往前跳的格数。

输出

若米兔能跳到最后一个格子上吃到胡萝卜,输出 “true“,否则输出 “false“

输入样例

2 0 1 0 0 3 4

输出样例

false

AC代码:

#include <bits/stdc++.h>
#include<iostream>
#include<map>
using namespace std;
int main()
{
    int b;
    vector<int>a;
    bool flag = false;
    while(cin>>b)
    {
                    a.push_back(b);
        if (cin.get() == '\n')
            break;
    }
    int mitu = 0;
    while(mitu<a.size()){
        mitu = mitu + a[mitu];
        if(mitu == a.size()-1){
            flag = true;
            break;
        }
        if(a[mitu] == 0){
            break;
        }
    }
    if(flag){
        cout<<"true"<<endl;
    }
    else
    cout<<"false"<<endl;
}

 

描述

给定一个整数N,求N!的末尾有多少个0.

输入

输入为一个整数N,1 <= N <= 1000000000.

输出

输出为N!末尾0的个数

输入样例

3
60
100
1024
23456
8735373

输出样例

0
14
24
253
5861
2183837

AC代码:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int num = 0;
    while(cin>>num)
    {
        int cnt = 0;
        for(int i = 1; i<num; i++)
        {
            int j = i;
            while(j%5  == 0)
            {
                cnt++;
                j= j/5;
            }
        }
        cout<<cnt<<endl;
    }
    return 0;
}

 

描述

给出一个数组,数组中的数字皆为正整数,除了某一个数字,其他的数字都会出现三次。 找出那个只出现一次的数。

输入

3n+1的正整数数组,使用逗号(,)分隔(n>0)

输出

单独出现的数字

输入样例

2,3,2,2
5,1,4,5,4,5,4

输出样例

3
1

AC代码:

#include <bits/stdc++.h>
#include<iostream>
#include<map>
using namespace std;
int main()
{

    map<int,int>a;
    int b;
    while(cin>>b)
    {
        a[b]++;
        if (cin.get() == '\n')
            break;
    }
    for(map<int,int>::iterator iter=a.begin();iter!=a.end();iter++){
          if(iter->second == 1){
            cout<<iter->first<<endl;
          }
	}
}

 

描述

我们知道,在逻辑表达式中广泛使用了括号,而括号都是层次化成对出现的。也就是任意左括号都应该存在一个在同一逻辑层级的右括号作为对应。 现在我们有一些仅由括号组成的字符串序列,保证每个字符为大括号”{”,”}”、中括号”[”,”]”和小括号”(”,”)”中的一种。 需要判断给定的的序列是否合法。

输入

一行仅由括号组成的字符串

输出

如果序列合法输出 1,否则输出 0

输入样例

[()]
({[])}
[()]{}

输出样例

1
0
1

小提示

栈的典型应用

AC代码:

#include <bits/stdc++.h>
#include<vector>
#include<stack>
using namespace std;

int main()
{
    string s;
    while(cin>>s)
    {
        stack<char>a;
        bool flag = false;
        for(int i = 0; i<s.length(); i++)
        {
            if(s[i]=='[' || s[i]=='(' || s[i]=='{')
            {
                a.push(s[i]);
            }
            else if(s[i]==']' )
            {
                if(!a.empty() &&a.top()=='[')
                {
                    a.pop();
                }
                else
                {
                    a.push(s[i]);
                }
            }
            else if(s[i]==')' )
            {
                if(!a.empty() &&a.top()=='(')
                {
                    a.pop();
                }
                else
                {
                    a.push(s[i]);
                }
            }
            else if(s[i]=='}' )
            {
                if(!a.empty() &&a.top()=='{')
                {
                    a.pop();
                }
                else
                {
                    a.push(s[i]);
                }
            }
        }
        if(a.empty())
        {
            cout<<"1"<<endl;
        }
        else cout<<"0"<<endl;
    }
}

 

描述

将 M 个同样的糖果放在 N 个同样的篮子里,允许有的篮子空着不放,共有多少种不同的分法? 比如,把 7 个糖果放在 3 个篮子里,共有 8 种分法(每个数表示篮子中放的糖果数,数的个数为篮子数): 1 1 5 1 2 4 1 3 3 2 2 3 2 5 0 3 4 0 6 1 0 7 0 0

注意:相同的分布,顺序不同也只算作一种分法,如 7 0 0、0 7 0 和 0 0 7 只算作一种。

输入

输入包含二个正整数 M 和 N,以(,)分开,M 表示有几个同样的糖果,N 表示有几个同样的篮子 M与N范围:1 <= M,N <= 100。

输出

输出一个正整数 K,表示有多少种分法。

输入样例

7,3

输出样例

8

AC代码:

#include <bits/stdc++.h>
#include<iostream>
#include<map>
using namespace std;

int calc(int m,int n)
{
    if(m== 0 || n==1)
    {
        return 1;
    }
    //当n=1时,所有苹果都必须放在一个盘子里,所以返回1;
    //当没有苹果可放时,定义为1种放法;
    //递归的两条路,第一条n会逐渐减少,终会到达出口n==1;
    //第二条m会逐渐减少,因为n>m时,我们会return f(m,m) 所以终会到达出口m==0.
    if(m < n)
    {
        return calc(m,m);
    }
    else
    {
        return calc(m-n,n)+calc(m,n-1);
        //所有篮子放满和有一个篮子空的情况
        //放满的时候分法和每个篮子拿去一个糖果相同
        //有一个篮子空的时候相当于和这个篮子不存在的时候的分法相同
    }
}


int main()
{
    int m = 0;
    int n = 0;
    while(cin>>m)
    {
        cin.get();
        cin>>n;
        cout<<calc(m,n)<<endl;
    }
}

 

描述

输入32位无符号整数,输出它的反向位。 例,输入4626149(以二进制表示为00000000010001101001011011100101),返回2808701440(以二进制表示为10100111011010010110001000000000)。

输入

一个无符号32位整数字符串

输出

一个无符号32位整数,为输入整数的反向位

输入样例

4626149

输出样例

2808701440

AC代码:

#include <bits/stdc++.h>
#include<vector>
#include<stack>
#include<sstream>
using namespace std;

int pow2(int a){
     int sum = 1;
     while(a--){
        sum = sum*2;
     }
     return sum;
}



int main()
{
    int number;
    while(cin>>number)
    {
        unsigned int result = 0;
        int cnt = 0;
        unsigned int mask = 1;
    mask = mask << 31;
    while(mask)
    {
        if(number & mask)
        {
            result = result + pow2(cnt) * 1;
        }
        else
        {
            result = result + pow2(cnt) * 0;
        }
        mask = mask>>1;
        cnt++;
    }
    cout<<result<<endl;
    }
}

 

描述

等差数列是常见数列的一种,如果一个数列从第二项起,每一项与它的前一项的差等于同一个常数,这个数列就叫做等差数列,而这个常数叫做等差数列的公差,公差常用字母d表示。即对于数列S,它满足了(S[i]-S[i-1]) = d (i \gt 1)(S[i]S[i1])=d(i>1)。 显然,一个数字无法构成等差数列,而任意两个数字可以形成一个等差数列。 这里给出了一个长度为N(0<N<200)的数字序列,每个位置有一个整数(100整数100),需要找到这个数字序列里包含多少个等差数列,序列顺序固定,无需排序。 输入数据格式:{S[0] S[1] S[2] … S[N]}S[0] S[1] S[2] … S[N](以半角空格符分隔,N>1) 输出数据格式:等差数列数量 MM; 其中数列 SS 的项为整数

请注意时间复杂度的限制。

输入

输入一个数列[ 2 7 4 5 6 ],该数列包含等差数列: [ 2 7 ] [ 2 4 ] [ 2 5 ] [ 2 6 ] [ 7 4 ] [ 7 5 ] [ 7 6 ] [ 4 5 ] [ 4 6 ] [ 5 6 ] [ 2 4 6 ] [ 4 5 6 ]

输出

上例共包含12组等差数列,故应输出12

输入样例

2 7 4 5 6
3 3 3 3

输出样例

12
11

AC代码:

#include <bits/stdc++.h>
#include<iostream>
#include<map>
using namespace std;

int dp[400+1][400+1];

int main()
{
        memset(dp,0,sizeof(dp));
        int b;
        int result = 0;
        vector<int>a;
        bool flag = false;
        while(cin>>b)
        {
            a.push_back(b);
            if (cin.get() == '\n')
                break;
        }
        for(int k = -200; k<=200; k++)
        {
            for(int j = 0; j<a.size(); j++)
            {
                for(int i = j+1; i<a.size(); i++)
                {
                    if(a[j]+k == a[i])
                    {
                        dp[i][k+200] = dp[i][k+200] + dp[j][k+200]+1;
                    }//k+200 因为数组下标必须大于等于0
                }
            }
        }
        for(int i = 0; i<a.size(); i++)
        {
            for(int k = -200; k<=200; k++)
            {
                result = result + dp[i][k+200];
            }
        }
        cout<<result<<endl;
}