数据结构——链和队列04

  1. 杨辉三角的链队列解法

以下代码结果不能输出!

检查代码似乎没错,因为不会debug,简单调试发现不会检查队列元素的值,暂定第二遍再搞;用的编译器是DEV C++;

之前还遇到个问题,if else语句中,即使只有一句语句,仍然需要加“{}”,否则报错,大致是else没有if 与之匹配。

#include<stdio.h>
#include"Unit3_8.cpp"
#define N 15

int main()
{
	LinkQueue node;
	LinkQueue *lqPtr=&node;
	LinkListNode *sPtr;
	datatype value;
	
	Initial(lqPtr);//初始化
	//0,1,1,0入队
	EnQueue(lqPtr,0);
	EnQueue(lqPtr,1);
	EnQueue(lqPtr,1);
	EnQueue(lqPtr,0);
	
	for(int i=0; i<N; i++)
	{
		sPtr=DeQueue(lqPtr);    //队头元素出队
		if(sPtr!=NULL)
		{
			//队头两元素求和生成新系数
			value=sPtr->data+lqPtr->front->next->data;
			if(sPtr->data!=0) printf("%d",sPtr->data);
			EnQueue(lqPtr, value);//新系数入队 
		 } 
		 if(lqPtr->front->next->data=0)//队头元素为分隔符
		 {
		 	EnQueue(lqPtr,0);    //分隔符入队
			printf("\n"); 
		  } 
	 } 
	 return 0;
}

2.各种队列结构的比较:

总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果无法预估队列的长度时,则用链队列。
在这里插入图片描述
3.队列的应用举例

1)基数排序

基数排序是采用“分配”与“收集”的方法实现的一种排序方法。基数排序法又称“桶子法”,它是通过关键字的部分信息,将要排序的元素分配至某些“桶”中,以此达到排序的作用。例:对12个两位数,要求按递增方式排序。

看完后如不理解为什么进行两次出入队大体是
先对所有元素按个位数入队,然后出队存放在一数组内;得到一数据序列按十位数重复以上操作。
在这里插入图片描述

排序步骤一:首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中,如图所示。
在这里插入图片描述
排序步骤二:分配结束后,将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来,得到仍然无序的数据序列。
在这里插入图片描述
排序步骤三:根据十位数值再来一次分配,分配结果如图所示。
在这里插入图片描述
排序步骤四:分配结束后,将所有桶中所盛的数据依次重新收集串接起来。
在这里插入图片描述
算法设计:
用队列来表示这些桶。设计编号为0 ~ 9十个队列。0 ~ 9 号队列分别与两位数某些数值的 0 ~ 9 对应。
以两位十进制数排序为例
(1)依次扫描每个两位数,若个位数是n,则插入n号队列;
(2)依次从0-9号队列中取出两位数;
(3)再对十位数做1)、2)步的操作。
在这里插入图片描述
取个位、十位
程序实现:

/*=============================================
函数功能:基数排序
函数输入:排序数组、排序数目n、排序数的最大位数m
函数输出:(排序数组)
===============================================*/
void RadixSort(int a[], int n, int m)
{
	int i,j,k,t;
	SeqQueue tub[D];//定义D个基数队列
	
	//初始化D个队列
	for(i=0; i<D; i++)
	{
		initialize_SqQueue(&tub[i]);
	 } 
	 //进行m次分配和收集
	 for(i=0; i<m; i++)
	 {
	 	for(j=0; j<n; j++)               //对n个数字进行处理
		 {
		 	k=(int)(a[j]/pow(D,i))%D;    //取 a[j]中第i位数字
			 Insert_SqQueue(&tub[k],a[j]);//把 a[j]放入第k个队列 
		  } 
		  t=0;
		  //在D个队列中收集数字放回a数组中
		  for(j=0; j<D; j++)
		  {
		  	while(!Empty_SqQueue(&tub[j]))
		  	{
		  		k=Delete_SqQueue(&tub[j]);
		  		a[t]=tub[j].data[k];
		  		t++;
			  }
		   } 
	  } 
}

程序测试:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include"Unit3_7.cpp"//顺序队列操作函数
#define D 10 //桶数
#define N 9 //排序树

