【数据结构】- 栈

前言:

经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽


目录

前言:

栈的应用

括号匹配

逆波兰表达式

数制转换

栈的实现

栈的初始化,销毁

入栈和出栈操作

栈的大小

判空

访问栈顶元素

验证:

完整代码:

stack.h

stack.c

test.c

总结:


概念:

栈(Stack)是一种先进后出(LIFO)的数据结构,元素的添加(入栈)和删除(出栈)仅在栈的顶部进行。出栈操作是从栈的顶部移除一个元素,并返回该元素的值。

++在栈里你如果要拿出一个数据,需要先拿出来后面放进去的;要拿出来前面放进去的,就得把后面放进去的全部拿出来++

栈的应用
括号匹配

栈可以用于检查括号是否匹配。算法的基本思想是遇到左括号时将其压入栈中,遇到右括号时从栈中弹出一个左括号。如果栈为空或最终栈不为空,则括号不匹配。

cpp 复制代码
#include <iostream>
#include <stdexcept>
#include <string>

using namespace std;

class Stack {
private:
    char* data;
    int capacity;
    int top;

public:
    Stack(int size) {
        capacity = size;
        data = new char[capacity];
        top = -1;
    }

    ~Stack() {
        delete[] data;
    }

    void push(char c) {
        if (top >= capacity - 1) {
            throw runtime_error("栈已满");
        }
        data[++top] = c;
    }

    char pop() {
        if (isEmpty()) {
            throw runtime_error("栈为空");
        }
        return data[top--];
    }

    bool isEmpty() const {
        return top == -1;
    }
};

bool balanced_parentheses(const string& parentheses) {
    Stack stack(parentheses.size());
    
    for (char parenthesis : parentheses) {
        if (parenthesis == '(') {
            stack.push(parenthesis);
        } else if (parenthesis == ')') {
            if (stack.isEmpty()) {
                return false;
            }
            stack.pop();
        } else {
            throw runtime_error("非法字符");
        }
    }
    
    return stack.isEmpty();
}

int main() {
    string test1 = "((()))";    // 平衡
    string test2 = "(()))";     // 不平衡
    string test3 = "((a))";     // 含非法字符
    
    try {
        cout << boolalpha;
        cout << test1 << ": " << balanced_parentheses(test1) << endl;
        cout << test2 << ": " << balanced_parentheses(test2) << endl;
        cout << test3 << ": " << balanced_parentheses(test3) << endl;
    } catch (const runtime_error& e) {
        cerr << "错误: " << e.what() << endl;
    }
    
    return 0;
}
逆波兰表达式

栈可以用于计算逆波兰表达式。算法的基本思想是遇到数字时将其压入栈中,遇到运算符时从栈中弹出两个元素进行运算,并将结果压入栈中。

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <functional>
#include <cmath> // for pow()

using namespace std;

int reversePolishExpr(const string& expr = "12,4,*,1,2,+,+") {
    vector<int> stack;
    unordered_map<string, function<int(int, int)>> ops = {
        {"^", [](int a, int b) { return static_cast<int>(pow(a, b)); }},
        {"*", [](int a, int b) { return a * b; }},
        {"/", [](int a, int b) { return a / b; }},  // 整数除法
        {"+", [](int a, int b) { return a + b; }},
        {"-", [](int a, int b) { return a - b; }}
    };

    istringstream iss(expr);
    string token;
    while (getline(iss, token, ',')) {
        if (ops.find(token) != ops.end()) {
            if (stack.size() < 2) {
                throw runtime_error("栈中的元素个数必须大于或等于两个");
            }
            int b = stack.back(); stack.pop_back();
            int a = stack.back(); stack.pop_back();
            stack.push_back(ops[token](a, b));
        } else {
            stack.push_back(stoi(token));
        }
    }

    if (stack.size() != 1) {
        throw runtime_error("表达式不完整或存在多余操作数");
    }
    return stack[0];
}

