数据结构与算法之栈

一、栈和队列

      1.栈的定义

  • 栈是一种重要的线性结构,或者说,栈是前面讲的线性表的一种具体形式;
  • 栈是后进先出的数据结构(类似手枪的弹夹)。
  • 官方定义:栈(Stack)是一个后进先出(Last in first out,LIFO)的线性表,他要求只在表尾进行删除和插入操作。

      个人定义,所谓的栈,其实也就是一个特殊的线性表(顺序表、链表),但是它在操作上有一些特殊的要求和限制:

  • 栈的元素必须“后进先出”;
  • 栈的操作只能再这个线性表的表尾进行;
  • 注:对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)。

      2.栈的插入和删除操作

  • 栈的插入操作(Push),叫做进栈,也称为压栈,入栈。类似于子弹放入弹夹的动作。
  • 栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。

      3.栈的顺序存储结构

  • 因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也分为栈的顺序存储结构和栈的链式存储结构
  • 最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。

      栈的顺序结构:

      

      代码实现:

      这里定义了一个顺序存储的栈,它包含了三个元素:base,top,stackSize。其中base是指向栈底的指针变量,top是指向栈顶的指针变量,stackSize是表示栈当前可使用的最大容量

typedef struct{
    ElemType *base;
    ElemType *top;
    int stackSize;
}sqStack;

     4.栈的相关操作

      (1)创建一个栈的代码实现

#define STACK_INIT_SIZE 100
initStack(sqStack *s){
    s->base = (ElemType *)malloc(STACK_INIT_SIZE * sizeof(ElemType));
    
    if(!s->base){
        exit(0);
    }
    s->top = s->base; // 最开始栈顶就是栈底
    s->stackSize = STACK_INIT_SIZE;
}

      (2)入栈操作

  • 入栈操作又叫压栈操作,就是向栈中存放数据;
  • 入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止;

      代码实现:

#define STACKINCREMENT 10

Push(SaStack *s, ElemType e){
    // 如果栈满,追加空间
    if(s->top - s->base >= s->stackSize){
        s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
        if(!s->base){
            exit(0);    
        }
        s->top = s->base + s->stackSize; // 设置栈顶
        s->stackSize = s->stackSize + STACKINCREMENT ; // 设置栈的最大容量
    }
    
    *(s->top) = e;
    s->top++;

}

      (3)出栈操作

  • 出栈操作就是在栈顶取出数据,栈顶随指针随之下移的操作;
  • 每当从栈内弹出一个数据,栈的当前容量就-1。

      代码实现:

Pop(sqStack * s, ElemType e){
    // 如果当前栈为空栈
    if(s->top == s->base){
        return;
    }
    *e = *--(s->top);
}

      注意:我们先将s->top减1之后再取出数据赋给e,因为top是指向当前的栈顶,它指向栈顶所在位置是没有东西的,是准备存放数据的位置,我们要取出最高层的数据,就是再栈顶的下一个位置。

      (4)清空一个栈

  • 所谓清空一个栈,就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(注意区别销毁)。
  • 因此我们只要将s->top的内容赋值为s->base即可,这样s->base等于s->top,也就表明这个栈是空的了。这个原理就跟高级格式化一样,只是单纯的清空文件列表,而没有覆盖硬盘的原理是一样的。

      代码实现:

ClearStack(sqStack *s){
    s->top = s->base;
}

      (5)销毁一个栈

      销毁一个栈与清空一个栈不同,销毁一个栈是要释放掉该栈所占据的物理内存空间,因此不要把销毁一个栈与清空一个栈这两个操作混淆。

      代码实现:

Destory(sqlStack *s){
    int i, len;
    len = s->stackSize;

    for(i=0; i<len; i++){
        free(s->base);
        s->base++;
    }

    s->base = s->top = NULL;
    s->stackSize = 0;
}

      (6)计算栈的当前容量

  • 计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s.top-s.base即可;
  • 注意,栈的最大容量是指该栈占据内存空间的大小,其值是s.stackSize,它与栈的当前容量不是一个概念。
  • 注意,s.top-s.base并不是指两个地址的相减后的差,如果是整型的话,它们相减后的值就是它们之间隔了几个元素,相当于两个地址相减再去除以sizeof(ElemType),即除以这个元素的所占的空间,注意两个地址的类型必须一致,而且注意指针是不能相加的,只能++,--,相减

     5.实例分析

     (1)题目:利用栈的数据结构特点,将二进制转换为十进制数

     分析:

  • 地球人都知道,二进制数是计算机数据的存储形式,它是由一串0和1组成的,每个二进制数转换成相应的十进制数方法如下
  • XnXn-1......X3X2X1 = X1*2^0+X2*2^1+......+Xn*2^(n-1)
  • 一个二进制数要转换为相应的十进制数,就是从最低位起每一位去乘以对应位的积,也就是说用第n位去乘以2^(n-1),然后全部相加。

      例如,由于栈具有后进先出的特性,我们输入11001001这样的二进制数,如图:

     

      代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define STACK_INIT_SIZE 20
#define STACKINCREMENT 10

typedef char ElemType;
typedef struct{
    ElemType *base;
    ElemType *top;
    int stackSize;
}sqStack;

// 创建一个栈
void InitStack(sqlStack *s){
    s->base = (ElemType *)malloc(STACK_INIT_SIZE *sizeof(ElemType));

    if(!s->base){
        exit(0);
    }
    
    s->top = s->base;
    s->stackSize = STACK_INIT_SIZE;
}

// 压栈
void Push(sqStack *s, ElemType e){

    if(s->top - s->base >= s->stackSize){
        s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT)*sizeof(ElemType));
        if(!s->base){
            exit(0);
        }
    }

    *(s->top) = e;
    s->top++;
}

void Pop(sqStack *s, ElemType *e){
    if(s->top == s->base){
        return;
    }
    *e = *--(s->top);
}

// 计算栈的当前容量 
int StackLen(sqStack s){
    return (s.top - s.base);
}

int main(){
    ElemType c;
    sqStack s;
    int len, i, sum = 0;

    printf("请输入二进制数,输入#符号表示结束!\n");
    scanf("%c", &c);
    while(c != '#'){
        Push(&s, c);
        scanf("%c", &c);
    }

    getchar(); // 把回车'\n'从缓冲区去掉

    len = StackLen(s);
    printf("栈的当前容量是:%d\n", len);

    for(i=0; i<len; i++){
        Pop(&s, &c);
        sum = sum + (c-48) * pow(2, i); // pow(2, i)表示2^i
    }

    printf("转换为十进制数是:%d\n", sum);
    
    return 0;

}

      (2)从二进制到八进制

  • 可见一个字节(8bit)刚好用两个十六进制数可以表示完整,也大大的节省了显示空间;
  • 而八进制,是因为早期的计算机系统都是三的倍数,所以用八进制比较方便;
  • 在进行二进制转为八进制的操作时,我们只需要将二进制数的每三位转换成一个八进制数来表示,然后按顺序输出即可。

          

      二进制到八进制,使用两个栈,从栈A每次取3位,转换为八进制后放进栈B

 

本文为原创文章,如果对你有一点点的帮助,别忘了点赞哦!比心!如需转载,请注明出处,谢谢!

 

转载于:https://my.oschina.net/aibinxiao/blog/1836533

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值