也称哈希映射(hash map)或者散列表,是根据关键码值(key value)而直接进行访问的数据结构。它通过计算一个散列函数,将关键码值映射到表中一个位置,以实现直接访问存储在该位置的数据,大大提高查找的效率。
顺序二叉树(Binary Search Tree,BST)是一种特殊的二叉树数据结构。它具有以下特征:
顺序二叉树是一个二叉树,每个节点最多有两个子节点(左子节点和右子节点)。
若任意节点的左子节点不为空,则左子节点的值必须小于该节点的值;
若任意节点的右子节点不为空,则右子节点的值必须大于该节点的值;
任意节点的左、右子树也必须是顺序二叉树。
也就是说,顺序二叉树的任意节点的值都大于其左子树上所有节点的值,小于其右子树上所有节点的值。查找过程就相当于二分查找
是一种线性存储结构
对于普通用户,如果用户过多我们可以使用哈希表+顺序二叉树去查找 存储结构如下
1.对于初始化
? ? ? ? *要考虑初始化的时候要不要顺便读取文件内容然后打印
2.增删改查
? ? ? ? *对于增加用户,因为我们使用的是hash表,所以需要主键,就要考虑主键重复问题(我使用名字? ? ? ? ? ? ?当主键)
注意:我们在输入字符或者字符串时候呢肯定会出现多输入字符比如空格或者回车或者多输入了需对字符,所以我们要是使用getchar的话只能解决前两种问题,无法解决第三中问题,所以我提供一种思路,我们可以声明一个 字符串来当做缓冲区,把所有多余的字符读取进去,然后最后使用 memset?释放掉
eg
//充当缓冲区
char buffer[1000];
printf("请输入你想删除的联系人姓名:\n");
scanf("%10s", name);
gets_s(buffer);
memset(buffer, 0, sizeof(buffer));
// 0代表从第一个位置开始删除,sizeof(buffer)代表删除到最后
这样呢就可以解决缓冲区的问题?
? ? ? ? *删除用户的话,因为我们有特别关心用户,所以呢,要保证删除掉普通用户的同时会删除特别关心用户,删除特别关心用户并不会影响普通用户 还有就是顺序二叉树的删除
? ? ? ? *修改用户就要保证在修改普通用户的同时又修改为特别关心用户的
? ? ? ? *查找就比较简单,但是查找还有在排重中使用,只需要注意没有用户时的查找处理.
3.打印信息
? ? ? ? 1.在终端对用户打印
? ? ? ? ? ? ? ? 注意二叉树的顺序打印,还有美化一下打印界面,比如带上索引
? ? ? ? 2.向文件中打印存储的信息,要注意打印格式方便读取,比如每种信息使用'#'隔开
? ? ? ? ? ? ? ? 注意小细节,因为姓名是关键字,所以我们存储特别关心用户时,我们可以只存储名字,打印时候只需要调用查找函数即可调用其他信息
?4.读取信息
? ? ? ? 根据存储的格式去读取
5.实现注意
? ? ? ? 头文件的使用,了解头文件的概念
? ? ? ? 特别要注意删除时候,对于地址的传参,会造成指针异常
一定要在一个工程文件里面!!!!!
#ifndef AddressBookTools
#define AddressBookTools
#define MaxNum 27 //一共26个首字母
const int INFOEMATIONNUMS = 3;
const int MaxPhone = 20;
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 使用二叉排序树 + 哈希表创建通讯录*/
typedef char keyName[11];//名字
typedef char Number[12];//电话号码
typedef char Sex;//性别
/*人物信息*/
typedef struct InforNode{
keyName name;//名字
Number num[MaxPhone];//电话号
Sex sex;//性别
/*......等等多个信息*/
}Information;
/*顺序二叉树的存储结构*/
typedef struct BSTNode {
Information information;
struct BSTNode* lchild, * rchild;//左右孩子
}BSTNode,*BSTree;
/*哈希表结构*/
typedef struct HashNode {
BSTree T;
}*HashTable,HashNode;
/*存储特别关心*/
typedef struct Node {
Information information;
struct Node* next;
}Node,*List;
//初始化功能
//=====================================================
//创建新的电话簿
void CreatHT(HashTable &H);//创建一个Hash表并复制NULL
//创建特别关心列表
void CreatL(List& L);
//=====================================================
//辅助函数
//=====================================================
//哈希函数
int Hash(keyName name);
//计算名字字符大小之和
int NameSum(keyName name);
//判断两个关键字是否相同 相同返回 1 不同返回 0
int equals(keyName name1, keyName name2);
//=====================================================
//插入信息功能 插入特别关心人
//=====================================================
//插入普通用户
Information InputInformation();//输入信息并返回输入后的节点
void InsertHT(HashTable &H);//在表里插入信息
//哈希表中的排序二叉树
void InsertBST(BSTree& T,Information E);
//插入特别关心人
void InsertLike(List& L, HashTable& H, keyName name);
//=====================================================
//打印信息
//=====================================================
//在终端打印普通用户信息
void PrintHT(HashTable H,List L);
void PrintTree(BSTree T);
//在终端打印特别关心用户信息
void PrintLike(List L);
//在文件打印信息
void PrintfLike(List L, FILE* fLike);
void PrintFile(HashTable H,FILE* f);
void PrintTreeF(BSTree T,FILE* f);
//=====================================================
//从文件中读取信息
//=====================================================
void ReadFile(FILE* f, FILE* fLike, HashTable& H, List& L);
//=====================================================
//删除某顶点
//=====================================================
//定位在哈希表的某个位置
void DeleteHT(HashTable& H, keyName name);
//删除二叉树的节点
void DeleteBST(BSTree& T, keyName name);
//删除链表节点
void DeleteList(List& L, keyName name);
//=====================================================
//查找联系人 等修改联系人信息
//=====================================================
//查找指定联系人的节点
BSTree Search(HashTable H, keyName name);
//查找链表中指定联系人
List SearchL(List L, keyName naem);
//修改指定联系人节点的信息
void Modify(HashTable& H,List &L, keyName name);
//=====================================================
//打印用户界面
int OperateMenu();
#endif // !AddressBookTools
#define _CRT_SECURE_NO_WARNINGS 1
#include "AddressBookTools.h"
/*创建功能*/
void CreatHT(HashTable &H) { //创建初始化哈希表
H = (HashNode*)malloc(sizeof(HashNode) * MaxNum);
if (H == NULL) exit(-1);//申请失败退出程序
for (int i = 0; i < MaxNum; ++i) {
H[i].T = NULL;
}
}
void CreatL(List& L) {
L = (Node*)malloc(sizeof(Node));
if (L == NULL) {
printf("申请内存失败!\n");
exit(-1);
}
L->next = NULL;
L->information = {};
}
int Hash(keyName name) {
if(name[0]>='a'&&name[0]<='z')
return (name[0]-97) % (MaxNum-1);
if (name[0] >='A' && name[0] <= 'Z')
return (name[0] - 65) % (MaxNum-1);
return 26;
}
/*关键字的每一个Ascll码之和作为权值*/
int NameSum(keyName name) {
int sum=0;
for(int i=0;name[i]!='\0';++i){
sum += name[i];
}
return sum;
}
int equals(keyName name1, keyName name2) {
int count = 0;
if (sizeof(name1) != sizeof(name2))return 0;
while (name1[count] != '\0' || name2[count] != '\0') {
if (name1[count] != name2[count])return 0;
count++;
}
return 1;
}
/*插入功能*/
//插入普通用户
Information InputInformation() {
//要对E进行初始化,要不然会产生地址访问冲突
Information E = { {},{}, ' '
};
for (int i = 0; i < MaxPhone; ++i) {
strcpy(E.num[i], " ");
}
//充当缓冲区
char buffer[1000];
printf("请输入联系人姓名\n");
scanf("%10s", E.name);
gets_s(buffer);
printf("你要输入电话和姓名吗? -1 要,-0不要\n");
int choice = 0;
scanf("%d", &choice);
gets_s(buffer);
int nums = 0;
switch (choice)
{
input :case 1:
printf("你想输入多少个号码(最多十个)?\n");
scanf("%d", &nums);
if (nums > MaxPhone) {
printf("数量过多!\n");
goto input;
}
gets_s(buffer);
for (int i = 0; i < nums; ++i) {
printf("请输入联系人电话号(11个数字)\n");
scanf("%11s", E.num[i]);
gets_s(buffer);
}
printf("请输入联系人的性别(一个字母 M:男性,W:女性)\n");
scanf("%c", &E.sex);
gets_s(buffer);
default:
break;
}
//清理缓存区
memset(buffer, 0, sizeof(buffer));
return E;
}
void InsertHT(HashTable& H) {
Information E = InputInformation();
BSTree p = Search(H, E.name);
if (p != NULL) {
printf("名字与电话簿已存在!\n插入失败\n");
return;
}
//调用哈希函数 获取存储的位置
int index = Hash(E.name);
//存入对应位置
//如果该位置为空则T指向新节点
InsertBST(H[index].T,E);
printf("插入成功!\n");
}
void InsertBST(BSTree& T,Information E) {
if (T == NULL) {
BSTree node = (BSTNode*)malloc(sizeof(BSTNode));
if (node == NULL)exit(0);
node->information = E;
node->lchild = NULL;
node->rchild = NULL;
T = node;
}
else {
//把名字的字母和作为关键字计算权值大小
int weightNew = NameSum(E.name);
int weightOld = NameSum(T->information.name);
if (weightNew <= weightOld) {
InsertBST(T->lchild, E);
}
else if(weightNew>=weightOld){
InsertBST(T->rchild, E);
}
}
}
//插入特别关心用户
void InsertLike(List& L,HashTable &H,keyName name) {
BSTree T = Search(H,name);
if (T == NULL) {
printf("没有该用户!\n");
return;
}
if (SearchL(L, name) != NULL) {
printf("该用户已存在!\n");
return;
}
List nexttemp = (Node*)malloc(sizeof(Node));
if (nexttemp == NULL) {
printf("申请内存失败!\n");
exit(-1);
}
List p = L;
//遍历到最后
while (p->next != NULL) {
p = p->next;
}
nexttemp->information = T->information;
p->next = nexttemp;
nexttemp->next = NULL;
}
/*打印功能*/
void PrintTree(BSTree T) {
if (T == NULL) return;
//中序遍历 从小到大遍历
PrintTree(T->lchild);
printf("||姓名:%s\t||",T->information.name);
printf("电话:||");
for (int i = 0; i<MaxPhone; ++i) {
if(strcmp(T->information.num[i], " ") != 0)
printf("%s\t||", T->information.num[i]);
}
printf("性别:%c||\n", T->information.sex);
PrintTree(T->rchild);
}
void PrintHT(HashTable H,List L) {
printf("**************************************************************************************************************************************************\n");
printf("特别关心\n");
PrintLike(L);
printf("**************************************************************************************************************************************************\n");
for (int i = 0; i < MaxNum-1; ++i) {
if (H[i].T != NULL) {
printf("==================================================================================================================================================\n");
printf("||索引:%c||\n",'a'+i);
printf("==================================================================================================================================================\n");
PrintTree(H[i].T);
}
}
if (H[MaxNum - 1].T != NULL) {
printf("===============================================================================================================================================\n");
printf("||索引:%c||\n", '#');
printf("===============================================================================================================================================\n");
PrintTree(H[MaxNum-1].T);
}
printf("==================================================================================================================================================\n");
}
void PrintLike(List L) {
List p = L->next;
while (p != NULL) {
printf("||姓名:%s\t||", p->information.name);
printf("电话:||");
for (int i = 0; i < MaxPhone; ++i) {
if (strcmp(p->information.num[i], " ") != 0)
printf("%s\t||", p->information.num[i]);
}
printf("性别:%c||\n", p->information.sex);
p = p->next;
}
}
void PrintTreeF(BSTree T,FILE* f) {
if (T == NULL) return;
//中序遍历 从小到大遍历
PrintTreeF(T->lchild,f);
fprintf(f,"%s#", T->information.name);
for (int i = 0; i<MaxPhone; ++i) {
fprintf(f, "%s#", T->information.num[i]);
}
fprintf(f,"%c\n", T->information.sex);
PrintTreeF(T->rchild,f);
}
void PrintFile(HashTable H,FILE* f) {
f = fopen("AddressBook.txt", "w");
for (int i = 0; i < MaxNum; ++i) {
if (H[i].T != NULL) {
PrintTreeF(H[i].T,f);
}
}
fclose(f);
}
void PrintfLike(List L, FILE* fLike) {
fLike = fopen("like.txt", "w");
List p = L->next;
while (p != NULL) {
fprintf(fLike,"%s\n", p->information.name);
p = p->next;
}
fclose(fLike);
}
/*读取存储的用户文件功能*/
void ReadFile(FILE* f, FILE* fLike, HashTable& H, List& L) {
f = fopen("AddressBook.txt", "r");
fLike = fopen("like.txt", "r");
if (f == NULL) {
printf("不存在该文件!\n");
return;
}
if (fLike == NULL) {
printf("不存在该文件!\n");
return;
}
char str[1000];
char* strMode = NULL;
int countStr = 0;
int countInformation = 0;
while (fgets(str, 1000, f)) {
Information* e = (Information*)malloc(sizeof(Information));
/*strtok 切割函数*/
strMode = strtok(str, "#");
int index = Hash(strMode);
strcpy(e->name, strMode);
for (int i = 0; i < MaxPhone; ++i) {
strMode = strtok(NULL, "#");
strcpy(e->num[i], strMode);
}
strMode = strtok(NULL, "#");
e->sex = strMode[0];
InsertBST(H[index].T, *e);
}
while (fgets(str, 1000, fLike)) {
int index = Hash(str);
strMode = strtok(str, "\n");
InsertLike(L, H, str);
}
fclose(f);
fclose(fLike);
}
/*删除功能*/
void DeleteHT(HashTable& H, keyName name) {
int index = Hash(name);
if (!H[index].T) {
printf("没有该联系人\n");
return;
}
DeleteBST(H[index].T, name);
}
void DeleteBST(BSTree& T, keyName name) {
//从二叉树排序树T中删除为name的节点
BSTree p = T;
BSTree f = NULL;//初始化
/*-----------使用while循环从跟找关键字-----------*/
while (p) {
if (equals(p->information.name, name)) break;
f=p;//f为p的双亲节点
if (NameSum(p->information.name) >= NameSum(name)) {
p = p->lchild;
}
else {
p = p->rchild;
}
}
if (p == NULL) { //没有找到该元素
printf("没有该元素\n");
return;
}
/*---- 考虑3种情况实现p所指子树内部的处理: p 的左右子树均不为空,无右子树,无左子树*/
BSTree q = p;
if ((p->lchild) && (p->rchild)) {
BSTree s = p->lchild;
while (s->rchild) { //向右到尽头
q = s;
s = s->rchild;
}
p->information = s->information;
if (q != p) q->rchild = s->lchild; //重接右子树
else q->lchild = s->lchild; //重接左子树
free(s);
return;
}
else if (!p->rchild) {
p = p->lchild;
}
else if (!p->lchild) {
p = p->rchild;
}
/*-----------将p所指的子树挂接到其双亲节点 f的相应位置---------------*/
if (!f) T = p; //被删除节点为根节点
else if (q == f->lchild) f->lchild = p; //挂接到左子树
else f->rchild = p; //挂接到右子树
free(q);
printf("删除成功!\n");
}
void DeleteList(List& L, keyName name) {
List prev = NULL;
List p = L;
//处理头节点的情况
if (p && equals(p->information.name, name)) {
L = p->next;
free(p);
printf("删除成功!\n");
return;
}
//循环找节点
while (p != NULL) {
if (equals(p->information.name, name)) {
if (prev == NULL) { //头节点
L = p->next;
}
else {
prev->next = p->next;
}
free(p);
printf("删除成功!\n");
return;
}
prev = p;
p = p->next;
}
printf("无该元素!\n");
return;
}
/*查找,修改功能*/
BSTree Search(HashTable H, keyName name) {
int index = Hash(name);
if (!H[index].T) {
return NULL;
}
BSTree p = H[index].T;
/*-----------使用while循环从跟找关键字-----------*/
while (p) {
if (equals(p->information.name, name)) {
return p;
}
if (NameSum(p->information.name) >= NameSum(name)) {
p = p->lchild;
}
else {
p = p->rchild;
}
}
if (p == NULL) { //没有找到该元素
return NULL;
}
}
List SearchL(List L, keyName name) {
List p = L->next;
while (p) {
if (equals(p->information.name, name)) {
return p;
}
p = p->next;
}
return NULL;
}
void Modify(HashTable& H, List& L, keyName name) {
int index = Hash(name);
if (!H[index].T) {
printf("无此人信息\n");
return ;
}
BSTree p = Search(H, name);
if(p==NULL) {
printf("无此人信息\n");
return;
}
/*-----------修改电话号,性别,姓名---------*/
/*姓名牵扯关键字,所以我们需要重新进行排序插入*/
int choice = 0;
int choice2 = 0;
printf("-------------修改选项----------------\n");
printf("-------------1.姓名------------------\n");
printf("-------------2.电话号----------------\n");
printf("-------------3.性别------------------\n");
printf("-------------4.退出------------------\n");
scanf("%d", &choice);
getchar();
char buffer[1000];
char newname[11];
char newnum[12];
char newsex = '0';
int index2 = 0;
int count = 0;
switch (choice)
{
//修改姓名
case 1:
printf("请输入你想修改的新姓名:\n");
scanf("%10s", newname);
gets_s(buffer);
Information newnode;
strcpy(newnode.name, newname);
for (int i = 0;i<MaxPhone; ++i) {
strcpy(newnode.num[i], p->information.num[i]);
}
newnode.sex = p->information.sex;
//删除旧的 插入新的
index = Hash(newname);
DeleteHT(H, name);
InsertBST(H[index].T, newnode);
//更新链表状态
DeleteList(L, name);
InsertLike(L,H,newname);
printf("修改成功!\n");
break;
//电话号修改
case 2:
printf("你想修改还是删除? -0 修改 -1删除 -2添加\n");
scanf("%d", &choice2);
gets_s(buffer);
switch (choice2)
{
case 0:
printf("你想修改第几个电话?\n");
scanf("%d", &index2);
gets_s(buffer);
if (strcmp(p->information.num[index2-1], " ") == 0) {
printf("改电话不存在\n");
break;
}
printf("请输入联系人电话号(11个数字)\n");
scanf("%11s", newnum);
strcpy(p->information.num[index2 - 1], newnum);
gets_s(buffer);
//更新特别关心列表
DeleteList(L, name);
InsertLike(L, H, name);
break;
case 1:
printf("请输入你想删除的第几个电话?\n");
scanf("%d", &index2);
gets_s(buffer);
if (strcmp(p->information.num[index2-1], " ") == 0) {
printf("改电话不存在\n");
break;
}
for (int i = index2; i < MaxPhone; ++i) {
strcpy(p->information.num[i- 1], p->information.num[i]);
}
printf("删除成功!\n");
//更新特别关心列表
DeleteList(L, name);
InsertLike(L, H, name);
break;
case 2:
printf("输入你想添加的电话号\n");
printf("请输入联系人电话号(11个数字)\n");
scanf("%11s", newnum);
gets_s(buffer);
count = 0;
for (int i = 0; i < MaxPhone; ++i) {
count++;
if (strcmp(p->information.num[i], " ") == 0) {
strcpy(p->information.num[i], newnum);
break;
}
}
if (count == MaxNum - 1) {
printf("电话存储空间已经满了\n");
break;
}
printf("添加成功!\n");
//更新特别关心列表
DeleteList(L, name);
InsertLike(L, H, name);
break;
default:
return;
}
break;
//性别
case 3:
printf("请输入联系人的性别(一个字母 M:男性,W:女性)\n");
scanf("%c", &newsex);
p->information.sex = newsex;
gets_s(buffer);
//更新特别关心列表
DeleteList(L, name);
InsertLike(L, H, name);
break;
default:
break;
}
//清理缓存区
memset(buffer, 0, sizeof(buffer));
}
/*用户界面*/
int OperateMenu() {
int choice;
printf("===========================================================\n");
printf("||<<<<<<<<<<<<<<<<<<<<<1.创建哈希表>>>>>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<2.插入联系人>>>>>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<3.打印>>>>>>>>>>>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<4.删除指定联系人信息>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<5.查找指定联系人>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<6.修改指定联系人信息>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<7.信息存入文件中>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<8.添加特别关心>>>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<9.删除特别关心>>>>>>>>>>>>>>>>>>>>||\n");
printf("||<<<<<<<<<<<<<<<<<<<<<输入其他.退出>>>>>>>>>>>>>>>>>>>>>||\n");
printf("===========================================================\n");
scanf("%d", &choice);
getchar();//吞掉回车
return choice;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "AddressBookTools.h"
int main() {
HashTable H=NULL;
List L = NULL;
CreatHT(H);
//接受的选项
int choice = 0;
//修改姓名
char name[11] = { 0 };
//树节点
BSTree p = NULL;
int nums = 0;
//存储用户文件指针
FILE* f=NULL;
//存储特别关心用户指针
FILE* fLike = NULL;
//充当缓冲区
char buffer[1000];
while (true)
{
choice = OperateMenu();
switch (choice)
{
case 1:
CreatHT(H);
CreatL(L);
//读取文件
ReadFile(f,fLike, H,L);
printf("创建成功!\n");
PrintHT(H,L);
break;
//--------------------------------------------
case 2:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
nums = 0;
printf("你想插入几个联系人:\n");
scanf("%d", &nums);
getchar();
for (int i = 0; i < nums; ++i)
InsertHT(H);
PrintHT(H,L);
PrintFile(H, f);
break;
//--------------------------------------------
case 3:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
PrintHT(H,L);
break;
//--------------------------------------------
case 4:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
printf("请输入你想删除的联系人姓名:\n");
scanf("%10s", name);
gets_s(buffer);
DeleteHT(H, name);
DeleteList(L, name);
PrintfLike(L, fLike);
PrintFile(H, f);
break;
//--------------------------------------------
case 5:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
printf("请输入你想查找的联系人姓名:\n");
scanf("%10s", name);
gets_s(buffer);
p = Search(H, name);
if (p == NULL)printf("查找不到该元素\n");
printf("||姓名:%s\t||", p->information.name);
printf("电话:%s\t||", p->information.num);
printf("性别:%c||\n", p->information.sex);
break;
//--------------------------------------------
case 6:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
printf("请输入你想修改的联系人姓名:\n");
scanf("%10s", name);
gets_s(buffer);
Modify(H,L, name);
PrintFile(H, f);
break;
//--------------------------------------------
case 7:
if (H == NULL) {
printf("该哈希表未被初始化!\n");
break;
}
PrintFile(H, f);
break;
case 8:
printf("请输入你想加入的姓名\n");
scanf("%10s", name);
gets_s(buffer);
InsertLike(L, H, name);
PrintHT(H, L);
PrintfLike(L, fLike);
break;
case 9:
printf("请输入你想删除的姓名\n");
scanf("%10s", name);
gets_s(buffer);
DeleteList(L, name);
PrintHT(H, L);
PrintfLike(L, fLike);
break;
default:
return 0;
}
memset(buffer, 0, sizeof(buffer));
}
}
创建 Addressbook.txt与like.txt文件来存储读入的文件,
1.如果都在一个项目我们只需要在fopen()函数里面输入文件名,即相对路径,
2.如果在文件外,我们就要输入全部路径又称绝对路径
?具体操作呢你们可以自己去使用,
本次实验使用了? 哈希表,顺序二叉树,链表存储结构,实现了相关的算法,有不足的地方可以评论区或者私聊本人指出错误