#头歌 数据结构 实验十一 查找的实现

第1关:顺序查找

任务描述

本关任务:实现顺序查找。

相关知识

为了完成本关任务,你需要掌握:1.静态查找表的类型定义,2.顺序查找。

静态查找表的类型定义

 
  1. #define MAXSIZE 100
  2. typedef int KeyType; /*关键字类型*/
  3. typedef struct
  4. {
  5. KeyType key;
  6. /*InfoType otherinfo;*/
  7. }RedType; /*记录类型*/
  8. typedef struct
  9. {
  10. RedType r[MAXSIZE+1]; /*r[0]闲置或用作"监视哨"*/
  11. int length;
  12. }SSTable; /*静态查找表的类型定义*/

顺序查找

算法思想:从表的一端(第一个或最后一个)开始,逐个进行比较。 适用:顺序表、链表、有序表、无序表均可。

编程要求

在右侧编辑器中补充代码,完成Search_Seq函数,以实现顺序查找。具体要求如下:

* Search_Seq:在表中查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0。

测试说明

可在右侧文件夹中查看step1/Main.cpp文件,以便于你的操作。

平台会对你编写的代码进行测试。

输入说明: 第一行输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出静态查找表L中各记录的关键字。 第二行输出查找的结果,如果找到,则输出该记录在表中的位置,否则输出not find!

测试输入: 37 21 75 55 64 19 -1 55 //待查找的关键字为55

预期输出: 37 21 75 55 64 19 4 //末尾换行

开始你的任务吧,祝你成功!

最后通关代码

/*************************************************************

    顺序查找  实现文件    

    更新于2020年6月23日

**************************************************************/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "search.h"

int Search_Seq(SSTable L, KeyType key)

{/*在静态查找表L中采用顺序查找法,查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0*/

    // 请在这里补充代码,完成本关任务

    /********** Begin *********/

int i = 0 ;

    while(i<L.length&&key!=L.r[i].key)

            i++;

    if(L.r[i].key==key)

        return i;

    else

        return 0;

    /*

    从第一位开始查找,直至数组结束或遇到第一个key值

    退出循环

    判断退出原因

                找到key -- 返回下标【从0开始】

                否则    -- 返回0

    */  

    /********** End **********/

}

void SSTableInput(SSTable &L) /*输入若干记录的关键字,存放到静态查找表L中*/

{

    int i=1; KeyType x;

    scanf("%d",&x);

    while(x!=-1)

    {

        L.r[i++].key=x; scanf("%d",&x);

    }

    L.length=i-1;

}

void SSTableOutput(SSTable L) /*输出静态查找表L中各记录的关键字*/

{

    int i;

    for(i=1;i<=L.length;i++)

        printf("%d ",L.r[i].key);

    printf("\n");

}


 

第2关:折半查找

任务描述

本关任务:实现折半查找。

相关知识

为了完成本关任务,你需要掌握:1.静态查找表的类型定义,2.折半查找。

静态查找表的类型定义

 
  1. #define MAXSIZE 100
  2. typedef int KeyType; /*关键字类型*/
  3. typedef struct
  4. {
  5. KeyType key;
  6. /*InfoType otherinfo;*/
  7. }RedType; /*记录类型*/
  8. typedef struct
  9. {
  10. RedType r[MAXSIZE+1]; /*r[0]闲置或用作"监视哨"*/
  11. int length;
  12. }SSTable; /*静态查找表的类型定义*/

折半查找

算法思想:先确定待查元素所在范围;然后找中间元素;将中间元素与给定值 x 比较:若相等,则查找成功;若 x 小,则缩小至左半区;若 x 大,则缩小至右半区;直到相等或范围的下界﹥上界为止。 适用:有序的顺序表。

编程要求

在右侧编辑器中补充代码,完成Search_Bin函数,以实现顺序查找。具体要求如下:

* Search_Bin:在递增或递减有序的表中查找其关键字等于key的记录,若找到返回该记录在表中位置,否则返回0。

测试说明

可在右侧文件夹中查看step2/Main.cpp文件,以便于你的操作。

平台会对你编写的代码进行测试。

输入说明: 第一行按递增或递减的顺序输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出静态查找表L中各记录的关键字。 第二行输出查找的结果,如果找到,则输出该记录在表中的位置,否则输出not find!

测试输入: 19 21 37 55 64 75 -1 55 //待查找的关键字为55

预期输出: 19 21 37 55 64 75 4 //末尾换行

开始你的任务吧,祝你成功!

最后通关代码

/*************************************************************

    折半查找  实现文件  

    更新于2020年6月17日

**************************************************************/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "search.h"

int Search_Bin(SSTable L, KeyType key)/*在递增有序的顺序表L中折半查找其关键字等于key的记录*/

{

    // 请在这里补充代码,完成本关任务

    /********** Begin *********/

int mid, low = 0,high = L.length;

    //设定初始查找范围,注意此处high初始值为L.length而不是L.length-1

   

    while(low<=high)

    {

        mid= (high + low)/2;

        if(key == L.r[mid].key)//找到返回key值下标

            return mid;

        if(key < L.r[mid].key)//中间值大于待查值--范围前缩

            high = mid - 1;

        if(key > L.r[mid].key)//中间值小于待查值--范围后缩

            low  = mid + 1;

    }

    return 0;//找不到要返回0 -- not find!

   

    /********** End **********/

}

void SSTableInput(SSTable &L) /*输入若干记录的关键字,存放到静态查找表L中*/

{

    int i=1; KeyType x;

    scanf("%d",&x);

    while(x!=-1)

    {

        L.r[i++].key=x; scanf("%d",&x);

    }

    L.length=i-1;

}

void SSTableOutput(SSTable L) /*输出静态查找表L中各记录的关键字*/

{

    int i;

    for(i=1;i<=L.length;i++)

        printf("%d ",L.r[i].key);

    printf("\n");

}


第3关:二叉排序树的查找

任务描述

