栈的应用:
由于栈的操作具有后进先出的固有特性,使得栈成为程序设计中的有用工具。另外,如果问题求解的过程具有“后进先出”的天然特性的话,则求解的算法中也必然需要使用“栈”。如(数制转换、表达式求值、括号匹配的检验、八皇后问题、行编辑程序、函数调用、迷宫求解、递归调用的实现)
队列的应用:
由于队列的操作具有先进先出的特性,使得队列成为程序设计中解决类似排队问题的有用工具。
栈的相关概念
栈是仅在表尾进行插入、删除操作的线性表。
表尾(即an端)称为栈顶Top;表头(即a1端)称为栈底Base
插入元素到栈顶(即表尾)的操作,称为入栈(压栈 PUSH)。
从栈顶(即表尾)删除最后一个元素的操作,称为出栈(POP)。
十进制整数N向其他进制数d(二、八、十六)的转换是计算机实现计算的基本问题。
转换法则:除以d倒取余 n=(n div d)*d + n mod d 其中div为整除运算,mod为求余运算。
例 把十进制数159转换为八进制数
(159)10=(237)8
假设表达式中允许包含两种括号:圆括号和方括号
其嵌套的顺序随意,即
- ([ ] ())或[([ ] [ ])]为正确格式
- [( ] )] 为错误格式
- ([ ())或(()] )为错误格式
存储方式:同一般线性表的顺序存储结构完全相同,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
但是,为了方便操作,通常top指示真正的栈顶元素之上的下标地址。
空栈:base == top 是栈空标志
栈满:top - base == stacksize
栈满时的处理方法:
使用数组作为顺序栈存储方式的特点:简单、方便、但易产生溢出(数组大小固定)
顺序栈的表示
#define MAXSIZE 100
typedef struct {
int* base; //栈底指针
int* top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
//顺序栈的初始化
bool InitStack(SqStack& s) {//构造一个空栈
s.base = new int[MAXSIZE];//或s.base=(int *)malloc(MAXSIZE*sizeof(int))
if (!s.base) return false;//存储分配失败
s.top = s.base;//栈顶指针等于栈底指针
s.stacksize = MAXSIZE;
return true;
}
//判断栈是否为空
bool StackEmpty(SqStack s) {
if (s.top == s.base)return true;
else return false;
}
//求顺序栈长度
int StackLength(SqStack s) {
return s.top - s.base;
}
//清空顺序栈
bool ClearStack(SqStack s) {
if (s.base) s.top = s.base;
return true;
}
bool DestroyStack(SqStack& s) {
if (s.base) {
delete s.base;
s.stacksize = 0;
s.base = s.top = 0;
}
return true;
}
【算法步骤】
bool Push(SqStack& s, int e) {
if (s.top - s.base == s.stacksize) return false;//栈满
*s.top++ = e;//*s.top=e; s.top++;
return true;
}
【算法步骤】
bool Pop(SqStack& s, int& e) {
if (s.top == s.base)//等价以if(StackEmpty(s))
return false;
e = *--s.top;// --s.top; e=*s.top
return true;
}
链栈的表示:
typedef struct StackNode {
int data;
struct StackNode* next;
}StackNode,*LinkStack;
LinkStack s;
int InitStack(LinkStack& s) {
LinkStack S;
S = NULL;
return true;
}
bool StackEmpty(LinkStack s) {
if (s == NULL) return true;
else return false;
}
bool Push_L(LinkStack& s, int e) {
LinkStack p = new StackNode;//生成新结点p
p->data = e;//将新结点数据域置为e
p->next = s;//进新结点插入栈顶
s = p;//修改栈顶指针
return true;
}
bool Pop_L(LinkStack& s, int& e) {
if (s == NULL) return false;
e = s->data;
LinkStack p;
p = s;
s = s->next;
delete p;
return true;
}
int GetTop(LinkStack s) {
if (s != NULL) return s->data;
}
递归的定义:
若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;
若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
如:递归求n的阶乘
long Fact(long n){
if(n==0) return 1;
else return n*Fact(n-1);
}
以下三种情况常常用到递归方法
递归问题——用分治法求解
分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解。
必备的三个条件:
- 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的。
- 可以通过上述转化而使问题简化。
- 必须有一个明确的递归出口,或称递归的边界。
分治法求解递归问题算法的一般形式:
void p(参数表){ if(递归结束条件) 可直接求解步骤; -------基本项 else p(较小的参数); --------归纳项 }
long Fact(long n){ if(n==0) return 1; //基本项 else return n*Fact(n-1); //归纳项 }
函数调用过程
调用前,系统完成:
调用后,系统完成:
当多个函数构成嵌套调用时:遵循后调用的先返回 栈
递归的优缺点
优点:结构清晰,程序易读。
缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。
递归—>非递归
方法1:尾递归、单向递归 -->循环结构
方法2:自用栈模拟系统的运行时栈
队列的顺序表示——用一维数组base[MAXQSIZE]
#define MAXSIZE 100
typedef struct {
int* base;//初始化的动态分配存储空间
int front;//头指针,若队列不空。指向队列头元素
int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置。
}SqQueue;
初始:front = rear = 0
入队:base [rear] = x ; rear++;
出队:x = base [ front] ; front++;
空队标志:front == rear
设数组大小为MAXSIZE,rear = MAXSIZE时,发出溢出
解决假上溢的方法
将队中元素依次向队头方向移动。
缺点:浪费时间。每移动一次,队中元素都要移动。
将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为MAXSIZE时,若向量的开始端空着,又可从头使用空着的空间。当front为MAXSIZE时,也是一样。
解决假上溢的方法——引入循环队列
base[0]接在base[MAXSIZE-1]之后,若rear+1==M,则令rear=0;
实现方法:利用模(mod,C语言中:%)运算。
插入元素:Q.base[Q.rear] = x;
Q.rear = (Q.rear + 1) % MAXSIZE;
删除元素:x = Q.base[s.front]
Q.front = (Q.front + 1) % MAXSIZE;
如何判断队空和队满?
解决方案:
循环队列解决队满时判断方法——少用一个元素空间:
队空:front == rear
队满:(rear + 1)% MAXSIZE == front
bool InitQueue(SqQueue& Q) {
Q.base = new int[MAXSIZE];//分配数组空间
//或Q.base=(int*)malloc(MAXSIZE*sizeof(int));
if (!Q.base) return false;//存储分配失败
Q.front = Q.rear = 0;//头指针尾指针置为0,队列为空
return true;
}
int QueueLength(SqQueue Q) {
return ((Q.rear - Q.front+MAXSIZE)%MAXSIZE);
}
bool EeQueue(SqQueue& Q, int e) {
if ((Q.rear + 1) % MAXSIZE == Q.front) return false;//队满
Q.base[Q.rear] = e;//新元素加入队尾
Q.rear = (Q.rear + 1) % MAXSIZE;//队尾指针+1
return true;
}
bool DeQueue(SqQueue& Q, int& e) {
if (Q.front == Q.rear) return false;//队空
e = Q.base[Q.front];//保存队头元素
Q.front = (Q.front + 1) % MAXSIZE;//队头指针+1
return true;
}
int GetHead(SqQueue Q) {
if (Q.front != Q.rear)//队列不为空
return Q.base[Q.front];//返回队头指针元素的值,队头指针不变
}
若用户无法估计所用队列的长度,则宜采用链队列
链队列的类型定义:
typedef struct Qnode {
int data;
struct Qnode* next;
}QNode,*QuenePtr;
typedef struct {
QuenePtr front;//队头指针
QuenePtr rear;//队尾指针
}LinkQueue;
链队列运算指针变化状况:
bool InitQueue_L(LinkQueue& Q) {
Q.front = Q.rear = (QuenePtr)malloc(sizeof(QNode));
if (!Q.front) return false;
Q.front->next = NULL;
return true;
}
算法思想:从队头结点开始,依次释放所有结点。
bool DestroyQueue(LinkQueue& Q) {
QNode* p;
while (Q.front) {
p = Q.front->next;
free(Q.front);
Q.front = p;
//或者Q.rear=Q.front->next; free(Q.front); Q.front=Q.rear;
}
return true;
}
bool EnQueue_L(LinkQueue& Q, int e) {
QNode* p;
p = (QuenePtr)malloc(sizeof(QNode));
if (!p) return false;
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return true;
}
bool DeQueue_L(LinkQueue& Q, int& e) {
if (Q.front == Q.rear) return false;
QNode* p;
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p)Q.rear = Q.front;
delete p;
return true;
}
bool GetHead(LinkQueue Q, int& e) {
if (Q.front == Q.rear) return false;
e = Q.front->next->data;
return true;
}