第十篇博客

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习查找的相关结构 |
| 姓名 | 卢伟杰 |

0.PTA得分截图

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

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

1.1 查找的性能指标

  • ASL : 即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。


其中n为查找表中元素个数,Pi为查找第i个元素的概率,通常假设每个元素查找概率相同,Pi=1/n,Ci是找到第i个元素的比较次数。

  • ASL分为查找成功情况下的ASL成功和查找不成功情况下的ASL不成功

  • 在顺序查找(Sequence Search)表中,查找方式为从头扫到尾,找到待查找元素即查找成功,若到尾部没有找到,说明查找失败。所以说,
    Ci(第i个元素的比较次数)在于这个元素在查找表中的位置,如第0号元素就需要比较一次,第一号元素比较2次......第n号元素要比较n+1次。所以Ci=i;所以

  • 一个算法的ASL越大,说明时间性能差,反之,时间性能好。

1.2 静态查找

分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。

1.2.1 顺序查找

  • 定义:顺序查找是一种最简单和最基本的查找方法,它从顺序表的一端开始,依次将每个元素的关键字同给定值 K 进行比较,直到相等或比较完毕还未找到。

  • 查找代码

int Seqsch(struct ElemType A[], int n, KeyType K)
{
    int i;
    for (i = 0; i < n; i++)
        if (A[i].key == K)
            break;
    if (i < n)
        return i;
    else
        return -1;
}

  • 顺序查找的平均查找长度为:

  • 顺序查找分析:

缺点是速度慢,平均查找长度为 (n + 1) / 2,时间复杂度为 O(n) .

优点是即适用于顺序表,也适用于单链表,同时对表中元素排列次序无要求,给插入和删除元素带来了方便

1.2.2 二分查找(折半查找)

  • 定义:作为二分查找对象的表必须是顺序存储的有序表,通常假定有序表是按关键字从小到大有序。
    查找过程是首先取整个有序表 A[0] ~ A[n - 1] 的中点元素 A[mid] (mid = (0+n-1)/2)
    的关键字同给定值 K 比较,相等则成功,若 K 较小,则对 剩余的左半部分进行同样操作,
    若 K 较大,则对其剩余的右半部分进行同样的操作。

  • 查找代码(分为递归和非递归两种算法)

int Binsch(struct ElemType A[], int low, int high, KeyType K)//递归法
{//在 A[low] ~ A[hight]区间进行查找,low、hight初值分别为 0 和 n-1
    if (low <= hight)
    {
        int mid = (low + high) / 2; //求中点元素下标
        if (K == A[mid].key)
            return mid;
        else if (K < A[mid].key)
            return Binsch(A, low, mid - 1, K);
        else 
            return Binsch(A, mid + 1, high, K);
    }
    else
        return -1; //查找失败
}
 
int Binsch1(struct ElemType A[], int low, int high, KeyType K)//非递归法
{//在 A[low] ~ A[hight]区间进行查找,low、hight初值分别为 0 和 n-1
    if (low <= hight)
    {
        int mid = (low + high) / 2; //求中点元素下标
        if (K == A[mid].key)
            return mid;
        else if (K < A[mid].key)
            high = mid - 1;
        else 
            low = mid + 1;
    }
    else
        return -1; //查找失败
}

  • 二分查找的平均查找长度为:

  • 二分查找的分析:

    二分查找的优点是比较次数少,速度快,但在查找之前要为建立有序表付出代价,同时对有序表的插入和删除也较为费力。

    二分查找适用于数据相对稳定的情况,而且只适用于顺序存储的有序表,不适用链接存储的有序表。

1.3 二叉搜索树

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

  • 二叉搜索树具有下列性质:

    若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上

    所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。在二叉搜索树的基础上,

    如果进行一次中序遍历,则会正好会把节点的权值从小到大搜一遍。

  • 二叉搜索树的操作

    首先把第一个元素拿出来作为根节点之后插入的每个结点拿出来与根结点做比较

    如果比根节点小,当前指针移到左子树
    如果比根节点大,当前指针移到右子树

    直到指针为空时插入,再创建一个新结点,由当前指针指向它(元素总是作为树的叶子结点插入)
    同理,把接下来的元素依次插入到树中即可

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

  • 二叉搜索树构建代码
BSTNode* CreatBST(KeyType A[], int n)//返回树根指针
{
	BSTNode* bt = NULL;//初始时bt为空树
	int i = 0;
	while (i < n)
	{
		InsertBST(bt, A[i]);//将结点A[i]插入树中
		i++;
	}
	return bt;//返回建立的二叉排序树的根指针
}
  • 二叉搜索树插入代码