本关任务:实现二叉排序树的查找。

相关知识

为了完成本关任务,你需要掌握:1.二叉排序树的定义,2.二叉排序树的查找,3.动态查找表的二叉链表存储表示。

二叉排序树的定义

二叉排序树或者是空树,或者是满足如下性质的二叉树: ● 如果其左子树非空,则左子树上所有结点的值均小于根结点的值; ● 如果其右子树非空,则右子树上所有结点的值均大于根结点的值; ● 左子树和右子树本身又各是一棵二叉排序树。

二叉排序树的查找

在二叉排序树T中查找其关键字等于key的记录结点: ● 若 key 等于根记录结点的关键字,则表示找到,查找成功; ● 若 key 小于根记录结点的关键字,则在左子树上继续查找; ● 若 key 大于根记录结点的关键字,则在右子树上继续查找; ● 若沿着某条路径碰到一个叶子结点还未找到,则查找失败。

动态查找表的二叉链表存储表示

 
  1. #define MAXSIZE 100
  2. typedef int KeyType; /*关键字类型*/
  3. typedef struct
  4. {
  5. KeyType key;
  6. /*InfoType otherinfo;*/
  7. }RedType; /*记录类型*/
  8. typedef struct BiTNode
  9. {
  10. RedType data;
  11. struct BiTNode *lchild,*rchild;
  12. }BiTNode, *BiTree; /*动态查找表的二叉链表存储表示*/
编程要求

在右侧编辑器中补充代码,完成Search_BST函数,以实现二叉排序树的查找。具体要求如下:

* Search_BST:在二叉排序树上查找其关键字等于key的记录结点。若找到返回该结点指针,parent指向其双亲;否则返回空指针,parent指向访问路径上最后一个结点。

测试说明

可在右侧文件夹中查看step3/Main.cpp文件,以便于你的操作。

平台会对你编写的代码进行测试。

输入说明: 第一行输入若干记录的关键字,以-1标志结束。 第二行输入待查找的关键字。 输出说明: 第一行输出二叉排序树先序遍历的结果。 第二行输出二叉排序树中序遍历的结果。 第三行输出查找的结果,如果找到,则输出该结点的双亲结点的关键字,否则输出not find!

测试输入: 19 14 66 21 83 27 55 13 10 50 -1 55 //待查找的关键字为55

预期输出: PreOrder:19 14 13 10 66 21 27 55 50 83 //先序遍历的结果 InOrder:10 13 14 19 21 27 50 55 66 83 //中序遍历的结果 27 //末尾换行

开始你的任务吧,祝你成功!

最后通关代码

/*************************************************************

    二叉排序树的查找  实现文件

    更新于2020年7月5日

**************************************************************/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "search.h"

BiTree Search_BST(BiTree T, KeyType key, BiTNode **parent)

{/*在二叉排序树T上查找其关键字等于key的记录结点。若找到返回该结点指针,parent指向其双亲;否则返回空指针,parent指向访问路径上最后一个结点。*/

    // 请在这里补充代码,完成本关任务

    /********** Begin *********/

///parent 指向一个存放树根地址的指针

    /*

    if(T!=NULL)

    {

        if(key==T->data.key)

            return T;

            *parent = T;//改变parent true

                        //parent = &T;error

         if(key<T->data.key)

            return Search_BST(T->lchild, key,parent);

        if(key>T->data.key)

            return Search_BST(T->rchild, key,parent);

       

    }

    else

        return NULL;

    */

   

    if(!T||key==T->data.key)

        return T;

        /*

            由 !T 进入if分支

            说明T为空,此时直接返回T也可达到返回空的效果

        */

    else

    {

        *parent = T;

        if(key<T->data.key)

            return Search_BST(T->lchild, key,parent);

        else

            return Search_BST(T->rchild, key,parent);

    }

   

    /********** End **********/

}

void Insert_BST(BiTree *T, RedType r)/*若二叉排序树T中没有关键字为r.key的记录,则插入*/

{

    BiTNode *p,*q,*parent;

    parent=NULL;

    p=Search_BST(*T,r.key,&parent); /*查找*/

    if(p) printf("BST中有结点r,无需插入\n");

    else

    {

        p=parent;

        q=(BiTNode *)malloc(sizeof(BiTNode)); q->data=r; q->lchild=q->rchild=NULL;

        if(*T==NULL) *T=q; /*若T为空,则q为新的根*/

        else if(r.key<p->data.key) p->lchild=q;

        else p->rchild=q;

    }

}

BiTree Create_BST( ) /*二叉排序树的构造*/

{/*输入若干记录的关键字(以-1标志结束),生成一棵BST,采用二叉链表存储,返回其根指针T*/

    BiTree T; RedType r;

    T=NULL; /*建空树*/

    scanf("%d",&r.key);

    while(r.key!=-1)

    {

        Insert_BST(&T, r);

        scanf("%d",&r.key);

    }

    return T;

}

void PreOrder(BiTree bt) /*先序遍历*/

{

    if(bt)

    {

        printf("%d ",bt->data.key);

        PreOrder(bt->lchild);

        PreOrder(bt->rchild);

    }

}

void InOrder(BiTree bt) /*中序遍历*/

{

    if(bt)

    {

        InOrder(bt->lchild);

        printf("%d ",bt->data.key);

        InOrder(bt->rchild);

    }

}

第4关:采用除留余数法加线性探测法建立哈希表

任务描述

本关任务:采用除留余数法加线性探测法建立哈希表。

相关知识

为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据处理冲突的不同方法,设计不同的哈希表结构并进行初始化。

哈希表的基本概念

顺序表、树表的缺点: 查找所需时间与比较次数有关,能不能不比较,一次存取就能得到所查元素?

哈希表(Hash Table)又称散列表,是除顺序存储结构链式存储结构索引表存储结构之外的又一种存储结构。

散列表的基本思想:根据记录选择一个关键字key,同时为每个key计算一个散列码Hash(key),将记录存储位置散列码关联起来。

