从零开始的数据结构
本期正式步入数据结构
本篇文章是学习视频中的笔记
参考视频 浙江大学数据结构(陈越、何钦铭)
堆栈
后缀表达式:
中缀表达式:运算符号位于两个运算数之间。如a+b*c-d/e
后缀表达式:运算符号位于两个运算数之后。如abc*+de/-
后缀表达式运算分析如下:先依次输入,a、b、c,然后遇到了*,运算b * c,然后遇到了+,运算a+b * c,然后输入d、e,遇到了/,运算d/e,然后遇到了-,运算a+b*c-d/e
总结:总左向右“扫描”,逐个处理运算数和运算符号。
由此引出堆栈的数据结构,先入后出(后入先出)
堆栈的抽象数据类型描述
堆栈(Stack):具有一定操作约束的线性表
只在一端(栈顶,Top)做插入,删除
插入数据:入栈(Push)
删除数据:出栈(Pop)
后入先出:Last In First Out(LIFO)
类型名称:堆栈(Stack)
数据对象集:一个有0个或多个元素的有穷线性表、
操作集:长度为 MaxSize 的堆栈 S∈Stack,堆栈元素 item∈ ElementType
1、Stack CreateStack ( int MaxSize ) :生成空堆栈,其最大长度为MaxSize;
2、int IsFull ( Stack S,int MaxSize ) :判断堆栈S是否已满;
3、void Push ( Stack S, ElementType item ) :将元素item压入堆栈;
4、int IsEmpty( Stack S ) :判断堆栈S是否为空;
5、ElementType Pop( Stack S ):删除并返回栈顶元素;
Push 和 Pop可以穿插交替进行
栈的顺序存储实现
栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成。
#define MaxSize <储存数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode{
ElementType Data{MaxSize};
int Top;
};
1、入栈
void Push( Stack PtrS,ElementType item)
{
if(PtrS->Top = MaxSize-1){
printf("堆栈满");
return;
}else{
PtrS->Data[++(PtrS->Top)] = item;
return;
}
}
2、出栈
ElementType Pop(Stack PtrS)
{
if(PtrS->Top = -1){
printf("堆栈空");
return ERROR; /ERROE是ElementType的特殊值
}else{
return ( PtrS->Data[(PtrS->Top)--]);
}
}
3、用一个数组实现两个堆栈
思路,左放一个,右放一个,从两头往中间增长,当两个栈的栈顶指针相遇,表示满
可以定义如下数据结构
#define MaxSize
struct DStack{
ElementType Data[MaxSize];
int Top1;//堆栈1的栈顶指针
int Top2;//堆栈2的栈顶指针
}S;
//如下表示堆栈满
S.Top = -1;
S.Top = MaxSize;
该数据结构的push操作
void Push(struct DStack *PtrS,ElementType item,int Tag)
{
if(PtrS->Top2-PtrS->Top1 == 1){
printf("堆栈满");
return ;
}
if(Tag == 1)//对第一个栈操作
PtrS->Data[++(PtrS->Top1)]=item;
else //对第二个栈操作
PtrS->Data[--(PtrS->Top2)]=item;
}
堆栈的链式存储实现
栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。
typedef struct SNode *Stack;
struct SNode{
ElementType Data;
struct SNode *Next;
};
1、堆栈初始化(建立空栈)
2、判断堆栈s是否为空
Stack CreateStack()
{
//构建一个堆栈的头结点,返回指针
Stack S;
S = (Stack)malloc(sizeof(struct SNode));
S->Next = NULL;
return S;
}
int IsEmpty(Stack S)
{
//判断堆栈S是否为空,若空函数返回整数1,否则返回0
return (S->Next == NULL);
}
void Push(ElementType item,Stack S)
{
//将元素item压入堆栈S
struct SNode *TemCell;
TemCell = (struct SNode *)malloc(sizeof(struct SNode));
TemCell->Element = item;
TemCell->Next= S->Next;
S->Next = TemCell;
}
ElementType Pop(Stack S)
{
//删除并返回堆栈S的栈顶元素
Struct SNode *FirstCell;
ElementType TopElem;
if (IsEmpty(S)){
printf("堆栈空");
return NULL;
}else{
FirstCell = S->Next;
S->Next = FirstCell->Next;
TopElem = FirstCell->Element;
free(FirstCell);
return TopElem;
}
}
中缀表达式求值
基本策略:将中缀表达式转换为后缀表达式,然后求值
转化方式:例子:2+9/3-5 ->2 9 3 / + 5 -
1、运算数相对顺序不变
2、运算符号顺序发生改变
- 需要存储”等待中“的运算符号
- 需将当前运 算符号与”等待中“的最后一个运算符号比较
下一个扫描到 - ,此时确定 / 先输入
有括号的运算符:
总结:从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
- 运算数:直接输出
- 左括号:压入堆栈
- 右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
- 运算符:
- 若优先级大于栈顶运算符时,则把它压栈;
- 若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈;
- 若各对象处理完毕,则把堆栈中存留的运算符一并输出。
综合例子:(2*(9+6/3-5)+4)
堆栈的其他应用:
- 函数调用及递归实现
- 深度优先搜索
- 回溯算法
- …
队列
队列的抽象数据类型描述
队列:具有一定操作约束的线性表
插入和删除操作:只能在一端插入,在另一端删除
- 数据插入:入队列
- 数据删除:出队列
- 先来先服务
- 先进先出:FIFO
类型名称:队列(Queue)
数据对象集:一个有0个或多个元素的有穷线性表
操作集:长度为MaxSize的队列Q∈Queue,队列元素item∈ElementType
1、Queue CreateQueue ( int MaxSize ) : 生成长度为MaxSize的空队列;
2、int IsFullQ ( Queue Q,int MaxSize ) : 判断队列Q是否已满;
3、void AddQ ( Queue Q,ElementType item ) : 将数据元素item插入队列Q中;
4、int IsEmptyQ ( Queue Q ) : 判断队列Q是否为空
5、ElementType DeleteQ ( Queue Q ) : 将队头数据元素从队列中删除并返回。
队列的顺序存储实现
队列的顺序存储结构通常由一个一维数组和一个记录队列头元素位置的变量front以及一个记录队列尾元素位置的变量rear组成。
#define MaxSize <储存数据元素的最大个数>
struct QNode {
ElementType Data[MaxSize];
int rear;
int front;
};
typedef struct QNode *Queue;
例如,先入队三个元素
Rear+3
然后出队一个元素
Front+1
所以,入队一个元素,Rear+1,出队一个元素Front+1
当如下图这种情况,数组后面排不下了怎么办
我们自然的想到,排到前面,由此引出我们的循环队列的概念
循环队列
我们在之前判断队列是否空的时候,是用Rear是否等于Front来判断的,然而,在循坏队列中,很容易出现队列满,Rear==Front的情况
于是我们就要换一种方法判断
- 使用额外标记:Size或tag域(插入一个,Size+1,删除一个,Size-1;插入一个,tag置1,删除一个,tag置0,由此看出最后一次操作是插入还是删除。)
- 仅使用n-1个数组空间
我们这里采取第二种原则,仅使用n-1个数组空间
实现循环队列的一个操作:求余
1、入队列
void AddQ(Queue PtrQ,Elementype item)
{
if((PtrQ->rear+1))%MaxSize == PtrQ->front)//先判断队列是不是满
{
printf("队列满");
return;
}
PtrQ->rear = (PtrQ->rear+1)%MaxSize;
PtrQ->Data[PtrQ->rear] = item;
}
2、出队列
ElementType DeleteQ(Queue PtrQ)
{
if(PtrQ->front == PtrQ->rear){
printf("队列空");
return ERROR;
}else{
PtrQ->front = (PtrQ->front+1)%MaxSize;
return PtrQ->Data[PtrQ->front];
}
}
队列的链式存储实现
队列的链式存储结构也可以用一个单链表实现,插入和删除操作分别在链表的两头进行。
front在链表头,rear在链表尾
struct Node{
ElementType Data;
struct Node *Next;
};
struct QNode{ /*链队列结构*/
struct Node *rear; /*指向队尾结点*/
struct Node *front;/*指向队头结点*/
};
typedef struct QNode *Queue;
Queue PtrQ;
不带头结点的链式队列出队操作的一个实例
ElementType DeleteQ(Queue PtrQ)
{
struct Node *FrontCell;
ElementType FrontElem;
if(PtrQ->front == NULL)
{
printf("队列空");
return ERROR;
}
FrontCell = PtrQ->front;
if(PtrQ->front == PtrQ->rear)
PtrQ->front = PtrQ->rear = NULL;
else
PtrQ->front = PtrQ->front->Next;
FrontElem = FrontCell->Data;
free(FrontCell);
return FrontElem;
}