DS博客作业05--查找

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习树结构设计及运算操作 |
| 姓名 | 章审 |

0.PTA得分截图

查找题目集总得分,请截图,截图中必须有自己名字。题目至少完成总题数的2/3,否则本次作业最高分5分。没有全部做完扣1分。

1.本周学习总结(0-5分)

1.1 查找的性能指标

ASL成功、不成功,比较次数,移动次数、时间复杂度
在查找运算中时间主要花费在关键字的比较上,把平均需要和给定值k进行比较的关键字次数称为平均查找长度(Average Search Length,ASL),其定义如下:

其中,n是查找表中元素的个数。pi是查找第i个元素的概率,通常假设每个元素的查找概率相等,此时pi=1/n(1<=i<=n),ci是找到第i个元素所需的关键字比较次数。
ASL分为查找成功情况下的ASL成功和查找不成功(失败)情况下的ASL不成功。
ASL成功表示成功查找到查找表中的元素,平均需要关键字比较次数(pi为查找到第i个元素的概率,有∑(n,i=1)pi=1)。
ASL不成功表示没有找到查找表中的元素,平均需要关键字比较次数(假设共有m种查找失败情况,qi为第i种情况的概率,有∑(m,i=1)qi=1)。
显然, ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。
在哈希表中,若出现哈希冲突则需要移动并且额外去比较。
O(n)越小,算法的时间复杂度越低,算法的效率越高。

1.2 静态查找

分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。
(1)顺序查找:
顺序查找(sequential search)是一种最简单的查找方法。它的基本思路是从表的一端向另一端逐个将元素的关键字和给定值k比较,若相等,则查找成功,给出该元素在查找表中的位置;若整个查找表扫描结束后仍未找到关键字等于k的元素,则查找失败。
顺序查找的算法如下(在顺序表R[0..n-1]中查找关键字为k的元素,成功时返回找到的元素的逻辑序号,失败时返回0)

int SeqSearch(RecType R[], int n, KeyType k)
{
	int i = 0;
	while (i < n && R[i].key != k) //从表头往后找
		i++;
	if (i >= n)//未找到返回0
		return 0;
	else
		return i + 1;//找到返回逻辑序号i+1
}

ASL成功:

ASL不成功=n
(2)二分查找
二分查找又称折半查找,折半查找(binary search)又称二分查找,它是一种效率较高的查找方法。但是,折半查找要求线性表是有序表,即表中的元素按关键字有序。在下面的讨论中,假设有序表是递增有序的。
折半查找的基本思路是设R[low..high]是当前的查找区间,首先确定该区间的中点位置mid=[(low+ high)/2],然后将待查的k值与R[mid].key比较:
(1)若k=R[mid]. key,则查找成功并返回该元素的逻辑序号。
(2)若k<R[mid]. key,则由表的有序性可知R[mid..high]. key均大于k,因此若表中存在关键字等于k的元素,则该元素必定是在位置mid左边的子表R[low..mid-1]中,故斤的查找区间是左子表R[low..mid-1].
(3)若k>R[mid]. key,则关键字为k的元素必在mid的右子表R[mid+1..high]中,即新的查找区间是右子表R[mid+1.high]。下一次查找是针对新的查找区间进行的。
上述过程如图所示。可以从初始的查找区间R[0..n-1]开始,每经过一次与当前查找区间的中点位置上的关键字比较,就可确定查找是否成功,不成功则当前的查找区间缩小一半。重复这一过程直到找到关键字为k的元素,或者直到当前的查找区间为空(即查找失败)时为止.

int BinSearch(RecType R[], int n, KeyType k) //折半查找算法
{
	int low = 0, high = n - 1, mid;
	while (low <= high)//当前区间存在元素时循环
	{
		mid = (low + high) / 2
			if (k == R[mid].key)//查找成功返回其逻辑序号mid+1
				return mid + 1;
		if (k < R[mid] key)//继续在R[low..mid-1]中查找
			high = mid - 1
		else//k> R[mid]. key
			low = mid + 1;//继续在R[mid+1high]中查找
	}
	return 0//未找到时返回0(查找失败)
}

