一、栈
1、定义
栈是只允许在一端进行插入或删除的线性表。首先栈是一种线性表。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。因此,对栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom )。不含元素的空表称为空栈。
2、栈的特征
主要特点是“后进先出”,即后进栈的元素先处理。因此栈又称为后进先出(last in first out, LIFO)表。
3、栈的常见操作
-
InitStack(&S):初始化一个空栈S。
-
StackEmpty(S):判断一个栈是否为空,若栈为空则返回true,否则返回false。
-
Push(&S, x):进栈(栈的插入操作),若栈S未满,则将x加入使之成为新栈顶。
-
Pop(&S, &x):出栈(栈的删除操作),若栈S非空,则弹出栈顶元素,并用x返回。
-
GetTop(S, &x):读栈顶元素,若栈S非空,则用x返回栈顶元素。
-
DestroyStack(&S):栈销毁,并释放S占用的存储空间(“&”表示引用调用)。
4、栈的抽象数据类型
public interface IStack<E>{
E push();
//入栈
E pop();
//出栈
E peek();
//取栈顶元素
int size();
//返回栈中元素的个数
boolean empty();
//判断栈是否为空
}
二、栈的顺序存储结构
1、栈的顺序存储
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
2、顺序栈的基本算法
(1)、InitStack(&S) 初始化栈
初始化顺序栈就是创建一个空栈,即调用 SeqStack<E>的构造函数,在构造函数中执行下面的步骤:
-
初始化 maxsize 为实际值。
-
为数组申请可以存储 maxsize 个数据元素的存储空间,数据元素的类型由实际应用而定。
-
初始化 top 为的值为一1。
void InitStack(SqStack *S){ S->top = -1; //初始化栈顶指针 }
(2)、Push(&S, x) 入栈
假设顺序栈顶端元素的索引保存在变量 top 中,栈中已有 top+1(0≤(top+1)≤maxsize-1)个数据元素,push操作是将一个给定的数据元素保存在堆栈的最顶端,执行过程如图所示:
代码:
/*插入元素e为新的栈顶元素*/
Status Push(SqStack *S, ElemType e){
//满栈
if(S->top == MAXSIZE-1){
return ERROR;
}
S->top++; //栈顶指针增加一
S->data[S->top] = e; //将新插入元素赋值给栈顶空间
return OK;
}
(3)、Pop(&S, &x) 出栈
出栈操作就是从堆栈的顶部取出数据。要进行出栈操作,需要执行以下的步骤:
-
检查堆栈中是否含有元素,如果无,返回 null;否则执行下面的步骤。
-
获取索引 top 中的元素。
-
将索引 top 的值减 1。
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack *S, ElemType *e){
if(S->top == -1){
return ERROR;
}
*e = S->data[S->top]; //将要删除的栈顶元素赋值给e
S->top--; //栈顶指针减一
return OK;
}
(4)、GetTop(S, &x) 取栈顶元素
取栈顶元素操作与出栈操作相似,只是取栈顶元素操作不改变原有堆栈,不删除取出的元素。
-
检查堆栈中是否含有元素,如果无,返回 null;否则执行下面的步骤:
-
获取索引 top 中的元素。
/*读栈顶元素*/
Status GetTop(SqStack S, ElemType *e){
if(S->top == -1){ //栈空
return ERROR;
}
*e = S->data[S->top]; //记录栈顶元素
return OK;
}
(5)、StackEmpty(S) 判栈空
判断一个栈是否为空,若栈为空则返回true,否则返回false。
-
当top指向栈顶元素存储的下一个存储单元的位置时,判空条件为top==0。
-
当top指向栈顶元素的存储位置时,判空条件为top==-1。
bool StackEmpty(SqStack S){
if(S.top == -1){
return true; //栈空
}else{
return false; //不空
}
}
3、顺序栈的上溢和下溢,以及防止溢出
上溢:栈满时在做进栈运算必定产生空间溢出
下溢:栈空时在做退栈运算也将产生溢出
防止溢出:将多个栈分配在同一个顺序存储空间内,既让多个共享存储空间,则可以相互进行调节,既节约了空间,又可降低发生溢出的频率。
4、顺序栈的Java实现
import java.lang.reflect.Array;
public class SeqStack<E> implements IStack<E> {
private int maxsize; // 顺序栈的容量
private E[] data; // 数组,用于存储顺序栈中的数据元素
private int top; // 指示顺序栈的栈顶
// 初始化栈
@SuppressWarnings("unchecked")
public SeqStack(Class<E> type, int size) {
data = (E[]) Array.newInstance(type, size); //使用Array.newInstance()创建动态数组
size = 0;
maxsize = size;
top = -1;
}
// 入栈操作
public E push(E item) {
if (!isFull()) {
data[++top] = item;
return item;
}
else
return null;
}
// 出栈操作
public E pop() {
E item = null;
if (!empty()) {
item = data[top--];
}
return item;
}
// 获取栈顶数据元素
public E peek() {
E item = null;
if (!empty()) {
item = data[top];
}
return item;
}
//求栈的长度
public int size() {
return top+1;
}
// 判断顺序栈是否为空
public boolean empty() {
if (top == -1) {
return true;
} else {
return false;
}
}
// 判断顺序栈是否为满
public boolean isFull() {
if (top == maxsize - 1) {
return true;
} else {
return false;
}
}
}
//主方法
public class Test {
public static void main(String[] args) {
int[] data = {23,56,89,17};
IStack<Integer> stack = new SeqStack<Integer>(Integer.class,50);
//入栈操作
for(int i = 0;i<data.length;i++) {
stack.push(data[i]);
System.out.println(data[i]+" 入栈");
}
int size = stack.size();
//出栈
for(int i = 0 ; i<size ; i++){
System.out.println(stack.pop()+" 出栈");
}
}
}
三、栈的链式存储结构
1、链栈
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头节点,Lhead指向栈顶元素
对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。
2、栈的链式存储结构
/*栈的链式存储结构*/
/*构造节点*/
typedef struct StackNode{
ElemType data;
struct StackNode *next;
}StackNode, *LinkStackPrt;
/*构造链栈*/
typedef struct LinkStack{
LinkStackPrt top;
int count;
}LinkStack;
3、链栈的基本算法
(1)、链栈的进栈
将元素 1、2、3、4 依次入栈,等价于将各元素采用头插法依次添加到链表中,每个数据元素的添加过程如下:
初始化:head——>NULL
添加元素1:head——>1——>NULL
添加元素2:head——>2——>1——>NULL
添加元素3:head——>3——>2——>1——>NULL
添加元素4:head——>4——>3——>2——>1——>NULL
//链表中的节点结构
typedef struct lineStack{
int data;
struct lineStack * next;
}lineStack;
//stack为当前的链栈,a表示入栈元素
lineStack* push(lineStack * stack,int a){
//创建存储新元素的节点
lineStack * line=(lineStack*)malloc(sizeof(lineStack));
line->data=a;
//新节点与头节点建立逻辑关系
line->next=stack;
//更新头指针的指向
stack=line;
return stack;
}
(2)、链栈的出栈
初始化:head——>4——>3——>2——>1——>NULL
元素4出栈:head——>3——>2——>1——>NULL
元素3出栈:head——>2——>1——>NULL
元素2出栈:head——>1——>NULL
元素1出栈:head————>NULL
//栈顶元素出链栈的实现函数
lineStack * pop(lineStack * stack){
if (stack) {
//声明一个新指针指向栈顶节点
lineStack * p=stack;
//更新头指针
stack=stack->next;
printf("出栈元素:%d ",p->data);
if (stack) {
printf("新栈顶元素:%d\n",stack->data);
}else{
printf("栈已空\n");
}
free(p);
}else{
printf("栈内没有元素");
return stack;
}
return stack;
}
四、队列
1、定义
队列(queue)是一种特殊的线性表,只允许在表的一端进行插入操作而在另一端进行删除操作的线性表。进行插入操作的端称为队尾(rear),进行删除操作的端称为队头(front)。队列中没有数据元素时称为空队列(empty queue)。
-
队头(Front):允许删除的一端,又称队首。
-
队尾(Rear):允许插入的一端。
-
空队列:不包含任何元素的空表。
2、队列特征
队列的操作是按照先进先出(first in first out)或后进后出( last in last out)的原则进行的 ,因此,队列又称为 FIFO 表或 LILO 表。
3、队列的常见基本操作
-
InitQueue(&Q):初始化队列,构造一个空队列Q。
-
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
-
EnQueue(&Q, x):入队,若队列Q未满,将x加入,使之成为新的队尾。
-
DeQueue(&Q, &x):出队,若队列Q非空,删除队头元素,并用x返回。
-
GetHead(Q, &x):读队头元素,若队列Q非空,则将队头元素赋值给x。
五、队列的顺序存储结构
1、顺序队列
队列的顺序存储类型:
#define MAXSIZE 50; //定义队列中元素的最大个数
typedef struct{
ElemType data[MAXSIZE]; //存放队列元素
int front,rear;
}SqQueue;
初始状态(队空条件):Q->front == Q->rear == 0。
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
出队操作:队不空时,先取队头元素值,再将队头指针加1。
(1)、初始化顺序队列 InitQueue(&Q)
初始化顺序队列就是创建一个空队列,即调用 SeqQueue<E>的构造函数,在构造函数中执行下面的步骤:
-
初始化 maxsize 为实际值。
-
为数组申请可以存储 maxsize 个数据元素的存储空间,数据元素的类型由实际应用而定。
-
将队头和队尾指示变量都置为-1。
(2)、入队操作 EnQueue(&Q, x)
入队操作是将一个给定的元素保存在队列的尾部,同时修改队头和队尾指示变量 front和 rear 的值。要执行人队操作,需要执行下面的步骤:
-
判断队列是否是满,如果是,返回 false。
-
设置 rear 的值为(rear + 1)% maxsize,使 rear 指向要插入元素的位置。
-
设置数组索引为 rear 的位置的值为入队元素的值。
(3)、出队操作 DeQueue(&Q, &x)
出队操作是指在队列不为空的情况下从队列的前端删除元素,使队头指示器 front 加1。要执行出队操作,需要执行下面的步骤:
(1)、检查队列是否为空。如果为空,返回 null
(2)、设置 front 的值为(front + 1)% maxsize,使 front 指向要删除元素的位置。
(3)、返回队头指示器 front 所在位置的元素。
(4)、取队头元素 GetHead(Q, &x)
取队头元素操作与出队操作相似,只是取队头元素操作不改变原有队列,不删除取出的队头元素。要执行取队头操作,需要检查队列是否为空,如果为空,返回 null;否则,返回队头指示器 front 所在位置的元素。
(5)、求队列的长度 size()
循环顺序队列的长度取决于队尾指示器 rear 和队头指示器 front。一般情况下,rear 大于 front,因为入队的元素肯定比出队的元素多。特殊的情况是 rear 到达数组的上限之后又从数组的低端开始,此时 rear 是小于 front 的,所以 rear 的大小要加上 maxsize。循环顺序队列的长度应该是(rear-front+maxsize)%maxsize。
(6)、队列是否为满 isFull()
a、牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志“
-
队满条件: (Q->rear + 1)%Maxsize == Q->front
-
队空条件仍: Q->front == Q->rear
-
队列中元素的个数: (Q->rear - Q ->front + Maxsize)% Maxsize
b、类型中增设表示元素个数的数据成员。这样,队空的条件为 Q->size == O ;队满的条件为 Q->size == Maxsize 。这两种情况都有 Q->front == Q->rear
c、类型中增设tag 数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致 Q->front == Q->rear ,则为队空;tag 等于 1 时,若因插入导致 Q ->front == Q->rear ,则为队满。
2、顺序队列Java表示
//接口类
public interface IQueue<E> {
boolean enqueue(E item);
E deueue();
E peek();
int size();
boolean isEmpty();
boolean isFull();
}
import java.lang.reflect.Array;
public class SeqQueue<E> implements IQueue<E>{
private int maxsize; //队列容量
private E[] data; //存储循环队列中的数据元素
private int front; //指示最近一个已经离开队列的元素所占的位置
private int rear; //指示最近一个已经入队列的元素的位置
@SuppressWarnings("unchecked")
public SeqQueue(Class<E> type, int size){
data = (E[]) Array.newInstance(type,size); //使用Array.newInstance()创建动态数组
maxsize = size;
front = rear = -1;
}
//入队列操作
@Override
public boolean enqueue(E item) {
if(!isFull()) {
rear = (rear + 1) % maxsize;
data[rear] = item;
return true;
}else
return false;
}
//出队列操作
@Override
public E deueue() {
if(!isEmpty()) {
front = (front + 1) % maxsize;
return data[front];
}else
return null;
}
//取队头元素
@Override
public E peek() {
if(!isEmpty()){
return data[(front+1) % maxsize];
}else
return null;
}
//求队列长度
@Override
public int size() {
return (rear-front+maxsize) % maxsize;
}
//判断循环顺序队列是否为空
@Override
public boolean isEmpty() {
if(front == rear){
return true;
}else
return false;
}
//判断循环队列是否为满
@Override
public boolean isFull() {
if ((front == -1 && rear == maxsize - 1) || (rear + 1) % maxsize == front) {
return true;
} else {
return false;
}
}
}
//主方法
public class Test {
public static void main(String[] args) {
int data[] = {45,89,78,56,15,29,36};
//循环队列长度比实际长度大1
IQueue<Integer> queue = new SeqQueue<Integer>(Integer.class,data.length+1);
//入队操作
for(int i = 0;i<data.length;i++){
queue.enqueue(data[i]);
System.out.println(data[i]+" 入队");
}
int size = queue.size();
//出队操作
for(int i= 0;i< size;i++){
System.out.println(queue.deueue()+" 出队");
}
}
}
3、循环队列
头尾相接的顺序存储结构称为循环队列。当队首指针Q->front = MAXSIZE-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。
-
初始时:Q->front = Q->rear=0。
-
队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。
-
队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。
-
队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。
3、循环队列常见基本算法
(1)、循环队列初始化
/*初始化一个空队列Q*/
Status InitQueue(SqQueue *Q){
Q->front = 0;
Q->rear = 0;
return OK;
}
(2)、循环队列判空
/*判队空*/
bool isEmpty(SqQueue Q){
if(Q.rear == Q.front){
return true;
}else{
return false;
}
}
(3)、求循环队列长度
/*返回Q的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue Q){
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
(4)、循环队列入队
/*若队列未满,则插入元素e为Q新的队尾元素*/
Status EnQueue(SqQueue *Q, ElemType e){
if((Q->real + 1) % MAXSIZE == Q->front){
return ERROR; //队满
}
Q->data[Q->rear] = e; //将元素e赋值给队尾
Q->rear = (Q->rear + 1) % MAXSIZE; //rear指针向后移一位置,若到最后则转到数组头部
return OK;
}
(5)、循环队列出队
/*若队列不空,则删除Q中队头元素,用e返回其值*/
Status DeQueue(SqQueue *Q, ElemType *e){
if(isEmpty(Q)){
return REEOR; //队列空的判断
}
*e = Q->data[Q->front]; //将队头元素赋值给e
Q->front = (Q->front + 1) % MAXSIZE; //front指针向后移一位置,若到最后则转到数组头部
}
六、队列的链式存储结构
1、链队列
队列的链式存储结构表示为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已。
空队列时,front和real都指向头结点。
2、链队列常见算法
(1)、链队列存储类型
/*链式队列结点*/
typedef struct {
ElemType data;
struct LinkNode *next;
}LinkNode;
/*链式队列*/
typedef struct{
LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;
(2)、链队列初始化
代码:
void InitQueue(LinkQueue *Q){
Q->front = Q->rear = (LinkNode)malloc(sizeof(LinkNode)); //建立头结点
Q->front->next = NULL; //初始为空
}
(3)、链队列入队
Status EnQueue(LinkQueue *Q, ElemType e){
LinkNode s = (LinkNode)malloc(sizeof(LinkNode));
s->data = e;
s->next = NULL;
Q->rear->next = s; //把拥有元素e新结点s赋值给原队尾结点的后继
Q->rear = s; //把当前的s设置为新的队尾结点
return OK;
}
(4)、链队列出队
/*若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR*/
Status DeQueue(LinkQueue *Q, Elemtype *e){
LinkNode p;
if(Q->front == Q-<rear){
return ERROR;
}
p = Q->front->next; //将欲删除的队头结点暂存给p
*e = p->data; //将欲删除的队头结点的值赋值给e
Q->front->next = p->next; //将原队头结点的后继赋值给头结点后继
//若删除的队头是队尾,则删除后将rear指向头结点
if(Q->rear == p){
Q->rear = Q->front;
}
free(p);
return OK;
}