数据结构——栈,队列,及其结构特点应用。1

简介: ​✅<1>主页:我的代码爱吃辣📃<2>知识讲解:数据结构——栈,队列。🔥<3>创作者:我的代码爱吃辣☂️<4>开发环境:Visual Studio 2022🏡<5>系统环境:windows 10💬<6>前言:今天来学习一下,数据结构中的栈和队列的实现和应用。

🦃一.栈

🐔(1)什么是栈

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。


60f8ba2f815343f2b721ba72b887eabc.png


压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈,出数据也在栈顶。


9db0a952046e479a9649582f6e273d81.gif


🐣(2)栈的实现:

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。因为栈即插入和删除(入栈和出战栈)都是在线性表的尾部进行操作,数组的尾部的插如和删除的时间复杂度都是 O(1) ,而如果是链式结构,去实现栈结构,针对尾插和尾删的时间复杂度都是 O(n) ,而双向循环带头的链表,虽然可以解决这个问题,但是.......


我们将栈的实现分为以下各个部分:

1.栈的定义 (StactInit)

typedef int STDateType;  //数据类型
typedef struct Stact
{
  STDateType* date; //数组
  int capacity;   //栈的容量
  int top;      //栈内数据的个数
}ST;
ST* StactInit()
{
  ST* Stact = (ST*)malloc(sizeof(ST));  //创建栈的结构
    //栈的结构初始化
  Stact->capacity = 4; 
  Stact->top = 0;
  Stact->date = (STDateType*)malloc(sizeof(STDateType)*Stact->capacity);
    //返回栈
  return Stact;
}

2.栈的销毁 (StactDestroy)

void StactDestory(ST* Stact)
{
  assert(Stact);
  //先释放数组申请的空间
  free(Stact->date);
  Stact->date = NULL;
  Stact->capacity = 0;
  Stact->top = 0;
  //在释放栈的结构体空间
  free(Stact);
}

3.栈的StactPushBank操作 (入栈)

void StactPushBank(ST* Stact, STDateType x)
{
  //断言Stact,当进行Push时,栈结构必须存在(Stact!=NULL),
  //如果不存也就是栈是空(Stact==NULL),那Push的操作就是非法的
  assert(Stact);
  //每次对栈的底层是数组,所以每次都要检查是否需要扩容
  if (Stact->capacity == Stact->top)
  {
    STDateType* tmp = (STDateType*)realloc(Stact->date, Stact->capacity * 2);
    if (tmp==NULL)
    {
      perror("realloc");
      exit(-1);
    }
    Stact->date = tmp;
    Stact->capacity *= 2;
  }
  //将x入栈,数组的栈内数据的个数加一
  Stact->date[Stact->top++] = x;
}

4.栈的StactPopBank操作 (出栈)

void StactPopBank(ST* Stact)
{
  assert(Stact);
  //当栈已经空了,还去出栈就是非法了
  assert(!StactEmpty(Stact));  
  Stact->top--;
}

5.栈的 StactTop (得到栈顶元素)

STDateType StactTop(ST* Stact)
{
  assert(Stact);
  //当栈已经空了,无法获取栈顶元素
  assert(!StactEmpty(Stact));
  return Stact->date[Stact->top - 1];
}

6.栈的StactSize (元素个数)

int StactSize(ST* Stact)
{
  assert(Stact);
  return Stact->top;
}

7.栈的判空 (StactEmpty)

bool StactEmpty(ST* stact)
{
  assert(stact);
  //当栈内数据个数为0时,栈为空
  return stact->top == 0;
}

测试:

void Print(ST* Stact)
{
  assert(Stact);
  for (int i = 0; i < Stact->top; i++)
  {
    printf("%d ", Stact->date[i]);
  }
  printf("\n");
}
int main()
{
  ST* Stact= StactInit();
  StactPushBank(Stact, 100); 
  StactPushBank(Stact, 200); 
  StactPushBank(Stact, 300); 
  StactPushBank(Stact, 400); 
  Print(Stact);
  printf("StactSize:%d\n",StactSize(Stact));
  //Pop一次
  StactPopBank(Stact);
  Print(Stact);
  printf("StactSize:%d\n", StactSize(Stact));
  StactDestory(Stact);
  return 0;
}