需要解决两个问题:

  1. 如何构造散列函数?对给定的一组关键字,使其计算简单,并且分布均匀,即减少冲突。
  2. 如何处理冲突?因为不可避免。
构造哈希函数的方法

构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构分布的不同,有多种构造哈希函数的方法,例如除留余数法。

除留余数法

思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数

举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?

H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016

哈希冲突解决方法

开放地址法 思想: 当冲突发生时,形成一个探查序列;沿此序列逐个地址探查,直到找到一个空位置(开放的地址),将发生冲突的记录放到该地址中 公式: Hi=(H(key)+di)%m,i=1,2,……k(k<=m-1)
其中:m是散列表表长,di是增量序列 线性探测法:di=1,2,3,……m-1 二次探测法:di=1²,-1²,2²,-2²,3²,……±k²(k<=m/2) 伪随机探测法:di=伪随机数序列

【例】假设哈希表长度m=13,采用除留余数法线性探测法建立如下关键字集合的哈希表:(16,74,60,43,54,90,46,31,29,88,77),除留余数法的哈希函数为:h(k)=k mod 13,散列表的存储空间是一个下标从0开始的一维数组。

构造的哈希表如下:

,

h(16)=3 ha[3]=16,共1次探测 h(74)=9 ha[9]=74,共1次探测 h(60)=8 ha[8]=60,共1次探测 h(43)=4 ha[4]=43,共1次探测 h(54)=2 ha[2]=54,共1次探测 h(90)=12 ha[12]=90,共1次探测 h(46)=7 ha[7]=46,共1次探测 h(31)=5 ha[5]=31,共1次探测 h(29)=3 有冲突 d0=3,d1=(3+1) % 13=4 仍有冲突 d2=(3+2) % 13=5 仍有冲突 d3=(3+3) % 13=6 ha[6]=29,共4次探测 h(88)=10 ha[10]=88,共1次探测 h(77)=12 有冲突 d0=12,d1=(12+1) % 13=0 ha[0]=77,共2次探测

查找成功时: ASL=(1×9+2×1+4×1) /11 = 15/11 查找失败时: ASL=(2+1+10+9+8+7+6+5+4+3+2+1+3) /13 = 61/13

开放定址哈希表的存储结构
 
  1. #define NULL_KEY -1 // ,初始化的哈希表中元素均为-1,-1为无记录标志
  2. typedef int KeyType; // 设关键字域为整型
  3. struct ElemType // 数据元素类型
  4. {
  5. KeyType key;
  6. };
  7. struct HashTable
  8. {
  9. ElemType *elem; // 数据元素存储基址,动态分配数组
  10. int count; // 当前数据元素个数
  11. int sizeindex; // sizeindex为哈希表表长
  12. };
哈希函数的基本操作函数
 
  1. // 哈希函数的基本操作函数
  2. void InitHashTable(HashTable &H); // 构造一个空的哈希表
  3. void DestroyHashTable(HashTable &H); // 销毁哈希表H
  4. unsigned Hash(KeyType K); // 哈希函数
  5. void collision(int &p,int d); // 线性探测再散列
  6. int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
  7. int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
  8. void TraverseHash(HashTable H,void(*Vi)(int));// 遍历哈希表
编程要求

根据提示,在右侧编辑器补充代码,采用除留余数法加线性探测法建立哈希表,编写函数实现哈希表的部分基本运算:

 
  1. unsigned Hash(KeyType K); // 哈希函数
  2. void collision(int &p,int d); // 线性探测再散列
  3. int SearchHash(HashTable H,KeyType K,int &p,int &c); // 在开放定址哈希表H中查找关键码为K的元素
  4. int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中
测试说明

平台会对你编写的代码进行测试:

测试输入: 13 13 11 16 74 60 43 54 90 46 31 29 88 77

输入说明: 第一行输入13,表示输入哈希表的表长; 第二行输入13,表示除留余数法的模; 第三行输入11,表示输入数据的个数; 第四行输入11个整数,表示输入具体的数据。

预期输出: 哈希地址0~12 0 1 2 3 4 5 6 7 8 9 10 11 12
77 -1 54 16 43 31 29 46 60 74 88 -1 90

输出说明: 第一行输出哈希表的地址范围 第二行输出哈希表的下标 第三行输出哈希表中的数据


开始你的任务吧,祝你成功!

最后通关代码

#include"stdio.h"

#include"stdlib.h"

#define NULL_KEY -1 // -1为无记录标志

#define SUCCESS 1

#define UNSUCCESS 0

#define DUPLICATE -1

#define N 100  // 数据元素个数

typedef int KeyType; // 设关键字域为整型

struct ElemType // 数据元素类型

{

   KeyType key;    

};

struct HashTable

{

   ElemType *elem; // 数据元素存储基址,动态分配数组

   int count; // 当前数据元素个数

   int sizeindex; // sizeindex为哈希表表长

};

// 哈希函数的基本操作函数

void InitHashTable(HashTable &H);    // 构造一个空的哈希表

void DestroyHashTable(HashTable &H);  // 哈希表H存在。操作结果:销毁哈希表H

unsigned Hash(KeyType K);    // 哈希函数

void collision(int &p,int d); // 线性探测再散列

int SearchHash(HashTable H,KeyType K,int &p,int &c);  // 在开放定址哈希表H中查找关键码为K的元素

int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中  

void TraverseHash(HashTable H,void(*Vi)(int));

void print(int p);

 int m=0;   // 全局变量, H(key) = key % m

 int main()

 {

   ElemType r[N]={0};

   HashTable h;

   int i,p,n;

   int j;

   KeyType k;    

   InitHashTable(h);

   scanf("%d",&m); // H(key) = key % m

   scanf("%d",&n); // 数据的个数    

   for(i=0;i<n;i++)

   {

       scanf("%d",&r[i].key);    

   }

   for(i=0;i<n;i++)

   { // 插入前N-1个记录

     j=InsertHash(h,r[i]);    

   }

   //printf("按哈希地址的顺序遍历哈希表:\n");

   TraverseHash(h,print);  

   DestroyHashTable(h);

 }

 // 哈希函数的基本操作函数

