数据结构_栈(基于顺序表和链表)
栈是一种特殊的线性表,只允许在固定的一端进行插入删除元素操作,操作元素的一端称为栈顶,另一端称为栈底。不含任何元素的栈称为空栈,栈具有后进先出的特点,栈也可以称为后进先出的线性表。
那有了线性表链表为什么还要有栈,栈的操作相比较于顺序表、链表来说操作比较简单,出栈、入栈、取栈顶元素,更简单的操作就限制了操作的权利,也就减少了出现错误的机会,在栈能满足的情况下,就不需要使用顺序表或者链表。
以下实现基于顺序表和链表实现栈的简单操作:
一、顺序表实现:
顺序表用数组来实现,数组是定长的,可以进行扩容,使用顺序表来模拟实现栈,对顺序表尾插实现入栈,尾删实现出栈。以下为代码实现:
// 头文件及结构体声明
1 #pragma once
2
3 #include <stdio.h>
4 #include <stddef.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 typedef char SeqStackType;
9
10 typedef struct SeqStack {
11 SeqStackType* data; // 指向开辟内存空间,用于扩容
12 size_t size; // 有效元素个数
13 size_t capacity; // data 指向的空间容纳的最大个数,size超过 capacity时进行扩容
14 }SeqStack;
1.初始化栈
3 void SeqStackInit(SeqStack* stack)
4 {
5 if(stack == NULL){
6 // 非法输入
7 return;
8 }
9 stack->size = 0;
10 stack->capacity = 1024;
11 stack->data = (SeqStackType*)malloc(stack->capacity * sizeof(SeqStackType)); // 申请初始内存空间
12 return;
13 }
2.销毁栈
15 void SeqStackDestroy(SeqStack* stack)
16 {
17 if(stack == NULL){
18 return;
19 }
20 free(stack->data);
21 stack->data = NULL;
22 stack->size = 0;
23 stack->capacity = 0;
24 return;
25 }
3.元素入栈
// 重置栈大小(扩容)
27 void SeqStackResize(SeqStack* stack)
28 {
29 if(stack == NULL){
30 return;
31 }
32 if(stack->size < stack->capacity){
33 return;
34 }
35 stack->capacity = stack->capacity * 2 + 1; // 改变扩大范围
36 SeqStackType* new = (SeqStackType*)malloc(stack->capacity * sizeof(SeqStackType));
37 size_t i = 0;
38 for(; i < stack->size; i++){ // 将原内存的数据搬运到新的内存空间
39 new[i] = stack->data[i];
40 }
41 free(stack->data);
42 stack->data = new; // 更新地址
43 return;
44 }
// 入栈
46 void SeqStackPush(SeqStack* stack, SeqStackType value)
47 {
48 if(stack == NULL){
49 return;
50 }
51 if(stack->size >= stack->capacity){
52 // 栈已满,扩容
53 SeqStackResize(stack);
54 }
55 stack->data[stack->size++] = value;
56 return;
57 }
4.元素出栈
59 void SeqStackPop(SeqStack* stack)
60 {
61 if(stack == NULL){
62 return;
63 }
64 if(stack->size == 0)
65 {
66 // 空栈
67 return;
68 }
69 stack->size--; // 出栈减有效元素
70 return;
71 }
5.取栈顶元素
73 int SeqStackTop(SeqStack* stack, SeqStackType* value)
74 {
75 if(stack == NULL || value == NULL){
76 return 0;
77 }
78 if(stack->size == 0){
79 // 空栈
80 return 0;
81 }
82 *value = stack->data[stack->size - 1]; // 返回栈顶元素,有效元素个数减小
83 return 1;
84 }
6.打印函数
96 void SeqStackPrint(const SeqStackType* msg, SeqStack* stack)
97 {
98 if(stack == NULL || msg == NULL){
99 return;
100 }
101 printf("%s\n", msg);
102 size_t i = 0;
103 for(; i < stack->size; i++){
104 printf("[%c] ", stack->data[i]);
105 }
106 printf("\n");
107 }
二、链表实现:
基于链表实现栈,使用带头结点链表操作方便,入栈新的元素申请空间,出栈释放元素,链表的尾部需要遍历或者保存起来,比较麻烦,可以采用头插头删来实现入栈出栈,以下为代码实现:
// 头文件及结构体声明
1 #pragma once
2
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<string.h>
6 #include<stddef.h>
7
8 typedef char LinkStackType;
9
10 typedef struct LinkStack{
11 LinkStackType data;
12 struct LinkStack* next;
13 }LinkStack;
1.初始化、销毁栈
// 初始化
3 void LinkStackInit(LinkStack** head)
4 {
5 if(head == NULL){
6 // 非法输入
7 return;
8 }
9 *head = (LinkStack*)malloc(sizeof(LinkStack));
10 (*head)->data = 0;
11 (*head)->next = NULL;
12 return;
13 }
// 销毁栈
15 void LinkStackDestory(LinkStack** head)
16 {
17 if(head == NULL){
18 return;
19 }
20 free(*head);
21 *head = NULL;
22 return;
23 }
2.入栈元素
// 创建结点
25 LinkStack* LinkStackCreateNode(LinkStackType value)
26 {
27 LinkStack* new_node = (LinkStack*)malloc(sizeof(LinkStack));
28 new_node->data = value;
29 new_node->next = NULL;
30 return new_node;
31 }
// 销毁结点
33 void LinkStackDestoryNode(LinkStack* pos)
34 {
35 if(pos){
36 free(pos);
37 }
38 pos = NULL;
39 return;
40 }
// 入栈
42 void LinkStackPush(LinkStack* head, LinkStackType value)
43 {
44 if(head == NULL){
45 return;
46 }
47 LinkStack* new_node = LinkStackCreateNode(value);
48 if(head->next == NULL){
49 // 空链表
50 head->next = new_node;
51 return;
52 }
53 LinkStack* tmp = head->next; // 保存要删除结点后边的结点
54 head->next = new_node;
55 new_node->next = tmp;
56 return;
57 }
3.出栈元素
59 void LinkStackPop(LinkStack* head)
60 {
61 if(head == NULL){
62 // 非法输入
63 return;
64 }
65 if(head->next == NULL){
66 printf("空栈.\n");
67 return;
68 }
69 LinkStack* tmp = head->next;
70 head->next = tmp->next;
71 LinkStackDestoryNode(tmp);
72 return;
73 }
4.取栈顶元素
75 int LinkStackTop(LinkStack* head, LinkStackType* top)
76 {
77 if(head == NULL || top == NULL){
78 return 0;
79 }
80 if(head->next == NULL){
81 return 0;
82 }
83 *top = head->next->data;
84 return 1;
85 }
5.打印函数
93 void LinkStackPrint(const LinkStackType* msg, LinkStack* head)
94 {
95 if(msg ==NULL || head ==NULL){
96 // 非法输入
97 return;
98 }
99 printf("%s\n", msg);
100 LinkStack* cur = head->next;
101 while(cur != NULL){
102 printf("[%c] ", cur->data);
103 cur = cur->next;
104 }
105 printf("\n");
106 }
二、顺序表、链表实现栈的差异
顺序表栈:出栈、取栈顶元素时间复杂度为 O(1),入栈的时间复杂度为O(n),扩容需要搬运元素,但是代码简单,直接对有效元素个数进行更改;
链表栈:入栈、出栈、取栈顶元素时间复杂度为 O(1) ,每次插入新元素需要单独申请空间,对元素的基本操作,需要改变指针,代码比顺序栈麻烦一点。