数据结构(3)线性表综合应用

  • 整理数据结构学习笔记
  • 基于C++
  • 一道线性表综合应用题

一、问题描述

设计一个程序,对商铺信息管理,商铺信息包括:商铺编号,商铺名,信誉度(0-5),(商品名称1,价格1,销量1),(商品名称2,价格2,销量2),(商品名称3,价格3,销量3)…。
商品名称包括(毛巾,牙刷,牙膏,肥皂,洗发水,沐浴露等6种以上商品),每个商铺具有其中事先确定若干商品及价格,由文件输入,销量初始为0。

[基本要求]

  1. 建立一个单向链表存储所有商铺信息(至少30个),以编号为序,编号从1开始递增,从文件中读取数据,并能将数据存储在文件。商铺信息结点的数据结构自行设计。
  2. 可以增、删商铺。增加商铺,编号自动加一,插入链表尾部;删除商铺,以编号为准,并修改后续结点的编号,保持编号连续性。可增、删商品。
  3. 查询某一种商品名称,建立一个双向循环链表,结点信息是包含该商品的所有商铺编号、商铺名、信誉度、商品名称、价格、销量,以信誉度从高至低,并按销量排序,并逐一显示。
  4. 购买某一商铺的商品,修改单向链表中商品的信息的销量。
  5. 建立一个顺序结构,按商品名Hash分配地址,存储当前每种商品总销量并输出。
  6. 任何的商铺信息变化,实现文件存储。

二、数据结构设计

根据题目要求设计如下结构

  1. 商店单链表,每个结点包含一个商店的相关信息和一个商品单链表头结点指针
  2. 商品单链表,每个结点包含一个商品的相关信息
  3. 双向循环链表,每个结点包含一个商品结点指针和一个商店结点指针
  4. 一个动态扩展的顺序表,存储每个商品及对应销量,用作哈希表
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
//商品单链表 
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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值