1.4 栈定义及部分实现

1.定义

栈( Stack ):栈应用非常广泛的数据结构,来自线性表数据结构,都是“操作受限”
的线性表。
限制在表的一端进行插入和删除操作的线性表。
称为后进先出 LIFO Last In First Out )或先进后出 FILO First In Last Out )线性表。
栈:限制在表的一端进行插入和删除操作的线性表。
栈顶( Top ):允许进行插入、删除操作的一端,又称为表尾。
        用栈顶指针(top )来 指示栈顶元素。
栈底( Bottom ):是固定端,又称为表头。

n 个不同元素进栈,出栈元素不同排列的个数为(卡特兰数) \frac{1}{n+1}\cdot\textrm{C}_{2n}^{n}

2.顺序栈

栈的顺序存储结构简称为顺序栈,用一维数组来存储栈。
根据数组是否可以根据需要增大,可分为静态顺序栈和动态顺序栈。
静态顺序栈实现简单,但不能根据需要增大栈的存储空间;
动态顺序栈可以根据需要增大栈的存储空间,但实现稍为复杂。

2.1.动态顺序存储

采用动态一维数组来存储栈。动态,指的是栈的大小可以根据需要增加。
bottom 表示栈底指针,栈底固定不变的;
top 表示栈顶指针,栈顶则随着进栈和退栈操作而变化。
结点进栈:首先将数据元素保存到栈顶( top 所指的当前位置),然后执行 top 1
使 top 指向栈顶的下一个存储位置;
结点出栈:执行 top 1 ,使 top 指向栈顶元素的存储位置,然后将栈顶元素取出。

#define STACK_DEFAULT_SIZE 100 /* 栈初始向量大小 */
#define STACKINCREMENT 10 /* 存储空间分配增量 */
typedef struct sqstack {
	int* bottom; /* 栈不存在时值为 NULL */
	int* top; /* 栈顶指针 */
	int stacksize; /* 当前已分配空间,以元素为单位 */
}SqStack;
/**
 * 栈初始化
 */
bool init_stack(SqStack* stack) {
	if (stack == nullptr)
		return false;
	
	stack->stacksize = STACK_DEFAULT_SIZE;
	//【1】动态分配栈内存空间
	stack->bottom = (int*)malloc(sizeof(int) * stack->stacksize);
	if (stack->bottom == nullptr) {
		return false;
	}
	//【2】初始条件top=bottom
	stack->top = stack->bottom;
	return true;
}

bool free_stack(SqStack* stack) {
	if (stack == nullptr || stack->bottom == nullptr)
		return false;

	free(stack->bottom);
	return true;
}

bool push(SqStack* stack, int e) {
	if (stack == nullptr)
		return false;

	if (stack->top - stack->bottom > stack->stacksize) {
		//栈满时,则重新开辟更大的空间
	}
	//栈顶指针top指向栈顶的下一个空位置
	//所以,top位置先存数据,再自增
	*stack->top = e;
	stack->top += 1;
	return true;
}

bool pop(SqStack* stack, int *e) {
	if (stack == nullptr) {
		return false;
	}
	if (stack->bottom == stack->top) {//栈空,返回错误
		return false;
	}
	//栈顶指针top指向栈顶的下一个空位置
	//所以先自减,再弹出数据
	stack->top--;
	*e = *stack->top;
	return true;
}

int pop(SqStack* stack) {
	int e;
	bool ok = pop(stack, &e);
	return e;
}

void test1() {
	SqStack s;
	init_stack(&s);
	push(&s, 1);
	push(&s, 6);
	push(&s, 7);
	printf("%d\n", pop(&s));//7
	printf("%d\n", pop(&s));//6
	printf("%d\n", pop(&s));//1
	free_stack(&s);
}

2.2.静态顺序存储

采用静态一维数组来存储栈。 栈底固定不变的;
栈顶则随着进栈和退栈操作而变化,用一个整型变量 top (称为栈顶指针)来指示当
前栈顶位置。
top=0 表示栈空的初始状态
每次 top 指向栈顶在数组中的存储位置。
#define MAX_STACK_SIZE 100 /* 栈初始向量大小 */
typedef struct sqstack {
	int data[MAX_STACK_SIZE];
	int top;
}SqStack;
/**
 * 栈初始化
 */
SqStack create_stack() {
	SqStack s;
	s.top = 0;
	return s;
}

bool push(SqStack* stack, int e) {
	if (stack->top >= MAX_STACK_SIZE) {
		return false;
	}
	stack->data[stack->top++] = e;
	return true;
}

bool pop(SqStack* stack, int* e) {
	if (stack->top <= 0) {
		return false;
	}
	stack->top--;
	*e = stack->data[stack->top];
	return true;
}

