数据结构&算法学习笔记——栈和队列

目录

 

栈的定义

栈的抽象数据类型ADT

栈的表示和实现

顺序栈

初始化一个顺序栈算法

释放一个顺序栈算法

取顺序栈栈顶元素算法

顺序栈入栈算法

顺序栈出栈算法

链栈

入栈算法

出栈算法

栈的应用举例

数制转换

表达式求值

问题描述

求解算法

算法描述

表达式表达法

后缀式表达式求值算法描述

中缀式变后缀式算法描述

递归的实现

递归的含义

递归和栈的关系

队列

队列定义

队列的抽象数据类型

顺序队列的表示和实现

顺序队列的表示

初始化一个顺序队列算法

顺序队列入队算法

顺序队列出队算法

顺序队列存在的问题

存在问题

解决方案 

循环队列

循环队列的表示

初始化操作

入队操作

出队操作

链队列表示和实现

链队列的类型定义

初始化一个链队列算法

链队列入队算法

链队列出队算法


  • 栈的定义

定义:栈(Stack)是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除这一端为栈顶(Top),另一端为栈底(Buttom)。当表中没有元素时称为空栈。
 

数据结构&算法学习笔记——栈和队列_第1张图片

假设栈S=(a,a2,a3,…ai),则a,称为栈底元素,ai为栈顶元素。栈中元素按a1,a2,a3,…ai的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的。因此,栈称为后进先出表(LIFO)

栈的插入操作被形象地称为进栈入栈

栈的删除操作被形象地称为出栈退栈

通判规则:

若输入序列是...Pj...Pk...Pi…(Pj,一定不存在这样的输出序列...Pi...Pj...Pk…

即对于输入序列1,2,3,不存在输出序列3,1,2

  • 栈的抽象数据类型ADT

ADT Stack

{   数据对象:D={ai|1<=i<=n,ai属于ElemType类型}    

    数据关系: R={|ai,ai+1 ∈D,i=1,2,…,n-1}    

    基本运算:InitStack(&s)                          

                      ClearStack(&s)                          

                      StackLength(s)                          

                      StackEmpty(s)                          

                      Push(&s,e)                        

                      Pop(&s,&e)                          

                      GetTop(s,&e)                          

                      DispStack(s)

  • 栈的表示和实现

栈在计算机中主要有两种基本的存储结构:顺序存储结构链式存储结构。 

顺序存储的栈称为顺序栈; 

链式存储的栈称为链栈。 

  • 顺序栈

定义:顺序栈利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针top(栈顶指针)来动态地指示栈顶元素在顺序中的位置。通常以top=0表示空栈。

数据结构&算法学习笔记——栈和队列_第2张图片         数据结构&算法学习笔记——栈和队列_第3张图片

template 
class SqStack
{
private:
	T *base;//保持不变,NULL不存在栈
	T *top;//栈顶,指向不用(空)元素,与定义不同
	int stacksize;
public:
	SqStack();
	~SqStack();
	Status Push(T e);
	Status Pop(T &e);
    Status GetTop(T &e);
	int StackLength();
	Status IsEmpty();
	void DispStack();
};
  • 初始化一个顺序栈算法

template 
SqStack::SqStack(){
	base = new T[STACK_INIT_SIZE];
	top = base;
	stacksize = STACK_INIT_SIZE;
}
  • 释放一个顺序栈算法

template 
SqStack::~SqStack(){
	delete [] base;
}
  • 取顺序栈栈顶元素算法

template 
Status SqStack::GetTop(T &e){  //取栈顶元素
	if (top == base)  return ERROR;
	e = *(top - 1);
	return OK;
}
  • 顺序栈入栈算法

template 
Status SqStack::Push(T e){  //入栈操作
	if (top - base >= stacksize)  
        return FULL;
	*top++ = e;  //先复制,再加指针
	return OK;
}
  • 顺序栈出栈算法

template 
Status SqStack::Pop(T &e){  //出栈操作
	if (top == base) return EMPTY;
	e = *--top;  //先减指针,再取值
	return OK;
}
  • 链栈

定义链栈是采用链表作为存储结构实现的栈。为便于操作,采用带头结点的单链表实现栈。因为栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针。 

数据结构&算法学习笔记——栈和队列_第4张图片

template
struct Node
{
	T data;
	Node *next;
};

template 
class LinkStack
{
private:
	Node *top;
public:
	LinkStack();
	~LinkStack();
	Status Push(T e);
	Status Pop(T &e);
    Status GetTop(T &e);
	int StackLength();
	Status IsEmpty();
	void DispStack();
};
  • 入栈算法

1、首先要向系统申请一个结点的存储空间;

2、将新元素的值写入新结点的数据域中;

3、然后修改栈顶指针。

template 
Status LinkStack::Push(T e)
{
    Node *p = new Node;
    if (!P) return ERROR;  
	p->data = e;
    p->next = top->next;
	top->next = p;
	return OK;
}

 

相当于线性表的正序建表

  • 出栈算法

1、先取出栈顶元素的值;

2、再修改栈顶指针;

3、释放原栈顶结点。

template 
Status LinkStack::Pop(T &e) 
{
	Node *p;
	if (top->next == NULL)
		return Empty;
	else
    {
		p = top->next;
		e= p->data;
		top->next = p->next;
		delete p;
		return OK;
    }
}

栈的应用举例

由于栈结构具有的后进先出的固有特性,使栈成 为程序设计中常用的工具。 

  • 数制转换

十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理:

N=(n div d)*d+n mod d ( 其中:div为整除运算,mod为求余运算) 

示例 :把十进制数159转换成八进制数 

数据结构&算法学习笔记——栈和队列_第5张图片数据结构&算法学习笔记——栈和队列_第6张图片

//数制转换(十进制转八进制)
int conversionPop(int num) 
{
	//对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数
    SqStack s;
    int e;
    int result = 0;
	while (Num) 
    {
		s.Push(Num % 8);
        Num = Num / 8;
    }
	while (s.IsEmpty()!=EMPTY)
    {
		s.Pop(e);
		result = result *10 + e;
    }
    return result;
}//conversion
  • 表达式求值

  • 问题描述

用户输入一个包含“+”、“-”,“*”、“/”和正整数和圆括号的合法算术表达式,计算该表达式的运算结果。

例如:计算算术表达式 2+4-3*6 

说明:为了得到正确的运算结果必须遵循下面的规则:

① 先乘除后加减

② 从左算到右

③ 先括号内,后括号外 

  • 求解算法

Eg 3-2 计算算术表达式 2+4-3*6

其运算过程借助两个栈完成,一个操作数栈,一个符号栈。

(1)操作数栈为空,表达式起始符为“#”,为运算符栈底元素

(2)依次读入表达式中每个字符,如果是操作数进操作数栈,如果是运算符则和当前运算符栈的栈顶元素进行优先级比较,做相应处理,直到整个表达式求值完毕。 

数据结构&算法学习笔记——栈和队列_第7张图片

  • 算法描述

/*表达式求值*/
int EvalExpr(char *ptr)
{
	SqStack OPTR;//操作符栈
	OPTR.Push('#');
	SqStackOPND;  // 操作数栈
	char op, c, theta, x,m;
	int a, b;

	c = *ptr++;
	OPTR.GetTop(op);
	while (c != '#' || op != '#')
	{
		if (!In(c))/*如果是数字则直接进栈*/
		{
			m = atoi(&c);
			OPND.Push(m);
			c = *ptr++;
		}
		else
			/*如果是符号的话,则先将这个符号和栈顶符号进行比较*/
		{
			switch (Precede(op,c))
			{
			case '<':            /*栈页符号的优先级小*/
				OPTR.Push(c);
				c = *ptr++;
				break;
			case '=':
				OPTR.Pop(×);    /*脱括号*/
				c = *ptr++;
				break;
			case '>':
				OPTR.Pop(theta);
				OPND.Pop(b);
				OPND.Pop(a);
				OPND.Push(operate(a,theta,b));
				break;
			}
		}
		OPTR.GetTop(op);
	}

	OPND.GetTop(a);
	return a;
}
  • 表达式表达法

算术表达式中最常见的表示法形式有中缀、前缀和后缀表示法。

中缀表示法是书写表达式的常见方式,而前缀和后缀表示法主要用于计算机科学领域。 

中缀表示法:

中缀表示法是算术表达式的常规表示法。称它为中缀表示法是因为每个操作符都位于其操作数的中间,这种表示法只适用于 操作符恰好对应两个操作数的时候(在操作符是二元操作符如加、减、乘、除以及取模的情况下)。对以中缀表示法书写的表达式进行语法分析时,需要用括号和优先规则排除多义性。 

Syntax: operand1 operator operand2

Example: (A+B)*C-D/(E+F) 

前缀表示法:

前缀表示法中,操作符写在操作数的前面。这种表示法经常用于计算机科学,特别是编译器设计方面。为纪念其发明家 — Jan Lukasiewicz,这种表示法也称波兰表示法。 

Syntax : operator operand1 operand2

Example : -*+ABC/D+EF 

后缀表示法:

在后缀表示法中,操作符位于操作数后面。后缀表示法也称逆波兰表示法(reverse Polish notation,RPN),因其使表达式求值变得轻松,所以被普遍使用。 

Syntax : operand1 operand2 operator

Example : AB+C*DEF+/- 

  • 后缀式表达式求值算法描述

算法描述:

① 初始化一个空堆栈  

② 从左到右读入后缀表达式  

③ 如果字符是一个操作数,把它压入堆栈。  

④ 如果字符是个操作符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。如果您不能够弹出两个操作数,后缀表达式的语法就不正确。  

⑤ 到后缀表达式末尾,从堆栈中弹出结果。若后缀表达式格式正确,那么堆栈最后应该为空

// 算法3.5表达式求值后缀式
/*表达式求值后缀式*/
int EvalExpr_RPN(char *exp)
{
	SqStack s;
	int a, b, e;
	while (*exp != "\0")
	{
		if (!In(*exp))  s.Push(*exp - 48);
		else
		{
			s.Pop(b); 
			s.Pop(a);
			s.Push(Operate(a, *exp, b));
		}
		exp++;
	}
	s.Pop(e);
	return (e);
}

Eg 3-3 计算 4+3*5(后缀表达式:435*+

数据结构&算法学习笔记——栈和队列_第8张图片

问题:怎么把一个中缀式变成后缀式?

  • 中缀式变后缀式算法描述

转换规则是:设立一个栈,存放运算符,首先栈为空,程序从左到右扫描中缀表达式ptr

① 若ptr=操作数,直接输出,并输出一个空格作为两个操作数的分隔符;

② 若ptr=运算符,则必须与栈顶比较,运算符级别比栈顶级别高则进栈,否则退出栈顶元素并输出,然后输出一个空格作分隔符;

③ 若遇到ptr=左括号,进栈;若遇到ptr=右括号,则一直退栈输出,直到退到左括号止。

如果字符串扫描完毕,当栈不为空时,持续进行退栈操作,直到栈变成空时,则输出的结果即为后缀表达式。

 

/*中缀式变后缀式算法*/
char *change(char *ptr)
{
	char *result = new char;
	int i = 0;
	SqStack OPTR;
	OPTR.Push('#');
	char e, op;
	while (*ptr != '#')
	{
		if (!In(*ptr))
			*(result + i++) = *ptr;
		else
		{
			if (*ptr == '(')    OPTR.Push(*ptr);
			else if (*ptr == ')')
				do {
					OPTR.Pop(e);
					if (e != '(')
						*(result + i++) = e;
				} while (e != '( ');
			else
			{
				OPTR.GetTop(op);
				switch (Precede(op,*ptr))

				{
				case '<': OPTR.Push(*ptr); break;
				case '=':
				case '>':OPTR.Pop(e);
					*(result + i++) = e;
					OPTR.Push(*ptr);
				}
			}
		}
		ptr++;

	}//end of while
	while (OPTR.IsEmpty() != EMPTY)
	{
		OPTR.Pop(e);
		if (e != '#')*(result + i++) = e;
	}
	*(result + i) = '\0';
	return result;
}
  • 递归的实现

  • 递归的含义

定义:把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数,

迭代和递归的区别是迭代使用的是循环结构递归使用的是选择结构

递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存。

迭代则不需要反复调用函数和占用额外的内存。因此我们应该视不同情况选择不同的代码实现方式。

  • 递归和栈的关系

编译器使用栈来实现递归 

简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。 

队列

  • 队列定义

定义:队列(queue)是一种先进先出(first in first out,FIFO)的线性表,它只允许在表的一端进行插入,而在另一端删除元素。在队列中,允许插入数据一端称为队尾(rear),允许删除的一端称为 队头(front)。 

  • 队列的抽象数据类型

ADT Queue

{   数据对象:D={ai|1<=i<=n,ai属于ElemType类型}    

    数据关系: R={|ai,ai+1 ∈D,i=1,2,…,n-1}    

    基本运算: InitQueue(&q)                          

                       ClearQueue (&q)                          

                       QueueEmpty(q)                          

                       EnQueue(&q,e)                          

                       DeQueue(&q,&e)                          

                       GetHead(q,&e)                        

                       DispQueue(q)

  • 顺序队列的表示和实现

定义:顺序队列利用一组地址连续的存储单元依次存放自队首到队尾的数据元素,同时由于队的操作的特殊性,还必须附两个位置指针front和rear来动态地指示队首元素和队尾元素在顺序队列中的位置。 通常以 front=rear 表示空队列。 

  • 顺序队列的表示

template 
class SqQueue
{
private:
	T *base;
	T *front;
	T *rear;  //保持不变,NULL不存在栈
	int queuesize;
public:
	SqQueue();
	~SqQueue();
	Status EnQueue(T e);
	Status DeQueue(T &e);
	int QueueLength();
	Status IsEmpty();
	void DispQueue();
};
  • 初始化一个顺序队列算法

template 
SqQueue::SqQueue() 
{
	base = new T[QUEUE_INIT_SIZE];
	if (!base)  exit(OVERFLOW);
	front = rear = base;
	queuesize = QUEUE_INIT_SIZE;
}
  • 顺序队列入队算法

template 
Status SqQueue::EnQueue(T e) 
{  
	if (rear - base >= queuesize)
		return FULL;
	*rear++ = e;  //先复制,再加指针
	return OK;
}
  • 顺序队列出队算法

template 
Status SqQueue::DeQueue(T &e) 
{
	if (rear == front) 
		return EMPTY;
	e = *front++;  //先减指针,再取值
	return OK;
}
  • 顺序队列存在的问题

  • 存在问题

设数组大小为M,则:

当front=0,rear=M时,再有元素入队发生溢出——真溢出

当front!=0,rear=M时,再有元素入队发生溢出——假溢出

  • 解决方案 

① 队首固定,每次出队将剩余元素向下移动——浪费时间

② 采用循环队列基本思想:把队列设想成环形,让sq[0]接在sq[M-1]之后,若 rear+1==M,则令rear=0; 

数据结构&算法学习笔记——栈和队列_第9张图片

实现:利用“模”运算

入队:  sq[rear]=x; rear=(rear+1)%M;  

出队:  x=sq[front]; front=(front+1)%M;  

队满、队空判定条件 

数据结构&算法学习笔记——栈和队列_第10张图片

  • 循环队列

  • 循环队列的表示

template 
class CircularQueue
{
private:
	T *base;
	T *front;
	T *rear;
	int queuesize;
public:
	CircularQueue();
	~CircularQueue();
	Status EnQueue(T e);
	Status DeQueue(T &e);
	int QueueLength();
	Status IsEmpty();
	void DispQueue();
};
  • 初始化操作

template 
CircularQueue::CircularQueue()
{
	base = new T[QUEUE_INIT_SIZE];
	if (!base)  exit(OVERFLOW);//存储分配失败
	front = rear = base;
	queuesize = QUEUE_INIT_SIZE;
}
  • 入队操作

template 
Status CircularQueue::EnQueue(T e)
{
	if ((rear + 1) % QUEUE_INIT_SIZE == front)
	{
		return FULL;
	}
	base[rear] = e;
	rear = (rear + 1) % QUEUE_INIT_SIZE;
	return OK;
}
  • 出队操作

template 
Status CircularQueue::DeQueue(T &e)
{
	if (rear == front)
	{
		return EMPTY;
	}
	e = base[front];
	front = (front + 1) % QUEUE_INIT_SIZE;
	return OK;
}
  • 链队列表示和实现

定义链队列是采用链表作为存储结构实现的队列。为便于操作,采用带头结点的单链表实现队列。因为队列的插入和删除操作位置不同,所以链表的需要设置表头指针和表尾指针分别指队首和队尾。 

数据结构&算法学习笔记——栈和队列_第11张图片

  • 链队列的类型定义

template 
struct Node
{
	T data;
	Node * next;
};

template 
class LinkQueue
{
private:
	Node *front;
	Node *rear;
public:
	LinkQueue();
	~LinkQueue();
	Status EnQueue(T e);
	Status DeQueue(T &e);
	int QueueLength();
	Status IsEmpty();
	void DispQueue();
};
  • 初始化一个链队列算法

template 
LinkQueue::LinkQueue()
{
	front = new Node;
	front->next = NULL;
	rear = front;
}
  • 链队列入队算法

template 
Status LinkQueue::EnQueue(T e)
{
	Node *p = new Node;
	if (!p)exit(OVERFLOW);
	p->data = e;
	p->next = NULL;
	rear->next = p;
	rear = p;
	return OK;
}
  • 链队列出队算法

template 
Status LinkQueue::DeQueue(T &e)
{
	Node *p;
	if (front == rear)
		return EMPTY;
	p = front->next;
	e = p->data;
	front->next = p->next;
	if (rear == p)rear = front;
	delete p;
	return OK;
}

 

你可能感兴趣的:(笔记,数据结构,算法,链表,c++)