int main() {
    try {
        cout << reversePolishExpr() << endl;  // 默认表达式:输出 51
        cout << reversePolishExpr("3,4,*,2,5,+,+") << endl;  // 测试用例:输出 19
    } catch (const exception& e) {
        cerr << "错误: " << e.what() << endl;
    }
    return 0;
}
数制转换

栈可以用于将十进制数转换为其他进制。算法的基本思想是不断将N % 2的结果压入栈中,直到N为0,然后依次弹出栈中的元素组成结果。

cpp 复制代码
#include <iostream>
#include <stack>
#include <string>
#include <stdexcept>

using namespace std;

string conversion(int N, int K = 2) {
    // 检查进制范围是否合法(2-36)
    if (K < 2 || K > 36) {
        throw invalid_argument("进制K必须在2到36之间");
    }

    // 处理特殊情况:0在任何进制下都是0
    if (N == 0) {
        return "0";
    }

    // 处理负数情况
    bool isNegative = false;
    if (N < 0) {
        isNegative = true;
        N = -N;
    }

    const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    stack<int> Stack;

    // 计算各位数字
    while (N > 0) {
        Stack.push(N % K);
        N /= K;
    }

    // 构建结果字符串
    string res;
    if (isNegative) {
        res += "-";
    }

    while (!Stack.empty()) {
        int digit = Stack.top();
        Stack.pop();
        res += digits[digit];
    }

    return res;
}

int main() {
    // 测试用例
    try {
        cout << "10 转 2进制: " << conversion(10) << endl;      // 1010
        cout << "255 转 16进制: " << conversion(255, 16) << endl; // FF
        cout << "100 转 8进制: " << conversion(100, 8) << endl;   // 144
        cout << "-42 转 2进制: " << conversion(-42) << endl;     // -101010
        cout << "0 转 16进制: " << conversion(0, 16) << endl;     // 0
        cout << "1024 转 36进制: " << conversion(1024, 36) << endl; // RS
        // cout << "10 转 1进制: " << conversion(10, 1) << endl;  // 会抛出异常
    } catch (const invalid_argument& e) {
        cerr << "错误: " << e.what() << endl;
    }

    return 0;
}
栈的实现

在开始前我们需要给栈定义一下,capacity为栈容量,top为栈顶指针,指向下一个插入位置

cpp 复制代码
typedef struct Stack {
    STDataType* a;   // 动态数组存储栈元素
    int capacity;    // 栈容量
    int top;         // 栈顶指针(指向下一个插入位置)
} ST;
栈的初始化,销毁

初始化:

断言指针有效,再动态分配空间,再看看是否分配成功,将初始容量设为4,栈顶指针初始化为0

销毁:

断言指针有效,释放动态数组,将指针置为空,容量和top也置为0

cpp 复制代码
void STInit(ST* ps) {
    assert(ps);  // 确保传入有效栈指针
    
    ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);  // 初始分配4个元素空间
    if (ps->a == NULL) {
        perror("malloc fail");  // 内存分配失败提示
        return;  // 返回后需由调用者处理错误
    }
    
    ps->capacity = 4;   // 设置初始容量
    ps->top = 0;        // 栈顶指针初始为0,表示空栈
}
cpp 复制代码
void STDestroy(ST* ps) {
    assert(ps);  // 确保栈指针有效
    
    free(ps->a);    // 释放动态数组内存
    ps->a = NULL;   // 指针置空避免野指针
    ps->capacity = 0;  // 容量归零
    ps->top = 0;       // 栈顶指针重置
}
入栈和出栈操作

入栈操作是将新元素放到栈顶,使其成为新的栈顶元素。出栈操作是从栈顶删除元素,使其相邻的元素成为新的栈顶元素。当栈中没有任何元素时称为空栈。

入栈:

检查栈容量,观察是否需要扩容,需要就扩容,不需要就将元素放入栈顶,然后将栈顶指针向上移动