int pop(SqStack* stack) {
	int e;
	bool ok = pop(stack, &e);
	return e;
}

void test1() {
	SqStack s = create_stack();
	push(&s, 1);
	push(&s, 6);
	push(&s, 7);
	printf("%d\n", pop(&s));//7
	printf("%d\n", pop(&s));//6
	printf("%d\n", pop(&s));//1
}

3.链式栈

栈的链式存储结构称为链栈,是运算受限的单链表。
其插入和删除操作只能在表头位置上进行.

链栈附加头结点,栈顶指针 top 就是链表的头指针。
图是栈的链式存储表示形式。

typedef struct LStackNode {
	int data;
	struct LStackNode* next;
}LStackNode,*LinkStack;
/**
 * 栈初始化
 */
LinkStack create_stack() {
	LStackNode* top = (LStackNode*)malloc(sizeof(LStackNode));
	top->next = nullptr;
	return(top);
}
/**
 * 压栈(头插)
 */
bool push(LinkStack stack, int e) {
	LStackNode *p = (LStackNode*)malloc(sizeof(LStackNode));
	if (!p) {
		return false; /* 申请新结点失败,返回错误标志 */
	}
	p->data = e;
	p->next = stack->next;
	stack->next = p;
	return true;
}
/**
 * 弹栈(头出)
 */
bool pop(LinkStack stack, int* e) {
	LStackNode* top = stack;
	if (top->next == nullptr)
		return false; /* 栈空,返回错误标志 */
	
	LStackNode* node = top->next;
	*e = node->data;
	top->next = node->next;
	return true;
}

int pop(LinkStack stack) {
	int e;
	bool ok = pop(stack, &e);
	return e;
}

void test1() {
	LinkStack s = create_stack();
	push(s, 1);
	push(s, 6);
	push(s, 7);
	printf("%d\n", pop(s));//7
	printf("%d\n", pop(s));//6
	printf("%d\n", pop(s));//1
}

4.栈的应用

4.1.括号匹配

算法思想:
设置一个栈,
1)当读到左括号时,左括号进栈。
2)当读到右括号时,则从栈中弹出一个元素,与读到的左括号进行匹配,
     若匹配成功, 继续读入;
     否则匹配失败,返回 FLASE
bool match(const char* s) {
	LinkStack stack = create_stack();

	for (const char* p = s; *p != '\0'; p++) {
		//【1】遇到左括号,直接进栈
		if (*p == '[' || *p == '(') {
			push(stack, *p);
		}
		//【2】遇到右括号,先弹栈,与右括号进行匹配判断
		//匹配失败,则返回false
		else if (*p == ']') {
			if ((char)pop(stack) != '[') {
				return false;
			}
		}
		else if (*p == ')') {
			if ((char)pop(stack) != '(') {
				return false;
			}
		}
	}
	//【3】栈非空,说明匹配失败
	if (!is_empty(stack))
		return false;

	return true;
}

void main() {
	printf("%s\n", match("[][]()") ? "true" : "false");//true
	printf("%s\n", match("[()()][]") ? "true" : "false");//true
	printf("%s\n", match("[][]()[") ? "true" : "false");//false
	printf("%s\n", match("[][]()]") ? "true" : "false");//false
	printf("%s\n", match("[][]()[[") ? "true" : "false");//false
}

4.2.进制转换

算法思想:
1. 初始化空栈
2. 十进制数 N 非零时,循环以下操作:
1 N 8 求余,余数入栈;
2 N 更新为 N 8 的商
3. 栈非空时,循环以下操作:
1 )弹出栈顶元素;
2 )输出 e
/**
 * 将十进制整数 N 转换为 d(2 或 8)进制数
 */
void conversion(int n, int d) {
	SqStack s = create_stack();
	for (; n > 0; n /= d) {//【1】将余数压栈
		push(&s, n % d);
	}
	while (s.top > 0){//【2】栈非空,弹栈打印
		printf("%d", pop(&s));
	}
	printf("\n");
}

void main() {
	conversion(10,8);//12
	conversion(10, 2);//1010
}

4.3.递归调用

递归调用:一个函数(或过程)直接或间接地调用自己本身,简称递归( Recursive )。
栈的另一个重要应用是在程序设计语言中实现递归和函数调用。

4.4.表达式求值

中缀表达式: b - a+5 * 3
后缀表达式 (Reverse Polish notation) : b a5+ 3 * -
前缀表达式 (Polish notation) : - b * +a5 3
注意:
        1、数据的相对先后顺序不变
        2、变化的是运算符号的位置和顺序
