首先来看一下百度百科关于栈的解释:
定义:栈是限定仅在表头进行插入和删除操作的线性表。要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。”栈“者,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
栈满足先进后出,接下来我们分别用顺序表和链表来实现一下栈。
申明:下面的代码是是一个粗糙版本的链式栈,虽然结果正确但存在地址空间的浪费,优化版本在该代码下面给出!!
LinkStack.h
pragma once
#include
#include
#include
#define TestType printf("\n############################# %s #############################\n",__FUNCTION__);
typedef char LinkStackType;
typedef struct LinkStack{
struct LinkStack* next;
struct LinkStack* tail;
LinkStackType data;
} LinkStack;
// 初始化堆
void LinkStackInit(LinkStack** phead);
//打印函数
void LinkStackPrint(LinkStack* head, const char* msg);
//入栈
void LinkStackPush(LinkStack** phead,LinkStackType value);
//出栈
void LinkStackPop(LinkStack** phead);
//取栈顶元素
int LinkStackGetFront(LinkStack* head,LinkStackType* value);
//销毁栈
void LinkStackDestroy(LinkStack** phead);
linkstack.c
#include"linkstack.h"
void LinkStackInit(LinkStack** phead){
if(phead == NULL){
//非法输入
return;
}
*phead = NULL;
}
void DestroyNode(LinkStack* to_delete){
free(to_delete);
}
void LinkStackPrint(LinkStack* head,const char* msg){
printf("[ %s ]:\n",msg);
if(head == NULL){
return;
}
LinkStack* cur = head;
while(cur != NULL){
printf("[%c][%p]->",cur->data,cur);
cur = cur->next;
}
printf("NULL\n");
return;
}
//创建新的节点
LinkStack* LinkStackCreateNode(LinkStackType value){
LinkStack* new_node = (LinkStack*)malloc(sizeof(LinkStack));
if(new_node == NULL){
//申请内存失败
return NULL;
}
new_node->data = value;
new_node->next = NULL;
return new_node;
}
//入栈
void LinkStackPush(LinkStack** phead,LinkStackType value){
if(phead == NULL){
return;
}
if(*phead == NULL){
// 如果栈为空,插入新的元素
*phead = LinkStackCreateNode(value);
(*phead)->tail = *phead;
return;
}
//如果栈不为空,让tail的next等于新节点
(*phead)->tail->next = LinkStackCreateNode(value);
//tail指针后移
(*phead)->tail = (*phead)->tail->next;
return;
}
//出栈
void LinkStackPop(LinkStack** phead){
if(phead == NULL){
//非法输入
return;
}
if(*phead == NULL){
//栈空,无法出栈
return;
}
LinkStack* cur = *phead;
while(cur->next != (*phead)->tail){
cur = cur->next;
}
DestroyNode((*phead)->tail);
cur->next = NULL;
(*phead)->tail = cur;
}
//取栈顶元素,这里的value是输出型参数
int LinkStackGetFront(LinkStack* head,LinkStackType* value){
if(head == NULL){
//如果失败返回0
return 0;
}
*value = head->tail->data;
//如果成功返回1
return 1;
}
void LinkStackDestroy(LinkStack** phead){
if(phead == NULL){
return;
}
LinkStack* cur = *phead;
LinkStack* tmp;
//遍历释放内存
while(cur != NULL){
tmp = cur;
cur = cur->next;
DestroyNode(tmp);
}
*phead = NULL;
return;
}
main.c
#include"linkstack.h"
void TestLinkStackPush(){
TestType;
LinkStack* head;
LinkStackInit(&head);
LinkStackPush(&head,'a');
LinkStackPrint(head,"a入栈");
LinkStackPush(&head,'b');
LinkStackPrint(head,"b入栈");
LinkStackPush(&head,'c');
LinkStackPrint(head,"c入栈");
return;
}
void TestLinkStackPop(){
TestType;
LinkStack* head;
LinkStackInit(&head);
LinkStackPush(&head,'a');
LinkStackPush(&head,'b');
LinkStackPush(&head,'c');
LinkStackPush(&head,'d');
LinkStackPrint(head,"abcd入栈");
LinkStackPop(&head);
LinkStackPrint(head,"d出栈");
LinkStackPop(&head);
LinkStackPrint(head,"c出栈");
LinkStackPop(&head);
LinkStackPrint(head,"b出栈");
return;
}
void TestLinkStackGetFront(){
TestType;
LinkStack* head;
int result;
LinkStackType value;
LinkStackInit(&head);
LinkStackPush(&head,'a');
LinkStackPush(&head,'b');
LinkStackPush(&head,'c');
LinkStackPush(&head,'d');
LinkStackPrint(head,"abcd入栈");
result = LinkStackGetFront(head,&value);
printf("expect:1,actual:%d\n",result);
printf("expect:d,actual:%c\n",value);
LinkStackPop(&head);
result = LinkStackGetFront(head,&value);
printf("expect:1,actual:%d\n",result);
printf("expect:c,actual:%c\n",value);
}
void TestLinkStackDestroy(){
TestType;
LinkStack* head;
LinkStackInit(&head);
LinkStackPush(&head,'a');
LinkStackPush(&head,'b');
LinkStackPush(&head,'c');
LinkStackPush(&head,'d');
LinkStackPrint(head,"abcd入栈");
LinkStackDestroy(&head);
LinkStackPrint(head,"销毁栈结构");
return;
}
int main(){
TestLinkStackPush();
TestLinkStackPop();
TestLinkStackGetFront();
TestLinkStackDestroy();
printf("\n\n");
return 0;
}
Makefile
LinkStack:linkstack.c main.c
gcc -o $@ $^ -g
.PHONY:clean
clean:
rm -f LinkStack
以上就是链式栈的代码,在编写完上面的代码,虽然结果正确,但是我发现这个代码写的有一些不妥之处,比如说把tail指针放到节点中,这种做法真的是很蠢。因为在每次开辟新的节点都会创建一个新的tail指针,但是我们明明可以使用一个tail指针就可以办到的事情,白白浪费了内存空间,一两个节点可能效果不明显,但是如果节点数量达到一定的数量级,那浪费的地址空间就相当大了。还好这是在学习,我们可以随时做优化代码。希望自己以后牢记这个问题!!!!!!!!!!
以下是优化代码
linkstack.h
#pragma once
#include
#include
#include
#define TestType printf("\n################################## %s #################################\n",__FUNCTION__)
#define LinkStackType char
typedef struct LinkNode{
struct LinkNode* next;
LinkStackType data;
} LinkNode;
//创建另外一个结构体存放链表的头指针和尾指针
typedef struct LinkStack{
struct LinkNode* head;
struct LinkNode* tail;
} LinkStack;
//初始化函数
void LinkStackInit(LinkStack* q);
//入栈函数
void LinkStackPush(LinkStack* q,LinkStackType value);
//打印函数
void LinkStackPrint(LinkStack* q,const char* msg);
//出栈函数
void LinkStackPop(LinkStack* q);
//取栈顶元素
int LinkStackFront(LinkStack* q,LinkStackType* value);
linkstack.c
#include"linkstack.h"
void LinkStackInit(LinkStack* q){
if(q == NULL){
//非法输入
return;
}
q->head = NULL;
q->tail = NULL;
return;
}
LinkNode* LinkStackCreateNode(LinkStackType value){
//开辟一个新的节点
LinkNode* new_node = (LinkNode*)malloc(sizeof(LinkNode));
if(new_node == 0){
//申请内存失败
return NULL;
}
new_node->next = NULL;
new_node->data = value;
return new_node;
}
void DestroyNode(LinkNode* to_delete){
if(to_delete == NULL){
return;
}
free(to_delete);
return;
}
void LinkStackPush(LinkStack* q,LinkStackType value){
if(q == NULL){
return;
}
LinkNode* new_node = LinkStackCreateNode(value);
if(q->head == NULL){
//如果栈为空
q->head = new_node;
q->tail = new_node;
return;
}
//栈不为空
q->tail->next = new_node;
q->tail = q->tail->next;
return;
}
void LinkStackPrint(LinkStack* q,const char* msg){
if(q == NULL){
return;
}
printf("[ %s ]:\n",msg);
LinkNode* cur = q->head;
while(cur != NULL){
printf("[%c][%p]->",cur->data,cur);
cur = cur->next;
}
printf("NULL\n\n");
return;
}
void LinkStackPop(LinkStack* q){
if(q == NULL){
return;
}
if(q->head == NULL){
return;
}
LinkNode* temp = q->tail;
LinkNode* cur = q->head;
while(cur->next != q->tail){
cur = cur->next;
}//找到tail前的一个节点
q->tail = cur;//tail指针前移一个节点
q->tail->next = NULL;
DestroyNode(temp);//将最后一个节点释放
return;
}
//value是输出型参数
int LinkStackFront(LinkStack* q,LinkStackType* value){
if(q == NULL){
return 0;
}
*value = q->tail->data;
return 1;
}
main.c
#include"linkstack.h"
void TestLinkStackPush(){
TestType;
LinkStack q;
LinkStackInit(&q);
LinkStackPush(&q,'a');
LinkStackPush(&q,'b');
LinkStackPush(&q,'c');
LinkStackPush(&q,'d');
LinkStackPush(&q,'e');
LinkStackPrint(&q,"abcde入栈");
return;
}
void TestLinkStackPop(){
TestType;
LinkStack q;
LinkStackInit(&q);
LinkStackPush(&q,'a');
LinkStackPush(&q,'b');
LinkStackPush(&q,'c');
LinkStackPush(&q,'d');
LinkStackPush(&q,'e');
LinkStackPrint(&q,"abcde入栈");
LinkStackPop(&q);
LinkStackPrint(&q,"e出栈");
LinkStackPop(&q);
LinkStackPrint(&q,"d出栈");
return;
}
void TestLinkStackFront(){
TestType;
LinkStack q;
LinkStackType value;
LinkStackInit(&q);
LinkStackPush(&q,'a');
LinkStackPush(&q,'b');
LinkStackPush(&q,'c');
LinkStackPush(&q,'d');
LinkStackPush(&q,'e');
LinkStackPrint(&q,"abcde入栈");
int ret = LinkStackFront(&q,&value);
printf("expect: 1, actual: %d\n",ret);
printf("expext: e, actual: %c\n",value);
LinkStackPop(&q);
ret = LinkStackFront(&q,&value);
printf("expect: 1, actual: %d\n",ret);
printf("expext: d, actual: %c\n",value);
return;
}
int main(){
TestLinkStackPush();
TestLinkStackPop();
TestLinkStackFront();
return 0;
}
首先就是在第一个版本的链式栈将tail放到了节点中,造成了不必要的资源浪费。
其次,在写的时候同桌墩子有一个问题,我觉得有必要说一下。
linknode要用malloc来开辟空间,而linkqueue为什么在函数中就没有在函数里用malloc来开辟空间呢?
这是因为在主函数里,我先创建了一个linkqueue结构体(linkqueue q),然后传入的函数的参数是取地址q,这样传参在创建结构体的时候已经分配了内存,所以不需要在函数里再malloc开辟空间了。但是如果在主函数里我传的参数是一个指针(linkqueue* q),那么在函数里就有必要手动为malloc指向的空间分配一段空间了。
此次介绍的顺序栈是可以动态开辟空间的顺序栈
seqstack.h
#pragma once
#include
#include
#include
#include
#define TestType printf("\n##################################### %s #######################################\n",__FUNCTION__)
typedef char SeqStackType;
//动态开辟内存
typedef struct SeqStack{
SeqStackType* data;
size_t size;
size_t capacity; //MAX_SIZE的替代品,data这段元素中能容纳的元素个数
} SeqStack;
//初始化函数
void SeqStackInit(SeqStack* stack);
//打印函数
void SeqStackPrint(SeqStack* stack, const char* msg);
//扩容函数
void SeqStackReSize(SeqStack* stack);
//入栈函数
void SeqStackPush(SeqStack* stack, SeqStackType value);
//出栈函数
void SeqStackPop(SeqStack* stack);
//销毁栈函数
void SeqStackDestroy(SeqStack* stack);
//取栈顶元素
int SeqStackGetFront(SeqStack* stack,SeqStackType* value);
seqstack.c
#include"seqstack.h"
void SeqStackInit(SeqStack* stack){
if(stack == NULL){
return;
}
stack->size = 0;
stack->capacity = 1000;//初始化capacity
stack->data = (SeqStackType*)malloc\
(stack->capacity * sizeof(SeqStackType));
//给data开辟空间
return;
}
void SeqStackPrint(SeqStack* stack, const char* msg){
if(stack == NULL){
return;
}
printf("[ %s ]:\n",msg);
size_t i = 0;
for(;i < stack->size;i++){
//循环打印数组下标0到size-1的元素
printf("%c ",*(stack->data+i));
}
printf("\n");
return;
}
void SeqStackReSize(SeqStack* stack){
if(stack == NULL){
return;
}
if(stack->size < stack->capacity){
return;
}
//capacity扩容策略自己定,+1是为了避免capacity为0带来的错误
stack->capacity = stack->capacity * 2 + 1;
SeqStackType* new_ptr = (SeqStackType*)malloc\
(stack->capacity * sizeof(SeqStackType));
//重新开辟空间
size_t i = 0;
for(;i < stack->size;++i){
new_ptr[i] = stack->data[i];
}//拷贝数组元素
free(stack->data);//释放data指向的空间
stack->data = new_ptr;//data指向新的内存空间
return;
}
void SeqStackPush(SeqStack* stack,SeqStackType value){
if(stack == NULL){
return;
}
if(stack->size > stack->capacity){
//数组元素size超出最大容量,进行扩容
SeqStackReSize(stack);
}
stack->data[stack->size++] = value;
return;
}
void SeqStackPop(SeqStack* stack){
if(stack == NULL){
return;
}
if(stack->size == 0){
return;
}
stack->size--;
return;
}
void SeqStackDestroy(SeqStack* stack){
if(stack == NULL){
return;
}
free(stack->data);
stack->capacity = 0;
stack->size = 0;
return;
}
int SeqStackGetFront(SeqStack* stack,SeqStackType* value){
if(stack == NULL){
return 0;
}
*value = stack->data[stack->size -1];
return 1;
}
main.c
#include"seqstack.h"
void TestSeqStackInit(){
TestType;
SeqStack stack;
SeqStackInit(&stack);
SeqStackPrint(&stack,"初始化栈");
}
void TestSeqStackReSize(){
TestType;
SeqStack stack;
SeqStackInit(&stack);
printf("扩容之前:%zu\n",stack.capacity);
stack.size = 1001;
SeqStackReSize(&stack);
printf("扩容之后:%zu\n",stack.capacity);
return;
}
void TestSeqStackPush(){
TestType;
SeqStack stack;
SeqStackInit(&stack);
SeqStackPush(&stack, 'a');
SeqStackPush(&stack, 'b');
SeqStackPush(&stack, 'c');
SeqStackPush(&stack, 'd');
SeqStackPrint(&stack, "abcd入栈");
return;
}
void TestSeqStackPop(){
TestType;
SeqStack stack;
SeqStackInit(&stack);
SeqStackPush(&stack, 'a');
SeqStackPush(&stack, 'b');
SeqStackPush(&stack, 'c');
SeqStackPush(&stack, 'd');
SeqStackPrint(&stack, "abcd入栈");
SeqStackPop(&stack);
SeqStackPrint(&stack, "d 出栈");
SeqStackPop(&stack);
SeqStackPrint(&stack, "c 出栈");
}
void TestSeqStackDestroy(){
TestType;
SeqStack stack;
SeqStackInit(&stack);
SeqStackPush(&stack, 'a');
SeqStackPush(&stack, 'b');
SeqStackPush(&stack, 'c');
SeqStackPush(&stack, 'd');
SeqStackPrint(&stack, "abcd入栈");
SeqStackDestroy(&stack);
SeqStackPrint(&stack, "销毁栈结构");
return;
}
void TestSeqStackGetFront(){
TestType;
SeqStack stack;
SeqStackType value;
int result;
SeqStackInit(&stack);
SeqStackPush(&stack, 'a');
SeqStackPush(&stack, 'b');
SeqStackPush(&stack, 'c');
SeqStackPush(&stack, 'd');
SeqStackPush(&stack, 'e');
SeqStackPrint(&stack, "abcd入栈");
result = SeqStackGetFront(&stack,&value);
printf("expect:1,actual:%d\n",result);
printf("expect:e,actual:%c\n",value);
SeqStackPop(&stack);
result = SeqStackGetFront(&stack,&value);
printf("expect:1,actual:%d\n",result);
printf("expect:d,actual:%c\n",value);
SeqStackPop(&stack);
return;
}
int main(){
TestSeqStackInit();
TestSeqStackReSize();
TestSeqStackPush();
TestSeqStackPop();
TestSeqStackDestroy();
TestSeqStackGetFront();
return 0;
}
Makefile
seqstack:seqstack.c main.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f seqstack
以上,关于顺序栈和链式栈的基本操作。
由于我的水平有限,上面的介绍可能有不妥或者Bug,欢迎发邮件到我的邮箱([email protected])批评指正!