数据结构与算法篇 之栈
关于栈,举一个确切的例子,就是我们洗盘子的时候,洗碗第一个放边上,洗完第二个有叠在第一个上面,,,然后就越来越高了,最后洗完第一篇了,接下来呢,就是再过一遍水,从最顶上一个个的拿出来,冲水冲洗干净后又一个个盘子摞起来。
这里面设计到栈的全部操作,出栈与入栈,还有只能依次一个个的取出来和先进后出的特性;最上面的元素叫做栈顶,最下面的元素叫做栈低,当栈没有任何元素的时候叫做空栈。
其实栈也就是链表或者数组,只不过限制了一些操作,只能在一端进行插入和删除数据,对于链表或者数组来说在特定的场合下,操作自由度太高了,这样会带来不可控的效果。
基于链表的栈叫做链栈,基于数组的栈叫做顺序栈
下面来实现一个链式栈
struct node
{
int data;
struct node *next;
};定义一个链表节点,包含一个int型数据和一个指针
struct stack
{
struct node *top;
int size;
};定义一个栈的节点,包含一个链表的节点,和栈的大小
struct stack *init_stack(void)
{
struct stack *s = malloc(sizeof(struct stack));
if(s != NULL)
{
s->top = NULL;
s->size = 0;
}
return s;
}初始化一个链栈,申请一个链大小的内存,并且进行初始化
void push(int data, struct stack *s)
{
struct node *new = malloc(sizeof(struct node));
if(new != NULL)
{
new->data = data;
}
new->next = s->top;
s->top = new;
s->size++;
}申请一个链表节点并分配空间,把要压栈的数据幅值给链表节点,
然后把链表节点的下一个节点指针指向栈顶指针,然后将刚刚进来的节点变为栈顶
bool is_empty(struct stack *s)
{
return s->size == 0;
}判断是否为空栈,也就是size是否为0
bool pop(struct stack *s, int *pm)
{
if(is_empty(s))
return false;
*pm = s->top->data;
struct node *tmp = s->top;
s->top = s->top->next;
free(tmp);
s->size--;
return true;
}如果空栈就返回,第一步把数据取出来,第二步将栈顶指针指向下一个,释放栈顶
下面来说一下关于基于数组的顺序栈
定义一个结构体包含一个固定大小的的数组和栈的成员个数两个成员
struct{
int arr[10];
int size;
}
bool push(int item)
{
if(count == 10){
return false;
}
arr[size] = item;
++size;
return true;
}
pop()
{
if(count == 0) return null;
tmp = arr[size-1];
size--;
return tmp;
}
由上面的代码就可以看出出栈和入栈的空间复杂度都是O(1),因为只需要申请一个临时变量,
注意这里说的空间复杂度是指除了本来的数据存储空间外,算法运行是额外需要的存储空间。
至于时间空间复杂度也是O(1),每次只是一个出栈和入栈的操作。
下面说一下怎么动态扩容,链表的话不需要,理论上要多大就多大;数组的话一开始的时候就需要确定具体的大小,所以扩容的话,要重新申请一块更大的空间,1.5倍?2倍?,,然后将原来数组内容拷贝过去
动态扩容的时间复杂度是多少?
O(n)?,全部搬一次还不是O(n)?
对的,不是,答案是O(1)
接下来又是均摊出场的,把扩容的O(n)均摊到每一次入栈,哈哈
上面讲了一大堆一大堆的理论
下面需要讲一下实际的东西,操作系统给每个线程分配了一块独立的内存空间,这个内存空间是栈的数据结构,用来存储函数调用的临时变量。
int main()
{
int a = 1;
int ret = 0;
int res = 0;
ret = add(3,5);
res = a + ret;
return 0;
}
int add (int x,int y)
{
int sum = 0;
sum = x + y;
return sum;
}
a=1->ret = 0->res = 0->... ->y=5->x=3->sum = 0->...
再来举一个例子,编译器是任何利用栈来实现表达式求值
编译器是通过两个栈来实现的,一个保存操作数的栈,一个保存运算符的栈,我们从左向右遍历表达式,当遇到数字放入数字栈,当遇到运算符就与运算符栈的栈顶元素进行比较,如果比运算符栈顶元素的优先级高就将当前运算符压入栈,如果低或者相同,从运算符栈中取出栈顶运算符,从操作数取出两个操作数进行计算,再把计算出的结果压人栈,继续比较。
栈的四则运算代码好久以前老师写的:https://download.****.net/download/weixin_38452632/10714039