基本的抽象数据类型(ADT)是编写C程序必要的过程,这类ADT有链表、堆栈、队列和树等,本文主要讲解下堆栈的几种实现方法以及他们的优缺点。

  堆栈(stack)的显著特点是后进先出(Last-In First-Out, LIFO),其实现的方法有三种可选方案:静态数组、动态分配的数组、动态分配的链式结构。

  静态数组:特点是要求结构的长度固定,而且长度在编译时候就得确定。其优点是结构简单,实现起来方便而不容易出错。而缺点就是不够灵活以及固定长度不容易控制,适用于知道明确长度的场合。

  动态数组:特点是长度可以在运行时候才确定以及可以更改原来数组的长度。优点是灵活,缺点是由此会增加程序的复杂性。

  链式结构:特点是无长度上线,需要的时候再申请分配内存空间,可最大程度上实现灵活性。缺点是链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。

  首先先确定一个堆栈接口的头文件,里面包含了各个方案下的函数原型,放在一起是为了实现程序的模块化以及便于修改。然后再接着分别介绍各个方案的具体实施方法。

  堆栈接口stack.h文件代码:

[cpp]  view plain  copy
  1. /* 
  2. ** 堆栈模块的接口 stack.h 
  3. */  
  4. #include<stdlib.h>  
  5.   
  6. #define STACK_TYPE int /* 堆栈所存储的值的数据类型 */  
  7.   
  8. /* 
  9. ** 函数原型:create_stack 
  10. ** 创建堆栈,参数指定堆栈可以保存多少个元素。 
  11. ** 注意:此函数只适用于动态分配数组形式的堆栈。 
  12. */  
  13. void create_stack(size_t size);  
  14.   
  15. /* 
  16. ** 函数原型:destroy_stack 
  17. ** 销毁一个堆栈,释放堆栈所适用的内存。 
  18. ** 注意:此函数只适用于动态分配数组和链式结构的堆栈。 
  19. */  
  20. void destroy_stack(void);  
  21.   
  22. /* 
  23. ** 函数原型:push 
  24. ** 将一个新值压入堆栈中,参数是被压入的值。 
  25. */  
  26. void push(STACK_TYPE value);  
  27.   
  28. /* 
  29. ** 函数原型:pop 
  30. ** 弹出堆栈中栈顶的一个值,并丢弃。 
  31. */  
  32. void pop(void);  
  33.   
  34. /* 
  35. ** 函数原型:top 
  36. ** 返回堆栈顶部元素的值,但不改变堆栈结构。 
  37. */  
  38. STACK_TYPE top(void);  
  39.   
  40. /* 
  41. ** 函数原型:is_empty 
  42. ** 如果堆栈为空,返回TRUE,否则返回FALSE。 
  43. */  
  44. int is_empty(void);  
  45.   
  46. /* 
  47. ** 函数原型:is_full 
  48. ** 如果堆栈为满,返回TRUE,否则返回FALSE。 
  49. */  
  50. int is_full(void);  

  一、静态数组堆栈

  在静态数组堆栈中,STACK_SIZE表示堆栈所能存储的元素的最大值,用top_element作为数组下标来表示堆栈里面的元素,当top_element == -1的时候表示堆栈为空;当top_element == STACK_SIZE - 1的时候表示堆栈为满。push的时候top_element加1,top_element == 0时表示第一个堆栈元素;pop的时候top_element减1。

  a_stack.c 源代码如下:

[cpp]  view plain  copy
  1. /* 
  2. **  
  3. ** 静态数组实现堆栈程序 a_stack.c ,数组长度由#define确定 
  4. */  
  5.   
  6. #include"stack.h"  
  7. #include<assert.h>  
  8. #include<stdio.h>  
  9.   
  10. #define STACK_SIZE 100 /* 堆栈最大容纳元素数量 */  
  11.   
  12. /* 
  13. ** 存储堆栈中的数组和一个指向堆栈顶部元素的指针 
  14. */  
  15. static STACK_TYPE stack[STACK_SIZE];  
  16. static int top_element = -1;  
  17.   
  18. /* push */  
  19. void push(STACK_TYPE value)  
  20. {  
  21.     assert(!is_full()); /* 压入堆栈之前先判断是否堆栈已满*/  
  22.     top_element += 1;  
  23.     stack[top_element] = value;  
  24. }  
  25.   
  26. /* pop */  
  27. void pop(void)  
  28. {  
  29.     assert(!is_empty()); /* 弹出堆栈之前先判断是否堆栈已空 */  
  30.     top_element -= 1;  
  31. }  
  32.   
  33. /* top */  
  34. STACK_TYPE top(void)  
  35. {  
  36.     assert(!is_empty());  
  37.     return stack[top_element];  
  38. }  
  39.   
  40. /* is_empty */  
  41. int is_empty(void)  
  42. {  
  43.     return top_element == -1;  
  44. }  
  45.   
  46. /* is_full */  
  47. int is_full(void)  
  48. {  
  49.     return top_element == STACK_SIZE - 1;  
  50. }  
  51.   
  52. /* 
  53. ** 定义一个print函数,用来打印堆栈里面的元素。 
  54. */  
  55. void print(void)  
  56. {  
  57.     int i;  
  58.     i = top_element;  
  59.     printf("打印出静态数组堆栈里面的值: ");  
  60.     if(i == -1)  
  61.         printf("这是个空堆栈\n");  
  62.     while(i!= -1)  
  63.         printf("%d ",stack[i--]);  
  64.     printf("\n");  
  65. }  
  66. int main(void)  
  67. {  
  68.     print();  
  69.     push(10); push(9); push(7); push(6); push(5);  
  70.     push(4);  push(3); push(2); push(1); push(0);  
  71.     printf("push压入数值后:\n");  
  72.     print();  
  73.     printf("\n");  
  74.     pop();  
  75.     pop();  
  76.     printf("经过pop弹出几个元素后的堆栈元素:\n");  
  77.     print();  
  78.     printf("\n");  
  79.     printf("top()调用出来的值: %d\n",top());  
  80.     return 1;  
  81. }  
  结果如下图:



  二、动态数组堆栈

  头文件还是用stack.h,改动的并不是很多,增加了stack_size变量取代STACK_SIZE来保存堆栈的长度,数组由一个指针来代替,在全局变量下缺省为0。

  create_stack函数首先检查堆栈是否已经创建,然后才分配所需数量的内存并检查分配是否成功。destroy_stack函数首先检查堆栈是否存在,已经释放内存之后把长度和指针变量重新设置为零。is_empty 和 is_full 函数中添加了一条断言,防止任何堆栈函数在堆栈被创建之前就被调用。

  d_stack.c源代码如下:

[cpp]  view plain  copy
  1. /* 
  2. ** 动态分配数组实现的堆栈程序 d_stack.c 
  3. ** 堆栈的长度在创建堆栈的函数被调用时候给出,该函数必须在任何其他操作堆栈的函数被调用之前条用。 
  4. */  
  5. #include"stack.h"  
  6. #include<stdio.h>  
  7. #include<malloc.h>  
  8. #include<assert.h>  
  9.   
  10. /* 
  11. ** 用于存储堆栈元素的数组和指向堆栈顶部元素的指针 
  12. */  
  13. static STACK_TYPE *stack;  
  14. static int        stack_size;  
  15. static int        top_element = -1;  
  16.   
  17. /* create_stack */  
  18. void create_stack(size_t size)  
  19. {  
  20.     assert(stack_size == 0);  
  21.     stack_size = size;  
  22.     stack = (STACK_TYPE *)malloc(stack_size * sizeof(STACK_TYPE));  
  23.     if(stack == NULL)  
  24.         perror("malloc分配失败");  
  25. }  
  26.   
  27. /* destroy */  
  28. void destroy_stack(void)  
  29. {  
  30.     assert(stack_size > 0);  
  31.     stack_size = 0;  
  32.     free(stack);  
  33.     stack = NULL;  
  34. }  
  35.   
  36. /* push */  
  37. void push(STACK_TYPE value)  
  38. {  
  39.     assert(!is_full());  
  40.     top_element += 1;  
  41.     stack[top_element] = value;  
  42. }  
  43.   
  44. /* pop */  
  45. void pop(void)  
  46. {  
  47.     assert(!is_empty());  
  48.     top_element -= 1;  
  49. }  
  50.   
  51. /* top */  
  52. STACK_TYPE top(void)  
  53. {  
  54.     assert(!is_empty());  
  55.     return stack[top_element];  
  56. }  
  57.   
  58. /* is_empty */  
  59. int is_empty(void)  
  60. {  
  61.     assert(stack_size > 0);  
  62.     return top_element == -1;  
  63. }  
  64.   
  65. /* is_full */  
  66. int is_full(void)  
  67. {  
  68.     assert(stack_size > 0);  
  69.     return top_element == stack_size - 1;  
  70. }  
  71.   
  72.   
  73. /* 
  74. ** 定义一个print函数,用来打印堆栈里面的元素。 
  75. */  
  76. void print(void)  
  77. {  
  78.     int i;  
  79.     i = top_element;  
  80.     printf("打印出动态数组堆栈里面的值: ");  
  81.     if(i == -1)  
  82.         printf("这是个空堆栈\n");  
  83.     while(i!= -1)  
  84.         printf("%d ",stack[i--]);  
  85.     printf("\n");  
  86. }  
  87. int main(void)  
  88. {  
  89.     create_stack(50);  
  90.     print();  
  91.     push(10); push(9); push(7); push(6); push(5);  
  92.     push(4);  push(3); push(2); push(1); push(0);  
  93.     printf("push压入数值后:\n");  
  94.     print();  
  95.     printf("\n");  
  96.     pop();  
  97.     pop();  
  98.     printf("经过pop弹出几个元素后的堆栈元素:\n");  
  99.     print();  
  100.     printf("\n");  
  101.     printf("top()调用出来的值: %d\n",top());  
  102.     destroy_stack();  
  103.     return 1;  
  104. }  
  结果如下图:



  三、链式堆栈

  由于只有堆栈顶部元素才可以被访问,因此适用单链表可以很好实现链式堆栈,而且无长度限制。把一个元素压入堆栈是通过在链表头部添加一个元素实现。弹出一个元素是通过删除链表头部第一个元素实现。由于没有长度限制,故不需要create_stack函数,需要destroy_stack进行释放内存以避免内存泄漏。

  头文件stack.h 不变,l_stack.c 源代码如下:

[cpp]  view plain  copy
  1. /* 
  2. ** 单链表实现堆栈,没有长度限制 
  3. */  
  4. #include"stack.h"  
  5. #include<stdio.h>  
  6. #include<malloc.h>  
  7. #include<assert.h>  
  8.   
  9. #define FALSE 0  
  10.   
  11. /* 
  12. ** 定义一个结构以存储堆栈元素。 
  13. */  
  14. typedef struct STACK_NODE  
  15. {  
  16.     STACK_TYPE value;  
  17.     struct STACK_NODE *next;  
  18. } StackNode;  
  19.   
  20. /* 指向堆栈中第一个节点的指针 */  
  21. static StackNode *stack;  
  22.   
  23. /* create_stack */  
  24. void create_stack(size_t size)  
  25. {}  
  26.   
  27. /* destroy_stack */  
  28. void destroy_stack(void)  
  29. {  
  30.     while(!is_empty())  
  31.         pop();  /* 逐个弹出元素,逐个释放节点内存 */  
  32. }  
  33.   
  34. /* push */  
  35. void push(STACK_TYPE value)  
  36. {  
  37.     StackNode *new_node;  
  38.     new_node = (StackNode *)malloc(sizeof(StackNode));  
  39.     if(new_node == NULL)  
  40.         perror("malloc fail");  
  41.     new_node->value = value;  
  42.     new_node->next = stack;  /* 新元素插入链表头部 */  
  43.     stack = new_node;       /* stack 重新指向链表头部 */  
  44. }  
  45.   
  46. /* pop */  
  47. void pop(void)  
  48. {  
  49.     StackNode *first_node;  
  50.       
  51.     assert(!is_empty());  
  52.     first_node = stack;  
  53.     stack = first_node->next;  
  54.     free(first_node);  
  55. }  
  56.   
  57. /* top */  
  58. STACK_TYPE top(void)  
  59. {  
  60.     assert(!is_empty());  
  61.     return stack->value;  
  62. }  
  63.   
  64. /* is_empty */  
  65. int is_empty(void)  
  66. {  
  67.     return stack == NULL;  
  68. }  
  69.   
  70. /* is_full */  
  71. int is_full(void)  
  72. {  
  73.     return FALSE;  
  74. }  
  75.   
  76.   
  77. /* 
  78. ** 定义一个print函数,用来打印堆栈里面的元素。 
  79. */  
  80. void print(void)  
  81. {  
  82.     StackNode *p_node;  
  83.     p_node = stack;  
  84.     printf("打印出链式堆栈里面的值: ");  
  85.     if(p_node == NULL)  
  86.         printf("堆栈为空\n");  
  87.     while(p_node != NULL)  
  88.     {  
  89.         printf("%d ", p_node->value);  
  90.         p_node = p_node->next;  
  91.     }  
  92.     printf("\n");  
  93. }  
  94. int main(void)  
  95. {  
  96.     print();  
  97.     push(10); push(9); push(7); push(6); push(5);  
  98.     push(4);  push(3); push(2); push(1); push(0);  
  99.     printf("push压入数值后:\n");  
  100.     print();  
  101.     printf("\n");  
  102.     pop();  
  103.     pop();  
  104.     printf("经过pop弹出几个元素后的堆栈元素:\n");  
  105.     print();  
  106.     printf("\n");  
  107.     printf("top()调用出来的值: %d\n",top());  
  108.     destroy_stack();  
  109.     return 1;  
  110. }  
