这个作业属于哪个班级 | C语言–网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业05–查找 |
这个作业的目标 | 学习查找的相关结构 |
姓名 | 骆锟宏 |
0.PTA得分截图
插入的做法:
叶子结点直接删除;
被删结点左子树为空,右子树不为空,那就直接把右子树接上来;
被删结点右子树为空,左子树不为空,那就直接把左子树接上来;
被删结点左右子树都不为空去左子树找最左的结点,或者去右子树找最右的结点来充当新的结点。
BinTree Insert(BinTree BST, ElementType X)
{
//当发现当前结点是空结点时,便是插入的时刻了
if (BST == NULL)
{
BST = (BinTree)malloc(sizeof(BinTree));
BST->Data = X;
BST->Left = BST->Right = NULL;
return BST;
}
else if (BST->Data == X)
{
return BST;
}
else if(BST->Data > X)
{
//别忘了插入后的结点该返回给谁
BST->Left = Insert(BST->Left, X);
}
else//BST->Data < X
{
BST->Right = Insert(BST->Right, X);
}
//最后建完树应该把建完的整棵树返回回去。
return BST;
}
BinTree Delete(BinTree BST, ElementType X)
{
if (BST == NULL)//树空没得删 / 找不到的递归出口
{
printf("Not Found\n");
return BST;
}
if (X < BST->Data)//递归查找左子树
{
BST->Left = Delete(BST->Left, X);
}
else if (X > BST->Data)//递归查找右子树
{
BST->Right = Delete(BST->Right, X);
}
else//找到了
{
/*
BinTree Keep;
//左右子树都为空的叶子结点的情况,处理方法同任一子树为空的情况。
if (BST->Left == NULL)//左子树为空
{
Keep = BST;
BST = BST->Right;
free(Keep);
}
else if (BST->Right == NULL)//右子树为空
{
Keep = BST;
BST = BST->Left;
free(Keep);
}
else//当前要删的结点左右子树都存在。
{
BinTree LeftMax;
LeftMax = FindMax(BST->Left);//查找左子树最右的结点。
BST->Data = LeftMax->Data;
//下面这一句代码很有水平。
BST->Left = Delete(BST->Left, LeftMax->Data);
//找的是左子树的最大结点,那删的也得是左子树上的那个最大结点,
//删完之后的那棵左子树还得返回回来到它在我当下根结点所在的左子树的位置上。
}
*/
//中间这部分代码还可以优化,如下:
BinTree Keep;
if (BST->Left != NULL && BST->Right != NULL)//当前要删的结点左右子树都存在。
{
BinTree LeftMax;
LeftMax = FindMax(BST->Left);//查找左子树最右的结点。
BST->Data = LeftMax->Data;//替代关键字
BST->Left = Delete(BST->Left, LeftMax->Data);//在左子树上删除最大结点。
}
else
{
Keep = BST;
if (BST->Left == NULL)//左子树为空
{
BST = BST->Right;
}
else if (BST->Right == NULL)//右子树为空
{
BST = BST->Left;
}
free(Keep);
}
}
return BST;
}
更深入的map容器的学习,可以考虑研究一下这篇博客:
详细的map讲解
感谢作者codertcm
B-树和AVL树区别,其要解决什么问题?
B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况。
关于B-树和B+树的问题,想要详细了解的话,还可以参考这篇博客:
B树和B+树
感谢作者nullzx
哈希表的设计
typedef int KeyType; //关键字类型
typedef char * InfoType; //其他数据类型
typedef struct node
{
KeyType key; //关键字域
InfoType data; //其他数据域
int count; //探查次数域
} HashTable[MaxSize]; //哈希表类型
特点:1.存在关键字域,2.存在探查次数,3.需要预先确定哈希表的长度。
void InsertHT(HashTable ha, int& n,int m, KeyType k, int p)
//哈希表插入数据,n表示哈希表数据个数,k为插入关键字,p为除数,m为哈希表长度
{
int count;
int adress;
//求关键字对应的哈希地址;
adress = k % p;
//若对应位置已经被删除或者为空则直接插入即可
if (ha[adress].key == NULLKEY || ha[adress].key == DELKEY)
{
ha[adress].key = k;
ha[adress].count = 1;
}
else
//否则应查找直到找到可以插入的位置为止;
{
count = 1;
//线性探测
do
{
adress = (adress + 1) % m;
count++;//每探测一次的话,探测次数要增加;
} while (ha[adress].key != NULLKEY && ha[adress].key != DELKEY);
ha[adress].key = k;
ha[adress].count = count;
}
//别忘了,新插入一个的话,列表的总数要加1
n++;
}
特点:1.先算哈希地址,这里采用除留余数法;
2.对于空结点或者被删结点可以直接插入;
3.遇到哈希冲突则使用线性探测法,并累计探测次数;
4.插入完后,对于表示哈希表长度的变量要加一
void CreateHT(HashTable ha, KeyType x[], int n, int m, int p) //创建哈希表,x为输入数组,n输入数据个数,m为哈希表长度,这里假设m=p
{
int i;
//初始化哈希表,赋空值
for (i = 0; i < m; i++)
{
ha[i].key = NULLKEY;
ha[i].count = 0;
}
//初始化哈希表元素个数,得在插入建表之前。
int keepNum = n;//后面插入哈希表的时候要用到,所以要保存下来。
n = 0;
//采用插入的方法一个一个把数据插入到哈希表中
for (i = 0; i < keepNum; i++)
{
InsertHT(ha, n, x[i], p);
}
}
特点:1.需要初始化键值为NULLKEY和探测次数为0;2.建表前应归零表长;3.插入N个数据调用N次插入函数来建表。
int SearchHT(HashTable ha, int p, KeyType k) //在哈希表中查找关键字k,找不到返回-1,找到返回查找地址。
{
int count = 0;
int m = p;//m为哈希表的长度
int adress = k % p;
//查找的过程
while (ha[adress].key != NULLKEY && ha[adress].key != k && count <= m)
{
count++;
adress = (adress + 1) % m;
}
if (ha[adress].key == k)
{
//找到了;
return adress;
}
else
{
uns_count = count + 1;//这里要加1是因为最后查找不成功的那一次也要加上去!
return -1;
}
}
特点:1.当查找次数不超过表长,查找到的数据不为空,键值不等时,继续往下找;2.找到NULLKEY跳出。
成功的ASL的分子来源于每个不同的数据的探测次数的累加,在代码上体现为count值的累加。分母来自于哈希表内载入的数据的个数。
不成功的ASL的分母来自于除留余数法当中p值的确定,而分子来源于,每一个可能的地址值探索到确认为查找不成功时的查找次数,要注意的是,对哈希表而言,查找不成功,这次也算有在查找的行列中,所以查找次数也要增加上去。
首指针的地址是连续的,所以要相互连接起来。
每个结点结构体有两个区域,一个是指针域,一个是数字域这一点也要明确出来。
首指针的左侧要标清楚对应的地址的下标,且下标从0开始。
对于成功的ASL,分母依旧来自于存在的结点的个数,分子取决于,探测到对饮的数据的次数。
对于不成功的ASL,分母是除留余数法中p的值,分子取决于探测到空结点前,和存在的结点的比较的次数,且和空结点的比较,并不计入比较的次数当中。
7-1 是否完全二叉搜索树
7-5(哈希链) 航空公司VIP客户查询
建二叉树
while(numb--)//numb为插入数据的个数
{
cin >> key;
调用Insert函数
}
Insert 函数
{
树空,T->data = key
树不空,
T->data > key ,递归插入左子树
T->data < key ,递归插入右子树
}
层次遍历
队列1,存key值。
队列2,存树结点。
按树的层次遍历来。
不同的是,
1. 子树无论空不空都入队。
2. 当结点为空结点时,入队的key值为#
当有#值入队时,标记get_block(遍历到了空结点)为真,如果后续再次出现数字,
则标记isCBBT(CBBT为完全平衡二叉树)为false。
3.输出队列1为层次遍历序列,其中#不输出。
4.依照isCBBT来判断是否为完全二叉树。
本题的完成是在刚开始课程的时候,有一堂上机课,课上老师讲完具体的思路之后做的,当时一次性过了,背后是多次调试的结果。而出于各种原因,写博客时的时间离写这道题目的时间已经相距很远了,所以具体的内容其实已经记得不太清楚了。所以这里不作具体的阐述,把更多的内容放在第三部分去仔细分析。
建哈希表———>>纳入题目数据
初始化哈希链每个表头的next指针为NULL
for(执行N次)
{
输入id 和 mileage(里程数)
对里程数进行最小量修正
调用insert函数来建表
}
insert函数
{
计算哈希地址
查找是否已存在用户
if(exist)
{
里程直接累加
}
else
{
建立新结点
头插法,插入对应哈希链中
}
哈希函数的构造:
将身份证字符串转成具体数字,x当作10来处理
用除留余数法,p取100000 + 7(质数/奇数)
具体代码如下:
#include
#include
#include
不过不排除存在测试数据的偶然性,只是看到这个现象,于是记录下来。
1.数据结构的选择。
* 用map的数组来存放文件,每一个数据代表一个文件,每个文件内可以存多个词组,
string用来存单词,bool用来标记存在一个单词,方便后续单词数量的统计。
* same[101][101] 二维数组是相似矩阵,两个下标是所对应的文件的编号,
存的值是两个文件共有的单词数量。
* num[101]用来存放对应下标的文件所含有的词组的个数。
2.数据的输入:
1.使用 cin.get()一个一个读取字符,直到遇到终止的字符'#'。
2.使用 toupper()函数将读入的小写字母转化为大写字母。
3.将输入的连续字母存入s_word数组(要求不超过10个)
4.中途遇到非字母字符或者超过10个的时候,都直接封尾,并存入map,
初始化s_word的下标继续读取数据。
3.数据的处理:
1.使用了C++中的迭代器,迭代统计每个map文件中的单词数量,并将每两个文件进行
一一的元素对比,如果数据相同的话,就在相似矩阵的对应位置的值加1.
2.对角线的元素本质上等于对应文件的单词数,可以对map求size()得到。
4.根据相似矩阵和长度表来计算相识度并输出。
用fixed函数和setprecision(1)来控制计算出来的数值的小数位数,并转化成文本形式。
#include
#include
iomainip
的解释:这是c++中用来对所输入的内容进行格式上的处理的一个库,其中带有许多可用的方法。
toupper
函数可以将小写字母转化成大写字母。详细可以参考该博客:iomainip
感谢作者
* 而本题中之所以会使用该库的原因在于,题中提到不对文件信息的大小写进行讨论,所以与其等接受具体的数据后再对数据进行各种条件判断的处理,不如直接在输入的时候就对格式进行统一的规划,这样在后续对数据进行比较的时候,会方便很多这也是本题使用该库的库文件的目的。
cstring
的解释:其实cstring
本质上就是C中的string.h
只不过是过渡到C++中改了个名字而已。而本题之所以需要使用这个函数的原因在于,本题只讨论前10个字符,且需要对每个字符进行一一比对从而达到检查的目的,所以需要字符数组的很多操作函数的支持。
cin.get()
相当于getchar()
map
容器的操作和使用。map::iterator it
定义了一个名称为it
的迭代器,迭代器可以用于对容器内数据的遍历,通过begin()
和end()
来进行控制。因为对于容器我们不知道它的具体长度的话,就无法定量遍历,用迭代器的话,就能够很好地解决这个问题。fixed
的用法:将数字按指定的小数位数进行取整,利用句号和逗号以十进制格式对该数进行格式设置,并以文本形式返回结果。setprecision
的作用:用来控制显示浮点数值的有效数的数量,这里的1,表示保留1位小数。