目录
前言
手撕顺序栈、链式栈,拳打顺序队列、链式队列,接下来上菜!!
提示:以下是本篇文章正文内容,下面案例可供参考
一、线性表(栈)
栈和队列都是线性表 线性结构
举例:
(1)只能在栈顶进行插入和删除的线性表叫栈(堆栈)
(2)规则
生活案例: 一个杯子,往杯子里面放大饼,先放进去的大饼,最后一个拿出来
先进后出(FILO),后进先出(LIFO)
I in O out F first L last
(3)栈 : 栈顶和栈底
栈针:指向栈顶的指针(top),始终指向栈顶 //栈针,不是C语言上的 指针的改变
//从实际应用的角度考虑:
//顺序表 存下5个整数 11 22 33 44 55 应用频繁使用查找功能,你就用顺序表
//链表 存下5个整数 11 22 33 44 55 应用频繁使用插入和删除功能,你就用链表
栈是一种思想 先进后出
你可以用数组实现栈的思想 ----- 顺序栈 ,在内存当中是连续存储的
也可以用链表去实现栈的思想 ----- 链式栈, 在内存当中是不连续存储的
顺序栈和链式栈的区别是什么??
存储结构不用,顺序栈, 顺序存储结构 链式栈 链式存储结构
1.1 顺序栈
顺序表与顺序栈结构体的对比
/顺序栈 sequence 顺序 stack 栈
逻辑结构: 线性结构
存储结构:顺序存储
//顺序表的结构体
#define N 100
typedef struct
{
int data[N]; //用来存储有效元素的数组
int last; //last的值,时刻代表有效元素的个数
}seqlist_t;
//顺序表的插入位置在哪儿都可以插入
11 22 33 44 55
1000 11 22 33 44 55 //插入在头
11 22 33 44 55 1000 //插入在尾巴
11 22 33 1000 44 55 //插入在中间
//顺序栈的结构体
#define MAX 5
typedef struct
{
int data[MAX]; //用来存储有效数据的数据
int top; //top栈针,top的值,也时刻代表数组中有效元素的个数
}seqstack_t;
顺序栈的操作(增删改查)
栈顶(栈顶就是顺序表的尾巴进行插入和删除操作)进行插入和删除
顺序栈操作 顺序表操作
1.创建一个空的栈createEmptyStack 创建一个空的顺序表
2.入栈pushStack() 在顺序表的尾巴上插入一个数据 有效元素个数+1
3.出栈popStack() 删除顺序表的尾巴 尾巴里面的数据域返回 有效元素个数-1
//popStack函数的返回值,就是栈顶的元素(出栈的数据)
4.判断栈是否为满isFullStack() 判断顺序表是否未满
5.判断栈是否为空isEmptyStack() 判断顺序表是否为空
6.获取栈顶元素int getTopValue() 将顺序表的尾巴节点里面的数据域返回,不讲栈顶元素删除
7.清空栈 clearStack() 清空顺序表
具体代码举例,上菜!! 慢慢品尝
#include <stdio.h>
#include <stdlib.h>
#define MAX 5
typedef struct
{
int data[MAX];//用来保存有效元素的数组
int top;//栈针,top的值时刻代表有效元素的个数,同时也被当做数组的下标来使用
}seqstack_t;
//1.创建一个空的顺序栈
seqstack_t* createEmptyStack()
{
//申请一个结构体空间大小
seqstack_t* p = (seqstack_t*)malloc(sizeof(seqstack_t));
if(p == NULL)
{
printf("createEmptyStack malloc failed!!\n");
return NULL;//返回值是NULL,没用-1,是因为函数的返回值是指针类型
}
//因为是空的栈,所以有效元素个数要初始化为0
p->top = 0;
return p;
}
//2.入栈
void pushStack(seqstack_t* p, int x)
{
//容错判断
if(isFullStack(p))
{
printf("isFullStack!!\n");
return;//提前结束函数
}
//有效元素个数,当做下标,正好是栈的尾巴
p->data[p->top] = x;
//有效元素个数+1
p->top++;
}
//3.判断栈是否为满 满返回1,未满返回0
int isFullStack(seqstack_t* p)
{
//有效元素个数 == 数组的最大长度就是 满
return p->top == MAX ? 1 : 0;
//return p->top == MAX;
}
//4.出栈,将栈顶的最后一个有效元素返回
int popStack(seqstack_t*p)
{
//容错判断
if(isEmptyStack(p))
{
printf("isEmptyStack!!\n");
return;
}
p->top--;//有效元素个数-1,正好的得到即将出栈最后一个有效元素的下标
return p->data[p->top];
}
//5.判断栈是否为空,空返回1,未空返回0
int isEmptyStack(seqstack_t* p)
{
//有效元素个数 == 0 栈空
return p->top == 0 ? 1 : 0;
//return p->top == 0;
}
//6.获取栈顶数据元素的值,并不是将栈顶的数据元素删除
int getTopValue(seqstack_t* p)
{
//容错判断
if(isEmptyStack(p))
{
printf("isEmptyStack!!\n");
return;
}
return p->data[p->top-1];//有效元素个数-1 == 最后一个有效元素的下标
}
//7.清空栈,有效元素个数为0,即空的栈
void clearStack(seqstack_t* p)
{
//有效元素个数为0,就是空栈
p->top = 0;
}
int main(int argc, const char *argv[])
{
seqstack_t* p = createEmptyStack();
pushStack(p,11);
pushStack(p,22);
pushStack(p,33);
pushStack(p,44);
pushStack(p,55);
pushStack(p,66);//入栈失败,因为MAX == 5
printf("栈顶是%d\n",getTopValue(p));
// clearStack(p);
while(!isEmptyStack(p))//只要栈不为空,就进入,继续出栈
{
printf("%d ",popStack(p));
}
//入栈是 11 22 33 44 55 出栈 55 44 33 22 11
printf("\n");
//这个while循环结束后,已经是空的栈了
return 0;
}
#####练习#####
定义一个函数,将其转换为二进制数,将二进制位存储到栈内,再出栈打印输出
先 % 再 /
先求出来的是低位,后求出来是高位
我们打印的时候 先打印高位 后打印低位
用栈的思想实现:每求出一个二进制位就入栈,所有的二进制位入栈后,再统一输出
void showBin(seqstack_t *p, int num)
{
//先%再/,一直到0
while(num != 0)
{
pushStack(p,num % 2);
num /= 2;
}
//将所有的二进制位出栈,打印输出
while(!isEmptySeqStack(p))
{
printf("%d ",popStack(p));
}
printf("\n");
}
int main(int argc, const char *argv[])
{
seqstack_t *p = createEmptySeqstack(100);
showBin(p,15);
return 0;
}
1.2 顺序栈实操
练习:使用顺序栈实现计算器,例如:3+5*2-1+8*2=?
char a[] = "3+5*2-1+8*2"
5 * 2 == 10
10 + 3 == 13
13 - 1 == 12
2 * 8 = 16
12 + 16 = 28
上面的表达式可以用栈来实现
创建2个栈
1 操作数栈(3, 5, 2, 1, 8, 2) 专门存数字
2 运算符栈(+ * - + *) 专门存运算符'+'也是整数
自左至右扫描表达式,如果遇到数字,进入操作数栈
如果遇到运算符 判断
如果当前运算符栈为空,直接将运算符入运算符栈
如果当前运算符栈不为空,将栈顶的运算符与当前运算符进行优先级比较
如果,当前运算符优先级高于栈顶运算符优先级,将当前运算符入运算符栈
如果,当前运算符优先级不高于栈顶运算符优先级,将栈顶运算符从栈中取出,再从操作数栈中取出
两个数,进行运算,运算结果后,将结果数字,再次放入操作数栈
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
typedef struct
{
int data[MAX];//用来保存有效元素的数组
int top;//栈针,top的值时刻代表有效元素的个数,同时也被当做数组的下标来使用
}seqstack_t;
//1.创建一个空的顺序栈
seqstack_t* createEmptyStack()
{
//申请一个结构体空间大小
seqstack_t* p = (seqstack_t*)malloc(sizeof(seqstack_t));
if(p == NULL)
{
printf("createEmptyStack malloc failed!!\n");
return NULL;//返回值是NULL,没用-1,是因为函数的返回值是指针类型
}
//因为是空的栈,所以有效元素个数要初始化为0
p->top = 0;
return p;
}
//2.入栈
void pushStack(seqstack_t* p, int x)
{
//容错判断
if(isFullStack(p))
{
printf("isFullStack!!\n");
return;//提前结束函数
}
//有效元素个数,当做下标,正好是栈的尾巴
p->data[p->top] = x;
//有效元素个数+1
p->top++;
}
//3.判断栈是否为满 满返回1,未满返回0
int isFullStack(seqstack_t* p)
{
//有效元素个数 == 数组的最大长度就是 满
return p->top == MAX ? 1 : 0;
//return p->top == MAX;
}
//4.出栈,将栈顶的最后一个有效元素返回
int popStack(seqstack_t*p)
{
//容错判断
if(isEmptyStack(p))
{
printf("isEmptyStack!!\n");
return;
}
p->top--;//有效元素个数-1,正好的得到即将出栈最后一个有效元素的下标
return p->data[p->top];
}
//5.判断栈是否为空,空返回1,未空返回0
int isEmptyStack(seqstack_t* p)
{
//有效元素个数 == 0 栈空
return p->top == 0 ? 1 : 0;
//return p->top == 0;
}
//6.获取栈顶数据元素的值,并不是将栈顶的数据元素删除
int getTopValue(seqstack_t* p)
{
//容错判断
if(isEmptyStack(p))
{
printf("isEmptyStack!!\n");
return;
}
return p->data[p->top-1];//有效元素个数-1 == 最后一个有效元素的下标
}
//7.清空栈,有效元素个数为0,即空的栈
void clearStack(seqstack_t* p)
{
//有效元素个数为0,就是空栈
p->top = 0;
}
//比较优先级函数
int getPriority(char oper)
{
switch(oper)
{
case '+':
case '-':
return 0;
case '*':
case '/':
return 1;
}
}
int calcNum(int num1, char oper, int num2)
{
switch(oper)
{
case '+':
printf("%d + %d = %d\n",num1,num2,num1+num2);
return num1 + num2;
case '-':
printf("%d - %d = %d\n",num1,num2,num1-num2);
return num1 - num2;
case '*':
printf("%d * %d = %d\n",num1,num2,num1*num2);
return num1 * num2;
case '/':
printf("%d / %d = %d\n",num1,num2,num1/num2);
return num1 / num2;
}
}
int main(int argc, const char *argv[])
{
int in,out;//用来保存优先级
int num1,num2;//用来保存出栈的两个数字
char oper;//出栈的运算符
int ret;//用来保存计算结果
char a[] = "3+5*2-1+8*2";
seqstack_t* numStack = createEmptyStack();//操作数栈
seqstack_t* operStack = createEmptyStack();//运算符栈
//遍历字符串,只要是数字就进操作数栈,运算符,需要判断
int i = 0;
while(a[i] != '\0')
{
if(a[i] >= '0' && a[i] <= '9')//只要是数字就入操作数栈
{
pushStack(numStack, a[i]-'0');// '3' - '0' == 3
}
else//运算符
{
if(isEmptyStack(operStack))//如果运算符栈为空,直接将运算符入栈
{
pushStack(operStack,a[i]);//运算符入栈
}
else//运算符栈不为空,需要将栈外的运算符的优先级与栈内栈顶的运算符优先级做比较
{
out = getPriority(a[i]);//获取栈外运算符优先级
in = getPriority(getTopValue(operStack));//获取栈顶运算符优先级
if(out > in)//栈外优先级高于栈内,入栈
{
pushStack(operStack,a[i]);
}
else//栈外的优先级,不高于栈内的优先级
{
//将栈内的运算符出栈,再从操作数栈取出两个数
oper = popStack(operStack);//出栈运算符
num2 = popStack(numStack);//先出栈的数字,放在运算符的右边
num1 = popStack(numStack);//后出栈的数字,放在运算符的左边
ret = calcNum(num1, oper, num2);//得到计算结果
//将计算的结果入操作数栈
pushStack(numStack,ret);
i--;//因为下一次循环,我要继续拿当前的运算符与栈顶的运算符进行优先级比较
}
}
}
i++;
}
//上面的循环结束后,运算符栈内和数字栈内有残留
while(!isEmptyStack(operStack))//只要运算符栈不为空
{
//将栈内的运算符出栈,再从操作数栈取出两个数
oper = popStack(operStack);//出栈运算符
num2 = popStack(numStack);//先出栈的数字,放在运算符的右边
num1 = popStack(numStack);//后出栈的数字,放在运算符的左边
ret = calcNum(num1, oper, num2);//得到计算结果
//将计算的结果入操作数栈
pushStack(numStack,ret);
}
printf("%s = %d\n",a,popStack(numStack));
return 0;
}
1.3 链式栈
/顺序栈 sequence 顺序 stack 栈
逻辑结构: 线性结构
存储结构:顺序存储
//链表的结构体
typedef struct node_t//链表的插入位置在哪儿都可以插入
{
int data; //用来存储有效元素的数据域 i<=post-1
struct node_t *next; //指针next指向的位置,由插入位置的前一个节点
}link_node_tlist_t;
11 22 33 44 55
1000 11 22 33 44 55 //插入在头
11 22 33 44 55 1000 //插入在尾巴
11 22 33 1000 44 55 //插入在中间
//链式栈的结构体的插入位置只能在栈顶位置
typedef struct node_t
{
int data; //用来存储有效数据的数据
struct node_t* next; //头插法,指针时刻指向链表中头节点的下一个节点
}link_node_t;
链式栈的操作思想(增删改查)
栈顶(栈顶就是链表的表头进行插入和删除操作)
1.创建一个空的栈createEmptyLink 创建一个空的链表
2.入栈pushLink() 在链表的表头上插入一个数据,插队到第一个
3.出栈popLink() 删除链表的表头里面的指针域
//popLink函数的返回值,就是栈顶的元素(出栈的数据)
4.判断栈是否为空isEmptyLink() 判断链表头指针域是否为空
5.获取栈顶元素int getTopValue()将表头里的数据域返回,释放刚刚读取的空间
6.清空栈 clearLink() 将指针赋值为NULL
1.4 链式栈实操
链式栈案例代码,上菜!!
#include <stdio.h>
#include <stdlib.h>
typedef struct node_t
{
int data;//数据域
struct node_t *next;//指针域
}linknode_t;
//1.创建空的链表
linknode_t *createEmptyLinklist()
{
//创建一个节点,作为有头单向链表的头节点
linknode_t *p = malloc(sizeof(linknode_t));//malloc函数的返回值
//申请成功,返回的是申请空间的首地址 失败,返回的是空指针
if(NULL == p)
{
printf("createEmptyLinklist malloc failed!!\n");
return NULL;
}
//给节点赋值
p->next = NULL;
return p;
}
//2.向链表的表头位置插入数据
int insertIntoLinklist(linknode_t *p, int x)
{
int i;
//1.将头指针p赋值给指针m,保证p始终指向第一个位置
linknode_t *m=p;
//2.创建一个新的节点,保存数据
linknode_t *pnew = malloc(sizeof(linknode_t));
if(NULL == pnew)
{
printf("pnew's malloc failed!!\n");
return -1;
}
//3.给新节点装数据
pnew->data = x;
pnew->next = NULL;
//将新节点链接到链表中,先连后面,再连前面
pnew->next = m->next;
m->next = pnew;
return 0;
}
//3.遍历打印链表
void showLinklist(linknode_t *p)
{
while(p->next != NULL)
{
p = p->next;
printf("%d ",p->data);
}
printf("\n");
}
//4.求链表的长度
int getLengthLinklist(linknode_t *p)
{
int count = 0;
while(p->next != NULL)
{
p = p->next;
count++;
}
return count;
}
//5.判断单向链表是否为空 空返回1 非空返回0
int isEmptyLinklist(linknode_t *p)
{
return p->next == NULL;
}
//6.删除链表中指定位置的数据
int deletePostLinklist(linknode_t *p)
{
//0.对删除进行容错判断
if(isEmptyLinklist(p))
{
printf("deletePostLinklist failed!!\n");
return -1;
}
//1.将头指针p赋值给指针m,保证p始终指向第一个位置
linknode_t *m=p;
//2.定义一个pdel指针,指向被删除的节点
linknode_t * pdel = m->next;
//3.跨过被删除节点
m->next = pdel->next;
//4.释放被删除的节点
free(pdel);
return 0;
}
//7.清空链表
void clearLinklist(linknode_t *p)
{
linknode_t *pdel = NULL;
while(p->next != NULL)//只要链表不为空,就进行砍头操作
{
//1.将头指针移动到删除位置的前一个位置 第1步可以省略,因为每次删除的头节点的下一个节点
//也就意味着,头节点是每次删除节点的前一个位置p可以保持不动
//2.定义一个pdel指向被删除的节点
pdel = p->next;
//3.跨过被删除的节点
p->next = pdel->next;
//4.释放被删除节点
free(pdel);
}
}
//8.查找指定数据出现的位置,出现在第几个位置
//x代表的是被查找的数据
int searchDatePostLinklist(linknode_t *p,int x)
{
int i = 0;
while(p->next != NULL)
{
p = p->next;
i++;
if(p->data == x)
{
return i;
}
}
return -1;//-1代表没有找到
}
int main(int argc, const char *argv[])
{
linknode_t *h = createEmptyLinklist();
insertIntoLinklist(h,10);
insertIntoLinklist(h,20);
insertIntoLinklist(h,30);
insertIntoLinklist(h,40);
showLinklist(h);
printf("len is %d\n",getLengthLinklist(h));
printf("30 post:%d\n",searchDatePostLinklist(h,30));
deletePostLinklist(h);
showLinklist(h);
clearLinklist(h);
showLinklist(h);
return 0;
}
二、线性表(队列)
队列
(1)只能在两端进行插入和删除操作的线性表(在队列头进行删除,队列的尾进行插入)
去银行办理业务: 先来的,先办业务,后来的,后办业务
(2)规则 先进先出,后进后出 FIFO LILO
(3)有两个指针
存数据端(rear) //后面的插入
取数据端(front)//前面的删除
用数组实现的队列称为循环队列 也叫顺序队列
用链表实现的队列叫链式队列
顺序队列(顺序存储)和链式队列(链式存储)最大的区别是????
存储结构不同:
顺序队列(循环队列):顺序存储结构,在内存当中是连续存储的
链式队列:链式存储结构,在内存当中是不连续存储的
2.1 顺序队列
循环队列中最多存储N-1个元素,N代表数组的元素个数
//顺序表的结构体
#define MAX 5
typedef struct
{
int data[MAX];//用来保存有效元素的数组
int top;//栈针,top的值时刻代表有效元素的个数,同时也被当做数组的下标来使用
}seqstack_t;
//顺序队的结构体 queue 队列 sequence 顺序
#define N 5
typedef struct
{
int data[N]; //用来存储数据的
int rear; //后面 队列尾 插入 p->data[p->rear] 存数据端
int front;//前面 队列头 删除 p->data[p->front] 取数据端
}sequeue_t;
2.2 顺序队列实操(增删改查)
#include <stdio.h>
#include <stdlib.h>
#define N 5
typedef struct
{
int data[N];
int rear;//后面 队列的尾巴 插入
int front;//前面 队列的头 删除
}sequeue_t;
//1.创建一个空的队列
sequeue_t *createEmptyQueue()
{
//1.申请一个结构体空间的大小
sequeue_t *p = malloc(sizeof(sequeue_t));
if(NULL == p)
{
printf("createEmptyQueue malloc failed!!\n");
return NULL;
}
//2.给结构体成员变量进行赋值
p->rear = 0;
p->front = 0;
//p->front 和 p->rear的初始值只要相等就可以(初始值不能超出数组下标范围)
return p;
}
//2.入列,插入,用的是rearx代表入列的数据
int inQueue(sequeue_t *p,int x)
{
//0.容错判断,判断队列是否为满
if(isFullQueue(p))
{
printf("isFullQueue!!\n");
return -1;
}
//1.入列,插入数据
p->data[p->rear] = x;
//2.rear++
p->rear = (p->rear+1) % N;//rear当做下标,不能数组越界,所以%N
return 0;
}
//3.判断队列是否为空 空返回1,非空返回0
int isEmptyQueue(sequeue_t *p)
{
return p->rear == p->front;
}
//4.出列,删除,用的是front
int outQueue(sequeue_t *p)
{
//容错判断
if(isEmptyQueue(p))
{
printf("isEmptyQueue!!\n");
return -1;
}
int x = p->data[p->front];
p->front = (p->front+1)%N;
return x;
}
//5.判断队列是否为满 满返回1 为满返回0
int isFullQueue(sequeue_t *p)
{
return (p->rear+1) % N == p->front ? 1 : 0;
}
//6.求队列的长度
int getLengthQueue(sequeue_t *p)
{
if(p->rear >= p->front)
return p->rear - p->front;
else
return p->rear - p->front + N;
/*
if(p->rear == p->front)
{
return 0;
}
else if(p->rear > p->front)
{
return p->rear - p->front;
}
else if(p->rear < p->front)
{
return p->rear - p->front + N;
}
*/
}
int main(int argc, const char *argv[])
{
sequeue_t *p = createEmptyQueue();
inQueue(p,10);
inQueue(p,20);
inQueue(p,30);
inQueue(p,40);
inQueue(p,50);//入列失败,最多能存N-1个
while(!isEmptyQueue(p))//打印输出 10 20 30 40 先进先出
{
printf("%d\n",outQueue(p));
}
return 0;
}
2.3 链式队列
数组和链表都可以实现栈和队列
顺序栈和链式栈最大区别是什么?? 存储结构
顺序队列和链式队列最大的区别是什么?? 存储结构
数组实现的内存当中是连续存储的 //存储结构 顺序存储结构
链表实现的内存当中是不连续存储的 //存储结构 链式存储结构
为什么说数组的元素个数为N的循环队列,最多能够存储 N-1 个数据??
因为循环队列 p->rear == p->front 做为队列为空的条件 那么队列为满的条件就要更改
在入队列之前, 提前对rear的下一个位置,判断是否等于 front,来判断队列是否为满
(p->rear + 1) % N == p->front
//方法一
if(p->rear >= p->front)
return p->rear - p->front;
else
return p->rear + N - p->front;
//方法二
return (p->rear + N - p->front) % N;
总结
这里对文章进行总结:手撕顺序栈、链式栈,拳打顺序队列、链式队列,接下来累了,休息消化!!