- 整理数据结构学习笔记
- 基于C++
- 一道线性表综合应用题
文章目录
一、问题描述
设计一个程序,对商铺信息管理,商铺信息包括:商铺编号,商铺名,信誉度(0-5),(商品名称1,价格1,销量1),(商品名称2,价格2,销量2),(商品名称3,价格3,销量3)…。
商品名称包括(毛巾,牙刷,牙膏,肥皂,洗发水,沐浴露等6种以上商品),每个商铺具有其中事先确定若干商品及价格,由文件输入,销量初始为0。
[基本要求]
- 建立一个单向链表存储所有商铺信息(至少30个),以编号为序,编号从1开始递增,从文件中读取数据,并能将数据存储在文件。商铺信息结点的数据结构自行设计。
- 可以增、删商铺。增加商铺,编号自动加一,插入链表尾部;删除商铺,以编号为准,并修改后续结点的编号,保持编号连续性。可增、删商品。
- 查询某一种商品名称,建立一个双向循环链表,结点信息是包含该商品的所有商铺编号、商铺名、信誉度、商品名称、价格、销量,以信誉度从高至低,并按销量排序,并逐一显示。
- 购买某一商铺的商品,修改单向链表中商品的信息的销量。
- 建立一个顺序结构,按商品名Hash分配地址,存储当前每种商品总销量并输出。
- 任何的商铺信息变化,实现文件存储。
二、数据结构设计
根据题目要求设计如下结构
- 商店单链表,每个结点包含一个商店的相关信息和一个商品单链表头结点指针
- 商品单链表,每个结点包含一个商品的相关信息
- 双向循环链表,每个结点包含一个商品结点指针和一个商店结点指针
- 一个动态扩展的顺序表,存储每个商品及对应销量,用作哈希表
//商品单链表
typedef struct product
{
char name[20]; //名称
int price; //价格
int sales; //销量
product *next;
}product,*productList;
//商店单链表
typedef struct store
{
char name[20]; //名称
int no; //编号
int credit; //信誉
int num; //商品数
product *pd; //商品信息
store *next;
}store,*storeList;
//商品双向循环链表
typedef struct DCNode
{
store *s;
product *p;
DCNode *next; //后继
DCNode *prior; //前驱
}DCNode,*DCList;
//哈希表中商品元素
typedef struct
{
char name[20]; //关键字
int sales;
}HPNode;
//哈希表
typedef struct
{
HPNode *Elem; //基地址
int Length; //当前长度
int ListSize; //当前分配存储容量
}HashList;
/*--------------------------
三、部分程序介绍
1、随机生成测试数据
- 先设一商品名数组和以售价数组,手动写入若干商品名&售价,初始化为若干个商品结构体。
- 随机生成商店个数Snum,for循环Snum次向文件输出商店信息。每次循环,输出商店名为包含循环变量的字符串,输出随机生成的信誉,再随机生成商品数Pnum,for循环在所有商品结构体中随机选取Pnum个不同商品信息输出。为方便读取,在两个商店间信息间输出一道分割线。
/*------------------------------------生成商店信息--------------------------------------------------*/
/*
毛巾 10
牙刷 5
牙膏 3
肥皂 10
洗发水 20
沐浴露 20
肥宅快乐水 3
汉堡 10
咖啡 5
地锅鸡 9
鸡排饭 11
电脑 8000
GTX1080 2000
从这些里面随机选6到10个作为生成的商店商品信息 */
void initProduct(product *p,char *name,int price)
{
strcpy(p->name,name);
p->price=price;
p->sales=0;
}
void createFile()
{
//初始化商品(这些商品信息在后面从文件读取时是不可见的)
char *Pname[13]={"毛巾","牙刷","牙膏","肥皂","洗发水","沐浴露","肥宅快乐水","汉堡","咖啡","地锅鸡","鸡排饭","电脑","GTX1080"};
int Pprice[13]={10,5,3,10,20,20,3,10,5,9,11,8000,2000};
product init[13];
for(int i=0;i<13;i++)
initProduct(&init[i],Pname[i],Pprice[i]);
//随机生成初始商店个数 30~60
srand((unsigned)time(NULL));
FILE *p;
if((p=fopen("test.txt","w"))==NULL)
{
printf("can not open file\n");
exit(0);
}
int num=30+rand()%31; //初始化商店数量30~60
int flag[13]; //商品是否加入此店的标志
memset(flag,0,sizeof(flag));
for(int i=1;i<=num;i++)
{
int Pnum=6+rand()%5; //商品数6~10
if(i!=1)
fprintf(p,"\n");
fprintf(p,"No.%d\n",i); //编号
fprintf(p,"store No.%d\n",i); //名字
fprintf(p,"%d\n",rand()%6); //信誉 0~5
fprintf(p,"%d\n\n",Pnum); //商品数
int k;
for(int j=1;j<=Pnum;j++)
{
do k=rand()%13;
while(flag[k]!=0);
flag[k]=1;
fprintf(p,"%s %d %d\n",init[k].name,init[k].price,init[k].sales);
}
fprintf(p,"-------------------\n");
memset(flag,0,sizeof(flag));
}
}
生成的测试数据如下,从上到下为编号、商店名、信誉、商品数、(商品名,价格,销量)
2、建立双向循环链表
题目要求“以信誉度从高至低,并按销量排序”,我理解为先要按信誉度降序分成几个大区间,各区间内按照销量降序排列。
- 在读取信息的同时,按信誉度0到5建立六个双向循环链表,再对每一个链表进行插入排序,按销量排降序。
- 按照信誉降序把各链表连接为一个符合要求的完整链表。
(1)双向链表的插入排序
算法思想和普通插入排序一样,数组下标1到n-1循环,把i左侧看作有序区,右侧看作无序区,每次循环把下标i的元素插入到左侧有序区中。区别在于使用链表操作实现。
1、得到第i个点
设一个指针,从头结点开始遍历链表查找即可
//得到第i个点
DCNode *getNode(DCList &L,int i)
{
int cnt=1;
DCNode *p=L->next;
while(p!=L && cnt<i)
{
p=p->next;
cnt++;
}
if(i>cnt+1 || cnt>i)//i大于表长+1 或 小于1
return NULL;
return p;
}
2、在位置i插入
双向链表的插入比较麻烦,分四步进行
- S->next=P->next
- P->next=S
- S->next->prior=S
- S->prior=P
//在第i位插入
//newOne对应图上S,q对应图上P
bool insertDC(DCList &L,int i,DCNode *newOne)
{
DCNode *p=getNode(L,i);
DCNode *q;
if(p==NULL)
return ERROR;
q=p->prior;
newOne->next=q->next;
q->next=newOne;
newOne->next->prior=newOne;
newOne->prior=q;
return OK;
}
3、删除第i位
删除结点分两步进行
- P->next=S->next
- P->next->prior=P
不要忘记释放结点ai
//删除第i位
bool deletDC(DCList &L,int i)
{
DCNode *p=getNode(L,i);
DCNode *q;
if(p==NULL)
return ERROR;
q=p->prior;
q->next=p->next;
q->next->prior=q;
free(p);
return OK;
}
4、双向循环链表的插入排序
算法思想和普通插入排序一样,区别在于使用链表操作实现。
//插入排序
void DCinsertSort(DCList &L,int n)
{
//进行n-1轮插入,每轮把当前数插入左侧有序序列中
for(int i=2;i<=n;i++)
{
int left=i-1;
DCNode *temp=getNode(L,i);
while(left>=1)//比较,找到插入位置
{
DCNode *LEFT=getNode(L,left);
if(temp->p->sales > LEFT->p->sales)
left--;
else
break;
}
deletDC(L,i);
insertDC(L,left+1,temp);
}
}
(2)连接两个双向循环链表
将链表T连接到链表L头结点后,也是分四步进行
- L->prior->next=T->next
- T->prior->next=L
- T->next->prior=L->prior
- L->prior=T->prior
画图比较麻烦就不画了,大家可以自己拿纸画画看
//连接两个双向循环链表
void linkDCList(DCList &L,DCList T)
{
if(T->next==T)
return;
L->prior->next=T->next;
T->prior->next=L;
T->next->prior=L->prior;
L->prior=T->prior;
}
(3)创建双向循环链表
- 先按信誉分别建立6个单独的双向循环链表
- 分别进行插入排序
- 最后把它们合成一个完整的有序双向循环链表
//创建双向循环链表
/* DCL-建立的双向循环链表
name-要对这个商品建表
SL-已经建立的商店链表 */
bool CreatDCList(DCList &DCL,char *name,storeList SL)
{
//初始化完整的双向循环链表
InitDCList(DCL);
//初始化信用0-5的六个双向链表
DCNode *(DCp)[6];
int cnt[6];
DCp[5]=DCL;
for(int i=0;i<6;i++)
{
cnt[i]=0;
InitDCList(DCp[i]);
}
store *sp=SL->next;
product *pp;
//遍历商店
while(sp)
{
pp=sp->pd->next;
//遍历商店中所有商品
while(pp)
{
//找到目标商品,建立双向链表节点
if(strcmp(name,pp->name)==0)
{
DCNode *newOne=(DCNode*)malloc(sizeof(DCNode));
newOne->p=pp;
newOne->s=sp;
newOne->next=newOne->prior=NULL;
//按信誉度将新建结点插入对应的子链表中
switch(sp->credit)
{
case 0: cnt[0]++;
insertDC(DCp[0],cnt[0],newOne); break;
case 1: cnt[1]++;
insertDC(DCp[1],cnt[1],newOne); break;
case 2: cnt[2]++;
insertDC(DCp[2],cnt[2],newOne); break;
case 3: cnt[3]++;
insertDC(DCp[3],cnt[3],newOne); break;
case 4: cnt[4]++;
insertDC(DCp[4],cnt[4],newOne); break;
case 5: cnt[5]++;
insertDC(DCp[5],cnt[5],newOne); break;
}
break;
}
pp=pp->next;
}
sp=sp->next;
}
for(int i=5;i>=0;i--)
{
//对子链表进行插入排序
DCinsertSort(DCp[i],cnt[i]);
//将子链表接入完整链表中
linkDCList(DCL,DCp[i]);
}
return OK;
}
3、建立商品哈希表
首先要找到所有商店一共有几种商品及它们的总销量信息。按商店链表遍历所有商店的所有商品,建立一个单独的商品链表,在遍历过程中同时对商品种类和销量进行计数。由于使用线性探测再散列法处理冲突,若商品种类太多,哈希表会发生溢出错误,故商品链表建立完毕后,需要先比较哈希表空间和商品种类数,若空间不足则需要动态申请空间补充。接下来遍历刚刚的商品链表,依次计算哈希地址填入哈希表中,用线性探测再散列处理冲突,并释放链表中结点,直到所有商品信息填入哈希表为止。
- 哈希公式为: 6*(商品名第一个字符-‘0’)
- 使用线性探测再散列方法避免冲突
- 由于商品数可能变化,为避免哈希表溢出,使用动态扩展的顺序表作为哈希表
由于本文重点不在哈希表,这里不说得太详细了
//创建哈希表
void CreatHSList(HashList &HSL,storeList SL)
{
store *sp=SL->next;
product *pp;
int cnt=0;//商品计数
//遍历所有商店的所有商品,用一个单链表来统计所有商品及销量
product *pl=(product*)malloc(sizeof(product));
product *ql=pl;
pl->next=NULL;
while(sp)
{
pp=sp->pd->next;
//遍历商店中所有商品
while(pp)
{
if(cheakHsName(ql,pp))
{
cnt++;
product *newOne=(product*)malloc(sizeof(product));
strcpy(newOne->name,pp->name);
newOne->sales=pp->sales;
newOne->next=pl->next;
pl->next=newOne;
pl=pl->next;
}
pp=pp->next;
}
sp=sp->next;
}
//哈希表空间不足
while(HSL.ListSize<cnt)
{
HPNode *newbase =(HPNode *)realloc(HSL.Elem,(HSL.ListSize+HASH_INCREMENT)*sizeof(HPNode));
if(!newbase)
exit(OVERFLOW);
HSL.Elem = newbase;
for(int i=HSL.ListSize-1;i<HSL.ListSize+HASH_INCREMENT;i++)
{
strcpy(HSL.Elem[i].name,"NULL");//这个是判断空位条件,决不能丢
HSL.Elem[i].sales=0;
}
HSL.ListSize += HASH_INCREMENT;
}
//遍历单链表,填哈希表,释放链表
product *t=ql->next;
product *k;//帮助free
while(t)
{
int i=calHsAdd(t->name,HSL);//计算哈希地址
//cout<<i<<endl;
if(strcmp(HSL.Elem[i].name,"NULL")==0)//空位子
{
strcpy(HSL.Elem[i].name,t->name);
HSL.Elem[i].sales=t->sales;
}
else
{
while(strcmp(HSL.Elem[i].name,"NULL")!=0)//线性探测再散列
i=(i+1)%HSL.ListSize;
strcpy(HSL.Elem[i].name,t->name);
HSL.Elem[i].sales=t->sales;
}
k=t->next;
free(t);
t=k;
}
k=t=NULL;
// for(int i=0;i<HSL.ListSize;i++)
// cout<<HSL.Elem[i].name<<" "<<HSL.Elem[i].sales<<endl;
}
//在哈希表中检索销量
void searchHS(char *name,HashList L)
{
int i=calHsAdd(name,L);
while(strcmp(name,L.Elem[i].name)!=0)
{
if(strcmp("NULL",L.Elem[i].name)==0)
{
cout<<"不存在此商品!"<<endl;
return;
}
i=(i+1)%L.ListSize;
}
cout<<"销量:"<<L.Elem[i].sales<<endl;
}
四、效果展示
五、源代码
源代码下载 密码:9vba