🌱(3).栈的应用

(1)例题

若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()

A 1,4,3,2

B 2,3,4,1

C 3,1,4,2

D 3,4,2,1


答案:C


分析:选项A,1进1出,2进3进4进,4出3出2出,所以出栈顺序1,4,3,2,选项A正确。


          选项B,1进2进,2出,3进3出,4进,4出1出,所以出栈顺序2,3,4,1,选项B正确。


          选项C,1进2进3进,3出,此时想要出2,就必须先出栈1,所以选项C错误。


          选项D,1进2进3进,3出,4进,4出2出1出,所以出栈顺序3,4,2,1,选项D正确。


(2)例题


LeetCode ———— 20. 有效的括号


题目描述:


b76eff3ad7f143498a9b83d75e64c680.png


思路分析:


遇到左括号就进栈,遇到右括号就出栈,将出栈的左括号与右括号进行匹配,如果匹配成功就继续,如果匹配失败就返回false。


8c617e2b121d49f6beb66afc278e5d41.gif


//栈结构
typedef char STDateType;
typedef struct Stact
{
  STDateType* date;
  int capacity;
  int top;
}ST;
ST* StactInit();
void StactPushBank(ST* Stact, STDateType x);
int StactSize(ST* Stact);
void StactDestory(ST* Stact);
void StactPopBank(ST* Stact);
STDateType StactTop(ST* Stact);
bool StactEmpty(ST* stact);
ST* StactInit()
{
  ST* Stact = (ST*)malloc(sizeof(ST));
  Stact->capacity = 4;
  Stact->top = 0;
  Stact->date = (STDateType*)malloc(sizeof(STDateType) * Stact->capacity);
  return Stact;
}
void StactPushBank(ST* Stact, STDateType x)
{
  assert(Stact);
  if (Stact->capacity == Stact->top)
  {
    STDateType* tmp = (STDateType*)realloc(Stact->date, Stact->capacity * 2);
    if (tmp == NULL)
    {
      perror("realloc");
      exit(-1);
    }
    Stact->date = tmp;
    Stact->capacity *= 2;
  }
  Stact->date[Stact->top++] = x;
}
bool StactEmpty(ST* stact)
{
  assert(stact);
  return stact->top == 0;
}
STDateType StactTop(ST* Stact)
{
  assert(Stact);
  assert(!StactEmpty(Stact));
  return Stact->date[Stact->top - 1];
}
void StactPopBank(ST* Stact)
{
  assert(Stact);
  assert(!StactEmpty(Stact));
  Stact->top--;
}
void StactDestory(ST* Stact)
{
  assert(Stact);
  free(Stact->date);
  Stact->date = NULL;
  Stact->capacity = 0;
  Stact->top = 0;
  free(Stact);
}
int StactSize(ST* Stact)
{
  assert(Stact);
  return Stact->top;
}
bool isValid(char* s) {
  //创建栈
  ST* stact = StactInit();
  while (*s)
  {
    //遇到左括号进栈
    if (*s == '(' || *s == '{' || *s == '[')
    {
      StactPushBank(stact, *s);
      s++;
    }
    else
    { //遇到右括号出栈,如果出栈的括号时,
      //栈中已经空,此时括号匹配已经失败
      if (StactEmpty(stact))
      {
        StactDestory(stact);
        return false;
      }
      //如果栈不为空,出栈的左括号,与遇到的右括号匹配
      //如果匹配成功就,继续向后走,匹配失败就返回false
      char ch = StactTop(stact);
      StactPopBank(stact);
      if ((ch == '(' && *s == ')') ||
        (ch == '[' && *s == ']') ||
         ch == '{' && *s == '}')
      {
        s++;
      }
      else
      {
        StactDestory(stact);
        return false;
      }
    }
  }
  //当括号匹配匹配完时,如果栈中还有括号,
  //也就意味着匹配失败。
  if (!StactEmpty(stact))
  {
    StactDestory(stact);
    return false;
  }
  StactDestory(stact);
  return true;
}


🌾二.队列

🌲(1)什么是队列

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。


6443b536dae748559067d052b566dfd1.gif


🌵 (2)队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。


1.队列的定义——QueueInit