/*=============================================
函数功能:基数排序
函数输入:排序数组、排序数目n、排序数的最大位数m
函数输出:(排序数组)
===============================================*/
void RadixSort(int a[], int n, int m)
{
	int i,j,k,t;
	SeqQueue tub[D];//定义D个基数队列
	
	//初始化D个队列
	for(i=0; i<D; i++)
	{
		initialize_SqQueue(&tub[i]);
	 } 
	 //进行m次分配和收集
	 for(i=0; i<m; i++)
	 {
	 	for(j=0; j<n; j++)               //对n个数字进行处理
		 {
		 	k=(int)(a[j]/pow(D,i))%D;    //取 a[j]中第i位数字
			 Insert_SqQueue(&tub[k],a[j]);//把 a[j]放入第k个队列 
		  } 
		  t=0;
		  //在D个队列中收集数字放回a数组中
		  for(j=0; j<D; j++)
		  {
		  	while(!Empty_SqQueue(&tub[j]))
		  	{
		  		k=Delete_SqQueue(&tub[j]);
		  		a[t]=tub[j].data[k];
		  		t++;
			  }
		   } 
	  } 
}
int main()
{
	int test[N]={710,342,45,686,6,429,134,68,24};
	int i,m=3;
	RadixSort(test, N, m);
	for(i=0; i<N; i++)
	printf("%d ", test[i]);
	return 0;
 } 

在这里插入图片描述
2)卡片游戏

一叠牌,从上往下依次编号为1~n,发牌的方式为每次都从上面拿两张,先拿的一张发给客人,后拿的一张放到整叠牌的最后。输入n,输出每次发给客人的牌的编号,以及最后剩下的2张牌。

采用循环队列的方法如下:

#include<stdio.h>
#define MAXN 50
#define FALSE 0
int queue[MAXN];
int main()
{
	int n, front, rear;
	char ch;
	printf("要继续输入y\n");
	ch=getchar();
	
	while(ch=='y')
	{
		printf("请输入牌的总张数\n");
		scanf("%d",&n);
		while(n<2||n>=50)    //n 需要大于1并且小于50
		{
			printf("牌的总数应该在2到50之间\n");
			scanf("%d",&n);
		 } 
		 for(int i=0; i<n; i++)//初始化队列
		 {
		 	queue[i]=i+1;
		  } 
		  front=0;
		  rear=n;
		  while(front!=rear)
		  {
		  	printf("%d ",queue[front]);    //输出每次发给客人的牌的编号
			  //当队头指针未超过已用表长,后移一位
			  front==n-1?front%=n-1:++front;
			  if(rear==n) rear=0;         //否则,使其重新指向queue[0]
			  queue[rear]=queue[front];   //后拿的一张放到整叠牌的最后
			  rear==n-1?rear%=n-1:++rear;
			  //队头指针后移一位,所指内容为先拿的那张牌的编号
			  front==n-1?front%=n-1:++front; 
		  }
		  fflush(stdin);                  //清屏
		  printf("\n要继续测试请输入 y\n");
		  ch=getchar(); 
	}
	return FALSE;
 } 

采用链表的方法如下:
Unit3_8.cpp是关于链表的基本操作;
Front函数是取队头的值;

int Front(LinkQueue *lq, datatype *x) {
	if ( Empty(lq))  return FALSE;  	//队空
	*x = lq->front->next->data;			//取队头结点值
	return *x;
}

#include<stdio.h>
#include<stdlib.h>
#include"Unit3_8.cpp"

int main() {
	char ch;
	datatype *x=(datatype *)malloc(sizeof(datatype));//x 为指向队头元素的指针
	LinkQueue *lq=(LinkQueue *)malloc(sizeof(LinkQueue));//lq为队列
	int n;
	Initial(lq);

	printf("要继续输入y\n");
	ch=getchar();

	while(ch=='y')
	{
		printf("请输入牌的总张数\n");
		scanf("%d",&n);
		while(n<2||n>=50) { //n须大于1小于50
			printf("牌的总数应该在2到50之间\n");
			scanf("%d",&n);
		}

		for(int i=0; i<n; i++) {           //初始化队列
			EnQueue(lq, i+1);
		}
		while(Front(lq,x)) {
			printf("%d",*x);            //输出每次发给客人的牌的编号
			DeQueue(lq);               //取下队头结点
			if(!Empty(lq)) {           //判队空
				Front(lq,x);
				EnQueue(lq,*x);        //后拿的一张放到整叠牌的最后
				DeQueue(lq);
			}
		}
		fflush(stdin);//清屏
		printf("\n要继续测试请输入y\n");
		ch=getchar();
	}
	return FALSE;
}