void InitHashTable(HashTable &H)

 { // 操作结果:构造一个空的哈希表

  int i;

  H.count=0; // 当前元素个数为0  

  scanf("%d",&H.sizeindex ); // 哈希表存储容量

  H.elem=(ElemType*)malloc(H.sizeindex*sizeof(ElemType));  

  for(i=0;i<H.sizeindex;i++)

    H.elem[i].key=NULL_KEY; // 未填记录的标志

 }

void DestroyHashTable(HashTable &H)

{ // 初始条件:哈希表H存在。操作结果:销毁哈希表H

   free(H.elem);

   H.elem=NULL;

   H.count=0;

   H.sizeindex=0;

 }

unsigned Hash(KeyType K)

{ // 一个简单的哈希函数(m为全局变量)

  /********** Begin **********/    

        return K%m;

  /********** End **********/    

}

void collision(int &p,int d) // 线性探测再散列

{ // 开放定址法处理冲突

  /********** Begin **********/

    p=(p+1)%m;

  /********** End **********/

 }

int SearchHash(HashTable H,KeyType K,int &p,int &c)

{  // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据

   // 元素在表中位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS

   // c用以计冲突次数,其初值置零,供建表插入时参考。

   /********** Begin **********/

   p=Hash(K);

  while( H.elem[p].key!=NULL_KEY && K!= H.elem[p].key )

  {

    c++;

    if(c<H.sizeindex)

      collision(p,c);

    else

      break;

  }

  if (K == H.elem[p].key)      

     return SUCCESS;

  else

     return UNSUCCESS;

   /********** End **********/

 }

int InsertHash(HashTable &H,ElemType e)

{ // 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;

  // 若冲突次数过大,则重建哈希表,算法9.18

  /********** Begin **********/

   int c,p;

  c=0;

  if(SearchHash(H,e.key,p,c))

    return DUPLICATE;

  else if(c<H.sizeindex)

  {

    H.elem[p]=e;

    ++H.count;

    return SUCCESS;

  }

  else

    return UNSUCCESS;

  /********** End **********/

 }

 void TraverseHash(HashTable H,void(*Vi)(int))

 { // 按哈希地址的顺序遍历哈希表

    printf("哈希地址0~%d\n",H.sizeindex-1);

    for(int i=0;i<H.sizeindex;i++)

    // if(H.elem[i].key!=NULL_KEY) // 有数据

    Vi(i);

    printf("\n");

    for(int i=0;i<H.sizeindex;i++)

    // if(H.elem[i].key!=NULL_KEY) // 有数据

       Vi(H.elem[i].key);

    printf("\n");      

 }

void print(int p)

{

   printf("%d\t",p);

}

第5关:在采用除留余数法加线性探测法建立的哈希表中查找数据

任务描述

本关任务:在采用除留余数法加线性探测法建立的哈希表中查找数据,并将查找过程输出。

已知哈希表长度m=13,采用除留余数法加线性探测法建立如下关键字集合的哈希表:(16,74,60,43,54,90,46,31,29,88,77),除留余数法的哈希函数为:h(k)=k mod 13,散列表的存储空间是一个下标从0开始的一维数组。

构造的哈希表如下:

,

h(16)=3 ha[3]=16,共1次探测 h(74)=9 ha[9]=74,共1次探测 h(60)=8 ha[8]=60,共1次探测 h(43)=4 ha[4]=43,共1次探测 h(54)=2 ha[2]=54,共1次探测 h(90)=12 ha[12]=90,共1次探测 h(46)=7 ha[7]=46,共1次探测 h(31)=5 ha[5]=31,共1次探测 h(29)=3 有冲突 d0=3,d1=(3+1) % 13=4 仍有冲突 d2=(3+2) % 13=5 仍有冲突 d3=(3+3) % 13=6 ha[6]=29,共4次探测 h(88)=10 ha[10]=88,共1次探测 h(77)=12 有冲突 d0=12,d1=(12+1) % 13=0 ha[0]=77,共2次探测

查找成功时: ASL=(1×9+2×1+4×1) /11 = 15/11 查找失败时: ASL=(2+1+10+9+8+7+6+5+4+3+2+1+3) /13 = 61/13

编程要求

根据提示,在右侧编辑器补充代码,采用除留余数法加线性探测法建立哈希表,编写函数实现在哈希表在查找数据的过程:

 
  1. int Find(HashTable H,KeyType K,int &p); // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据元素在表中位置,并返回SUCCESS;否则,返回UNSUCCESS
测试说明

平台会对你编写的代码进行测试:

测试输入: 13 13 11 16 74 60 43 54 90 46 31 29 88 77 29

输入说明: 第一行输入13,表示输入哈希表的表长 第二行输入13,表示除留余数法的模 第三行输入11,表示输入数据的个数 第四行输入11个整数,表示输入具体的数据 第五行输入要查找的数据

预期输出: 哈希地址0~12 0 1 2 3 4 5 6 7 8 9 10 11 12
77 -1 54 16 43 31 29 46 60 74 88 -1 90
哈希地址为3,第1次冲突 哈希地址为4,第2次冲突 哈希地址为5,第3次冲突 哈希地址为6,第4次查找成功 查找成功:29

输出说明: 第一行输出哈希表的数据范围 第二行输出哈希表的下标 第三行输出哈希表中的数据 第四行起输出查找数据的过程


开始你的任务吧,祝你成功!

最后通关代码

#include"stdio.h"

#include"stdlib.h"

#define NULL_KEY -1 // -1为无记录标志

#define SUCCESS 1

#define UNSUCCESS 0

#define DUPLICATE -1

#define N 100  // 数据元素个数

typedef int KeyType; // 设关键字域为整型

struct ElemType // 数据元素类型

{