由于队列是,队尾入数据,队头出数据,为了方便数据的入队,我们除了队头指针,还会设计一个队尾指针。

typedef int QUDateType;
typedef struct QueueNode
{
  QUDateType date;
  struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
  QueueNode* head;
  QueueNode* tail;
  int size;
}Queue;
void QueueInit(Queue* pque)
{
  assert(pque);
  pque->head = NULL;
  pque->tail = NULL;
  pque->size = 0;
}
int main()
{
  Queue Q;
  QueueInit(&Q);
  return 0;
}

2.入队操作——QueuePush

void QueuePush(Queue* pque,QUDateType x)
{
  //断言队列结构,当进行入队操作时,队列结构一定不能为空
  assert(pque);
  //申请空间
  QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
  if (newnode == NULL)
  {
    perror("malloc");
    exit(-1);
  }
  //赋值
  newnode->date = x;
  newnode->next = NULL;
  //如果队列为空,此时入队的就是第一个数据,也将是队列的头尾数据
  if (pque->head == NULL)
  {
    pque->head = pque->tail = newnode;
  }
  //如果队列不为空,就将数据连接到队尾巴后面
  else
  {
    pque->tail->next = newnode;
    pque->tail = newnode;
  }
  //队列中数据个数加一
  pque->size++;
}

3.队列销毁——QueueDestroy

void QueueDestroy(Queue* pque)
{
  assert(pque);
  QueueNode* cur = pque->head;
  while (cur)
  {
    QueueNode* nextnode = cur->next;
    free(cur);
    cur = nextnode;
  }
  pque->head = pque->tail = NULL;
}

4.得到对头数据——QueueFront

QUDateType QueueFront(Queue*pque)
{
  assert(pque);
  assert(!QueueEmpty(pque));
  return pque->head->date;
}

5.队列判空——QueueEmpty

bool QueueEmpty(Queue* pque)
{
  assert(pque);
  return pque->head == NULL && pque->tail == NULL;
}

6.删除对头数据——QueuePop

void QueuePop(Queue* pque)
{
  assert(pque);   //队列结构不能为空
  assert(!QueueEmpty(pque));   //删除数据时队列不能为空 
  //队列中只有一个数据的时候
  if (pque->head->next == NULL)
  {
    free(pque->head);
    pque->tail = pque->head = NULL;
  }
  //队列中数据个数大于1
  else
  {
    QueueNode* popnode = pque->head;
    pque->head = pque->head->next;
    free(popnode);
  }
  //数据个数减一
  pque->size--;
}

7.得到队尾数据——QueueBack

QUDateType QueueBack(Queue* pque)
{
  assert(pque);
  assert(!QueueEmpty(pque));
  return pque->tail->date;
}

8.得到队列中数据个数——QueueSize

int QueueSize(Queue* pque)
{
  assert(pque);
  return pque->size;
}

测试:

int main()
{
  Queue Q;
  QueueInit(&Q);
  //插如100 .200 300 ,400 ,
  QueuePush(&Q, 100);
  QueuePush(&Q, 200);
  QueuePush(&Q, 300);
  QueuePush(&Q, 400);
  //输出队头数据
  printf("%d ", QueueFront(&Q));
  //输出队尾数据
  printf("%d ", QueueBack(&Q));
  //输出队列数据个数
  printf("%d \n", QueueSize(&Q));
  //输出队列中的数据
  while (!QueueEmpty(&Q))
  {
    printf("%d ", Q.head->date);
    QueuePop(&Q);
  }
  //队列销毁
  QueueDestroy(&Q);
  return 0;
}


bb036e81f0a54c54a7e86300c4f55968.png

相关文章
|
5天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
|
5天前
|
前端开发
07_用队列实现栈
07_用队列实现栈
06_用栈来求解汉诺塔问题
06_用栈来求解汉诺塔问题
01_设计一个有getMin功能的栈
01_设计一个有getMin功能的栈
05_用一个栈实现另一个栈的排序
05_用一个栈实现另一个栈的排序
03_如何仅用递归函数和栈操作逆序一个栈
03_如何仅用递归函数和栈操作逆序一个栈
|
5天前
|
测试技术
02_由两个栈组成的队列
02_由两个栈组成的队列
|
9天前
|
存储
|
24天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
26天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
151 3