1、栈
1.1、栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。__进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。__栈中的数据元素遵守__后进先出(先进后出)__LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈(stack)是一种特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。

栈是仅在表尾进行插入、删除操作的线性表。
表尾(即an端)称为栈顶Top;表头(即a1端)称为栈底Base。
例如:
栈 S = (a1,a2,a3…an)
总结:表尾对应栈顶,表头对应栈底。
栈有两种:数组栈,链表栈。


链式栈:
- 如果是用尾做栈顶,尾插尾删,要设计成双向链表,否则删除数据效率低。
 - 如果是用头做栈顶,头插头删,要设计成单向链表。
 
两种都可以,非要选一种,数组栈结构稍微好一点。
数组栈存放数据的方式:

链式栈存放数据的方式:
-  
尾做栈顶:

 -  
头做栈顶

 
总结:栈和队列时线性表的子集,是插入金额删除位置受限的线性表。
1.2、栈的思考题
问:假设与3个元素a,b,c。入栈顺序是a,b,c。
则它们的出栈出栈顺序有几种可能?
答案:5种可能。下面解释:
(1)c、b、a。原因:入栈顺序为a、b、c,然后依次取出。
(2)a、b、c。原因:a先入栈,然后出栈,之后b入栈,b出栈,最后c入栈,c出栈。
(3)a、c、b。原因:a先入栈,然后出栈,之后b、c入栈,最后,c、b出栈。
(4)b、a、c。原因:a、b先入栈,之后b、a出栈,最后c入栈,c出栈。
(5)b、c、a。原因:a、b先入栈,之后b出栈,然后c入栈,最后c、a出栈。
2、顺序栈的实现(用数组实现)
栈主要有以下几个接口函数
- 初始化栈(StackInit)
 - 销毁(StackDestroy)
 - 栈顶插入数据(StackPush)
 - 栈顶删除数据(StackPop)
 - 取栈顶数据(StackTop)
 - 统计栈种元素个数(StackSize)
 - 判断栈是否为空(StackEmpty)
 
2.1、定义结构体和main函数
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int SLDataType;
typedef struct Stack
{
	SLDataType* a;
	int top;        //栈顶
	int capacity;
};
 
main函数
#include "stack.h"
int main()
{
	ST st;
	return 0;
}
 
2.2、初始化栈
//初始化栈
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
 
2.3、销毁
//销毁
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
 
2.4、栈顶插入数据
//栈顶插入数据
void StackPush(ST* ps, SLDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
 
2.5、栈顶删除数据
//栈顶删除数据
void StackPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}
 
2.6、取栈顶元素
//取栈顶数据
SLDataType StackTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}
 
2.7、统计栈中元素个数
//统计栈中元素个数
int StackSize(ST* ps)
{
	return ps->top;
}
 
2.8、判断栈是否为空
//判断栈是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}
 
3、全代码展示
这里使用三个文件:
- stack.h:用于结构体、各种函数接口的声明
 - stack.c:用于各种函数接口的定义。
 - test.c:用于创建链表,实现链表。
 
3.1、stack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;        //栈顶
	int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);        //取栈顶的数据
int StackSize(ST* ps);       //统计栈里面有多少个数据
bool StackEmpty(ST* ps);
 
3.2、stack.c
#include "stack.h"
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	//初始化时,top给的是0,意味着top指向栈顶数据的下一个。
	//初始化时,top给的是-1,意味着top指向栈顶数据。
	ps->top = 0;
	ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
//删除数据
void StackPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}
STDataType StackTop(ST* ps)   //取栈顶的数据
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top-1];
}
int StackSize(ST* ps)       //统计栈里面有多少个数据
{
	return ps->top;
}
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}
 
3.3、test.c
#include "stack.h"
void TestStack()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 6);
	//遍历栈元素
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st)); 
		StackPop(&st);
	}
	StackDestroy(&st);
}
int main()
{
	TestStack();
	return 0;
}
 