   KeyType key;    

};

struct HashTable

{

   ElemType *elem; // 数据元素存储基址,动态分配数组

   int count; // 当前数据元素个数

   int sizeindex; // sizeindex为哈希表表长

};

// 哈希函数的基本操作函数

void InitHashTable(HashTable &H);    // 构造一个空的哈希表

void DestroyHashTable(HashTable &H);  // 哈希表H存在。操作结果:销毁哈希表H

unsigned Hash(KeyType K);    // 哈希函数

void collision(int &p,int d); // 线性探测再散列

int SearchHash(HashTable H,KeyType K,int &p,int &c);  // 在开放定址哈希表H中查找关键码为K的元素

int InsertHash(HashTable &H,ElemType e); // 查找不成功时插入数据元素e到开放定址哈希表H中  

void TraverseHash(HashTable H,void(*Vi)(int));

void print(int p);

int Find(HashTable H,KeyType K,int &p);

 int m=0;   // 全局变量, H(key) = key % m

 int main()

 {

   ElemType r[N]={0};

   HashTable h;

   int i,p,n;

   int j;

   KeyType k;    

   InitHashTable(h);

   scanf("%d",&m); // H(key) = key % m

   scanf("%d",&n); // 数据的个数    

   for(i=0;i<n;i++)

   {

    scanf("%d",&r[i].key);    

   }

   for(i=0;i<n;i++)

   { // 插入前N-1个记录

     j=InsertHash(h,r[i]);    

   }

   //printf("按哈希地址的顺序遍历哈希表:\n");

   TraverseHash(h,print);

   //printf("请输入待查找记录的关键字: ");

   scanf("%d",&k);

   j=Find(h,k,p);

   if(j==SUCCESS)

     {

     

       printf("查找成功:");  

       print(h.elem[p].key);

     }

     

   else

     printf("查找失败\n");  

   DestroyHashTable(h);

 }

 // 哈希函数的基本操作函数

void InitHashTable(HashTable &H)

 { // 操作结果:构造一个空的哈希表

  int i;

  H.count=0; // 当前元素个数为0  

  scanf("%d",&H.sizeindex ); // 哈希表存储容量

  H.elem=(ElemType*)malloc(H.sizeindex*sizeof(ElemType));  

  for(i=0;i<H.sizeindex;i++)

    H.elem[i].key=NULL_KEY; // 未填记录的标志

 }

void DestroyHashTable(HashTable &H)

{ // 初始条件:哈希表H存在。操作结果:销毁哈希表H

   free(H.elem);

   H.elem=NULL;

   H.count=0;

   H.sizeindex=0;

 }

unsigned Hash(KeyType K)

{ // 一个简单的哈希函数(m为全局变量)

  return K%m;

}

void collision(int &p,int d) // 线性探测再散列

{ // 开放定址法处理冲突

  p=(p+1)%m;

 }

int SearchHash(HashTable H,KeyType K,int &p,int &c)

{ // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据

  // 元素在表中位置,并返回SUCCESS;否则,以p指示插入位置,并返回UNSUCCESS

  // c用以计冲突次数,其初值置零,供建表插入时参考。

  p=Hash(K); // 求得哈希地址

  while( H.elem[p].key!=NULL_KEY && K!= H.elem[p].key )

  { // 该位置中填有记录.并且关键字不相等

    c++;

    if(c<H.sizeindex)

      collision(p,c); // 求得下一探查地址p

    else

      break;

  }

  if (K == H.elem[p].key)      

     return SUCCESS; // 查找成功,p返回待查数据元素位置

  else

     return UNSUCCESS; // 查找不成功(H.elem[p].key==NULL_KEY),p返回的是插入位置  

 }

int InsertHash(HashTable &H,ElemType e)

{ // 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;

  // 若冲突次数过大,则重建哈希表

  int c,p;

  c=0;

  if(SearchHash(H,e.key,p,c)) // 表中已有与e有相同关键字的元素

    return DUPLICATE;

  else if(c<H.sizeindex) // 冲突次数c未达到上限,(c的阀值可调)

  { // 插入e

    H.elem[p]=e;

    ++H.count;

    return SUCCESS;

  }

  else

    return UNSUCCESS;

 }

 void TraverseHash(HashTable H,void(*Vi)(int))

 { // 按哈希地址的顺序遍历哈希表

    printf("哈希地址0~%d\n",H.sizeindex-1);

    for(int i=0;i<H.sizeindex;i++)

    // if(H.elem[i].key!=NULL_KEY) // 有数据

    Vi(i);

    printf("\n");

    for(int i=0;i<H.sizeindex;i++)

    // if(H.elem[i].key!=NULL_KEY) // 有数据

       Vi(H.elem[i].key);

    printf("\n");      

 }

void print(int p)

{

   printf("%d\t",p);

}

int Find(HashTable H,KeyType K,int &p)

{ // 在开放定址哈希表H中查找关键码为K的元素,若查找成功,以p指示待查数据

  // 元素在表中位置,并返回SUCCESS;否则,返回UNSUCCESS

  /********** Begin **********/

  p=Hash(K);

  int c=0;

  while(H.elem[p].key!=NULL_KEY&&H.elem[p].key!=K)

  {

    c++;

    printf("哈希地址为%d,第%d次冲突\n",p,c);

    if(c<H.sizeindex)

    {

      collision(p,c);

    }

  }

  c++;

  if(H.elem[p].key==K)

  {

    printf("哈希地址为%d,第%d次查找成功\n",p,c);

    return SUCCESS;

  }

  else

  {

    printf("哈希地址为%d,第%d次查找为空\n",p,c);

  return UNSUCCESS;

  }

  /********** End **********/

 }

任务描述

本关讨论散列存储,哈希函数使用除留余数法,冲突解决方法采用独立链表地址法。

相关知识

为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据链地址法处理冲突,设计哈希表。

哈希表的基本概念

哈希表不可避免冲突现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。 具有相同函数值的关键字对该哈希函数来说称为同义词