为讨论方便,不妨设判定树中内部结点的总数为n=2h次方-1,将该判定树近似看成是高度为h=log2(n+1)的满二叉树(高度h不计外部结点)。树中第i层上的结点个数为2(i-1)的次方,查找该层上的每个结点恰好需要进行i次比较。因此,在等概率假设下折半查找成功时的平均查找长度为:

二分查找在查找失败时所需比较的关键字个数不超过判定树的深度,在最坏情况下查找成功的比较次数也不超过判定树的深度。即为:二分查找的最坏性能和平均性能相当接近。

1.3 二叉搜索树

1.3.1 如何构建二叉搜索树(操作)

  • 结合一组数据介绍构建过程,及二叉搜索树的ASL成功和不成功的计算方法。
    对于一组关键字集合序列为(5,2,1,6,7,4,8,3,9) ,生成二叉排序树的过程如图9.10(a)所示;

    二叉排序树asl成功的计算方法:将每层的节点数和层数相乘并求其他的和再除以树的节点数。
    以上题为例,其asl成功为:(1·1+2·2+3·3+2·4+1·5)/9=3
    asl不成功的计算方法:将每层的空节点数和层数-1相乘并求其他的和再除以树的节点数。
    以上题为例,其asl不成功为:(1·2+4·3+1·4)/6=3

  • 如何在二叉搜索树做插入、删除。
    插入:在二叉排序树中插入一个关键字为k的结点要保证插入后仍满足BST性质。其插入过程是:若二叉排序树bt为空,则创建一个key域为k的结点,将它作为根结点;否则将k和根结点的关键字比较,若两者相等,则说明树中已有此关键字k,无须插入,直接返回假;若k key,则将k插人根结点的左子树中,否则将它插入右子树中。
    其过程如上图构建二叉树所示。
    删除:在从二叉排序树中删除一个结点时不能直接把以该结点为根的子树都删去,只能删除该结点本身,并且还要保证删除后所得的二叉树仍然满足BST性质。也就是说,在二叉排序树中删去一个结点相当于删去有序序列(即该树的中序序列)中的一个元素。
    删除操作必须首先进行查找,假设在查找结束时p指向要删除的结点。删除过程分为以下几种情况:
    (1)若p结点是叶子结点,直接删去该结点。如图(a)所示,直接删除结点9。这是最简单的删除结点的情况。
    (2)若p结点只有左子树而无右子树。根据二叉排序树的特点,可以直接将其左孩子替代结点p(结点替换)。如图(b)所示,p指向结点4,要删除p结点,只需将其左孩子结点3替代它。
    (3)若p结点只有右子树而无左子树。根据二叉排序树的特点,可以直接将其右孩子替代结点p(结点替换)。如图(c)所示,p指向结点7,要删除p结点,只需将其右孩子结点8替代它。
    (4)若p结点同时存在左、右子树。根据二叉排序树的特点,可以从其左子树中选择关键字最大的结点r,用结点r的值替代结点p的值(结点值替换),并删除结点r(由于r结点一定是没有右子树的,删除它属于情况(2) ),其原理是用中序前驱替代被删结点。
    也可以从其右子树中选择关键字最小的结点r,用结点r的值替代结点p的值(结点值替换),而且将它删除(由于r结点一定是没有左子树的,删除它属于情况(3)),其原理是用中序后继替代被删结点。
    通常采用前一种删除方式,如图9.12(d)所示,p指向结点5,它的左子树中关键字最大的结点是结点4,将结点p的关键字改为4,并删除结点4

1.3.2 如何构建二叉搜索树(代码)

1.如何构建、插入、删除及代码。
创建一棵二叉排序树是从一个空树开始的,每插入一个关键字,就调用一次插入算法将它插入到当前已生成的二叉排序树中。从关键字数组a[0..n-1]生成二叉排序树的算法CreateBST()如下:

BSTNode* CreateBST(KeyType A[], int n)//创建二叉排序树
//返回BST树根结点指针
{
	BSTNode* bt = NULL;//初始时bt为空树
	int i = 0;
	while (i < n)
	{
		InsertBST(bt, a[i]);//将关键字a[门插入二叉排序树bt中
		i++;
	}
	return bt;//返回建立的二叉排序树的根指针
}