【表达式求值算法思想】
0)构建两个栈, 数栈S1 (存储数字)和 符号栈S2 (存储运算符号和括号)
1)从左->右扫描表达式,若 遇到数字直接,直接S1.push(e)
2)若 遇到符号,设符号为CH
        a.如果 S2为空栈 ,直接S2.push(CH)
        b.如果 CH是左括号 ,直接S2.push(CH)
        c.如果 S2栈顶元素是左括号 ,直接S2.push(CH)
        d.如果 CH优先级大于S2栈顶符号优先级 ,直接S2.push(CH);
           如果 CH优先级小于或等于S2栈顶符号优先级 ,从S2中弹出栈顶元素,
           从S1中弹出两个数后完成运算,并将结果压回S1,回到步骤2,继续扫描
表达式求值功能代码
/**
 * 运算符优先级函数
 */
int priority(char ch) {
	if (ch == '(' || ch == ')')
		return 0;
	else if (ch == '+' || ch == '-')
		return 1;
	else if (ch == '*' || ch == '/' || ch == '%')
		return 2;
}

int calc(int d1, int d2, char ch) {
	switch (ch)
	{
	case '+':return d1 + d2;
	case '-':return d1 - d2;
	case '*':return d1 * d2;
	case '/':return d1 / d2;
	case '%':return d1 % d2;
	default:return INT_MIN;
	}
	return INT_MIN;
}

/**
 * 从数字栈种弹出两个元素,从符号栈中弹出栈顶符号,
	完成运算,并将结果压回数字栈
 */
void calc_stack_num(SqStack *stack_num, SqStack* stack_ch) {
	int d1 = pop(stack_num);
	int d2 = pop(stack_num);
	int n = calc(d2, d1, pop(stack_ch));
	push(stack_num, n);
}

/**
 * 计算表达式exp的值,并返回计算结果
 */
int calculate(const char* exp) {
	const char* ch = exp;
	
	//【1】创建符号栈和数栈
	SqStack stack_num = create_stack();
	SqStack stack_ch = create_stack();

	while (*ch != '\0') {
		if (isdigit(*ch)) {
			int n = 0;
			while (isdigit(*ch)) {
				n = n * 10 + (*ch - '0');
				ch++;
			}
			push(&stack_num, n);
			continue;
		}
		else {
			//[1]符号栈空或栈顶元素是左括号,或当前ch是左括号,直接压栈
			if(empty(&stack_ch) || *ch == '('|| top(&stack_ch) == '(')
				push(&stack_ch, *ch);
			//[2]当前扫描到的符号是右括号
			else if (*ch == ')') {
				while (top(&stack_ch) != '(') {
					calc_stack_num(&stack_num, &stack_ch);
				}
				pop(&stack_ch);//从符号栈弹出左括号,丢弃
			}
			//[3]当前符号优先级大于符号栈顶元素优先级,直接压栈
			else if(priority(*ch) > priority(top(&stack_ch)))
				push(&stack_ch, *ch);
			//[4]当前符号优先级小于或等于符号栈顶元素优先级
			//数字栈弹出两个元素,从符号栈中弹出栈顶元素,完成运算,
			//并将结果压回数字栈,继续检测ch与栈顶符号优先级
			else {
				while (priority(*ch) <= priority(top(&stack_ch)) 
                    && top(&stack_ch) != '(') {
					calc_stack_num(&stack_num, &stack_ch);
				}
			}
		}
		//继续扫描下一个字符
		ch += 1;
	}
	//[5]扫描结束后,符号栈不空时,继续计算结果
	while(!empty(&stack_ch)) {
		calc_stack_num(&stack_num, &stack_ch);
	}

	return top(&stack_num);
}

void main() {
	printf("%d\n", calculate("3+5"));//8
	printf("%d\n", calculate("3+5*2"));//13
	printf("%d\n", calculate("(3+5)*2"));//16
	printf("%d\n", calculate("20-(3+5)*2"));//4
	printf("%d\n", calculate("(20-(3+5)*2)*2"));//8
}

运算结果:

例题:假设栈初始为空,将中缀表达式 6/3+ 2*4-3*2 /4 转换为等价后缀表达式的
过程中,栈的变化.

5.附件

5.1公共头文件

/**
 * common.h 默认包含头文件
 */
#ifndef __IOSTREAM_H__
#define __IOSTREAM_H__
#include <iostream>
#endif

#ifndef __STDIO_H__
#define __STDIO_H__
#include <stdio.h>
#endif

#ifndef __STDARG_H__
#define __STDARG_H__
#include <stdarg.h>
#endif

#ifndef __CTYPE_H__
#define __CTYPE_H__
#include <ctype.h>
#endif

#ifndef __INITIALIZER_LIST__
#define __INITIALIZER_LIST__
#include <initializer_list>
#endif

  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值