因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表

构造哈希函数的方法

构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构分布的不同,有多种构造哈希函数的方法,例如除留余数法。

除留余数法

思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数

举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?

H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016

哈希冲突解决方法

本关讨论采用独立链地址法解决冲突问题。

将所有关键字为"同义词"的记录链接在一个线性链表中。此时的散列表以"指针数组"的形式出现,数组内各个分量存储相应散列地址的链表的头指针。

举例:已知一组关键字(19,56,23,14,68,82,70,36,91 ), 散列函数为:hash(key)=key%7,用链地址法处理冲突,构造的哈希表存储结构如下所示:

,

由 7 个独立链表组成,散列值相同的关键码在同一个链表里,独立链表的头结点组成散列表,一共 7 行,编号 0 , 1 , … , 6 。 哈希表类型定义如下:

 
  1. typedef int KeyType; // 设关键字域为整型
  2. struct ElemType // 数据元素类型
  3. {
  4. KeyType key;
  5. };
  6. typedef struct Node //链地址,其实这个指针就是链表
  7. {
  8. ElemType r;
  9. struct Node* next;
  10. }Node;
  11. typedef struct
  12. {
  13. Node** rcd; //(指向指针的指针)存放指针的数组
  14. int size; //哈希表的容量
  15. int count; //当前表中含有的记录个数
  16. }HashTable;

定义如下操作,各操作函数的功能详见下面:

 
  1. unsigned Hash(KeyType K,int m)
  2. { // 一个简单的哈希函数
  3. return K%m;
  4. }
  5. int InitHash(HashTable& H, int size)
  6. {
  7. H.rcd = (Node**)malloc(size*sizeof(Node*));
  8. if (H.rcd != NULL)
  9. {
  10. for (int i = 0; i < size; i++)
  11. {
  12. H.rcd[i] = NULL;
  13. }
  14. H.size = size;
  15. H.count = 0;
  16. return 1;
  17. }
  18. else
  19. {
  20. return 0;
  21. }
  22. }
  23. void HashTraverse(HashTable H)
  24. {
  25. if (H.rcd != NULL)
  26. {
  27. Node* np, * nq;
  28. for (int i = 0; i < H.size; i++)
  29. {
  30. printf("%d: ", i);
  31. if (H.rcd [i])
  32. {
  33. printf("%d ",H.rcd [i]->r .key);
  34. Node* curr=H.rcd [i]->next;
  35. while (curr) {
  36. printf("-> %d ", curr->r.key);
  37. curr=curr->next;
  38. }
  39. printf("\n");
  40. }
  41. else
  42. printf("-\n");
  43. }
  44. }
  45. }
  46. int DestroyHash(HashTable& H)
  47. {
  48. if (H.rcd != NULL)
  49. {
  50. Node* np, * nt;
  51. for (int i = 0; i < H.size; i++)
  52. {
  53. np = H.rcd[i];
  54. while (np != NULL)
  55. {
  56. nt = np;
  57. np = np->next;
  58. free(nt);
  59. }
  60. }
  61. H.size = 0;
  62. H.count = 0;
  63. return 1;
  64. }
  65. else
  66. {
  67. return 0;
  68. }
  69. }
编程要求

本关的编程任务是补全函数来分别实现哈希表的查找、插入操作。

 
  1. Node* SearchHash(HashTable H, KeyType key); //查找
  2. int InsertHash(HashTable& H, ElemType e); //插入
测试说明

平台会对你编写的代码进行测试:

样例输入: 7 9 19 56 23 14 68 82 70 36 91

输入说明: 第一行输入7,表示输入哈希表的表长 第二行输入9,表示输入数据的个数 第三行输入9个整数,表示输入具体的数据

样例输出: 采用链地址法得到的哈希表为: 0: 91 -> 70 -> 14 -> 56 1: 36 2: 23 3: - 4: - 5: 82 -> 68 -> 19 6: -

输出说明: 输出采用除留余数法加链地址法建立哈希表,输出n个独立链表。

开始你的任务吧,祝你成功

最后通关代码

#include <stdio.h>

#include <stdlib.h>

#define N 20  // 数据元素个数

typedef int KeyType; // 设关键字域为整型

struct ElemType // 数据元素类型

{

   KeyType key;    

};

typedef struct Node {                 //链地址,其实这个指针就是链表

    ElemType r;

    struct Node* next;

}Node;

typedef struct {

    Node** rcd;                             //(指向指针的指针)存放指针的数组

    int size;                               //哈希表的容量

    int count;                              //当前表中含有的记录个数  

}HashTable;

int InitHash(HashTable& H, int size);   //初始化哈希表

int DestroyHash(HashTable& H);                                  //销毁哈希表

Node* SearchHash(HashTable H, KeyType key);                         //查找

int InsertHash(HashTable& H, ElemType e);                           //插入

int DeleteHash(HashTable& H, KeyType key, ElemType& e);         //删除

void HashTraverse(HashTable H);                                     //遍历

unsigned Hash(KeyType K,int m);

int main()

{

    HashTable h;

    KeyType k;

    ElemType r[N]={0};  

    int m,n,i,j;  

   scanf("%d",&m); // H(key) = key % m

   scanf("%d",&n); // 数据的个数  

   InitHash(h,m);  

   for(i=0;i<n;i++)

   {

    scanf("%d",&r[i].key);    

   }

   for(i=0;i<n;i++)

   { // 插入前N-1个记录    

        InsertHash(h, r[i]);        

   }

    printf("采用链地址法得到的哈希表为:\n");    

    HashTraverse(h);    

    DestroyHash(h);

}

unsigned Hash(KeyType K,int m)

{ // 一个简单的哈希函数

  return K%m;

}

int InitHash(HashTable& H, int size)

{

    H.rcd = (Node**)malloc(size*sizeof(Node*));

    if (H.rcd != NULL)

    {

        for (int i = 0; i < size; i++)

        {      

            H.rcd[i] = NULL;            

        }

        H.size = size;      

        H.count = 0;

        return 1;

    }

    else

    {

        return 0;

    }

}