<think>我们正在讨论数据结构中关于的操作,特别是清空的方法和实现。根据之前的引用内容,清空通常是将顶指针重置到初始状态(如-1),而不是释放的存储空间。销毁则是释放所占用的内存资源。 根据引用[1]中的清空操作: ```c /* 清空 */ bool ClearStack(SqStack &S) { if (S.top == -1) return false; S.top = -1; // 将顶指针指向-1,遍历的时候到top就结束 return true; } ``` 这里,清空的操作非常简单,直接将顶指针top置为-1(初始状态)即可。这样中原来的数据虽然没有被覆盖,但因为顶指针已经指向了初始位置,后续的操作会覆盖原来的数据,因此逻辑上已经被清空。 引用[3]中也提到了初始化时是将顶指针置为-1,并且将数组中元素置0(可选操作)。清空的操作与初始化类似,但通常不需要将数组中的每个元素都重置(除非有特殊的安全要求),因为只需要重置顶指针,在逻辑上就为空了。 另外,注意清空和销毁的区别: - 清空(ClearStack):重置的状态,使其成为空,但的存储空间仍然保留,可以继续使用。 - 销毁(DestroyStack):释放所占用的内存资源,不再存在。引用[1]中销毁的操作为: ```c /* 销毁 */ bool DestroyStack(SqStack &S) { if (S.top == -1) return false; free(S.data); // 释放的存储空间 return true; } ``` 注意:销毁的操作中释放了的存储数组(S.data),并将顶指针置为-1(在引用[1]的销毁操作中没有重置top,但通常销毁后结构本身可能也不再使用,所以重置top不是必须的,但释放内存是必须的)。另外,如果结构本身是动态分配的(如引用[1]中的是静态数组,但如果是动态分配的整个结构体,则销毁时需要释放整个结构体),则销毁方式会有所不同。 下面我将详细介绍清空的方法和实现,包括顺序(静态分配和动态分配)和链式。 ## 一、顺序的清空操作 ### 1. 静态分配的顺序 静态分配的顺序使用固定大小的数组存储元素,清空操作只需重置顶指针。 ```c // 结构定义 #define MAXSIZE 100 // 的最大容量 typedef struct { int data[MAXSIZE]; // 静态数组 int top; // 顶指针,初始为-1 } SqStack; // 清空 void ClearStack(SqStack *S) { S->top = -1; // 重置顶指针 } ``` ### 2. 动态分配的顺序 动态分配的顺序在初始化时动态分配了存储数组,清空操作同样只需重置顶指针,不需要释放数组内存(因为清空后还要继续使用)。 ```c // 动态分配的顺序结构 typedef struct { int *data; // 动态数组 int top; // 顶指针 int capacity; // 的容量 } DynStack; // 清空 void ClearStack(DynStack *S) { S->top = -1; // 重置顶指针 // 注意:动态数组的内存不释放,因为还要继续使用 } ``` ## 二、链式的清空操作 链式使用链表存储元素,清空操作需要释放所有节点的内存,并将顶指针置为NULL。 ```c // 链式节点 typedef struct StackNode { int data; struct StackNode *next; } StackNode; // 链式结构 typedef struct { StackNode *top; // 顶指针 } LinkStack; // 清空(需要释放所有节点) void ClearStack(LinkStack *S) { StackNode *p = S->top; while (p) { StackNode *temp = p; p = p->next; free(temp); // 释放节点 } S->top = NULL; // 顶指针置空 } ``` ## 三、注意事项 1. **清空与销毁的区别**: - 清空:重置为空状态,保留存储空间。 - 销毁:释放所有资源,不再存在。 2. **时间复杂度**: - 顺序清空:$O(1)$(只需重置指针) - 链式清空:$O(n)$(需要遍历释放所有节点) 3. **安全考虑**: - 在清空链式时,要防止内存泄漏,必须释放所有节点。 - 顺序清空后,原来的数据可能残留在数组中,如果涉及敏感数据,应该手动覆盖(但通常清空操作只重置指针)。 ## 四、清空操作示例 以下是一个完整的顺序(静态分配)清空操作的示例代码: ```c #include <stdio.h> #include <stdbool.h> #define MAXSIZE 10 typedef struct { int data[MAXSIZE]; int top; } SqStack; // 初始化 void InitStack(SqStack *S) { S->top = -1; } // 清空 void ClearStack(SqStack *S) { S->top = -1; } // 判断空 bool StackEmpty(SqStack S) { return S.top == -1; } int main() { SqStack S; InitStack(&S); // ... 入操作 ... // 清空 ClearStack(&S); if (StackEmpty(S)) { printf("已清空\n"); // 输出:已清空 } return 0; } ``` ## 五、引用说明 - 清空顺序只需重置顶指针(静态和动态分配)[^1][^3]。 - 链式的清空需要逐个释放节点并将顶指针置空。 §§相关问题§§ 1. 的清空操作和销毁操作在哪些场景下需要分别使用? 2. 链式的清空操作中如何避免内存泄漏? 3. 如何实现一个支持撤销操作(使用多个)的编辑器? 4. 在递归函数调用中如何保存现场? 5. 顺序和链式在清空操作上的性能差异对实际应用有什么影响? </think>## 数据结构中清空的方法与实现详解 ### 一、清空的核心概念 清空(Clear Stack)是指**重置的状态使其成为逻辑空**,但不释放的存储空间。这与销毁(Destroy Stack)有本质区别: - 清空:保留数据结构,仅移除所有元素(时间复杂度$O(1)$) - 销毁:释放占用的内存资源(时间复杂度$O(1)$或$O(n)$)[^1][^3] ### 二、顺序的清空实现 #### 1. 静态分配的顺序 ```c /* 清空静态顺序 */ bool ClearStack(SqStack *S) { if (S->top == -1) return false; // 已是空 S->top = -1; // 核心操作:重置顶指针 return true; } ``` - **操作原理**:顶指针`top`置为初始值-1,标记为空状态 - **时间复杂度**:$O(1)$ - **空间复杂度**:$O(1)$ - **特点**:原数据未被覆盖但逻辑失效[^1] #### 2. 动态分配的顺序 ```c /* 清空动态顺序 */ bool ClearStack_Dynamic(DynStack *S) { if (S->top == -1) return false; S->top = -1; // 重置顶指针 // S->data数组保留不释放 return true; } ``` - **关键区别**:保留动态分配的存储数组,仅重置指针[^3] ### 三、链式的清空实现 ```c /* 清空链式 */ void ClearStack_Linked(LinkStack *S) { while (S->top != NULL) { // 遍历释放所有节点 StackNode *temp = S->top; S->top = S->top->next; free(temp); // 释放节点内存 } S->count = 0; // 重置节点计数器 } ``` - **时间复杂度**:$O(n)$(需遍历所有节点) - **空间复杂度**:$O(1)$ - **特点**:必须显式释放每个节点内存[^3] ### 四、清空操作的技术要点 1. **状态重置**: - 顺序:`top = -1` - 链式:`top = NULL` + `count = 0` 2. **内存处理差异**: ```mermaid graph LR A[清空操作] --> B[顺序] A --> C[链式] B --> D[仅重置指针] C --> E[释放所有节点] ``` 3. **错误处理**: - 检查是否已空(避免冗余操作) - 检查指针有效性(防野指针) ### 五、清空 vs 销毁 vs 初始化 | 操作 | 目标 | 内存变化 | 状态 | |--------|-----------------------|--------------------|--------------| | 清空 | 移除所有元素 | 存储空间保留 | 逻辑空 | | 销毁 | 释放所有资源 | 内存完全释放 | 不存在 | | 初始化 | 创建空结构 | 分配/重置内存 | 初始空 | > **应用场景**:清空操作常用于需要复用结构的场景,如编译器语法分析中的符号重用[^1] ### 六、完整示例(顺序清空) ```c #include <stdio.h> #define MAXSIZE 100 typedef struct { int data[MAXSIZE]; int top; } SqStack; // 初始化 void InitStack(SqStack *S) { S->top = -1; // 初始空标记[^3] } // 清空 void ClearStack(SqStack *S) { S->top = -1; // 核心清空操作 } // 检测空 int IsEmpty(SqStack S) { return (S.top == -1); } int main() { SqStack S; InitStack(&S); // ... 入操作 ... ClearStack(&S); printf("是否空: %d\n", IsEmpty(S)); // 输出1(真) return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值