前言
有一种说法是程序是由数据结构和算法组成的,这很能体现出数据结构在编码中的重要性。而代码优化的能力也是区别有基础的程序员和码农的重要标准,所以对于这一块的学习一定要稳重与细致,每一个章节都要实打实敲出能够实现该种结构的代码才算完成。
数据结构的学习本质上是让我们能见到很多前辈在解决一些要求时间和空间的难点问题上设计出的一系列解决方法,我们可以在今后借鉴这些方法,也可以根据这些方法在遇到具体的新问题时提出自己的解决方法。(所以各种定义等字眼就不用过度深究啦,每个人的表达方式不一样而已)在此以下的所有代码都是仅供参考,绝对不是唯一的答案,任何一种操作能达到相同的结果,只要逻辑上能行的通,复杂度上差不多,是无所谓怎么去实现最后的功能的。
一、抽象数据类型
在一开始学习数据结构的时候不用关注复杂度的问题,等到基础的数据结构学习完之后再去了解复杂度不迟。
学习数据结构之前先了解什么是抽象数据类型。我们现在认识的数据类型就是一些类似整形字符型等表示数据取值范围的类型,而最初的程序员使用语言的是机器语言,即以0和1存储来实现功能,这样数据没有类型,我们每次在对他进行操作的时候都不清楚这个数据可以做的具体操作方式(是加减乘除还是合并),每次存储的时候也要去找相对于的空间。为了解决这个问题后来的程序员引入了数据类型的概念,这样我们再存储的时候我们就不需要再去寻找存储器的地址了,直接定义变量之后赋值就行。
抽象数据类型的用途与普通的数据类型类似,他的本质就是一组数学模型,里面存放着这个模型的具体数值和这个模型能做的具体操作。(比如int能实现加减乘除,字符串能实现字符串的拼接)。而他与数据类型的区别就是,数据类型是前人给我们定义好的,而抽象数据类型是我们自己定义的。
这就可以引出下一章线性表,线性表的属性是什么,操作是什么,他的本质就是一个抽象数据类型。
二、线性结构
定义:除了第一个和最后一个元素,其他每个元素只有一个前和一个后的结构。
线性结构又分为两种:顺序表和链表。
1.顺序表
特性:一组地址连续的存储单元(数组)
主要用到的操作:增删改查,初始化
以下是对应的抽象数据类型 :
parr 用来存放数据 size 当前顺序表内数据个数 length 顺序表容量
1. typedef struct slist 2. { 3. int* parr;//用来存放数据 4. int size;//当前顺序表内数据个数 5. int length;//当前顺序表的容量 6. }slist;
①在最后一位增加
这个操作非常的简单,只要知道当前的顺序表内元素个数,然后在+1的位置执行插入操作就好了。
1. void add_slist(slist* array, int key) 2. { 3. if (array->size == array->length)//先判断是否需要空间 4. { 5. array->length += UNIT; 6. int* newp = (int*)realloc(array->parr, sizeof(int) * (array->length));//第二个数不得等于0 7. if (!newp)//判断是否扩容失败 8. { 9. printf("顺序表扩容失败"); 10. system("pause"); 11. } 12. array->parr = newp; 13. } 14. array->parr[array->size] = key; 15. array->size++; 16. }
那么这里的扩容操作是什么意思呢,这里我们通过数组去实现的顺序表,那众所周知数组的长度是固定的,那么一旦我们添加进去的数据超过了原来的容量,就只能扩容了,这里通过realloc来实现扩容。UNIT是每次扩容的长度,这根据具体的情况进行修改,不过多阐述。
②在最后一位之前插入
从对这个数组的影响上来看,这一个操作与1的增加都是往数组里赋值了一个数据。但从函数的操作上和函数接口的设计上(这个具体看下面的代码)与增加是不同的。
从操作上看,这个数据插入的时候要把所有这个数据以后的数据都向后移动一位,来给这个要赋进去的数据腾出一个位置,然后再插入这个数据。从函数的接口设计上,因为与在最后一位增加的操作不同,插入的操作需要知道你插入的位置,所以还需要往函数里给一个位置的参数。这时候会不会觉得那干脆把增加的操作想象成往最后一个位置做插入,把这两个操作写成一个函数会不会更简单一些?在一些时间有限的竞赛中是推荐这么做的,但在开发项目里是不建议这么做的。(比如你只是想往里面一直放元素,难道你每次还要再指定你的位置吗?而且到后面我们接触的项目规模逐渐变大的时候,每一种非常细小差别的操作都是需要分开的)
1. void iadd_slist(slist* array, int key, int index) 2. { 3. if (array->size == array->length)//先判断是否需要空间 4. { 5. array->length += UNIT; 6. int* newp = (int*)realloc(array->parr, sizeof(int) * (array->length));//第二个数不得等于0 7. if (!newp)//判断是否扩容失败 8. { 9. printf("顺序表扩容失败"); 10. system("pause"); 11. } 12. array->parr = newp; 13. } 14. if (index > array->length) 15. { 16. printf("顺序表查找下标超出最大值"); 17. system("pause"); 18. } 19. array->size++; 20. for (int i = 0; i < array->size - index - 1; i++) 21. { 22. *(array->parr + array->size - i - 1) = *(array->parr + array->size - i - 2); 23. } 24. *(array->parr + index) = key; 25. }
③修改和查找一个数据
修改和查找的操作非常的相似且简单,是给定一个下标返回对应该下标对应的值,或者是修改改下标里的值,那这里就只举一个查找的例子了。
1. int inquiry_slist(slist* array, int index) 2. { 3. if (index > array->length) 4. { 5. printf("顺序表查找下标超出最大值"); 6. system("pause"); 7. } 8. return(*(array->parr + index)); 9. }
④删除一个数据
这里只要把该数据后面的全部数据一个一个前移就能实现删除的操作了。那这里最后的4要不要清除掉呢?视具体情况而定,一般有统计当前元素个数的变量就不用清除最后4的数据了。
1. void delete_slist(slist* array, int index) 2. { 3. if (index > array->size) 4. { 5. printf("顺序表删除下标超过最大值"); 6. system("pause"); 7. } 8. else 9. { 10. for (int i = 0; i < array->size - 1 - index; i++) 11. { 12. *(array->parr + index + i) = *(array->parr + index + i + 1); 13. } 14. } 15. }
这里便能体现出一个顺序表的缺点,虽然进行了删除数据的操作,但是在内存上我并没有减少内存的使用,我删除的那一个内存只是数据消失了但仍然是一个被占有的状态。
⑤初始化顺序表
当程序刚开始的时候是不存在这样的数据类型的,所以我们要创建该类型并完成初始化,具体代码如下。
1. slist init_slist() 2. { 3. slist arr; 4. arr.length = UNIT; 5. arr.parr = (int*)malloc(sizeof(int) * UNIT); 6. if (!arr.parr)//判断初始化分配内存失败 7. { 8. printf("顺序表初始化失败"); 9. system("pause"); 10. } 11. arr.size = 0; 12. arr.length = UNIT; 13. return arr; 14. }
⑥顺序表的其他操作
其实顺序表主要的操作在上面都已经实现完成了,写这一块的意义是想说明学习数据结构和写项目都一样,代码是死的人是活的,不要按照书上写的生搬硬套,哪个代码能更好的实现功能,哪个代码效率高,那他就是优秀的代码。学习数据结构的时候也是,主要功能写完之后为了测试或是为了提高熟练度,可以自行的增加或删除一些操作(像上面的顺序表就没有写释放空间的操作,因为学习顺序表不需要)。下面就是补充的一些操作 清空顺序表(指数据) 销毁顺序表(指内存)打印顺序表 还有测试以上所有功能的main函数。
1. #include <stdio.h> 2. #include <stdlib.h> 3. 4. #define UNIT 5 5. 6. void clear_slist(slist* array)//清空顺序表 7. { 8. if (array->size == 0) 9. { 10. ; 11. } 12. else 13. { 14. for (int i = 0; i < array->size; i++) 15. { 16. *(array->parr + i) = NULL; 17. } 18. } 19. } 20. 21. void destory_slist(slist* array)//销毁顺序表 22. { 23. if (array->parr != NULL) 24. { 25. free(array->parr); 26. array->parr = NULL; 27. } 28. } 29. 30. void print_slist(slist* array)//打印顺序表 31. { 32. for (int i = 0; i < array->size; i++) 33. { 34. printf("%d", *(array->parr + i)); 35. } 36. } 37. 38. int main() 39. { 40. slist slist1 = init_slist(); 41. slist* pslist1 = &slist1; 42. int a = 0; 43. while (a < 10) 44. { 45. add_slist(pslist1, a); 46. a++; 47. } 48. print_slist(pslist1); 49. iadd_slist(pslist1, a, 2); 50. print_slist(pslist1); 51. clear_slist(pslist1); 52. print_slist(pslist1); 53. destory_slist(pslist1); 54. 55. system("pause"); 56. return 0; 57. }
⑦顺序表的优缺点
顺序表有一个特别明显的优点,就是知道下标之后查找和修改的操作效率都非常的高。但同时他的缺点也比较明显,就是在中间插入数据和中间删除数据的时候需要把后面的所有数据全部移动一遍,虽然这次事例中的数据不多,但是在之后的项目里可能会遇到千万条的数据,这时候每一次添加和删除的代价就非常大了;还有一个缺点就是顺序表的长度是固定的,这就导致肯定会有浪费的空间和肯定需要扩容的操作;再还有一个缺点就是对于无序的数组,他的搜索效率底的。
2.单向链表
特性:物理上不连续,逻辑上连续,大小不固定
主要的操作:增(此章还增加了头插)删改查,初始化
在了解他的抽象数据类型之前,我们先要了解几个概念。头指针,就是一个指向链表第一个节点的指针。首元节点,是真正存放数据的第一个节点。带头节点单向链表和不带头节点单向链表,这是有一点区别的两种单向链表,具体的操作区别和实用性后面会说。
这是以上概念的图示,上半部分是带头节点的,下半部分是不带头节点的。
接下来了解一个链表的具体组成。链表是怎么通过逻辑来让物理上不连续的内存变得连续的呢?答案是通过指针,我们在每一个数据的节点里在放一个变量来存储下一个节点所在的地址,就可以一个接着一个的去连接所有的数据了,具体组成如下。
pnext 指向下一个节点的地址 data 存放数据
1. typedef struct linklist 2. { 3. linklist* pnext;//指向下一个节点的地址 4. int date;//存放数据 5. }linklist;
①增加节点
那具体要怎么实现数据的插入呢,首先我们需要执行一个操作,就是定位到我们需要插入的位置,这一步只要一直p = pnext直到到了想要的位置停止就行了。然后我们新建一块内存用来存放我们要插入的数据和pnext,pnext的值等于我们所要插入位置的前一个位置的pnext,然后我们再把前一个位置的pnext指向我插入的这个数据的地址就行了,要是觉得绕看图一下就懂了。
接下来我以不带头节点来举例,增加数据的操作会比带头结点的麻烦,为什么这么说呢?因为如果在不带头结点的第一个位置去插入数据,那头指针指向的位置就必须是新添加进来的这个节点,则需要我们再去改变头指针的值,而带头结点的链表在第一个位置插入数据也是在头节点的下一个位置插入节点,所以是不需要改变头指针的位置的。那么就说明一个正常的插入操作,不带头结点的可能需要改变两个变量(新的节点和头指针),需要两种操作才能实现,而带头结点的一种(新的节点)就行了,这也印证了为什么带头结点的链表使用率更高一点。(带头结点的头节点内存不能说是浪费,一个节点和庞大的项目里的数据比起来实在是太微小了)
1. void headadd_linklist(linklist** p, int key) 2. { 3. linklist* head = (linklist*)malloc(sizeof(linklist)); 4. if (!head) 5. { 6. printf("链表初始化失败"); 7. system("pause"); 8. } 9. else 10. { 11. head->pnext = (*p)->pnext; 12. head->date = key; 13. (*p)->pnext = head; 14. } 15. 16. } 17. 18. void add_linklist(linklist* head, int key, int num) 19. { 20. if (num == 0) 21. { 22. headadd_linklist(&head, key); 23. } 24. else 25. { 26. linklist* p = (linklist*)malloc(sizeof(linklist)); 27. if (!p) 28. { 29. printf("链表初始化失败"); 30. system("pause"); 31. } 32. else 33. { 34. linklist* temp = head; 35. for (int i = 0; i < num; i++) 36. { 37. if (!temp->pnext) 38. { 39. printf("增加位置下标为空指针"); 40. system("pause"); 41. } 42. temp = temp->pnext; 43. } 44. p->pnext = temp->pnext; 45. p->date = key; 46. temp->pnext = p; 47. } 48. } 49. }
从这我们能发现链表的一个坏处,他不如顺序表能够一下到我们想要的位置,就比如如果我们要在链表的最后一个位置插入一个值,那就得先遍历前面全部的数据再去插入操作。从这个方面来讲,如果我们要经常性的进行尾插的操作,我们就可以再写一个尾指针(功能和头指针一样)。
②删除节点
删除操作就更加简单了。我们要做的就是让这一组数据不会在链表中出现就好了。
这里有一个要注意的地方,只改变了前一个节点指向的下一个节点后,要删除的节点的确不存在链表里了,但他在内存里还是处于被占用的状态,所以我们要提前记录要被删除节点的地址(因为不在链表里之后我们就找不到这个节点了),再通过这个地址去free那一块内存。
1. void deletenum_linklist(linklist* headp, int num) 2. { 3. linklist* temp = headp; 4. for (int i = 0; i < num; i++) 5. { 6. if (!temp->pnext) 7. { 8. printf("删除下标为空指针"); 9. system("pause"); 10. } 11. temp = temp->pnext; 12. } 13. linklist* tempfree = temp->pnext; 14. temp->pnext = (temp->pnext)->pnext; 15. free(tempfree); 16. }
③初始化
这里因为举的是无头节点的例子,所以初始化的时候只要建一个头指针就够了,当然也可以提前创建一个第一个节点的内存。有头结点的则需要创建一个头指针和一个头节点。
1. void init_linklist(linklist** p) 2. { 3. *p = (linklist*)malloc(sizeof(linklist)); 4. if (!(*p)) 5. { 6. printf("链表初始化失败"); 7. system("pause"); 8. } 9. (*p)->pnext = NULL; 10. }
这里有个小tip,c和c++里所有的指针,在初始化的时候如果没有具体指向的值,一定要指向null,如果什么都不指那他就是一个野指针,后面是有可能会用不了的。
④其他的操作即测试main函数
改和查的操作比较简单且相似,相信在逻辑理顺和上述代码有自己去敲过之后都能够写的出来的。这里就给一个打印列表和main函数。
1. void printf_linklist(linklist* p) 2. { 3. linklist* temp = p->pnext; 4. while (temp->pnext) 5. { 6. printf("%d", temp->date); 7. temp = temp->pnext; 8. } 9. printf("%d", temp->date); 10. } 11. 12. int main() 13. { 14. linklist* t1; 15. init_linklist(&t1); 16. for (int i = 0; i < 13; i++) 17. { 18. add_linklist(t1, i, i); 19. } 20. printf_linklist(t1); 21. 22. deletenum_linklist(t1, 0); 23. printf_linklist(t1); 24. 25. system("pause"); 26. return 0; 27. }
3.循环链表
循环链表与一般的单项链表的所有其他操作都是相同的,唯一不同的地方是单向链表的尾结点的指针指向的是NULL,而循环链表的尾结点指向的是他的头节点。那么怎么去实现这种操作呢?增删改查的操作不变,只要在链表初始化的时候让他的第一个节点指针指向自己就好了。
这里因为操作与单向链表大同小异,就不放测试代码了。
4.双向循环链表
双向循环链表的实现与单向链表相比稍微复杂一些,但是原理上也是大同小异。双向循环链表在单向链表的基础上实现了一个节点指向前一个节点的功能,我们就可以通过后一个节点去找到前一个节点的位置(这里也能够说明数据结构具体的实现不是一成不变的,而是根据我们想要达到的功能去自由的改动)。双向循环链表的循环部分上一节已经有了,这里主要围绕他的双向的操作来解析
双向循环链表的具体组成看下图,以下是他的数据类型。
pprior 指向前一个节点 pnext 指向后一个节点 data 存放数据
1. typedef struct double_loop_linklist 2. { 3. double_loop_linklist* pprior; 4. double_loop_linklist* pnext; 5. int date; 6. }double_loop_linklist;
双向循环链表与普通链表区别较大的地方是插入和删除操作,下面也会主要围绕这两个操作解释
①插入操作
双向链表的插入操作原理与单向链表相同,操作稍微复杂一点,具体操作看下图。
这里的编号表示插入操作的顺序,这个顺序最好不要变动。
1. void add_double_loop_linklist(double_loop_linklist* head,int key,int num) 2. { 3. double_loop_linklist* p=(double_loop_linklist*)malloc(sizeof(double_loop_linklist)); 4. if(!p) 5. { 6. printf("链表初始化失败"); 7. system("pause"); 8. } 9. else 10. { 11. double_loop_linklist* temp=head; 12. for(int i=0;i<num;i++) 13. { 14. if(!temp->pnext) 15. { 16. printf("增加位置下标为空指针"); 17. system("pause"); 18. } 19. temp=temp->pnext; 20. } 21. p->pnext=temp->pnext; 22. (temp->pnext)->pprior=p; 23. p->date=key; 24. p->pprior=temp; 25. temp->pnext=p; 26. head->date++; 27. } 28. }
②删除操作
删除也相似,只是在单链表删除操作的基础上多一个指针的操作,具体步骤如下。
1. void deleteplace_double_loop_linklist(double_loop_linklist* place) 2. { 3. if(!place) 4. { 5. printf("链表传入空指针"); 6. system("pause"); 7. } 8. double_loop_linklist* temp=place->pnext; 9. place->pnext=(place->pnext)->pnext; 10. (place->pnext)->pprior=place; 11. free(temp); 12. }
③其他操作即main函数
这里就是一些初始化 打印 等调试的函数 还有main函数
1. #include <stdio.h> 2. #include <stdlib.h> 3. 4. void init_double_loop_linklist(double_loop_linklist **p)//初始化链表 5. { 6. *p=(double_loop_linklist*)malloc(sizeof(double_loop_linklist)); 7. if(!(*p)) 8. { 9. printf("链表初始化失败"); 10. system("pause"); 11. } 12. (*p)->pnext=*p; 13. (*p)->pprior=*p; 14. (*p)->date=0; 15. } 16. //查找每个数据的位置 17. double_loop_linklist* findplace_double_loop_linklist(double_loop_linklist* p,int key) 18. { 19. double_loop_linklist* temp=p->pnext; 20. while(temp!=p) 21. { 22. if((temp->pnext)->date==key) 23. { 24. return temp; 25. } 26. temp=temp->pnext; 27. } 28. return NULL; 29. } 30. 31. void printf_double_loop_linklist(double_loop_linklist* p)//打印双向链表 32. { 33. double_loop_linklist* temp=p->pnext; 34. for(int i=0;i<p->date;i++) 35. { 36. printf("%d",temp->date); 37. temp=temp->pnext; 38. } 39. printf("%d",temp->date); 40. } 41. 42. int main() 43. { 44. double_loop_linklist* t1; 45. init_double_loop_linklist(&t1); 46. for(int i=0;i<13;i++) 47. { 48. add_double_loop_linklist(t1,i,i); 49. } 50. printf_double_loop_linklist(t1); 51. 52. deleteplace_double_loop_linklist(findplace_double_loop_linklist(t1,3)); 53. printf_double_loop_linklist(t1); 54. 55. printf_double_loop_linklist(t1); 56. 57. system("pause"); 58. return 0; 59. }
5.栈
栈的设计是模仿的是内存里的堆栈。实现的功能也与堆栈相同,即先进后出,后进先出(很多的内存也是这么产生和销毁的)直观一点看,栈就是加了一定约束条件的链表,在基础的链表上多了数据只能先进后出,后进先出的约束条件。栈的应用随便举个例子:像是函数的调用,项目里总是函数套函数,函数套函数,那我进入最里面的函数时编译器怎么回到原先正在执行的函数呢,这时候就是通过栈来实现(所有的数据结构的用途都要在实际的问题或者是项目里去自己思考,这里就不多说了)
为了实现这种功能我们先了解他的数据类型。
top 栈顶 end 栈底 maxsize当前栈的容量
①插入数据(压栈)
一般的栈有固定大小的内存,通过两个指针来实现增和删的操作(也就是说不论是增加还是删除,实际上程序所占内存大小是不会变得,只是指针指向的起始和结束位置在改变而已)。既然内存大小是固定的,那么在增加的时候我们就需要一个判断栈是否满的操作(top == maxsize);在删除的时候我们就需要一个判断栈是否空的操作(top == 0)。
这时候插入的操作就是给top所在的位置赋值,并把top++,代码如下。
1. void push_shun_stack(int key) 2. { 3. if (top - end == maxsize) 4. { 5. printf("栈满了"); 6. system("pause"); 7. } 8. else 9. { 10. top++; 11. shun_stack[top] = key; 12. } 13. }
②删除操作(出栈)
删除的操作也是通过移动我们的栈顶指针来实现的,我们让这个数据结构取数据的时候只从end取到top。所以我们删除的操作只要让top--就好了,这里删除位置的内存没有消失,甚至连要删除位置存储的数据都没有改变,因为最后取数据只要从end取到top,所以我们根本不用知道top之后的数据是什么样的。
③其他操作和main函数
1. #include <stdio.h> 2. #include <stdlib.h> 3. 4. void init_shun_stack() 5. { 6. shun_stack = (int*)malloc(maxsize * sizeof(int)); 7. end = -1; 8. top = -1; 9. } 10. 11. int main() 12. { 13. init_shun_stack(); 14. push_shun_stack(1); 15. push_shun_stack(2); 16. for (int i = 0; i < 2; i++) 17. { 18. printf("%d\n", shun_stack[i]); 19. } 20. pop_shun_stack(); 21. printf("%d\n", shun_stack[0]); 22. system("pause"); 23. return 0; 24. }
6.队列
栈是先进后出的数据结构,那队列刚好与他相反,是先进先出,后进后出。队列的应用也挺多,比如双十一的抢票(多用户资源竞争),打印机(主机和外部设备速度不匹配)。这里的队列是单向队列,也就是最普通的队列,在单向链表的基础上实现了先进先出,后进后出。下面看他的数据类型。
front 头指针 end 尾指针 maxsize 当前队列容量
1. int* shun_queue;//数组用来表示队列 2. int front;//头指针 3. int end;//尾指针 4. int maxsize = 5;//目前栈最大存储数量
①插入操作
队列的插入操作与栈完全相同,这里直接放代码。
1. void insert_shun_queue(int key) 2. { 3. if (isfull_shun_queue()) 4. { 5. printf("队列满了"); 6. system("pause"); 7. } 8. else 9. { 10. shun_queue[front] = key; 11. front = (front + 1) % maxsize; 12. } 13. }
为什么这里的else的最后一句要这么写呢?为什么判断队满的操作不是直接front == maxsize呢?(这是唯一和栈的写法不同的地方),这里我们先跳过,在后面删除的操作里再细讲。
②删除操作
删除的操作通过移动end指针来实现,每次执行删除的时候把end++就可以了(这里删除位置的内存没有消失,删除位置存储的数据也没有改变,因为最后取数据只要从end取到top,所以我们根本不用知道front之后的数据是什么样的),代码如下。
1. void delete_shun_queue() 2. { 3. if (front == end) 4. { 5. printf("队列空了"); 6. system("pause"); 7. } 8. else 9. { 10. end = (end + 1) % maxsize; 11. } 12. }
这时候如果仅仅是将end++就会出现一个问题,当我一直删除数据,直到end到了maxsize的位置的时候,明明这时候的队列还有空的位置,但不论我们怎么存数据都已经取不出我们存进去的数据了,这时候的front应该也是在maxsize的位置,我们再给front++的时候甚至会出现假溢出的情况(有溢出报错但是队列其实并没有满)。这种情况怎么解决呢?我们要把队列从一个单向的顺序表改成一个环形的顺序表,这样就不会出现刚刚的情况了。但随之而来的是第二个问题,我怎么去区分队列满和队列空呢?如果队列是环形的,我们就不能用maxsize == front这种操作来判断队的状态,而front == end时既有可能是队满也有可能是队空,这种问题怎么解决?解决的方法是少使用一个空间。在这里我们判定front == end 为队空的情况 而(front + 1) % maxsize == end 为队满的情况,这样就能解决无法区分两种状态的问题。
③其他操作即mani函数
1. #include <stdio.h> 2. #include <stdlib.h> 3. 4. void init_shun_queue() 5. { 6. shun_queue = (int*)malloc(maxsize * sizeof(int)); 7. front = end = 0; 8. } 9. 10. int isfull_shun_queue() 11. { 12. if ((front + 1) % maxsize == end) 13. { 14. return 1; 15. } 16. else 17. { 18. return 0; 19. } 20. } 21. 22. int isempty_shun_queue() 23. { 24. if (end == front) 25. { 26. return 1; 27. } 28. else 29. { 30. return 0; 31. } 32. } 33. 34. int main() 35. { 36. init_shun_queue(); 37. insert_shun_queue(1); 38. insert_shun_queue(2); 39. for (int i = end; i < front; i++) 40. { 41. printf("%d\n", shun_queue[i]); 42. } 43. delete_shun_queue(); 44. printf("%d\n", shun_queue[end]); 45. system("pause"); 46. return 0; 47. }