C语言范例学习03-中
栈和队列
这两者都是重要的数据结构,都是线性结构。它们在日后的软件开发中有着重大作用。后面会有实例讲解。
两者区别和联系,其实总结起来就一句。栈,后进先出;队列,先进先出。
可以将栈与队列的存储空间比作一个只够一个身位的水管。
栈的水管,有一头被堵住了。所以当人们进去后,想出来就只能让最靠近出口的那位先出去,依次推出。(后进先出)。
队列的水管,类似单向车道。所以当人们进去后,想出来就只能一直向前,走出来,不可以从入口出来。(先进先出)。
所以,道理还是很简单的。接下来就是学习一些专属函数就ok了。
实例090 应用栈实现进制转换
问题:应用栈实现进制转换,可以将十进制数转换为其他进制数。
逻辑:首先我们来想一下进制转换的算法,就如之前提到的,不断地取余,求商。依次循环进行,再按顺序输出,获取最终结果。
代码:
1 #include<stdio.h> 2 typedef struct 3 { 4 DataType *base; 5 //有人问DataType是什么东东。 6 //DataType是一种数据类型,是数据结构专属。采取的是类C的语言。所以需要将其映射为C数据类型。 7 DataType *top; 8 int stacksize; 9 //提醒一句,stack是栈的英文。 10 }SeqStack; 11 12 13 //************接下来就是重点 14 void Initial(SeqStack *s) 15 //栈的初始化函数,构造一个空栈S。 16 { 17 s->base=(DataType *)malloc(STACK_SIZE *sizeof(DataType)); 18 //类似于链表的获取存储空间,连使用的函数都一样。 19 //注意,我这里使用的是base。所以,这个栈是“向上生长”的栈。 20 if(!s->base) 21 exit(-1); 22 //exit()是一个退出函数,其参数是退出状态代码。(0是正常退出状态码) 23 s->top=s->base; 24 //初始化时,栈内为空。从而可以这样初始化其中的top变量。 25 s->stacksize=STACK_SIZE; 26 //获取存储空间大小。 27 } 28 int IsEmpty(SeqStack *S) 29 //判断栈是否为空的函数 30 { 31 return S->top==S->base; 32 //返回值为判断结果。 33 } 34 int IsFull(SeqStack *S) 35 //判断栈是否满了。 36 { 37 return S->top-S->base==STACK_SIZE-1; 38 //注意看,中间那个是减号。 39 } 40 void Push(SeqStack *S,DataType x) 41 //栈的入栈函数 42 { 43 if(IsFull(S)) 44 { 45 printf("栈上溢出"); 46 exit(1); 47 } 48 *S->top++=x; 49 //入栈时,切记 栈指针top,“先压后加”。 50 } 51 DataType Pop(SeqStack *S) 52 //栈的出栈函数 53 { 54 if(IsEmpty(S)) 55 { 56 printf("栈为空"); 57 exit(1); 58 } 59 return *--S->top; 60 //注意上面是*(--s),别问我*--是什么。出栈时,切记 栈指针to[,“先减后弹”。 61 } 62 DataType Top(SeqStack *S) 63 //栈顶变化函数。 64 //因为这个栈是从栈底构建的,所以栈底不变。变化的是栈顶。 65 { 66 if(IsEmpty(S)) 67 { 68 printf("栈为空"); 69 exit(1); 70 } 71 return *(S->top-1); 72 //改变栈顶地址。 73 } 74 //************以上是重点 75 76 77 78 void conversion(int N,int B) 79 //解决问题的进制转换函数 80 { 81 int i; 82 SeqStack *S; 83 Initial(S); 84 //初始化栈S 85 while(N) 86 { 87 Push(S,N%B); 88 N=N/B; 89 } 90 //这个循环不用我讲了吧。就知平时进制转化的循环。 91 while(!IsEmpty(S)) 92 { 93 i=Pop(S); 94 printf("%d",i); 95 //依次输出栈中元素,其实就是我们的结果了。 96 } 97 } 98 void main() 99 //主函数不用细讲。就是完成输入,输出,调用变换函数。 100 { 101 int n,d; 102 printf("Input the integer you want to transform:"); 103 scanf("%d",&n); 104 printf("Input the integer of the system:"); 105 scanf("%d",&d); 106 printf("result:"); 107 conversion(n,d); 108 getch(); 109 }
反思:栈的应用主要就是初始化、判断空、判断满、入栈、出栈、栈顶六个函数。懂了这六个函数,应用就没什么了。
其中的技巧,代码备注也提到了,比如:“先压后加”等。不过这是针对这个栈的,一个“向上生长”的栈。
ps:其实还有用栈设置密码、实现编辑程序等。但其中关于栈的部分基本大同小异,所以不再赘述。
如果,还有不懂,可以看看视屏。比如,张洪瑞与严蔚敏老师的视屏讲解。(我不会告诉你,计算机考研的专业课可是要求严蔚敏老师的书。不过建议张洪瑞老师的视屏。主要严蔚敏老师讲的。总感觉和我不是一个时代,事实上,也确实不是一个时代的。)
实例095 链队列
问题:采取链式存储法编程实现元素入队、出队以及将队列中的元素显示出来,要求整个过程以菜单选择的形式实现。
逻辑:逻辑与栈没有太多区别。将队列的主要操作:创建、入队、出队、展示等功能分为多个函数进行。最后主函数实现题目要求的界面功能,及输入,调用等功能。
代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 typedef int ElemType; 4 //怕你们不懂之前说的DataType到底怎么弄。这个就是一个例子。如果没有这句话,你们会发现在VC里,这个程序报错报炸了。 5 typedef struct node 6 //这个结构体是用来存储节点的。 7 { 8 ElemType data; 9 //节点数据 10 struct node *next; 11 //节点指向下一个节点地址 12 }quenode; 13 //这个节点结构体名称。 14 struct quefr 15 //这个结构体用来存放队列的队首、队尾地址指针。 16 { 17 quenode *front,*rear; 18 //quenode是什么?额,就是我们之前定义的节点结构体。 19 }; 20 void creat(struct quefr *q) 21 //定义函数creat,用来创建、初始化队列。 22 { 23 quenode *h; 24 h=(quenode*)malloc(sizeof(quenode)); 25 //获取存储空间,空间大小与quenode同步。 26 h->next=NULL; 27 //初始化h->next,为空。 28 q->front=h; 29 q->rear=h; 30 //初始化相关的quefr中数据,均为h。(队列刚刚建立,那么队首,队尾当然是同一个元素(结构体quenode的h))。 31 } 32 void enqueue(struct quefr *q,int x) 33 //定义入队函数enqueue,用来实现元素x进入队列q。 34 { 35 quenode *s; 36 //建立一个新的节点s,用来存储新的入队元素x。 37 s=(quenode*)malloc(sizeof(quenode)); 38 //依旧是获取存储空间 39 s->data=x; 40 //向quenode结构体s导入新的入队元素x。 41 s->next=NULL; 42 //s的指针域设置为空,作为初始化。 43 //也许,有人就问了。这里设置为空,那么队列还怎么连接起来啊。 44 //嗯。其实,有关连接方面的数据处理统一交给了quefr处理了。看下面就知道了。 45 q->rear->next=s; 46 //这里实现了队列元素的连接。切记,其中rear指针的数据类型可是quenode,所以可以实现调用next,可以实现连接。 47 q->rear=s; 48 //这时队尾指针地址改变,因为增加了新的元素了。(这个程序设定是队首地址不变的,向队尾添加元素。) 49 //我这个程序设定的是队尾入队,队首出队。 50 } 51 ElemType dequeue(struct quefr *q) 52 //定义出队函数dequeue,用来实现元素离开队列q。(由于,之前提到的队列性质,所以离开队列的函数必然是队首元素。) 53 { 54 quenode *p; 55 ElemType x; 56 //初始化,详细参考上面的。 57 p=(quenode*)malloc(sizeof(quenode)); 58 //有人说,初始化,入队要获取存储空间就算了。为什么出队还要获取存储空间呢? 59 //其实,不是没有道理。但是,1,我们对数据处理部分采用的一直都是结构体quenode,出队是便于数据处理,当然还是得建立结构体,自然就要获取存储空间;2.很简单一句,便于模块化操作。 60 if(q->front==q->rear) 61 //判断原队列是否为空 62 { 63 printf("queue is NULL\n"); 64 x=0; 65 } 66 else 67 { 68 p=q->front->next; 69 //赋值指针p,为q指针的下一个地址。 70 q->front->next=p->next; 71 //重新定位q->front->next,从而重新定位了q。 72 //这两句也许有些人会看得有点晕,可以自己画画图,或者参考一些之前的链表。主要就是把原来q的地址遗失了。重新定位q。 73 if(p->next==NULL) 74 //判断原队列是否就一个元素 75 q->rear=q->front; 76 //重新定位。很好奇这步有没有必要。 77 x=p->data; 78 //获取出队列元素 79 free(p); 80 //释放空白空间。 81 } 82 return(x); 83 //返回出队元素 84 } 85 void display(struct quefr dq) 86 //定义函数display,用于展示队列内的所有元素 87 { 88 quenode *p; 89 p=(quenode*)malloc(sizeof(quenode)); 90 //为p分配存储空间 91 p=dq.front->next; 92 //赋值p 93 while(p!=NULL) 94 //等同于判断dq.front->next是否不为空 95 { 96 printf("data=%d\n",p->data); 97 //展示元素 98 p=p->next; 99 //将计数器指向下一个元素地址。 100 } 101 printf("end\n"); 102 //最终输出end,表示元素展示完毕。 103 } 104 main() 105 //建立主函数作为程序入口,实现题目要求的界面,以及输入,调用,输出。 106 { 107 struct quefr *que; 108 //定义*que的数据类型为struct quefr。 109 int n,i,x,sel; 110 void display(); 111 void creat(); 112 void enqueue(); 113 ElemType dequeue(); 114 do 115 { 116 printf("\n"); 117 printf(" 1 creat queue \n"); 118 printf(" 2 into the queue \n"); 119 printf(" 3 delete from queue \n"); 120 printf(" 4 display \n"); 121 printf(" 5 exit \n"); 122 printf("--------------------------------------------\n"); 123 printf("please choose(1,2,3,4,5)\n"); 124 scanf("%d",&sel); 125 switch(sel) 126 //采用switch判断语句。 127 { 128 case 1: 129 que=(struct quefr*)malloc(sizeof(struct quefr)); 130 creat(que); 131 printf( 132 "please input the number of element which do you want to creat:"); 133 scanf("%d",&n); 134 for(i=1;i<=n;i++) 135 { 136 scanf("%d",&x); 137 enqueue(que,x); 138 } 139 break; 140 case 2: 141 printf("please input the element:"); 142 scanf("%d",&x); 143 enqueue(que,x); 144 break; 145 case 3: 146 printf("x=%d\n",dequeue(que)); 147 break; 148 case 4: 149 display(*que); 150 break; 151 case 5: 152 exit(0); 153 } 154 } 155 while(sel<=4); 156 //使用了do while循环。当sel为5时,sel>4,跳出循环。 157 }
反思:之所以将队列的问题分为多个函数分别解决,是为了更好地模块化操作。越发地感受到编程中模块化操作的重要性,及好处。
实例096 循环缓冲区问题
ps:这个问题原本想将代码打出来的,但我觉得之前队列程序已经将主要架构显现出来了。所以这个问题只会简单一提,如果有人有需要,可以找我要。
逻辑:队列的这个实例主要采用了循环队列。可以很好地解决顺序队列“假溢出“的现象。这里的循环队列和之前循环链表一样,就是将顺序队列构建成一个首尾相连的循环表。这样可以更好地解决我之前在循环链表提到的循环存储问题。
总结:到此为止,栈与队列队列的问题就差不多了。你会发现,这两个就是差不多的东西。事实上,也就两种特殊的顺序表。操作思想是一致的。更重要的是体会到其中越发明显的模块化操作的思想。
串与广义表
编程中的数据处理,无非数值处理与非数值处理,而非数值处理基本上是字符串数据。而现在计算机的硬件及其架构更多地是为了数值计算,所以相对而言,处理字符串数据就更为复杂了。其中广义表是线性表的推广,目前多用于人工智能等领域的表处理语言中。所以,有这方面兴趣的可以多多关注。
实例098 简单的文本编辑器
问题:要求实现三个功能:第一,要求对指定行输入字符串;第二,删除指定行的字符串;第三,显示输入的字符串的内容。
逻辑: 串其实就是由零个,或多个字符串组成的有限序列。
串的存储方式由两种:静态存储与动态存储。 其中动态存储结构又有两种:链式存储结构与堆结构存储。这个问题的解决采用了链式存储结构。
串的链式存储结构是包含数据域和指针域的节点结构(是不是感觉很眼熟,和链表好像啊)。
由于每个节点只存放一个字符,太过浪费空间,为节省空间,故令每个节点存放若干个字符(题目中采用了50个字符为一块),这种结构叫块链结构。(题目中采用了这种结构)。
代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #define MAX 100 4 void Init(); 5 void input(); 6 void Delline(); 7 void List(); 8 int Menu(); 9 //仅仅是函数声明,由于主函数位于最后面,所以这五个语句去掉也可以。 10 11 typedef struct node 12 //定义存放字符串的节点。 13 { 14 char data[50]; 15 struct node *next; 16 }strnode; 17 18 typedef struct head 19 //定义每行的头结点。 20 { 21 int number; 22 int length; 23 strnode *next; 24 }headnode; 25 //如之前的一样,两个结构体,前者作为数据节点,存储数据,后者则负责存储的位置等数据以外的事情。 26 27 headnode Head[MAX]; 28 //定义有100行。 29 30 void Init() 31 //定义函数Init,用来初始化串。 32 { 33 int i; 34 for(i=0;i<MAX;i++) 35 { 36 Head[i].length=0; 37 //设定长度均为0 38 } 39 } 40 41 int Menu() 42 //定义函数Menu,用来控制函数的人机交互界面。 43 { 44 int i; 45 i=0; 46 printf("1.Input\n"); 47 printf("2.Delete\n"); 48 printf("3.List\n"); 49 printf("4.Exit\n"); 50 while(i<=0||i>4) 51 { 52 printf("please choose\n"); 53 scanf("%d",&i); 54 } 55 return i; 56 //返还用户的输入函数 57 } 58 59 void input() 60 //定义函数input,作为串的输入函数。 61 { 62 strnode *p,*find(); 63 int i,j,LineNum; 64 char ch; 65 while(1) 66 { 67 j=-1; 68 //这里j=-1,其实,由于采用的是while(1)循环。计数器的增加在开头 j++。所以,其实还是从j=0开始的。 69 printf("input the number of line(0-100),101-exit:\n"); 70 scanf("%d",&LineNum); 71 //输入要输入字符串的所在行数。 72 if(LineNum<0||LineNum>=MAX) 73 return; 74 //超出可能的行数,即直接返回。 75 printf("please input,#-end\n"); 76 i=LineNum; 77 Head[i].number=LineNum; 78 Head[i].next=(strnode*)malloc(sizeof(strnode)); 79 //申请存储空间。 80 p=Head[i].next; 81 //p就是我们将要存储字符串的头结点(字符串的第一个字符的地址) 82 ch=getchar(); 83 while(ch!='#') 84 { 85 j++; 86 //验证前面所说的j问题 87 if(j>=50) 88 //这里采用了50为一行字符串的上限。 89 { 90 p->next=(strnode*)malloc(sizeof(strnode)); 91 p=p->next; 92 //大于50了,就会重新申请存储空间,继续存储字符串。所以一个head节点只存储50字符串长度的数据。 93 //但是,由于我们并没有修改number,所以在其表现来看,我们输入的超过50字符的字符串还是同一个字符串。 94 //这里可以将这个方法,应用到更多的地方。 95 } 96 p->data[j%50]=ch; 97 //理解j%50即可。 98 ch=getchar(); 99 } 100 Head[i].length=j+1; 101 //因为我们j是从0开始的。 102 } 103 } 104 105 void Delline() 106 //定义函数Delline(),用来删除确定的字符串。 107 { 108 strnode *p,*q; 109 int i,LineNum; 110 while(1) 111 { 112 printf("input the number of line which do you want to delete(0-100),101-exit:\n"); 113 scanf("%d",&LineNum); 114 //输入具体要删除的字符串所在的行数。 115 if(LineNum<0||LineNum>=MAX) 116 return; 117 i=LineNum; 118 p=Head[i].next; 119 //中间这四行代码同input函数一样 120 if(Head[i].length>0) 121 //判断所要删除的函数并非空 122 while(p!=NULL) 123 //判断所要删除字符串的下一个字符串非空时,采取的处理。 124 { 125 q=p->next; 126 free(p); 127 p=q; 128 //和之前链表,栈等的删除相同。 129 } 130 Head[i].length=0; 131 Head[i].number=0; 132 //确定该字符串的头文件中length、number均为0,等同于消除存在。 133 //正如之前Head[i].length>0这句代码,有时可通过对length、number的判断来显示,该Head是否为空。 134 } 135 } 136 137 void List() 138 //自定义函数List(),用来展示所有的字符串。 139 { 140 strnode *p; 141 int i,j,m,n; 142 for(i=0;i<MAX;i++) 143 //老样子,这个串,说白了数据深度就两层。所以两个循环就OK了。第二个循环在后面。要看成整体,中间只是多个判断和顺序。 144 //其中我是用printf一个字符一个字符地输出的。所以一个字符串就是一层循环。 145 //但是,如果你要换成一个字符串一个字符串的输出,类似putstr。自己改啊。 146 { 147 if(Head[i].length>0) 148 //这里就验证了之前提到的用length判断是否为空。空时就不会输出该字符串了。 149 { 150 printf("line%d: ",Head[i].number); 151 //输出所要输出字符串所在的行数。 152 n=Head[i].length; 153 m=1; 154 p=Head[i].next; 155 for(j=0;j<n;j++) 156 //第二个循环。用来将字符串中字符一个一个输出。 157 if(j>=50*m) 158 //还记得我之前在输入函数中提到的50长度的问题吗?这里就有了解决之道。 159 { 160 p=p->next; 161 m++; 162 } 163 else 164 printf("%c",p->data[j%50]); 165 //输出一个个字符。 166 printf("\n"); 167 } 168 } 169 printf("\n"); 170 } 171 172 main() 173 //创建主函数mian(),作为程序的入口。控制程序的选择界面、函数调用等。 174 { 175 int sel; 176 Init(); 177 while(1) 178 { 179 sel=Menu(); 180 switch(sel) 181 { 182 case 1: 183 input(); 184 break; 185 case 2: 186 Delline(); 187 break; 188 case 3: 189 List(); 190 break; 191 case 4: 192 exit(0); 193 } 194 } 195 //主函数不做解释了。这和之前的一模一样啊。 196 }
反思:其中关于块链结构的应用那两段代码,应当好好理解。是个很好用的思路。
实例099 广义表的存储 实例100 广义表的存储
ps:广义表是一种非线性的列表。是线性表和树的推广。广泛应用与人工智能领域。有兴趣的小伙伴要多多注意了。
问题:我将两个问题和在了一起。编程实现用动态链接结构存储广义表与广义表的复制,要求输出该广义表的长度和深度,以及表头和表尾。
逻辑: 在一个广义表中,数据元素可以是一个单一元素,也可以是子表,相应地在对应的存储结构中,存储结构节点也有单元素节点和子表节点之分。单元素节点包含值域和指向其后继结点的指针域。对于子表节点,应包含指向子表中第一个节点的表头指针和指向其后继结点的指针域。为了区分广义表中单元素节点和子表元素,我们设置tag作为标志,当tag=0时表示本节点为单元素,当tag=1时表示本节点为子表。
当tag=1时,说明其为子表节点,则第二域是sublist域,存放的是指向其子表的指针,next域存放指向与该节点同层的直接后继结点的指针,当该节点是所在层的最后一个节点时,此时next为空(和之前链表等,一样)。
广义表的复制可以采用递归实现。
其实,广义表这个东西如果有图,看一下图,就立马懂了。我过会儿找找,有的话,就贴上。(我才不会说,我还不知道怎么添加图片呢。)
代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 typedef char ElemType; 4 //替换的实现 5 typedef struct lnode 6 // 7 { 8 int tag; 9 union 10 //由于,节点元素要么是单一元素,要么是子表元素。所以采用了union来统一管理,同时实现存储空间的节约。 11 { 12 ElemType data; 13 //用来存放单一元素。 14 struct lnode *sublist; 15 //用来存放子表元素--子表头结点指针地址。 16 }val; 17 //值域 18 struct lnode *next; 19 //指向后继节点的指针域。 20 }GLNode; 21 //广义表结构体名。 22 23 void creatGList(struct lnode **gl) 24 //创建函数creatGList(),用来实现广义表的创建。 25 { 26 char ch; 27 ch=getchar(); 28 if(ch=='#') 29 { 30 *gl=NULL; 31 } 32 else if(ch=='(') 33 //输入的字符若是左括号,则建立子表。 34 { 35 *gl=malloc(sizeof(struct lnode)); 36 //创建存储空间。 37 (*gl)->tag=1; 38 //表明是子表 39 creatGList(&((*gl)->val.sublist)); 40 //调用函数creatGList(),创建子表。 41 } 42 else 43 { 44 *gl=malloc(sizeof(struct lnode)); 45 //创建存储空间 46 (*gl)->tag=0; 47 //表明是字符。 48 (*gl)->val.data=ch; 49 //存储输入的字符。 50 } 51 ch=getchar(); 52 if(*gl==NULL) 53 { 54 ; 55 } 56 else if(ch==',') 57 //输入的是",",表示递归构造后继表。 58 { 59 creatGList(&((*gl)->next)); 60 } 61 else 62 (*gl)->next=NULL; 63 return; 64 } 65 66 void printGList(struct lnode *gl) 67 //创建函数printGList(),用于输出广义表。 68 { 69 if(gl->tag==1) 70 //如果类型是子表,则输出括号 71 { 72 printf("("); 73 //先输出左括号 74 if(gl->val.sublist==NULL) 75 { 76 printf("#"); 77 } 78 else 79 { 80 printGList(gl->val.sublist); 81 //递归输出子表 82 } 83 printf(")"); 84 //输出右括号 85 } 86 else 87 //否则,即输出的是字符。 88 printf("%c",gl->val.data); 89 //直接输出字符。 90 if(gl->next!=NULL) 91 { 92 printf(","); 93 printGList(gl->next); 94 //递归输出后继表。 95 } 96 return; 97 } 98 99 int GLLength(GLNode *gl) 100 //创建函数GLLength(),用来计算广义表的长度。 101 { 102 int n=0; 103 gl=gl->val.sublist; 104 while(gl!=NULL) 105 { 106 n++; 107 //长度计数。 108 gl=gl->next; 109 //指向下一个节点。 110 } 111 return n; 112 } 113 114 int GLDepth(GLNode *gl) 115 //创建函数GLDepth(),用来计算广义表的深度。 116 { 117 int max=0,dep; 118 if(gl->tag==0) 119 return 0; 120 gl=gl->val.sublist; 121 if(gl=NULL) 122 return 1; 123 while(gl!=NULL) 124 { 125 if(gl->tag==1) 126 //类型为子表 127 { 128 dep=GLDepth(gl); 129 //递归计算广义表的深度。 130 if(dep>max) 131 max=dep; 132 //记录下最大深度。 133 } 134 gl=gl->next; 135 //指向下一个节点。 136 } 137 return(max+1); 138 } 139 140 GLNode *GLCopy(GLNode *gl) 141 //创建函数GLCopy(),用来实现广义表的复制。 142 { 143 GLNode *q; 144 if(gl==NULL) 145 return NULL; 146 q=(GLNode*)malloc(sizeof(GLNode)); 147 //申请存储空间 148 q->tag=gl->tag; 149 //复制元素类型。 150 if(gl->tag==1) 151 q->val.sublist=GLCopy(gl->val.sublist); 152 //递归复制子表 153 else 154 q->val.data=gl->val.sublist; 155 //递归复制数据元素 156 q->next=GLCopy(gl->next); 157 //复制next信息,指向下一个节点,递归复制。 158 return q; 159 } 160 GLNode *head(GLNode *gl) 161 //创建函数head(),用来实现计算广义表的表头。 162 { 163 GLNode *p=gl->val.sublist; 164 //初始化,赋值p. 165 GLNode *q,*t; 166 //初始化q,t。 167 if(gl==NULL) 168 { 169 printf("NULL\n"); 170 return NULL; 171 } 172 if(gl->tag==0) 173 //如果广义表为空。 174 { 175 printf("atom is not head! \n"); 176 //输出广义表没有表头。 177 return NULL; 178 } 179 if(p->tag==0) 180 //如果元素的类型为字符 181 { 182 q=(GLNode*)malloc(sizeof(GLNode)); 183 //为q申请存储空间。 184 q->tag=0; 185 //记录数据类型 186 q->val.data=p->val.data; 187 //复制数据。 188 q->next=NULL; 189 //单个元素的下一个节点为空。 190 } 191 else 192 { 193 t=(GLNode*)malloc(sizeof(GLNode)); 194 //申请存储空间。 195 t->tag=1; 196 //记录元素的类型为子表 197 t->val.sublist=p->val.sublist; 198 //赋值子表 199 t->next=NULL; 200 //单个元素的下一个节点为空。 201 q=GLCopy(t); 202 //复制子表。 203 free(t); 204 //释放t的存储空间。 205 } 206 return q; 207 //返回q。 208 } 209 210 GLNode *tail(GLNode *gl) 211 //创建函数tail(),用来计算广义表的表尾。 212 //概念同上,故不做注释。参考上一个函数。 213 { 214 GLNode *p=gl->val.sublist; 215 GLNode *q,*t; 216 if(gl==NULL) 217 { 218 printf("NULL\n"); 219 return NULL; 220 } 221 if(gl->tag==0) 222 { 223 printf("atom is not tail!\n"); 224 return NULL; 225 } 226 p=p->next; 227 t=(GLNode*)malloc(sizeof(GLNode)); 228 t->tag=1; 229 t->val.sublist=p; 230 t->next=NULL; 231 q=GLCopy(t); 232 free(t); 233 return q; 234 } 235 236 main() 237 //创建主函数main(),作为程序入口。用来执行输入、调用函数等功能。 238 { 239 int len=0; 240 int dep=0; 241 struct lnode *g,*v; 242 struct lnode *h; 243 printf("Input Example: (a,b,(c,d,(e,f,g),h,i),j)"); 244 printf("\n"); 245 printf("PS:输入的‘()’是圆括号,并不是尖括号。"); 246 //添加备注。 247 printf("\n"); 248 creatGList(&h); 249 //创建广义表。切记这里是&h。 250 len=GLLength(h); 251 //计算广义表长度。 252 dep=GLDepth(h); 253 //计算广义表深度。 254 printf("\n"); 255 printf("The length is:"); 256 printf("%d",len); 257 printf("\n"); 258 printf("The depth is:"); 259 printf("%d",dep); 260 printf("\n"); 261 v=head(h); 262 g=tail(h); 263 //赋值。 264 if(v!=NULL) 265 //计算,并输出广义表的表头。 266 { 267 printf("The Head is:"); 268 printGList(v); 269 printf("\n"); 270 } 271 if(g!=NULL) 272 //计算,并输出广义表的表尾。 273 { 274 printf("The Tail is:"); 275 printGList(g); 276 printf("\n"); 277 } 278 if(h!=NULL) 279 //展示整个广义表。 280 { 281 printf("Glist is:"); 282 printGList(h); 283 printf("\n"); 284 } 285 else 286 printf("Glist is NULL"); 287 }
反思:其中有着不少的小点,需要记忆、理解。值得好好看看。
PS:1.关键字union:union 关键字的用法与struct 的用法非常类似。
union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。例子如下:
union StateMachine
{
char character;
int number;
char *str;
double exp;
};
一个union 只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大长度是double 型态,所以StateMachine 的空间大小就是double 数据类型的大小。
在C++里,union 的成员默认属性页为public。union 主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union。
以上关于union的说明采摘自:http://c.biancheng.net/cpp/html/450.html, 其中还有关于大小端模式、确认系统大小端方法的具体说明,感兴趣的可以看一看。
到这里,栈与队列,串与广义表就完成了。下一次就是这章的最后知识点--树与图。