3)多个相关广义表的输出

设有线性表,记为L=(a1,a2,a3,…,an)
其中L为表名,ai为表中元素。本例中ai有数值和字母两种情形(表中元素类型不同的线性表被称为广义线性表),
当ai为数值——元素;
当ai为大写字母——另一个表。

例:设有线性表
L=(6,3,4,K,8,0,8)
K=(1,5,8,P,9,4)
P=(4,7,8,9)
线性表L中包含线性表K,若此时K表中又出现L,则是循环定义。
本例的测试样例不能出现循环定义的线性表。

按线性表中字母出现的先后顺序编程输出只有数值的完整线性表
输出: 6,3,4,8,0,8 1,5,8,9,4 4,7,8,9

1.算法思路

(1)建立两种队列:

  • 线性表队列Q1:由Q1[A]到Q1[Z]共26个线性表队列,放各线性表中的内容,如线性表L的内容存放在Q1[L]中,线性表K的内容存放在Q1[K]中。
  • 字母队列Q2:只存放线性表名。记录线性表输出的先后顺序。

(2)根据题目要求,字母队列Q2中应先放入字母L,表示首先输出L表中的数据。
(3)将Q2队头元素ai出队,找到线性表队列Q1[ai],将其内容全部出队并输出,如果输出的数据中包含字母,则字母入Q2队列。
(4)重复步骤(3),直到字母队列Q2为空。
在这里插入图片描述
2.数据结构设计

【方法一】循环队列实现方法

队列Q1包括26个队列,队列放对应线性表的数值。
队列Q2放各线性表中的大写字母。
在这里插入图片描述
(1)Q1队列结构

#define N 26// 字母队列个数  
#define LEN 128// 循环队列长度

struct node {
	char  name; //线性表名
	char  data[LEN];//线性表内容
}   LetterQueue[N];//字母队列

(2)Q2队列结构

char Q2[N]

【方法二】链队列实现方法

可以设计用链队列来处理。Q1队列中,一个字母队列是一个链表,有26个链队列,链的头尾指针管理可以用结构做在一起,这样方便管理。Q2队列是一个单独的链队列。
在这里插入图片描述
在这里插入图片描述
(1)链表结点结构

struct node
{ char data;
  struct node *next;
} LinkListNode;

(2)链队列结构

typedef struct
{ 
 LinkListNode *front,*rear;
   // 链队列头尾指针
} LinkQueue;

(3)Q1队列结构

typedef struct GeneralizedLinearListQueue
{
     char name; //线性表名
     LinkQueue  *LinearList;
     //线性表链表头尾指针结构地址
} GLQueue; //广义表链队列结构

在这里插入图片描述
Q2队列结构(链表结点结构)

struct node
{   char data;
    struct node *next;
} LinkListL

伪代码描述:

初始化Q1队列
建立Q1字母链队列(队列数据由键盘输入)
输入线性表名ch、线性表内容gets(sPtr)
根据ch内容,将线性表内容sPtr插入相应字母队列

初始化Q2队列,放置首个线性表名

Q2非空,根据队头字母在Q1中找相应队列
取线性表表名存放在Name
若Q1中有表名为Name的线性表
将Name队列元素全部出队
若Name队列元素data是大写字母(线性表名)则data入Q2队列
data为数字则输出

程序实现:

#include<stdio.h>
#include"Unit3_8.cpp"
#define N 26        //字母队列个数
#define LEN 128     //循环队列长度
typedef struct GeneralizedLinearListQueue {
	char name;//线性表名
	LinkQueue *LinearList;//线性表链表头尾指针结构地址
} GLQueue;//广义表链队列结构
typedef LinkQueue *LetterQueue;