出栈:

当栈不为空时才能出栈,直接将栈顶指针减一

cpp 复制代码
void STPush(ST* ps, STDataType x) {
    assert(ps);  // 有效性检查
    
    // 检查是否需要扩容
    if (ps->top == ps->capacity) {
        STDataType* tmp = (STDataType*)realloc(ps->a, 
            sizeof(STDataType) * ps->capacity * 2);  // 尝试扩容为2倍
        
        if (tmp == NULL) {  // 扩容失败处理
            perror("realloc fail");
            return;  // 返回后栈保持原状态,但压栈失败
        }
        
        ps->a = tmp;                  // 更新数组指针
        ps->capacity *= 2;            // 更新容量
      
    }
    
    ps->a[ps->top] = x;  // 元素放入栈顶位置
    ps->top++;            // 栈顶指针上移
}
cpp 复制代码
void STPop(ST* ps) {
    assert(ps);
    assert(!STEmpty(ps));  // 确保栈非空
    ps->top--;  // 逻辑删除:仅下移栈顶指针
}
栈的大小

直接返回top,top指向下一个空闲位置,故等于元素数量

cpp 复制代码
int STSize(ST* ps) //大小
{
	assert(ps);
	return ps->top;

}
判空

返回top,top为0表示没有元素

cpp 复制代码
bool STEmpty(ST* ps)//判断是否为空
{
	assert(ps);

	return ps->top == 0;
}
访问栈顶元素

栈顶元素在top-1位置

cpp 复制代码
STDataType STTop(ST* ps)//访问栈顶元素top
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top-1];
}
验证:
cpp 复制代码
ST st;              // 声明栈变量(未初始化)
STInit(&st);       // 初始化栈结构

作用 :调用 STInit 分配初始容量为4的动态数组,设置 top=0

cpp 复制代码
printf("Stack empty? %s\n", STEmpty(&st) ? "true" : "false");  // 输出 true
printf("Stack size: %d\n", STSize(&st));                      // 输出 0

作用:测试是否为空栈

cpp 复制代码
for (int i = 1; i <= 5; i++) {
    STPush(&st, i);
    printf("Push %d, Top: %d, Size: %d, Capacity: %d\n", 
           i, STTop(&st), STSize(&st), st.capacity);
}

作用:将数据入栈

Push 1

cpp 复制代码
st.a → [1, ?, ?, ?]
top=1, capacity=4
输出: Push 1, Top: 1, Size: 1, Capacity: 4

Push 2-4

cpp 复制代码
st.a → [1, 2, 3, 4]
top=4, capacity=4

Push 5(触发扩容)

  • 检测到 top == capacity,调用 realloc 扩容至8。
cpp 复制代码
st.a → [1, 2, 3, 4, 5, ?, ?, ?]  (新数组)
top=5, capacity=8
输出: Push 5, Top: 5, Size: 5, Capacity: 8
cpp 复制代码
while (!STEmpty(&st)) {
    printf("Pop %d, Size: %d\n", STTop(&st), STSize(&st));
    STPop(&st);
}

作用:弹出栈里面的元素

将栈销毁

cpp 复制代码
STDestroy(&st);  // 释放动态数组内存,重置结构体
完整代码:
stack.h
cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct stack {
	int* a;
	int top;
	int capacity;
}ST;
void STInit(ST* ps);//初始化
void STdestory(ST* ps);//销毁

void STPush(ST* ps,STDataType x);//加 /插入
int STSize(ST* ps); //大小
bool STEmpty(ST* ps);//判断是否为空
void STPop(ST* ps);//删除/移出
STDataType STTop(ST* ps);//访问栈顶元素pop
stack.c
cpp 复制代码
#include"stack.h"

void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL) 
	{
		perror("malloc fail");
		return;
	}
	ps->capacity = 4;//
	ps->top=0;//0//top栈顶元素的下一个位置/-1,栈顶元素位置
}
void STDestroy(ST* ps)//销毁
{
	assert(ps);
	free(ps->a); ps->a = NULL;

	ps->capacity = 0;
	ps->top = 0;


}