int DestroyHash(HashTable& H)

{

    if (H.rcd != NULL)

    {

        Node* np, * nt;

        for (int i = 0; i < H.size; i++)

        {

            np = H.rcd[i];

            while (np != NULL)

            {

                nt = np;

                np = np->next;

                free(nt);

            }

        }

        H.size = 0;

        H.count = 0;

        return 1;

    }

    else

    {

        return 0;

    }

}

Node* SearchHash(HashTable H, KeyType key)

{

    /*********BEGIN*********/

    int p;

    Node* np;

    p = Hash(key,H.size);    

    np = H.rcd[p];

    while (np)

    {

        if(key == np->r.key)

                return np ;

        np = np->next;

    }

    return NULL;

    /**********END**********/

}

int InsertHash(HashTable& H, ElemType e)

{

    /*********BEGIN*********/  

    int p;

    Node* np;

    np = SearchHash(H, e.key);

    if (np == NULL)

    {

        p = Hash(e.key,H.size);

        np = (Node*)malloc(sizeof(Node));

        np->r = e;

        np->next = H.rcd[p];

        H.rcd[p] = np;

        H.count++;

        return 1;

    }

    else

    {

        return 0;

    }

    /**********END**********/

}

void HashTraverse(HashTable H)

{

    if (H.rcd != NULL)

    {

        Node* np, * nq;

        for (int i = 0; i < H.size; i++)

        {

            printf("%d:  ", i);

            if (H.rcd [i])

            {

                printf("%d  ",H.rcd [i]->r .key);

                Node* curr=H.rcd [i]->next;

                while (curr) {

                printf("-> %d  ", curr->r.key);

                curr=curr->next;

                }

                printf("\n");

            }

            else

                printf("-\n");

        }

    }

}

第7关:在采用除留余数法加链地址法建立的哈希表中删除数据

任务描述

本关实现在采用除留余数法加链地址法建立的哈希表中删除数据。

相关知识

为了完成本关任务,你需要掌握: 1.哈希表结构; 2.根据链地址法处理冲突,设计哈希表。

哈希表的基本概念

哈希表不可避免冲突现象:对不同的关键字可能得到同一哈希地址 即key1≠key2,而hash(key1)=hash(key2)。 具有相同函数值的关键字对该哈希函数来说称为同义词

因此,在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。可如下描述哈希表:根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映象到一个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表

构造哈希函数的方法

构造哈希函数的目标:使得到的哈希地址尽可能均匀地分布在m个连续内存单元地址上,同时使计算过程尽可能简单以达到尽可能高的时间效率。 根据关键字的结构分布的不同,有多种构造哈希函数的方法,例如除留余数法。

除留余数法

思想: 以关键字被某个数p除后所得余数作为散列地址 公式: H(key) = key % p 关键:p 如何选取? 一般选择小于表长的最大质数

举例:给定存储单元地址为1,000,000至1,000,027,即表长度为28,如何计算关键字172,148的散列地址?

H(key)= 172148 % 23 + 1,000,000 = 16+1,000,000 = 1,000,016

哈希冲突解决方法

本关讨论采用独立链地址法解决冲突问题。

将所有关键字为"同义词"的记录链接在一个线性链表中。此时的散列表以"指针数组"的形式出现,数组内各个分量存储相应散列地址的链表的头指针。

举例:已知一组关键字(19,56,23,14,68,82,70,36,91 ), 散列函数为:hash(key)=key%7,用链地址法处理冲突,构造的哈希表存储结构如下所示:

,

由 7 个独立链表组成,散列值相同的关键码在同一个链表里,独立链表的头结点组成散列表,一共 7 行,编号 0 , 1 , … , 6 。 哈希表类型定义如下:

 
  1. typedef int KeyType; // 设关键字域为整型
  2. struct ElemType // 数据元素类型
  3. {
  4. KeyType key;
  5. };
  6. typedef struct Node //链地址,其实这个指针就是链表
  7. {
  8. ElemType r;
  9. struct Node* next;
  10. }Node;
  11. typedef struct
  12. {
  13. Node** rcd; //(指向指针的指针)存放指针的数组
  14. int size; //哈希表的容量
  15. int count; //当前表中含有的记录个数
  16. }HashTable;

定义如下操作,各操作函数的功能详见下面:

 
  1. unsigned Hash(KeyType K,int m)
  2. { // 一个简单的哈希函数
  3. return K%m;
  4. }
  5. int InitHash(HashTable& H, int size)
  6. {
  7. H.rcd = (Node**)malloc(size*sizeof(Node*));
  8. if (H.rcd != NULL)
  9. {
  10. for (int i = 0; i < size; i++)
  11. {
  12. H.rcd[i] = NULL;
  13. }
  14. H.size = size;
  15. H.count = 0;
  16. return 1;
  17. }
  18. else
  19. {
  20. return 0;
  21. }
  22. }
  23. void HashTraverse(HashTable H)
  24. {
  25. if (H.rcd != NULL)
  26. {
  27. Node* np, * nq;
  28. for (int i = 0; i < H.size; i++)
  29. {
  30. printf("%d: ", i);
  31. if (H.rcd [i])
  32. {
  33. printf("%d ",H.rcd [i]->r .key);
  34. Node* curr=H.rcd [i]->next;
  35. while (curr) {
  36. printf("-> %d ", curr->r.key);
  37. curr=curr->next;
  38. }
  39. printf("\n");
  40. }
  41. else
  42. printf("-\n");
  43. }
  44. }
  45. }
  46. int DestroyHash(HashTable& H)
  47. {
  48. if (H.rcd != NULL)
  49. {
  50. Node* np, * nt;
  51. for (int i = 0; i < H.size; i++)
  52. {
  53. np = H.rcd[i];
  54. while (np != NULL)
  55. {
  56. nt = np;
  57. np = np->next;
  58. free(nt);
  59. }
  60. }
  61. H.size = 0;
  62. H.count = 0;
  63. return 1;
  64. }
  65. else
  66. {
  67. return 0;
  68. }
  69. }
