一、栈(Stack)
1、基本概念
栈是一种逻辑结构,是特殊的线性表。特殊在:
-
只能在固定的一端操作
只要满足上述条件,那么这种特殊的线性表就会呈现一种“后进先出”的逻辑,这种逻辑就被称为栈。栈 在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。
由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,另起了 下面这些特定的名称:
-
栈顶:可以进行插入删除的一端;
-
栈底:栈顶的对端;
-
入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push();
-
出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop();
-
取栈顶:取得栈顶元素,但不出栈,函数名通常为top()
基于这种固定一端操作的简单约定,栈获得了“后进先出”的基本特性,如下图所示,最后一个放入的元素,最先被拿出来:
2、存储形式
栈只是一种数据逻辑,如何将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈。
1.顺序栈(sequenceStack)
顺序存储意味着开辟一块连续的内存来存储数据节点,一般而言,管理栈数据除了需要一块连续的 内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置,如果有多线程还需要 配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:
// 顺序栈节点
struct seqStack
{
datatype *data; // 顺序栈入口
int size; // 顺序栈总容量
int top; // 顺序栈栈顶元素下标
};
2.链式栈(linkStack)
链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创 建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:
// 链式栈节点
typedef struct node
{
datatype data;
struct node *next;
}node;
// 链式栈管理结构体
struct linkStack
{
node *top; // 链式栈栈顶指针
int size; // 链式栈当前元素个数
};
3、完整代码
-
顺序栈(sequenceStack)
sstack.h
#ifndef __SSTACK_H #define __SSTACK_H // 数据类型 typedef int DATA; // 顺序栈结构体 typedef struct { DATA *pData; // 栈中元素的地址 int size; // 栈的总容量 int top; // 栈顶元素下标 }SeqStack; // 初始化栈 int SStack_init(SeqStack *s, int num); // 判断栈是否已满 int SStack_isfull(SeqStack *st); // 判断栈是否为空 int SStack_isempty(SeqStack *st); // 入栈/压栈 int SStack_push(SeqStack *st,DATA data); // 出栈/弹栈 int SStack_pop(SeqStack *st,DATA *data); // 回收栈 int SStack_free(SeqStack *st); #endif
sstack.c
#include <stdlib.h> #include "sstack.h" // 初始化栈 int SStack_init(SeqStack* s,int num) { s -> pData = (DATA*)calloc(sizeof(DATA),num); if(s -> pData == NULL) return -1; s -> size = num ; s -> top = -1; return 0; } // 判断栈是否已满 int SStack_isfull(SeqStack *st) { return st -> top + 1 == st -> size; } // 判断栈是否为空 int SStack_isempty(SeqStack *st) { return st -> top == -1; } // 压栈/入栈 int SStack_push(SeqStack *st,DATA data) { if(SStack_isfull(st)) return -1; st -> top++; st -> pData[st -> top] = data; return 0; } // 出栈/弹栈 int SStack_pop(SeqStack *st,DATA *data) { if(SStack_isempty(st)) return -1; *data = st -> pData[st -> top]; st -> top--; return 0; } // 回收栈 int SStack_free(SeqStack *st) { if(st -> pData) { free(st->pData); st -> pData = NULL; } st -> top = -1; }
sstack_main.c
#include "sstack.h" #include <stdio.h> int main(void) { SeqStack st; SStack_init(&st,10); register int i = 1; for(; i <=10; i++) SStack_push(&st,i); if(-1 == SStack_push(&st,1024)) fprintf(stderr,"满栈,插入失败\n"); while(!SStack_isempty(&st)) { DATA data = 0; SStack_pop(&st,&data); printf("%4d",data); } printf("\n"); SStack_free(&st); return 0; }
【课堂练习1】
使用顺序栈,接收键盘的输入,实现如下功能:
1. 输入数字时,依次入栈。
2. 输入字母时,依次出栈。
3. 每次入栈或者出栈,都将顺序栈中的各个元素输出出来。
-
完整代码(链式栈(linkStack))
linkstack.h
#ifndef __LINKSTACK_H #define __LINKSTACK_H // 数据类型 typedef int DATA; // 链式栈节点 typedef struct _node { DATA data; // 数据 struct _node *next; // 指向下一个栈的节点 }NODE; // 链式栈管理结构体 typedef struct { NODE *pHead;// 链式栈栈顶指针 int size; // 链式栈当前元素个数 int num; }LinkStack; // 初始化链式栈 int LStack_init(LinkStack *s, int num); // 判断栈是否已满 int LStack_isfull(LinkStack *st); // 判断栈是否为空 int LStack_isempty(LinkStack *st); // 压栈/入栈 int LStack_push(LinkStack *st,DATA data); // 弹栈/出栈 int LStack_pop(LinkStack *st,DATA *data); // 回收栈 int LStack_free(LinkStack *st); #endif
linkstack.c
#include "linkstack.h" #include <stdio.h> #include <stdlib.h> // 初始化栈 int LStack_init(LinkStack *st, int num) { st -> pHead = NULL; st -> size = num; st -> num = 0; return 0 ; } // 判断栈是否已满 int LStack_isfull(LinkStack *st) { return st -> num == st -> size; } // 判断栈是否为空 int LStack_isempty(LinkStack *st) { return st -> num == 0; } // 入栈 int LStack_push(LinkStack *st,DATA data) { if(LStack_isfull(st)) return -1; NODE* p = (NODE*)malloc(sizeof(NODE)); if(!p) return -1; p -> data = data; p -> next = st -> pHead; st -> pHead = p; (st -> num)++; return 0; } // 出栈 int LStack_pop(LinkStack *st,DATA *data) { if(LStack_isempty(st)) return -1; NODE* p = st -> pHead; if(!p) return -1; *data = p -> data; st -> pHead = p -> next; free(p); (st -> num)--; return 0; } // 回收栈 int LStack_free(LinkStack *st) { NODE* p = st -> pHead, *q = NULL; while(p) { q = p; p = p -> next; free(q); } st -> pHead = NULL; st -> num = 0; return 0; }
linkstack_main.c
#include "linkstack.h" #include <stdio.h> int main(void) { LinkStack st; LStack_init(&st,10); register int i = 1; for(; i <= 10; i++) LStack_push(&st,i); if(-1 == LStack_push(&st,1024)) fprintf(stderr,"满栈,插入失败\n"); while(!LStack_isempty(&st)) { DATA data = 0; LStack_pop(&st,&data); printf("%4d",data); } printf("\n"); LStack_free(&st); return 0; }
【课堂练习2】
使用链式栈,实现十进制转八进制:键盘输入一个十进制数,经过链式栈的相关算法,输出八进制 数。
代码:
二、队列(queue)
1、基本概念
队列是一种逻辑结构,是一种 特殊的线性表。特殊在: 只能在固定的两端操作线性表 只要满足上述条件,那么这种特殊的线性表就会呈现一种“先进先出”的逻辑,这种逻辑就被称为队列。
队列本质上,是从一端进,另一端出,为了保证跟生活的认知一致,我们建议把进的一端叫做队尾,出的一端叫做对头。
由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起个特殊的名称:
-
队头:可以删除节点的一端;
-
队尾:可以插入节点的一端;
-
入队:将节点插入到队尾之后,函数名通常为enQueue();
-
出队:将队头节点从队列中剔除,函数名通常为outQueue();
-
取队头:取得队头元素,但不出队,函数名通常为front()。
由于这种固定两端操作的简单约定,队列获得了“先进先出”的基本特性,如下图所示:
2、顺序存储的队列:循环队列(sequenceQueue)
与其他的逻辑结构类似,队列可以采用顺序存储形成循环队列,也可以采用链式存储形成链式队列。 顺序存储的队列之所以被称为循环队列,是因为可以利用更新队头队尾的下标信息,来循环地利用整个数组,出队入队时也不必移动当中的数据。循环队列示意图如下所示:
从上述动图中可以观察到,需要牺牲至少数组中的一个存储位置,来区分循环队列中的满队和空队。 满队和空队的约定如下:
-
当front与rear相等时,队列为空;
-
当rear循环加一与front相等时,队列为满;
与其他数据结构一样,管理循环队列除了需要一块连续的内存之外,还需要记录队列的总容量、当前 队列的元素个数、当前队头、队尾元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:
typedef int DATA;
typedef struct
{
DATA *pData; // 队列入口
int size; // 队列总容量
int head; // 队列队头元素下标
int tail; // 队列队尾元素下标
}SQueue;
3、完整代码(顺序-循环队列)
squeue.h
#ifndef __SQUEUE_H #define __SQUEUE_H typedef int DATA; typedef struct { DATA *pData; // 队列入口 int size; // 队列总容量 int head; // 队列队头元素下标 int tail; // 队列队尾元素下标 }SQueue; // 初始化队列 int SQ_init(SQueue *q, int num); // 判断队列是否已满 int SQ_isfull(SQueue *q); // 判断队列是否为空 int SQ_isempty(SQueue *q); // 入队 int SQ_push(SQueue *q,DATA data); // 出队 int SQ_pop(SQueue *q,DATA *data); // 回收队 int SQ_free(SQueue *q); #endif
squeue.c
#include <stdlib.h> #include "squeue.h" // 初始化队列 int SQ_init(SQueue* q,int num) { q -> pData = (DATA*)calloc(sizeof(DATA),num); if(q -> pData == NULL) return -1; q -> size = num ; q -> head = q -> tail = 0; return 0; } // 判断队列是否已满 int SQ_isfull(SQueue *q) { return (q -> tail + 1) % q -> size == q -> head; } // 判断队列是否为空 int SQ_isempty(SQueue *q) { return q -> tail == q -> head; } // 出队 int SQ_push(SQueue *st,DATA data) { if(SQ_isfull(st)) return -1; st -> pData[st -> tail] = data; st -> tail = (st -> tail+1) % st -> size; return 0; } // 入队 int SQ_pop(SQueue *st,DATA *data) { if(SQ_isempty(st)) return -1; *data = st -> pData[st -> head]; st -> head = (st -> head+1) % st -> size; return 0; } // 回收队列 int SQ_free(SQueue *st) { if(st -> pData) { free(st->pData); st -> pData = NULL; } st -> head = st -> tail = 0; }
注意: 循环队列中,需要牺牲一个存储位置来区分空队和满队
【课堂练习3】
构建一个顺序存储的循环队列,当用户输入数字时,将数字入队,当用户输入字母时,将队头元素出队。每次操作队列之后,将队列中的元素显示出来。
4、链式存储的队列:链式队列(linkQueue)
链式队列的组织形式与链表无异,只不过插入删除被约束在固定的两端。为了便于操作,通常也会创建所谓管理结构体,用来存储队头指针、队尾指针、队列元素个数等信息:
从上图可以看到,链式队列主要控制队头和队尾,由于管理结构体中保存了当前队列元素个数size,因 此可以不必设计链表的头节点,初始化空队列时只需要让队头队尾指针同时指向空即可。
以下是队列链表节点设计和管理结构体设计的示例代码:
typedef int DATA;
// 链式队列节点
typedef struct _node
{
DATA data;
struct _node *next;
}NODE/*,*PNODE*/;
// 链式队列管理结构体
typedef struct
{
NODE *pHead; // 队头指针
NODE *pTail; // 队尾指针
int size ; // 队列当前元素个数
int num ;
}LinkQueue;
5、完整代码(链式队列(linkQueue))
当进入新数据时,创建新节点
linkQueue.h
#ifndef __LINKQUEUE_H #define __LINKQUEUE_H typedef int DATA; // 链式队列节点 typedef struct _node { DATA data; struct _node *next; }NODE/*,*PNODE*/; // 链式队列管理结构体 typedef struct { NODE *pHead; // 队头指针 NODE *pTail; // 队尾指针 int size ; // 队列当前元素个数 int num ; }LinkQueue; void LQ_init(LinkQueue *q,int sz); int LQ_isfull(LinkQueue *q); int LQ_isempty(LinkQueue *q); int LQ_push(LinkQueue *q,DATA data); int LQ_pop(LinkQueue *q,DATA *data); int LQ_free(LinkQueue *q); #endif
linkQueue.c
#include "LinkQueue.h" #include <stdlib.h> void LQ_init(LinkQueue *q,int sz) { q -> pHead = q -> pTail = NULL; q -> size = sz; q -> num = 0; } int LQ_isfull(LinkQueue *q) { return q -> num == q -> size; } int LQ_isempty(LinkQueue *q) { return q -> num == 0; } int LQ_push(LinkQueue *q,DATA data) { if(LQ_isfull(q)) return -1; NODE* pNew = (NODE*)malloc(sizeof(NODE)); if(!pNew) return -1; pNew -> data = data; pNew -> next = NULL; if(!(q -> pTail)) q -> pHead = q -> pTail = pNew; else { q -> pTail -> next = pNew; q -> pTail = pNew; } q -> num ++; return 0; } int LQ_pop(LinkQueue *q,DATA *data) { if(LQ_isempty(q)) return -1; NODE* p = q -> pHead; *data = p -> data; if(p == q -> pTail) q -> pHead = q -> pTail = NULL; else q -> pHead = p -> next; free(p); q -> num --; return 0; } int LQ_free(LinkQueue *queue) { NODE* p = queue -> pHead, *q = NULL; while(p) { q = p; p = p -> next; free(q); } queue -> pHead = queue -> pTail = NULL; queue -> num = 0; return 0; }
linkQueue_main.c
#include "LinkQueue.h" #include <stdio.h> int main(void) { LinkQueue queue; LQ_init(&queue,10); register int i = 1; for(; i <= 10 ; i++) LQ_push(&queue, i); if( -1 == LQ_push(&queue,1024)) fprintf(stderr,"满队,入队失败!\n"); while(!LQ_isempty(&queue)) { DATA data; LQ_pop(&queue,&data); printf("%4d",data); } printf("\n"); LQ_free(&queue); return 0; }
【课堂练习4】
构建一个链式队列,当用户输入数字时,将数字入队,当用户输入字母时,将队头元素出队。每次操作队列之后,将队列中的元素显示出来。
章节作业
(栈的基本操作)
【1】编程实现功能:将键盘输入的十进制数,转换为十六进制输出。
(栈的基本操作)(选做)
【2】编程实现汉诺塔游戏。
(栈的基本操作)(选做)
【3】面试题
(队列的基本操作)(选做)
【4】蜗牛在制定今天的旅游计划,有 nn 个景点可选,它已经把这些景点按照顺路游览的顺序排成一排了,每个地方有相应的景观标号,这里用一个整数表示,相同的标号意味着相同的景观。 蜗牛希望选取连续的一段景点,还要选出来的每一个景点的景观都不同,问它最多能选出多少个景点进行旅游。
提示:
假设景点的景观标号是: 6 2 8 2 6
那么蜗牛可选的最长不重复的连续景点数是3,即前三个6 2 8,或后三个8 2 6
v图像 小部件