void STPush(ST* ps, STDataType x)//加 /插入
{
	assert(ps);
	if (ps->top==ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;

	ps->top++;

}
int STSize(ST* ps) //大小
{
	assert(ps);
	return ps->top;

}
bool STEmpty(ST* ps)//判断是否为空
{
	assert(ps);

	return ps->top == 0;
}
void STPop(ST* ps)//删除/移出
{
	assert(ps);
	assert(!STEmpty(ps));//为空时不能再减
	ps->top--;
}
STDataType STTop(ST* ps)//访问栈顶元素pop
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top-1];
}
test.c
cpp 复制代码
#include"stack.h"
//int main()
//{
//	ST st;
//	STInit(&st);
//	STPush(&st,1);
//	STPush(&st,2);
//	STPush(&st,3);
//	STPush(&st,4);
//	STPush(&st,5);
//	
//	while (!STEmpty(&st))
//	{
//		printf("%d ", STTop(&st));
//		STPop(&st);
//	}
//	STdestory(&st);
//	return 0;
//}
int main() {
    ST st;
    STInit(&st);  // 初始化栈

    // 测试空栈
    printf("Stack empty? %s\n", STEmpty(&st) ? "true" : "false");  // true
    printf("Stack size: %d\n", STSize(&st));  // 0

    // 压入数据并测试扩容
    for (int i = 1; i <= 5; i++) {
        STPush(&st, i);
        printf("Push %d, Top: %d, Size: %d, Capacity: %d\n",
            i, STTop(&st), STSize(&st), st.capacity);
    }
    // 输出:
    // Push 1, Top: 1, Size: 1, Capacity: 4
    // Push 2, Top: 2, Size: 2, Capacity: 4
    // Push 3, Top: 3, Size: 3, Capacity: 4
    // Push 4, Top: 4, Size: 4, Capacity: 4
    // Push 5, Top: 5, Size: 5, Capacity: 8 (触发扩容)

    // 弹出元素
    while (!STEmpty(&st)) {
        printf("Pop %d, Size: %d\n", STTop(&st), STSize(&st));
        STPop(&st);
    }
    // 输出:
    // Pop 5, Size: 5
    // Pop 4, Size: 4
    // Pop 3, Size: 3
    // Pop 2, Size: 2
    // Pop 1, Size: 1

    // 测试空栈行为(触发断言)
    // STPop(&st);  // 若取消注释,程序会因断言失败终止

    STDestroy(&st);  // 销毁栈
    
    return 0;
}

总结:

本篇关于栈的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习

相关推荐
JANYI201818 分钟前
单片机的各个种类及其详细介绍
c语言·单片机
wuqingshun3141591 小时前
蓝桥杯 11. 最大距离
数据结构·c++·算法·职场和发展·蓝桥杯
Dovis(誓平步青云)1 小时前
【数据结构】励志大厂版·初阶(复习+刷题):栈与队列
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
大魔王(已黑化)2 小时前
LeetCode —— 94. 二叉树的中序遍历
数据结构·c++·算法·leetcode·职场和发展
丰锋ff3 小时前
数据结构学习笔记
数据结构·笔记·学习
六点半8883 小时前
【蓝桥杯】第十六届蓝桥杯C/C++大学B组个人反思总结
c语言·c++·算法·蓝桥杯
✿ ༺ ོIT技术༻3 小时前
笔试强训:Day3
c++·笔记·算法
n33(NK)4 小时前
【算法基础】冒泡排序算法 - JAVA
java·算法·排序算法
Brookty5 小时前
【数据结构】String字符串的存储
数据结构
珊瑚里的鱼6 小时前
牛客网题解 | 栈的压入、弹出序列
开发语言·c++·笔记·算法·leetcode·stl