编程要求

本关的编程任务是补全函数来分别实现哈希表的删除操作。

 
  1. int DeleteHash(HashTable& H, KeyType key, ElemType& e); - //删除
测试说明

平台会对你编写的代码进行测试:

样例输入: 7 9 19 56 23 14 68 82 70 36 91 70

输入说明: 第一行输入7,表示输入哈希表的表长 第二行输入9,表示输入数据的个数 第三行输入9个整数,表示输入具体的数据 第四行输入要删除的整数

样例输出: 采用链地址法得到的哈希表为: 0: 91 -> 70 -> 14 -> 56 1: 36 2: 23 3: - 4: - 5: 82 -> 68 -> 19 6: - 删除元素后哈希表为: 0: 91 -> 14 -> 56
1: 36
2: 23
3: - 4: - 5: 82 -> 68 -> 19
6: -

输出说明: 输出采用除留余数法加链地址法建立哈希表,输出n个独立链表。 再输出删除元素后的哈希表。

开始你的任务吧,祝你成功

最后通关代码

#include <stdio.h>

#include <stdlib.h>

#define N 20  // 数据元素个数

typedef int KeyType; // 设关键字域为整型

struct ElemType // 数据元素类型

{

   KeyType key;    

};

typedef struct Node {                 //链地址,其实这个指针就是链表

    ElemType r;

    struct Node* next;

}Node;

typedef struct {

    Node** rcd;                                //(指向指针的指针)存放指针的数组

    int size;                                //哈希表的容量

    int count;                                //当前表中含有的记录个数    

}HashTable;

int InitHash(HashTable& H, int size);    //初始化哈希表

int DestroyHash(HashTable& H);                                    //销毁哈希表

Node* SearchHash(HashTable H, KeyType key);                            //查找

int InsertHash(HashTable& H, ElemType e);                            //插入

int DeleteHash(HashTable& H, KeyType key, ElemType& e);            //删除

void HashTraverse(HashTable H);                                        //遍历

unsigned Hash(KeyType K,int m);

int main()

{

    HashTable h;

    KeyType k;

    ElemType r[N]={0},e;  

    int m,n,i,j;  

   scanf("%d",&m); // H(key) = key % m

   scanf("%d",&n); // 数据的个数  

   InitHash(h,m);  

   for(i=0;i<n;i++)

   {

       scanf("%d",&r[i].key);    

   }

   for(i=0;i<n;i++)

   { // 插入前N-1个记录    

        InsertHash(h, r[i]);        

   }

    printf("采用链地址法得到的哈希表为:\n");      

    HashTraverse(h);    

   //printf("请输入要删除的整数值:\n");

   scanf("%d",&k);    

  if ( DeleteHash(h, k, e) )

   {

        printf("删除元素后哈希表为:\n");      

        HashTraverse(h);

   }

   else

        printf("删除元素失败。\n");

    DestroyHash(h);

}

unsigned Hash(KeyType K,int m)

{ // 一个简单的哈希函数

  return K%m;

}

int InitHash(HashTable& H, int size)

{

    H.rcd = (Node**)malloc(size*sizeof(Node*));

    if (H.rcd != NULL)

    {

        for (int i = 0; i < size; i++)

        {        

            H.rcd[i] = NULL;            

        }

        H.size = size;        

        H.count = 0;

        return 1;

    }

    else

    {

        return 0;

    }

}

int DestroyHash(HashTable& H)

{

    if (H.rcd != NULL)

    {

        Node* np, * nt;

        for (int i = 0; i < H.size; i++)

        {

            np = H.rcd[i];

            while (np != NULL)

            {

                nt = np;

                np = np->next;

                free(nt);

            }

        }

        H.size = 0;

        H.count = 0;

        return 1;

    }

    else

    {

        return 0;

    }

}

Node* SearchHash(HashTable H, KeyType key)

{

    int p;

    Node* np;

    p = Hash(key,H.size);    

    np = H.rcd[p];

    while (np)

    {

        if(key == np->r.key)

                return np ;

        np = np->next;

    }

    return NULL;

}

int InsertHash(HashTable& H, ElemType e)

{

    int p;

    Node* np;

    np = SearchHash(H, e.key);

    if (np == NULL)

    {

        p = Hash(e.key,H.size);

        np = (Node*)malloc(sizeof(Node));

        np->r = e;

        np->next = H.rcd[p];

        H.rcd[p] = np;

        H.count++;

        return 1;

    }

    else

    {

        return 0;

    }

}

int DeleteHash(HashTable& H, KeyType key, ElemType& e)

{

    /*********BEGIN*********/    

   Node* n;

   n = SearchHash(H, key);

   if (n != NULL)

   {

       int p = Hash(key,H.size);

       Node* np, * nq;

       np = H.rcd[p];

       nq = NULL;

       while (np != NULL)

       {

           if (np->r.key != key)

           {

               nq = np;

               np = np->next;

           }

           else

           {

               e = np->r;

               if (nq == NULL)

               {

                   H.rcd[p] = np->next;

               }

               else

               {

                   nq->next = np->next;

               }

               break;

           }

       }

       H.count--;

       free(np);

       return 1;

   }

   else

   {

       return 0;

   }

    /**********END**********/

}

void HashTraverse(HashTable H)

{

    if (H.rcd != NULL)

    {

        Node* np, * nq;

        for (int i = 0; i < H.size; i++)

        {

            printf("%d:  ", i);

            if (H.rcd [i])

            {

                printf("%d  ",H.rcd [i]->r .key);

                Node* curr=H.rcd [i]->next;

                while (curr) {

                printf("-> %d  ", curr->r.key);

                curr=curr->next;

                }

                printf("\n");

            }

            else

                printf("-\n");

        }

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值