其插入代码如下:

bool InsertBST(BSTNode*& bt, KeyType k)
//在二叉排序树bt中插入一个关键字为k的结点,若插入成功返回真,否则返回假
{
	if (bt == NULL)//原树为空,新插人的结点为根结点
	{
		bt = (BSTNode*)malloc(sizeof(BSTNode));
		bt->key = k; bt->lchild - bt->rchild - NULL;
		return true;
	}
	else if (k == bt->key)
		return false;//树中存在相同关键字的结点,返回假
	else if (k < bt->key)
		return InsertBST(bt → > lchild, k);//插入到左子树中
	else
		return InsertBST(bt->rchild, k);//插入到右子树中
}

其删除代码如下:

bool DeleteBST(BSTNode& bt, KeyType k)//在bt中删除关键字为k的结点
{
	if (bt == NULL)
		return false//空树删除失败,返回假
	else
	{
		if (k < bt->key)
			return DeleteBST(bt->lchild, k);//递归在左子树中删除为k的结点
		else if (k > bt->key)
			return DeleteBST(bt->rchild, k);//递归在右子树中删除为k的结点
		else//找到了要删除的结点bt
		{
			Delete(bt);//调用Delete(bt)函数删除结点bt
			return true;//删除成功,返回真
		}
	}
}

void Delete(BSTNode*& p)//从二叉排序树中删除结点p
{
	BSTNode* q;
	if (p->rchild == NULL) //结点p没有右子树(含为叶子结点)的情况
	{
		q = p;
		p = p->lchild;//用结点p的左孩子替代它
		free(q);
	}
	else if (p->lchild = NULL) //结点口没有左子树的情况
	{
		q = р;
		p = p->rchild;//用结点p的右孩子替代它
		free(q);
	}
	else Delete(p, p->lchild);//结点p既有左子树又有右子树的情况
}

void Deletel(BSTNode* p, BSTNode*& r) //被删结点p有左、右子树,r指向其左孩子
{
	BSTNode* q;
	if (r->rchild != NULL)
		Deletel(p, r->rchild);//递归找结点r的最右下结点
	else//找到了最右下结点r(它没有右子树)
	{
		p->key = r->key;//将结点r的值存放到结点p中(结点值替代)
		P->data = r->data;
		q = r;//删除结点r
		r = r->lchild;//即用结点r的左孩子替代它
		free(q);//释放结点a的空间
	}
}

2.分析代码的时间复杂度
就插入节点和删除并非有两个孩子的节点来讲,他们的代码都是经历查找后再去执行插入或删除,给定值的比较次数等于给定值节点在二叉排序树中的层数。如果二叉排序树是平衡的,则n个节点的二叉排序树的高度为Log2(n+1),其查找效率为O(Log 2(n)),近似于折半查找。如果二叉排序树完全不平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。一般的,二叉排序树的查找性能在O(Log 2n)到O(n)之间。
3.为什么要用递归实现插入、删除?递归优势体现在代码哪里?
递归可以大量减少代码的量(优势),并且二叉排序树也是一种树,树的许多操作也是基于递归实现的。