4、链栈的实现(用链表实现栈)
链栈的实现(用链表实现栈)
前面说过链栈的实现有两种方式:
- 链表头做栈顶,这样只需要单链表即可。
 - 链表尾做栈顶,这样需要双向链表。
 
这里我们就以单链表的形式实现链表。
以单链表的形式实现链表,链表的结构如下:
 
这个样的链表有如下特性:
- 链表的头指针就是栈顶。
 - 不需要头结点。
 - 基本不存在栈满的情况(需要结点就立即申请即可)。
 - 空栈相当于头指针指向NULL。
 - 插入和删除仅在栈顶处执行。
 
栈主要是用数组实现,链栈稍微少一点,所以这里直接放全代码,不在一个接口一个接口的分析了。
 以下是个人写的版本
4.1、stack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int SLDataType;
typedef struct Stack
{
	struct Stack* Next; 
	SLDataType data;
}ST;
//销毁
void StackDestory(ST** pps);
//插入数据
void StackPush(ST** pps, SLDataType x);
//删除数据
void StackPop(ST** pps);
//取栈顶元素
SLDataType StackTop(ST** pps);
//统计栈里面有多少个数据
int StackSize(ST** pps);
//判断栈是否为空
bool StackEmpty(ST** pps);
 
4.2、stack.c
#include "stack.h"
//插入数据
void StackPush(ST** pps, SLDataType x)
{
	//先扩容
	ST* newnode = (ST*)malloc(sizeof(ST));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->Next = *pps;
	*pps = newnode;
}
//销毁
void StackDestory(ST** pps)
{
	ST* cur = *pps;
	while (cur)
	{
		ST* next = cur->Next; 
		free(cur);
		cur = next;
	}
	*pps = NULL;
}
//删除栈顶
void StackPop(ST** pps)
{
	assert(*pps != NULL);
	ST* next = (*pps)->Next;
	free(*pps);
	*pps = next;
}
//取栈顶元素
SLDataType StackTop(ST** pps)
{
	assert(*pps != NULL);
	return (*pps)->data;
}
//统计栈里面有多少个数据
int StackSize(ST** pps)
{
	assert(*pps != NULL);
	int count = 0;
	ST* cur = *pps;
	while (cur)
	{
		count++;
		cur = cur->Next;
	}
	return count;
}
//判断栈是否为空
bool StackEmpty(ST** pps)
{
	assert(pps);
	return *pps == NULL;
}
 
4.3、test.c
#include "stack.h"
int main()
{
	ST* st = NULL;
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 7);
	
	ST** cur = &st;
	while (*cur)
	{
		st = *cur;
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	StackDestory(&st);
	return 0;
}
 
5、栈与递归
递归的定义:
- 若一个对象部分的包含它自己,或用它自己给自己定义,则称这个对象是递归的。
 - 若一个过程直接的或间接的调用自己,则称这个过程是递归的过程。
 以下三种情况常常用到递归方法
- 递归定义的数学函数。
 
- 阶乘函数。
 - 斐波那契数列。
 - 具有递归特性的数据结构。
 
- 二叉树。
 - 广义表。
 - 可递归求解的问题。
 
- 迷宫问题。
 - Hanoi塔问题。
 递归问题————使用分治法求解
__分治法:__对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解。
使用分治法的三个条件:
- 能将一个问题转变为一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些对象是变化有规律的。
 - 可以通过上述转化而使问题简化。
 - 必须有一个明确的递归出口,或称为递归边界。
 
我们再来分析函数递归掉调用过程:
调用前,系统完成:
- 将实参,返回地址等传递给被调用函数。
 - 为被调用函数的局部变量分配存储区。
 - 将控制转移到被调用函数。
 
调用后,系统完成:
- 保存被调用函数的计算结果。
 - 释放被调用函数的数据区。
 - 依照被调用函数保存的返回地址将控制转移到调用函数。
 



















