1.08k likes | 1.25k Views
数 据 结 构. 计算机系教师 段恩泽. 办公室: C7101 电话: 82878095 Email:duanenze@126.com. 第 3 章 栈和队列. 栈和队列也是线性结构 ,线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是 线性表的操作不受限制,而栈和队列的操作受到限制 。 栈的操作只能在表的一端进行 , 队列的插入操作在表的一端进行而其它操作在表的另一端进行 . 栈和队列称为 操作受限 的线性表。. 3.1 栈. 3.1.1 栈的定义及基本运算 栈 (Stack) 是操作限定在表的尾端进行的线性表。
E N D
数 据 结 构 计算机系教师 段恩泽 办公室:C7101 电话:82878095 Email:duanenze@126.com
第3章 栈和队列 • 栈和队列也是线性结构,线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是线性表的操作不受限制,而栈和队列的操作受到限制。 • 栈的操作只能在表的一端进行,队列的插入操作在表的一端进行而其它操作在表的另一端进行. • 栈和队列称为操作受限的线性表。
3.1栈 3.1.1栈的定义及基本运算 • 栈(Stack)是操作限定在表的尾端进行的线性表。 • 表尾要进行插入、删除等操作,把表尾称为栈顶(Top),另一端是固定的,叫栈底(Bottom)。 • 当栈中没有数据元素时叫空栈。
栈S= (a1,a2,…,an),a1为栈底元素,an为栈顶元素。 • 按照a1,a2,…,an的顺序依次入栈,而出栈的次序相反。 • 栈的操作是按照后进先出(Last In First Out,简称LIFO)或先进后出(First In Last Out,简称FILO)的原则进行的,因此,栈又称为LIFO表或FILO表。
出栈 入栈 an … a2 a1 top
栈的形式定义为:栈(Stack)简记为S,是一个二元组,栈的形式定义为:栈(Stack)简记为S,是一个二元组, S = (D, R) 其中:D是数据元素的有限集合, R是数据元素之间关系的有限集合。
刷洗盘子。把洗净的盘子一个接一个地往上放(相当于把元素入栈);取用盘子的时候,则从最上面一个接一个地往下拿(相当于把元素出栈)。刷洗盘子。把洗净的盘子一个接一个地往上放(相当于把元素入栈);取用盘子的时候,则从最上面一个接一个地往下拿(相当于把元素出栈)。 • 栈的操作是线性表操作的一个子集。栈的操作主要包括在栈顶插入元素和删除元素、取栈顶元素和判断栈是否为空等。 • 栈的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。
泛型栈接口取名为Istack: public interface IStack<T> : IDS<T> { void Push(T item); //入栈操作 T Pop(); //出栈操作 T GetTop(); //取栈顶元素 }
3.1.2 栈的存储和运算实现 1、顺序栈 • 用一片连续的存储空间来存储栈中的数据元素。 • 用一维数组来存放顺序栈中的数据元素,数组预设最大长度。栈底指示器top设在数组下标为0的端,top随着插入和删除而变化. • 当栈为空时,top=-1。
top a1 a7 a6 a5 a4 a3 a2 a1 a4 a3 a2 a1 top top top (c) 7个元素 (a) 空栈 (d) 4个元素 (b) 1个元素
顺序栈类SeqStack<T>,实现了接口IStack<T>。 • 字段data,数组,存储顺序栈中的元素。 • 字段maxsize,数组的容量。 • 字段top,栈顶,变化范围是0到maxsize-1。 • 顺序栈为空,top=-1。 • 需要实现判断顺序栈是否已满。
顺序栈的基本操作实现 求顺序栈的长度: • 顺序栈的长度就是数组中最后一个元素的索引top加1。 public int Count { get { return top + 1; } }
清空操作: • 使顺序栈为空,此时,top等于-1。 public void Clear() { top = -1; }
判断顺序栈是否为空: • 如果top为-1,顺序栈为空,返回true,否则返回false。 public bool IsEmpty { get { return top == -1; } }
判断顺序栈是否为满: • 顺序栈为满,top等于maxsize-1,返回true,否则返回false。 public bool IsFull { get { return top == maxsize - 1; } }
入栈操作: • 在顺序栈未满的情况下,在栈顶添加一个新元素,top加1。 public void Push(T item) { if(top == maxsize - 1) { Console.WriteLine("Stack is full"); return; } data[++top] = item; }
出栈操作: • 在栈不为空的情况下,使top减1。 public T Pop() { T tmp = default(T); if (top == -1) { Console.WriteLine("Stack is empty"); return tmp; } tmp = data[top--]; return tmp; }
取栈顶元素: • 顺序栈不为空,返回栈顶元素的值,否则返回特殊值。 public T GetTop() { if (top == -1) { Console.WriteLine("Stack is empty!"); return default(T); } return data[top]; }
2、链栈 • 采用链式存储的栈称为链栈。链栈通常用单链表来表示,它的实现是单链表的简化。 • 链栈的结点的结构与单链表一样。 • 由于链栈的操作只是在一端进行,把栈顶设在链表的头部,不需要头结点。
结点类(Node<T>)的实现: public class Node<T> { private T data; private Node<T> next;
public Node(T val, Node<T> p) { data = val; next = p; }
public Node(Node<T> p) { next = p; }
public Node(T val) { data = val; next = null; }
public Node() { data = default(T); next = null; }
public T Data { get { return data; } set { data = value; } }
public Node<T> Next { get { return next; } set { next = value; } } }
a1 a2 a3 a4 a5 a6∧ top 链栈示意图
链栈类LinkStack<T>。 • 字段top,栈顶指示器。 • 字段num,链栈中结点的个数。 • 求链栈长度的问题:出栈又入栈。
链栈的基本操作实现 求链栈的长度: • 通过返回num的值来求链栈的长度。 public int Count { get { return num; } }
清空操作: • 清除链栈中的结点,使链栈为空。top等于null并且num等于0。 public void Clear() { top = null; num = 0; }
判断链栈是否为空: • top为null并且num为0,链栈为空,返回true,否则返回false。 public bool IsEmpty { get { return t (top == null) && (num == 0); } }
入栈操作: • 栈顶添加一个新结点,top指向新的结点,num加1。 public void Push() { Node<T> q = new Node<T>(item); if (top == null) { top = q; }
else { q.Next = top; top = q; } ++num; }
出栈操作: • 在栈不为空的情况下,取出栈顶结点的值,top指向栈顶结点的直接后继结点,num减1。 public T Pop() { if (top == null) { Console.WriteLine("Stack is empty!"); return default(T); } Node<T> p = top; top = top.Next; --num; return p.Data; }
获取链顶结点的值: • 如果栈不为空,返回栈顶结点的值,否则返回特殊值。 public T GetTop() { if (top == null) { Console.WriteLine("Stack is empty!"); return default(T); } return top.Data; }
3.1.3 栈的应用举例 【例3-1】数制转换问题。 数制转换问题是将任意一个非负的十进制数转换为其它进制的数,这是计算机实现计算的基本问题。 其一般的解决方法的利用辗转相除法。 以将一个十进制数N转换为八进制数为例进行说明。假设N=5142。
N N/8(整除) N%8(求余) • 642 6 低 • 642 80 2 • 80 10 0 • 10 1 2 • 1 0 1 高
(5142)10=(12026)8。 • 转换得到的八进制数各个数位是按从低位到高位的顺序产生的。 • 转换结果的输出通常是按照从高位到低位的顺序依次输出。 • 输出的顺序与产生的顺序正好相反。 • 在转换过程中可以使用一个栈,每得到一位八进制数将其入栈,转换完毕之后再依次出栈。
算法思想如下: • 当N>0时,重复步骤1和步骤2。 • 步骤1:若N≠0,则将N%8压入栈中,执行步骤2; • 若N=0。则将栈的内容依次出栈,算法结束。 • 步骤2:用N/8代替N,返回步骤1。 • 用链栈存储转换得到的数位。
算法实现如下: public void Conversion(int n) { LinkStack<int> s = new LinkStack<int>(); while(n > 0) { s.Push(n%8); n = n/8; }
while(!s.IsEmpty) { n = s.Pop(); Console.WriteLine(“{0}”, n); } }
【例3-2】括号匹配。 • 括号匹配问题也是计算机程序设计中常见的问题。 • 为简化问题,假设表达式中只允许有两种括号:圆括号和方括号。 • 嵌套的顺序是任意的,([]())或[()[()][]]等都为正确的格式,而[(])或(([)])等都是不正确的格式。 • 检验括号匹配的方法要用到栈。
算法思想: 如果括号序列不为空,重复步骤1。 • 步骤1:从括号序列中取出1个括号,分为三种情况: a) 如果栈为空,则将括号入栈; b) 如果括号与栈顶的括号匹配,则将栈顶括号出栈。 c) 如果括号与栈顶的括号不匹配,则将括号入栈。 • 步骤2:如果括号序列为空并且栈为空则括号匹配,否则不匹配。
public bool MatchBracket(char[] charlist) { SeqStack<char> s = new SeqStack<char>(50); int len = charlist.Length; for (int i = 0; i < len; ++i) { if (s.IsEmpty) { s.Push(charlist[i]); }
else if((s.GetTop()==’(’&&charlist[i]==‘)’) || (s.GetTop()==‘[’&&charlist[i]==‘]’)) { s.Pop(); }
else { s.Push(charlist[i]); } }
if (s.IsEmpty) { return true; }
else { return false; } }
【例3-3】表达式求值。 • 表达式求值是程序设计语言编译中的一个基本问题。这里介绍“算符优先算法” 。 • “算符优先算法”是用运算符的优先级来确定表达式的运算顺序,从而对表达式进行求值。 • 在机器内部,任何一个表达式都是由操作数(Operand)、运算符(Operator)和界限符(Delimiter)组成。 • 操作数和运算符是表达式的主要部分,分界符标志了一个表达式的结束。 • 表达式分为三类,即算术表达式、关系表达式和逻辑表达式。 • 仅讨论四则算术运算表达式,并且假设一个算术表达式中只包含加、减、乘、除、左圆括号和右圆括号等符号,并假设‘#‘是界限符。
要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式,这需要了解算术四则运算的规则。要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式,这需要了解算术四则运算的规则。 • 算术四则运算的规则如下: 先乘除后加减; 先括号内后括号外; 同级别时先左后右。