1.4 AVL树

  • AVL树解决什么问题,其特点是什么?
    结合一组数组,介绍AVL树的4种调整做法。
    avl树又称平衡二叉树。若一棵二叉树中每个结点的左、右子树的高度最多相差1,则称此二叉树为平衡二叉树(balanced binary tree)。在算法中,通过平衡因子(balance factor, b)来具体实现上述平衡二叉树的定义。一个结点的平衡因子是该结点左子树的高度减去右子树的高度(或者该结点右子树的高度减去左子树的高度),从平衡因子的角度说,若一棵二叉树中某个结点的平衡因子的绝对值小于或等于1,即其平衡因子的取值为1、0或一1,该结点是平衡的,否则是不平衡的。若一棵二叉树的所有结点都是平衡的,称之为平衡二叉树。
    特点:每个结点的平衡因子的绝对值小于或等于1
    avl的4种调整做法:
    ll型:
    rr型:
    lr型:
    rl型:
    AVL树的高度和树的总节点数n的关系?
    h=log2(n+1)
    介绍基于AVL树结构实现的STL容器map的特点、用法。
    Map是STL的一个关联容器,翻译为映射,数组也是一种映射。如:int a[10] 是int 到 int的映射,而a[5]=25,是把5映射到25。数组总是将int类型映射到其他类型。这带来一个问题,有时候希望把string映射成一个int ,数组就不方便了,这时就可以使用map。map可以将任何基本类型(包括STL容器)映射到任何基本类型(包括STL容器)。
    特点:map提供关键字到值的映射 ,其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个称为该关键字的值。
    用法:
    begin()         返回指向map头部的迭代器
    end()           返回指向map末尾的迭代器
    rbegin()        返回一个指向map尾部的逆向迭代器
    rend()          返回一个指向map头部的逆向迭代器
    lower_bound()   返回键值>=给定元素的第一个位置
    upper_bound()    返回键值>给定元素的第一个位置
    empty()         如果map为空则返回true   
    max_size()      返回可以容纳的最大元素个数    
    size()          返回map中元素的个数
    clear()        删除所有元素
    count()         返回指定元素出现的次数
    equal_range()   返回特殊条目的迭代器对
    erase()         删除一个元素
    swap()           交换两个map
    find()          查找一个元素
    get_allocator() 返回map的配置器
    insert()        插入元素
    key_comp()      返回比较元素key的函数
    value_comp()     返回比较元素value的函数

1.5 B-树和B+树。

  • B-树和AVL树区别,其要解决什么问题?
    区别:b-树每个节点可以有多个关键字,而avl树每个节点只能有一个关键字。同时,b-树可以有多个子树,而avl树只能最多有两个子树。
    其要解决的问题:B-树是一棵多叉平衡搜索树,旨在比AVL树能够拥有更低的树高,提高查找的效率,但是同AVL树一样,面对插入和删除数据的操作后需要维持平衡,这可能带来一些得不偿失的情况。其次B-树可以被采用在外存的数据查询上,因为树高比较低,这样就可以减少磁盘的I/O次数。

  • B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况
    B.树(B tree)中所有结点的孩子结点的最大值称为B-树的阶,通常用m表示,从查找效率考虑,要求m>2。一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
    (1)树中每个结点最多有m棵子树(即最多含有m-1个关键字,设Max=m-1);
    (2)若根结点不是叶子结点,则根结点最少有两棵子树;
    (3)除根结点以外,所有非叶子结点最少有「m/2]棵子树(即最少含有[m/2]-1个关键字,设Min=[m/2]-1);
    (4)每个结点的结构为

    其中,n为该结点中的关键字个数,除根结点以外,其他所有结点的关键字个数n满足m/2]-1<=n<=m-1; k,(1<=i<=n)为该结点的关键字且满足ki<k(i+1);pi(0<=i<=n)为该结点的孩子结点指针,满足pi(0<=i<=n-1)所指子树上结点的关键字均大于ki,且小于k(i+1)pn所指子树上结点的关键字均大于k.
    (5)所有的外部结点在同一层,并且不带信息。在B-树中外部结点(可以看作是查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)不带信息,为了方便,在后面的B-树图中都没有画出外部结点层。通常在计算一棵B.树的高度时外部结点层也要计入一层。显然,如果一棵B-树中总共有n个关键字,则外部结点的个数为n+1。

  • 插入操作:
    a:

    b:

    c:

    d:

    e:

    f:

    g:

    h:

    由于m-5,所以Max=m-1-4,以在图(e)中插入关键字20为例说明插入过程。在图(e)中插入关键字20时,查找其插入结点是最右边的叶子结点,将其有序插人,该结点变成(11, 13, 16,17, 20),这时结点的关键字个数超界,需进行分裂,即由该结点变成两个结点,分别包含关键字11,13和17,20,并将中间关键字16移至双亲结点中,双亲结点变为(6,10,16)。
    再看在图(g)中插入关键字15,查找其插入结点是(11, 12, 13, 14)结点,将其有序插入,该结点变成(11, 12, 13, 14,15) ,这时该结点的关键字个数超界,需进行分裂,对应两个结点(11,12)和(14, 15) ,并将中间关键字13移至双亲结点中,双亲结点变为(3,6, 10, 13,16)。这时该结点的关键字个数超界,需进行分裂,对应两个结点(3,6)和(13,16) ,并将中间关键字10移至双亲结点中,由于分裂前的结点就是根结点,所以新建一个根结点,树的高度增加一层。最终创建的5阶B-树如图(h)所示。

  • 删除操作:
    a:
    b:
    c:
    d:
    在图(c)中删除关键字4时,找到它所在的结点(4,5)是一个叶子结点,从中删除关键字4,而左、右兄弟都只有两个关键字,不能借,将左兄弟(1,2)、双亲结点中分割的关键字3和该结点合并为一个结点(1,2,3,5) ,这样双亲结点变为(6),不满足5阶B-树的要求。
    而双亲结点(6)又没有兄弟可以借,继续合并,将右兄弟(13,18)、双亲结点中分割的关键字10和该结点合并为一个结点(6,10,13,18),这样导致B-树的高度减少了一层。
    B+树定义,其要解决问题