int main() {
	int i;
	char Name,data;
	char ch;
	char a[LEN];
	char *sPtr=a;
	LinkListNode *linknodePtr1,*linknodePtr2;    //Q1,Q2队列结点指针
	char FirstLinearListName;//首次处理的线性表名

	GLQueue Q1[N];  //26个广义表链队列
	LetterQueue Q2;

	//初始化Q1队列
	for(i=0; i<N; i++) {
		Q1[i].name='\0';
		Q1[i].LinearList=(LinkQueue *)malloc(sizeof(LinkQueue));
		Initial(Q1[i].LinearList);
	}

	//建立Q1字母链队列
	while(1) {
		printf("输入一个大写的线性表名,输入@停止\n");
		ch=getchar();
		fflush(stdin);//清空标准输入缓冲里多余的数据
		if(ch=='@') break;
		Q1[ch-'A'].name=ch;
		printf("输入线性表内容\n");
		gets(sPtr);
		fflush(stdin);
		while(*sPtr!='\0')
			EnQueue(Q1[ch-'A'].LinearList,*sPtr++);
	}
	printf("输入首次处理线性表名");
	scanf("%c",&FirstLinearListName);

	//初始化Q2队列
	Q2=(LinkQueue *)malloc(sizeof(LinkQueue));
	Initial(Q2);
	//首次从FirstLinearListName线性表开始处理
	EnQueue(Q2,FirstLinearListName);

	//Q2非空,根据队头字母在Q1中找相应队列
	while(!Empty(Q2)) {
		//Q2元素出队,其地址记录在linknodePtr2
		linknodePtr2=DeQueue(Q2);
		if(linknodePtr2 != NULL) {
			Name=linknodePtr2->data;//取线性表表名存放在Name中
			free(linknodePtr2);//释放Q2出队元素结点
		} else {
			printf("Delete_SeqQueue Failed,Error!\n");
			return -1;//main 异常返回
		}
		//Q1中有表名为Name的线性表
		if(Q1[Name-'A'].name==Name) {
			//将Name队列元素全部出队
			while(!Empty(Q1[Name-'A'].LinearList)) {
				//Name队列元素出队,其地址记录在linknodePtr1
				linknodePtr1=DeQueue(Q1[Name-'A'].LinearList);
				data=linknodePtr1->data;
				//若Name队列元素data是大写字母——线性表名,data入Q2队列
				if(data>='A'&&data<='Z')
					EnQueue(Q2,data);
				else printf("%c",data);//data为数字,则输出
				free(linknodePtr1);//释放Q1出队元素结点
			}
			Q1[Name-'A'].name='\0';//清除线性表名
		} else {
			printf("\nCan not find the %c List!\n",Name);
			return -1;//main异常返回
		}
	}
	printf("\n");
	return 0;//main正常返回
}

小结——栈各概念间的联系
在这里插入图片描述
队列各概念间的联系
在这里插入图片描述

顺序栈与链式栈实现方法的比较
通过入栈和出栈时间比较可以看出,数组实现在性能上还是有明显优势的,这是因为数组实现的栈在增加和删除元素时并不需要移动大量的元素,只是在扩大数组容量时,需要进行复制。然而链表实现的栈在入栈时需要将数据包装成结点,出栈时需要从结点中取出数据,同时还要维护栈顶指针和前驱指针。

链队列和循环队列比较
(1)时间上:循环队列事先申请好空间,使用期间不释放。链队列每次申请和释放结点存在一些时间开销,如果入队出队操作频繁,链队列性能稍差。
(2)空间上:循环队列必须有一个固定长度,所以可能会有队列空间利用率不高的问题。链队列不存在这个问题,在空间上更为灵活。

循环队列与链队列的选用原则
(1)在可以确定队列长度最大值的情况下,建议用循环队列。
(2)如果无法预估队列的长度,则用链队列。

===========================================================
线性表有两端,结点插入删除位置没有限;
若是特别地只在一端做运算,这就成了栈;
队列的出与入分别要在线性表的两个端。

把那结点顺序地存,
对栈而言就是顺序的栈,
在队列就是空间序列的循环。

结点离散存用指针域相连称为是链,
链栈与链队列看名称就知其意涵,
线性表的运算了熟于心,栈与队列便不难。

递归调用中那层层深入、步步退出,用的就是栈;
但凡是数据在那一头出,另一头入,队列的模型直接搬;
特例情形是存储空间的空与满。

发布了26 篇原创文章 · 获赞 3 · 访问量 1475

猜你喜欢

转载自blog.csdn.net/herui7322/article/details/104089713