void insert_node(Tree *tree, int value)
{
	Node *node=(Node *)malloc(sizeof(Node));
	node->data=value;
	node->left=NULL;
	node->right=NULL; 
	
	if (tree->root==NULL){
		tree->root=node;
	}else{
		Node *tmp=tree->root;
		while (tmp!=NULL){
			if (value<tmp->data){
				if (tmp->left==NULL){
					tmp->left=node;
					return;
				}else{
					tmp=tmp->left;
				}
			}else{
				if (tmp->right==NULL){
					tmp->right=node;
					return;
				}else{
					tmp=tmp->right;
				}
			}
		}
	}
} 

  • 二叉搜索树删除代码

    删除函数是这里面最困难的函数。被删除元素有三种具体情况:叶子结点、度为1的结点和度为2的结点。
    关键在于将度为2的结点通过其左子树的最大元素或右子树的最小元素替换成度为1的结点而完成删除操作。

int DeleteBST(BSTree& bt, KeyType k)
{
	if (bt == NULL)return 0;
	else
	{
		if (k < bt->key)return DeleteBST(bt->lchild, k);
		else if (k > bt->key)return DeleteBST(bt->rchild, k);
		else 
		{
			Delete(bt);
			return 1;
		}
	}
}
//从二叉树排序树中删除结点p 
void Delete(BSTree& p)
{
	BSTNode* q;
	if (p->rchild == NULL)
	{
		q = p;
		p = p->lchild;
		delete q;
	}
	else if (p->lchild == NULL)
	{
		q = p;
		p = p->rchild;
		delete q;
	}
	else Delete1(p, p->lchild);
}
//既有左子树又有右子树的删除 
void Delete1(BSTNode* p, BSTNode*& r)
{
	BSTNode* q;
	if (r->rchild != NULL)
		Delete1(p, r->rchild);
	else 
	{
		p->key = r->key;
		q = r;
		r = r->lchild;
		delete q;
	}
}

1.4 AVL树

1.4.1 AVL树解决什么问题,其特点是什么?

AVL树是二叉搜索树的优化版,又称平衡二叉搜索树,高度平衡树。我们都知道,当一棵二叉搜索树的结点
一直单边插入时,这时候它的查找效率趋近于O(n),非常慢。而AVL树的特点是:“AVL树中任何结点的
两个子树的高度最大差别为1” ,这样就克服了结点单边存储而导致查找效率低下的问题。

1.4.2 AVL树的高度和树的总节点数n的关系?

1.4.3 介绍基于AVL树结构实现的STL容器map的特点、用法。

map是STL的一个关联容器,它提供一对一(第一个称为关键字,第二个称为该关键字的值)的数据处理能力。map内部数据的组织,map内部自建一颗红黑树。

定义:map<typename1, typename2> mp; //map需要定义前映射类型(键key)和映射后类型(值value),所以需要在<>内填写两个类型

作用:解决映射问题

访问://注意:map中的键是唯一的,赋值会被后面的赋值所代替
对于一个定义为map<char,int> mp的map,可以通过mp[‘c’]的方式来访问它对应的整数