1.6 散列查找。

  • 哈希表的设计主要涉及哪几个内容?
    哈希表(hash table)又称散列表,其基本思路是,设要存储的元素个数为n,设置一个长度为m(m>n)的连续内存单元,以每个元素的关键字k,(o<isn-1)为自变量,通过一个称为哈希函数(hash function)的函数h(k,)把k,映射为内存单元的地址(或下标)h(k, ) ,并把该元素存储在这个内存单元中,h(k)也称为哈希地址(hash address)。把如此构造的线性表存储结构称为哈希表。
    在构建哈希表时可能存在这样的问题,两个关键字k,和k, (itj)有k,tk,,但会出现h(k,)=h(k,)的情况,把这种现象叫哈希冲突(hash collisions)。通常把这种具有不同关键字而具有相同哈希地址的元素称为同义词(synonym) ,这种冲突也称为同义词冲突。在哈希表存储结构中,同义词冲突是很难避免的,除非关键字的变化区间小于等于哈希地址的变化区间,而这种情况当关键字取值不连续时是非常浪费存储空间的。通常的实际情况是关键字的取值区间远大于哈希地址的变化区间。
    在哈希表中,虽然冲突很难避免,但发生冲突的可能性却有大有小,这会影响哈希查找的性能。哈希查找性能主要与3个因素有关:
    (1)与装填因子a有关。所谓装填因子(load factor)是指哈希表中已存入的元素数n与哈希地址空间大小m的比值,即a=n/m, a越小,冲突的可能性就越小; a越大(最大可取1),冲突的可能性就越大。这很容易理解,因为a越小,哈希表中空闲单元的比例就越大,所以待插入元素和已插入的元素发生冲突的可能性就越小;反之,a越大,哈希表中空闲单元的比例就越小,所以待插入元素和已插入的元素冲突的可能性就越大。另一方面,a越小,存储空间的利用率就越低;反之,存储空间的利用率也就越高。为了既兼顾减少冲突的发生,又兼顾提高存储空间的利用率这两个方面,通常使最终的a控制在0.6~0.9的范围内。
    (2)与所采用的哈希函数有关。若哈希函数选择得当,就可以使哈希地址尽可能均匀也分布在哈希地址空间上,从而减少冲突的发生;否则,若哈希函数选择不当,就可能使哈希地址集中于某些区域,从而加大冲突的发生。
    (3)当出现哈希冲突时需要采取解决哈希冲突的方法,所以哈希查找性能也与解决冲突的方法有关。
    哈希冲突的解决方法:
    (1)线性探测法:线性探测法(linear probing)是从发生冲突的地址(设为d0)开始,依次探测d0的下一个地址(当到达下标为m-1的哈希表表尾时,下一个探测地址是表首地址0),直到找到一个空闲单元为止(当m>=n时一定能够找到一个空闲单元)。线性探测法的数学递推描述公式为:
    d0 =h(k)
    di=(d(i-1)+1) mod m (1<=i<=m-1)
    (2)平方探测法:设发生冲突的地址为d0,平方探测法(square probing)的探测序列为d0+12,d0-12,d0+22,d0-22,…。平方探测法的数学描述公式为:
    d0=h(k)
    di=(d0±i^2) mod m (1<=i<=m-1)
    (3)拉链法:拉链法(chaining)是把所有的同义词用单链表链接起来的方法。如图9.34所示,所有哈希地址为i元素对应的结点构成一个单链表,哈希表地址空间为0~m-1,地址为i的单元是一个指向对应单链表的首结点。
    在这种方法中,哈希表的每个单元中存放的不再是元素本身,而是相应同义词单链表的首结点指针。由于在单链表中可插入任意多个结点,所以此时装填因子a根据同义词的多,既可以设定为大于1,也可以设定为小于或等于1,通常取a=1。

    与开放定址法相比,拉链法有以下几个优点:
    (1)拉链法处理冲突简单,且无堆积现象,即非同义词绝不会发生冲突,因此平均查找
    长度较短;
    (2)由于拉链法中各单链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
    (3)开放定址法为减少冲突要求装填因子a较小,故当数据规模较大时会浪费很多空间,而拉链法中可取a>1,且元素较大时拉链法中增加的指针域可忽略不计,因此节省空间;
    (4)在用拉链法构造的哈希表中,删除结点的操作更加易于实现。
    拉链法也有缺点:指针需要额外的空间,故当元素规模较小时开放定址法较为节省空间,若将节省的指针空间用来扩大哈希表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高了平均查找速度。
  • 结合数据介绍哈希表的构造及ASL成功、不成功的计算
    用关键字序列{7,8,30,11, 18, 9, 14)构造一个哈希表,哈希表的存储空间是一个下标从0开始的一维数组,哈希函数为H(key)= (key×3) mod 7,处理冲突采用线性探测法,要求装填(载)因子为0.7.
    由题可得所构造的哈希表如下图所示

    在等概率情况下:
    ASL成功=(1+2+1+1+1+3+3)/7=1.71
    由于任一关键字k, H(k)的值只能是0~6,在不成功的情况下,H(k)为0时需要比较3次, H(k)为1时需要比较两次,H(k)为2时需要比较一次, H(k)为3时需要比较两次,H(k)为4时需要比较一次,H(k)为5时需要比较5次, H(k)为6时需要比较4次,共7种情况,如下图所示。
  • 结合数据介绍哈希链的构造及ASL成功、不成功的计算
    假设哈希表的长度m=13,采用除留余数法加拉链法建立关键字集合(16,74,60,43,54,90,46,31,29,88,77)的哈希表。
    其所构建的哈希表如下图所示:

    对于哈希表中存在的某个关键字k,对应的结点在单链表h[k]中,它属于该单链表的第几个结点,成功找到它恰好需要几次关键字比较,所以有:
    ASL成功=(1X9+2×2)/11=1.182
    式中,1X9表示有9个结点成功找到各需要一次比较,2×2表示有两个结点成功找到各需要两次比较。
    若待查关键字k的哈希地址为d=h(k)(0<=d<=m-1),且第d个单链表中有i个结点,则当k不在该单链表中出现时需做i次关键字的比较(不包括空指针判定)才能确定查找失败,因此有:
    ASL=(0+0+1+2+1+1+0+1+1+1+1+0+2)/13=0.846

