【C语言数据结构】之栈
一、基本概念
栈和队列是被限定只能在端点处进行操作的线性表。本文介绍栈。
栈有两种实现方式,一种是顺序栈,一种是链式栈。顺序栈由数组的方式实现,链式栈由链表的形式实现。我们一般情况下使用顺序栈较多。
栈只能在尾部插入和尾部输出,我们称这种性质为后进先出,LIFO(Last in Fist out)
二、 顺序栈
顺序栈是基于顺序表(通常是数组)来实现的栈。
在栈中没有元素的时候,栈顶游标我们可以初始化为-1
。
为什么要初始化为-1
,因为当有一个新元素要入栈的话,栈顶游标++刚好就是0
,即数组的下标为0
的数组第一位。
1. 宏定义和结构体
# define True 1
# define False 0
# define Size 50
typedef struct Stack
{
int data[Size];
int itop;
}Stack;
2. 初始化栈
Stack* CreateStack()
{
Stack* p = (Stack*)malloc(sizeof(Stack));
if(p == NULL)
{
exit(1);
}
memset(p,0,sizeof(Stack));
p->itop = -1;
return p;
}
3. 判断栈空,栈满
当栈中没有元素就是栈空,这时候栈顶游标是-1
当栈顶游标等于初始化时候的大小Size-1
的时候,就是栈满。我们有50
个空间的栈,下标为0-49
,当栈顶游标为49
时就说明栈满了。
int IsEmpty(Stack* p)
{
if(p->itop == -1)
{
return True;
}
return False;
}
int IsFull(Stack* p)
{
if(p->itop == Size-1)
{
return True;
}
return False;
}
4. 入栈
void InsertStack(Stack* p,int num)
{
if(IsFull(p) == True)
{
return;
}
p->itop++;
p->data[p->itop] = num;
}
5. 出栈
出栈可以只出栈,也可以出栈并获取出栈元素,我们这里采用出栈并获取出栈元素的写法。
int PopStack(Stack* p)
{
if(IsEmpty(P) == True)
{
return;
}
int x = p->data[p->itop];
p->itop--;
return x;
}
6. 获取栈顶游标
直接返回栈顶游标即可。
int Getitop(Stack* p)
{
if(p == NULL)
{
return;
}
return p->itop;
}
7. 打印栈顶元素
当前栈顶游标就是栈顶元素在数组中的下标,根据栈顶游标找到栈顶元素并输出。
void PrintTop(Stack* p)
{
if(IsEmpty(p) == True)
{
return;
}
printf("栈顶元素为%d\n",p->data[p->itop]);
}
8. 打印栈中所有元素
说白了就是按顺序输出数组中的元素。
void PrintStack(Stack* p)
{
if(IsEmpty(p) == True)
{
return;
}
int i =0;
for(i;i<=p->itop;i++)
{
printf("%d ",p->data[i]);
}
putchar('\n');
}
三、 链式栈
采用链式存储的栈叫做链式栈。链式栈不存在栈满一说,链式栈的入栈出栈操作实际上就是单链表的尾插尾删操作。
对链式栈操作就看做单链表操作,只不过只允许尾插尾删。大家想看详细介绍可以移步我的【C语言数据结构】之单链表文章。
1. 宏定义和结构体
# define True 1
# define False 0
typedef struct Stack
{
struct Stack* next;
int data;
}Stack;
2. 初始化栈
Stack* CreateStack()
{
Stack* p = (Stack*)malloc(sizeof(Stack));
assert(p);
memset(p, 0, sizeof(Stack));
return p;
}
3. 判断栈空
int IsEmpty(Stack* p)
{
assert(p);
if (p->next == NULL)
{
return True;
}
return False;
}
4. 入栈
单链表尾插操作
void InsertStack(Stack* p,int num)
{
assert(p);
Stack* node = (Stack*)malloc(sizeof(Stack));
memset(node, 0, sizeof(Stack));
node->data = num;
Stack* tmp = p;
while (tmp->next != NULL)
{
tmp = tmp->next;
}
tmp->next = node;
node->next = NULL;
}
5. 出栈
单链表尾删操作
int PopStack(Stack* p)
{
assert(p);
if (IsEmpty(p) == True)
{
printf("空栈无法出栈\n");
return -1;
}
Stack* tmp1 = p;
Stack* tmp2 = p->next;
while (tmp2->next != NULL)
{
tmp1 = tmp1->next;
tmp2 = tmp2->next;
}
int x = tmp2->data;
free(tmp2);
tmp1->next = NULL;
return x;
}
6. 获取栈顶游标
int Getitop(Stack* p)
{
int i = -1;
if (IsEmpty(p) == True)
{
return i;
}
Stack* tmp = p->next;
while (tmp != NULL)
{
i++;
tmp = tmp->next;
}
return i;
}
7. 打印栈顶元素
void PrintTop(Stack* p)
{
assert(p);
if (IsEmpty(p) == True)
{
printf("栈空无栈顶元素\n");
return;
}
Stack* tmp = p->next;
while (tmp->next != NULL)
{
tmp = tmp->next;
}
printf("栈顶元素为%d \n", tmp->data);
}
8. 打印栈中所有元素
void PrintStack(Stack* p)
{
assert(p);
if (IsEmpty(p) == True)
{
printf("栈空,无元素\n");
return;
}
Stack* tmp = p->next;
while (tmp != NULL)
{
printf("%d ", tmp->data);
tmp = tmp->next;
}
putchar('\n');
}
四、总结
牢记栈的特性 – 后进先出(LIFO)
顺序栈和链式栈在进行入栈出栈操作的时候的时间复杂度都是O(1)
。
顺序栈优点就是逻辑上简单,它就是个数组,但是他在空间上没有灵活性。
链式栈相对来说难理解一点,但是它就是个限制了输入输出的单链表,我们只要掌握了单链表操作,对于链式栈来说就是一点问题也没有了,链式栈的优点是不用担心栈满,但是会增加内存开销。
数据结构最重要的就是单链表操作,说白了,双向链表,循环链表,栈,队列,哈希表等都是单链表的拓展,所以我们要学好单链表操作。