当建立映射时,就可以直接使用mp[‘c’]=20这样和普通数组一样的方式`

通过迭代器访问

C++迭代器是一种检查容器内元素并遍历元素的数据类型,迭代器的作用就相当于去除物品的工具的抽象

c++迭代器interator就是一个指向某STL对象的指针,通过该指针可以简单方便地遍历所有元素

map迭代器的定义与其他STL容器迭代器定义的方式相同:map<typename1, typename2>::iterator it;

typename1和typename2就是定义map时填写的类型,这样就得到了迭代器it。

map迭代器的使用方式和其他的STL容器的迭代器不同,因为map每一对迭代器映射都有两个typename,这决定了必须通过一个int来同时访问键和值。

1.5 B-树和B+树

1.5.1 B-树和AVL树区别,其要解决什么问题?

BST和AVL树都是内存中,适用小数据量。每个节点放一个关键字,树的高度比较大。

B-树和B+树一个节点可以放多个关键字,降低树的高度。可放外存,适合大数据量查找,如数据库中数据。

1.5.2 B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况

B树(B-树)

  是一种多路搜索树(并不是二叉的):

 1.定义任意非叶子结点最多只有M个儿子;且M>2;

  2.根结点的儿子数为[2, M];

  3.除根结点以外的非叶子结点的儿子数为[M/2, M];

  4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

  5.非叶子结点的关键字个数=指向儿子的指针个数-1;

  6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

  7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的
     子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

  8.所有叶子结点位于同一层;
  • B(B-)树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;

1.5.3 B+树定义,其要解决问题

B+树是B-树的变体,也是一种多路搜索树:

 1.其定义基本与B-树同,除了:

 2.非叶子结点的子树指针与关键字个数相同;

 3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);

 5.为所有叶子结点增加一个链指针;

 6.所有关键字都在叶子结点出现;
  • B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;

1.6 散列查找。

1.6.1 哈希表的设计主要涉及哪几个内容?

散列 (Hashing) 是一种重要的查找方法。它的基本思想是:以数据对象的关键字 key 为自变量,
通过一个确定的函数关系 h,计算出对应的函数值 h(key) ,把这个值解释为数据对象的存储地址
(可能不同的关键字会映射到同一个散列地址上会有冲突,需要解决),并按此存放,即“存储位置= h(key)”。

  • 解决冲突的方法
通过哈希函数去计算哈希值,难免会有冲突的时候,解决冲突的方法有如下几种:

    开放定址法: 依靠数组中的空位解决碰撞冲突
        线性探测法:直接检测散列表的下一个位置(即索引值加1),如果仍冲突,继续。
        二次探测法:即H + 1 2, H + 22, H + 32.。。
        伪随机探测
    再哈希法:使用多个哈希函数,第一个冲突时,使用第二个哈希函数,直到不冲突为止
    链地址法:将所有哈希地址相同的关键字,都链接在同一个链表中。

散列表(Hash Table)也称为哈希表。
散列查找的两项基本工作:
计算位置:构造散列函数确定关键字存储位置
解决冲突:应用某种策略解决多个关键词位置

1.6.2 结合数据介绍哈希表的构造及ASL成功、不成功的计算

将关键字序列(7、8、30、11、18、9、14)散列存储到散列表中。散列表的存储空间是一个下标从0开始的一维数组。
散列函数为: H(key) = (keyx3) MOD 7,处理冲突采用线性探测再散列法,要求装填(载)因子为0.7。
(1) 分别计算等概率情况下查找成功和查找不成功的平均查找长度。

1.查找长度:

1.1 查找成功的平均查找长度

查找数字A的长度 = 需要和散列表中的数比较的次数;
得到如下数据:
0 1 2 3 4 5 6 7 8 9
7 14 8 11 30 18 9
1 2 1 1 1 3 3
所以总的查找成功的平均查找长度= (1+1+1+1+3+3+2)/7 = 12/7

1.2查找不成功的平均查找长度

得到如下数据:
0 1 2 3 4 5 6 7 8 9
7 14 8 11 30 18 9
3 2 1 2 1 5 4
则查找不成功的平均查找长度 = (3+2+1+2+1+5+4)/7 = 18/7

1.6.3 结合数据介绍哈希链的构造及ASL成功、不成功的计算

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

介绍3题PTA题目

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

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

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

BinTree Insert(BinTree T, int x)//建树函数

    if T为空
        开辟结点
        T->Data = x;
        T->Left = T->Right 为 空
    else 
        if x >根结点值 
            插入到左子树
        else if x < 根结点值 
            插入到右子树

void Output(BinTree T)//输出函数

    while (!q.empty())
        p = q.front();
        q.pop();
        if (flag == 0)//控制空格
        {
            cout << p->Data;
            flag = 1;
        }
        else cout << ' ' << p->Data;
        if 左子树不空 输出左子树
        if 右子树不空 输出右子树

bool judge(BinTree T)//判断函数
  if(根节点不空)  入队根节点;
  初始化状态status = true;//如果status==false,则后续所有结点都只能是叶子结点
  while(队列不为空)
  出队队头结点t;
  if(t的左孩子不为空)
  if(status == false)     
        else  标记status = false;
            if(t的右孩子不为空)
            if(status == false)  
            t的右孩子入队;
        else  标记status = false;
            end while
            return true;

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

2.1.2 本题知识点

  • 二叉树的层次遍历

  • 判断是否完全二叉树的方法

  • 生成二叉搜索树及二叉搜索树结点的插入

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

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

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

for (int i = 0; i < 个数; i++)do
	输入身份证  里程数
	if (里程数 < 最小里程数)
		更改里程数
		end if
		调用Insert向H插入结点数;
		end for
		for(int i = 0; i < 查找次数; i++) do
		调用查找函数造H中查找数据
		找到数据赋值给ptr
		若 ptr == NULL do
		else
		查找成功
		end if
		end for

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

2.2.2 本题知识点

  • 哈希链的创建和数据插入,数据查找

  • 很大数字的数据处理和哈希函数的设计,哈希地址的计算

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

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

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

定义变量类型为<string,int[]>的map容器构建单词索引表
根据单词在单词索引表中构建映射
for i=0 to 文件组数
    输入文件编号
    for iterator=map.begin() to !map.end()遍历单词
        if 单词在两个文件都出现过
            修正重复单词数,合计单词数
        else if 单词在其中一个文件中出现过
            修正合计单词数

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

2.3.2 本题知识点

  • map库的使用
  • 容器迭代器的使用
  • string库函数的使用
posted @ 2021-06-14 21:16  昨日云流  阅读(69)  评论(0编辑  收藏  举报