2.PTA题目介绍(0--5分)

介绍3题PTA题目

2.1 是否完全二叉搜索树(2分)

本题务必结合完全二叉搜索树经过层次遍历后在队列的特点去设计实现。结合图形介绍。

2.1.1 伪代码(贴代码,本题0分)

伪代码为思路总结,不是简单翻译代码。

主函数
{
	建立二叉树
	树的层序遍历
	根据完全二叉树判断结果输出yes或者no
}
BSTree InsertBSTree//建立二叉树函数
{
	if 根节点为空则创建根节点
	else if 键值大进入左子树
	else 进入右子树
}
bool LevelBSTree//树的层序遍历函数
{
    用队列的结构储存当前结点
	用队列依次将节点输出
}
int IsBSTee//判断树是否为完全二叉树函数
{
	定义flag
	用队列的结构储存节点
	定义计数count
	若队列为空输出NULL
	while push循环
	if 队列读取到空结点
		修改 flag 表示接下来不再统计结点数
	else 队列头结点非空
		统计结点数
		左结点入队列
		右结点入队列
	队列头出队列
	直至空队列,结束循环
}

2.1.2 提交列表

2.1.3 本题知识点

建树,利用队列结构实现树的层次遍历,树的插入,

2.2 航空公司VIP客户查询(2分)

本题结合哈希链结构设计实现。请务必自己写代码,学习如何建多条链写法。

2.2.1 伪代码(贴代码,本题0分)

伪代码为思路总结,不是简单翻译代码。

定义常量maxn, INF;
定义整型变量 n(飞行记录), m(最低里程), v(路程), id(身份证号), cnt;
定义字符串变量 s[27](身份证号);

struct node {
    char s[27];
    int ans, next;
    node() { ans = 0; }
}a[maxn];
定义哈希表a[maxn];
定义下标head[maxn];

int get_id(char s[]) //将字符变量变为整型变量函数
{
    将前十六位字符改成数字
    如果第十七位为x,进行加十处理,否则和前十六位一样去处理
}

void add(int id, char s[], int v)//添加函数
{
    定义f=0;
    对哈希表进行遍历
        如果哈希表搜索到对应的会员,将飞行里程与飞行记录累加,并将f置为1
    if f为0
        插入到新的哈希表格中
}

void init()//初始化函数
{
    用memset函数将head全部置为-1;
    初始化cnt=0;
    输入m和n
    for (int i = 0; i < n; ++i) 
    {
        输入身份证号和飞行里程
        if 飞行里程小于最小里程,
           将飞行里程变为最小里程
        get_id 调用函数将身份证的储存方式从字符变量变为整型变量
        add;
    }
}

void print(char s[]) //打印函数
{
    将会员打印出,否则打印出No Info。
}

void solve() //输入并查找输出函数
{
    输入n和s;
    调用打印函数;
}

主函数
{
    初始化函数; 
    输入并查找输出函数;
}

2.2.2 提交列表

2.2.3 本题知识点

(1)哈希表的运用。利用哈希表来存放结点所在下标。
(2)用到指针便于传递数据,建立多条链。
(3)关于字符串的知识点,有利用字符串的有关操作.
(4)运用memset函数将变量初始化指定的值。

2.3 基于词频的文件相似度(1分)

本题设计一个倒排索引表结构实现(参考课件)。单词作为关键字。本题可结合多个stl容器编程实现,如map容器做关键字保存。每个单词对应的文档列表可以结合vector容器、list容器实现。

2.3.1 伪代码(贴代码,本题0分)
伪代码为思路总结,不是简单翻译代码。

主函数
{
	使用map库定义单词索引表Word_table和容器迭代器iterator;
	for (i=1 ;i++;i<sum)
		输入字符串str;
		while str 不等于 "#"
			遍历字符串
			if 该字符是字母
				if 组合单词word长度小于10
					加入组合word组成完整单词;
				else
					if word长度不小于3
						加入map容器单词索引表:Word_table[word][i] = 1;
					清空字符串
		end while
		再次输入新字符串str;
	end for;

	for i = 0 to 查询总数M
		输入文件编号file1,file2; 单词总数sum = 0,相同单词数count = 0;
		使用容器迭代器遍历Word_table中所有单词
		if 两个文件都出现
			sum加一,count加一;
		else if 出现一个文件
			sum加一;
		计算相似度count除以sum并输出;
	end for;
}

2.3.2 提交列表

2.3.3 本题知识
1.迭代器(STL迭代器)iterator
2.map函数的应用

posted @ 2021-06-14 20:46  51456  阅读(252)  评论(0编辑  收藏  举报