栈
简介
栈是一种特殊的线性表。
在栈(a0,a1,…,an-1)中 a0 称为栈底元素,an-1 称为栈顶元素。通常,栈的插入操作叫入栈,栈的删除操作叫出栈。
由于栈的插入和删除操作只允许在栈顶进行,所以每次入栈的元素即成为栈顶元素,每次出栈的总是栈顶元素。所以栈是一种后进先出的线性表。
栈的抽象数据类型描述
public interface IStack {
public void clear();//将栈置空
public boolean isEmpty();//判断栈是否为空
public int length();//返回栈的数据元素个数
public Object peek();//返回栈顶元素
public void push(Object x) throws Exception;//将数据元素x入栈
public Object pop();//将栈顶元素出栈并返回
public void display();//输出栈中的所有元素
}
栈的Java接口的实现方法主要有以下两种:
- 基于顺序存储的实现:顺序栈;
- 基于链式存储的实现:链栈;
顺序栈
顺序栈用数组实现,增加变量top来指示栈顶元素的位置,top指向栈顶元素存储位置的下一个存储单元的位置,空栈时 top = 0。
public class SqStack implements IStack{
private Object[] stackElem;//顺序栈存储空间
private int top;//指向栈顶元素的下一个存储单元位置
private int maxSize;//栈的最大存储单元个数
//构造最大存储单元个数为maxSize的空栈
public SqStack(int maxSize){
top=0;
this.maxSize=maxSize;
stackElem=new Object[maxSize];
}
//将栈置空
public void clear() {
top=0;
}
//判断栈是否为空
public boolean isEmpty() {
return top==0;
}
//返回栈中数据元素个数
public int length() {
return top;
}
//返回栈顶元素
public Object peek() {
if(!isEmpty())
return stackElem[top-1];
else
return null;
}
//入栈
public void push(Object x) throws Exception {
if(top==maxSize)
throw new Exception("栈已满");
stackElem[top]=x;
top++;
}
//出栈
public Object pop() {
if(isEmpty())
return null;
top--;
return stackElem[top];
}
//输出栈中的所有数据元素
public void display(){
if(top==0)
System.out.println("栈为空");
else{
for(int i=top-1;i>=0;i--){
System.out.print(stackElem[i]+" ");
}
}
}
}
顺序栈基本操作的实现
1:入栈操作
步骤:
- 判断顺序栈是否为满,若满则抛出异常;
- 将 x 存入 top 所指的存储单元位置;
- top 加 1 ;
//入栈
public void push(Object x) throws Exception {
if(top==maxSize)
throw new Exception("栈已满");
stackElem[top]=x;
top++;
}
2:出栈操作
步骤:
- 判断栈是否为空,若为空则返回null;
- top 减 1;
- 返回 top 所指的栈顶元素的值;
//出栈
public Object pop() {
if(isEmpty())
return null;
top--;
return stackElem[top];
}
入栈和出栈操作的实现为顺序表的尾插入和尾删除,时间复杂度为O(1)。
链栈
采用链式存储结构的栈称为链栈;
由于入栈和出栈只能在栈顶进行,不存在在栈的任意位置进行插入和删除操作,所以不需要设置头结点,只需要将指针 top 指向栈顶元素结点,每个结点的指针域指向其后继结点即可;
结点类:
public class Node {
public Object data;//存放结点数据值
public Node next;//存放后继结点
//无参构造函数
public Node(){
this(null,null);
}
//只有结点值的构造函数
public Node(Object data){
this(data,null);
}
//带有节点值和后继结点的构造函数
public Node(Object data,Node next){
this.data=data;
this.next=next;
}
}
链栈类:
public class LinkStack implements IStack{
private Node top;//栈顶元素
//将栈置空
public void clear() {
top=null;
}
//判断栈是否为空
public boolean isEmpty() {
return top==null;
}
//返回栈中数据元素个数
public int length() {
Node p=top;
int length=0;
while(p!=null){
p=p.next;
length++;
}
return length;
}
//返回栈顶元素
public Object peek() {
if(!isEmpty())
return top.data;
else
return null;
}
//入栈
public void push(Object x) throws Exception {
Node s=new Node(x);
s.next=top;
top=s;
}
//出栈
public Object pop() {
if(isEmpty())
return null;
Node p=top;
top=top.next;
return p.data;
}
//输出栈中的所有数据元素
public void display(){
Node p=top;
while(p!=null){
System.out.print(p.data+" ");
p=p.next;
}
}
}
链栈基本操作的实现
1:入栈操作
步骤:
- 构造数据值域为 x 的新结点;
- 改变新结点和首结点的指针域,使新结点成为新的栈顶结点;
//入栈
public void push(Object x) throws Exception {
Node s=new Node(x);
s.next=top;
top=s;
}
2:出栈操作
步骤:
- 判断链栈是否为空,若空则返回null;
- 修改top指针域的值,返回被删结点的数据域值;
//出栈
public Object pop() {
if(isEmpty())
return null;
Node p=top;
top=top.next;
return p.data;
}
使用单链表实现栈,入栈和出栈操作的实现为单链表的头插入和头删除,时间复杂度为O(1)。
队列
简介
队列是一种特殊的线性表,其插入操作只能在表的尾部操作,删除操作只能在表头进行。
在队列(a0,a1,…,an-1)中 a0 称为队首元素,an-1 称为队尾元素。通常,队列的插入操作叫入队,队列的删除操作叫出队。
由于插入和删除操作分别在队尾和队首进行,最先入队的元素总是最先出队,因此队列具有先进先出的特点。
队列的抽象数据类型描述
public interface IQueue {
public void clear();//将队列置空
public boolean isEmpty();//判断队列是否为空
public int length();//返回队列的数据元素个数
public Object peek();//返回队首元素
public void offer(Object x) throws Exception;//将数据元素x插入到队列成为队尾元素
public Object poll();//将队首元素删除并返回其值
public void display();//输出队列中的所有数据元素
}
队列的Java接口的实现方法主要有以下两种:
- 基于顺序存储的实现:顺序队列;
- 基于链式存储的实现:链队列;
顺序队列
顺序队列的存储结构与顺序栈类似,可用数组实现。因为入队和出队操作分别在队尾和队首进行,所以增加变量front来指示队首元素的位置,rear指示队尾元素的下一个存储单元的位置。
public class SqQueue implements IQueue {
private Object[] queueElem;//队列的存储空间
private int front;//指向队首元素
private int rear;//指向队尾元素的下一个存储单元
private int maxSize;//队列的最大存储单元个数
//构造最大存储单元个数为maxSize的空队列
public SqQueue(int maxSize){
front=rear=0;
queueElem=new Object[maxSize];
this.maxSize=maxSize;
}
//将队列置空
public void clear() {
front=rear=0;
}
//判断队列是否为空
public boolean isEmpty() {
return rear==front;
}
//返回队列的长度
public int length() {
return rear-front;
}
//读取队首元素并返回其值
public Object peek() {
if(isEmpty())
return null;
return queueElem[front];
}
//入队
public void offer(Object x) throws Exception {
if(rear==maxSize)
throw new Exception("队列已满");
queueElem[rear]=x;
rear++;
}
//出队
public Object poll() {
if(rear==front)
return null;
Object p=queueElem[front];
front++;
return p;
}
//输出队列中的所有数据元素
public void display() {
if(!isEmpty()){
for(int i=front;i<rear;i++){
System.out.print(queueElem[i]+" ");
}
}
else{
System.out.print("此队列为空");
}
}
}
循环顺序队列的描述及实现
分析发现,顺序队列的多次入队和出队操作会造成有存储空间却不能进行入队操作的 “假溢出” 现象,如图3.9所示。
顺序队列之所以会出现 “假溢出” 现象是因为顺序队列的存储单元没有重复使用机制,为了解决顺序队列因数组下标越界而引起的 “溢出” 问题,可将顺序序列的首尾相连,形成循环顺序队列。循环顺序队列进行入队和出队操作后的状态变化如图3.10所示:
有新问题产生----队空和队满的判定条件都变成 front= =rear,为了解决这一问题,可少利用一个存储单元,队列最多存放 maxSize-1 个数据元素,队空的判定条件为 front= =rear,队满的判定条件为 front = (rear+1)%maxSize。
循环顺序队列类和顺序队列类的Java语言描述相似。仅是指示变量 front 和 rear的修改以及队满的判定条件发生了变化;
public class CircleSqQueue {
private Object[] queueElem;//队列的存储空间
private int front;//指向队首元素
private int rear;//指向队尾元素的下一个存储单元
private int maxSize;//队列的最大存储单元个数
//构造最大存储单元个数为maxSize的空队列
public CircleSqQueue(int maxSize){
front=rear=0;
queueElem=new Object[maxSize];
this.maxSize=maxSize;
}
//将队列置空
public void clear() {
front=rear=0;
}
//判断队列是否为空
public boolean isEmpty() {
return rear==front;
}
//返回队列的长度
public int length() {
return (rear-front+maxSize)%maxSize;
}
//读取队首元素并返回其值
public Object peek() {
if(isEmpty())
return null;
return queueElem[front];
}
//入队
public void offer(Object x) throws Exception {
if((rear+1)%maxSize==front)
throw new Exception("队列已满");
queueElem[rear]=x;
rear=(rear+1)%maxSize;
}
//出队
public Object poll() {
if(rear==front)
return null;
Object p=queueElem[front];
front=(front+1)%maxSize;
return p;
}
//输出队列中的所有数据元素
public void display() {
if(!isEmpty()){
for(int i=front;i<rear;i=(i+1)%maxSize){
System.out.print(queueElem[i]+" ");
}
}
else{
System.out.print("此队列为空");
}
}
}
链队列
链队列采用单链表实现;
由于入队和出队分别在队尾和队首进行,不存在在队列的任意位置进行插入和删除的情况,所以不需要设置头结点。只需要将指针 front 和 rear 分别指向队首结点和队尾结点,每个结点的指针域指向其后继结点即可;
结点类:
public class Node {
public Object data;//存放结点数据值
public Node next;//存放后继结点
//无参构造函数
public Node(){
this(null,null);
}
//只有结点值的构造函数
public Node(Object data){
this(data,null);
}
//带有节点值和后继结点的构造函数
public Node(Object data,Node next){
this.data=data;
this.next=next;
}
}
链队列类:
public class LinkQueue implements IQueue {
private Node front;//队首指针
private Node rear;//队尾指针
//构造空队列
public LinkQueue(){
front=rear=null;
}
//将队列置空
public void clear() {
front=rear=null;
}
//判断队列是否为空
public boolean isEmpty() {
return front==null;
}
//返回队列的长度
public int length() {
Node p=front;
int length=0;
while(p!=null){
p=p.next;
length++;
}
return length;
}
//读取队首元素并返回其值
public Object peek() {
if(isEmpty())
return null;
return front.data;
}
//入队
public void offer(Object x) throws Exception {
Node s=new Node(x);
if(!isEmpty()){//如果队列非空
rear.next=s;
rear=s;
}
else{
front=rear=s;
}
}
//出队
public Object poll() {
if((front==null))
return null;
Node p=front;
front=front.next;
if(p==rear)//删除结点为队尾结点时,需要修改rear
rear=null;
return p.data;
}
//输出队列中的所有数据元素
public void display() {
if(!isEmpty()){
for(Node p=front;p!=null;p=p.next){
System.out.print(p.data+" ");
}
}
else{
System.out.print("此队列为空");
}
}
}
优先级队列
有些应用中的排队等待问题仅按照 “先来先服务” 原则不能满足要求。
例如:操作系统中的进程调度管理,每个进程都有一个优先级值表示进程的紧急程度,优先级高的进程先执行,同级进程按照先进先出原则排队等候,因此操作系统需要使用优先级队列来管理和调度进程。
优先级队列是在普通队列的基础之上将队列中的数据元素按照关键字的值进行有序排列。优先级队列在队首进行删除操作,但为了保证队列的优先级顺序,插入操作不一定在队尾进行,而是按照优先级插入到队列的合适位置;
优先级队列也可以采用顺序和链式两种存储结构。但为了快速地访问优先级高的元素以及快速地插入数据元素,通常使用链式存储结构;
结点类:
public class PriorityNode {
public Object data;//结点的数据域
public int priority;//结点的优先级
public PriorityNode next;//结点的指针域
public PriorityNode(Object x,int priority){
this.data=x;
this.priority=priority;
this.next=null;
}
}
优先级队列类:
public class PriorityQueue implements IQueue {
private PriorityNode front;
private PriorityNode rear;
public PriorityQueue(){
front=rear=null;
}
public void clear() {
front=rear=null;
}
//判断队列是否为空
public boolean isEmpty() {
return front==null;
}
//返回队列的长度
public int length() {
PriorityNode p=front;
int length=0;
while(p!=null){
p=p.next;
length++;
}
return length;
}
//读取队首元素并返回其值
public Object peek() {
if(isEmpty())
return null;
return front.data;
}
//入队
public void offer(Object x,int priority) throws Exception {
PriorityNode s=new PriorityNode(x,priority);
if(!isEmpty()){//如果队列非空
PriorityNode p=front;
PriorityNode q=front;
while(p.priority<=s.priority&&p!=null){//按优先级寻找元素所在的位置
q=p;
p=p.next;
}
//元素位置的三种情况
if(p==null){//队尾
rear.next=s;
rear=s;
}
else if(p==front){//队首
s.next=front;
front=s;
}
else{//队中
q.next=s;
s.next=p;
}
}
else{//队列为空
front=rear=s;
}
}
//出队
public Object poll() {
if((front==null))
return null;
PriorityNode p=front;
front=front.next;
if(p==rear)//删除结点为队尾结点时,需要修改rear
rear=null;
return p.data;
}
//输出队列中的所有数据元素
public void display() {
if(!isEmpty()){
for(PriorityNode p=front;p!=null;p=p.next){
System.out.print(p.data+" "+p.priority+" ");
}
}
else{
System.out.print("此队列为空");
}
}
public void offer(Object x) throws Exception {
}
}
注意:在此优先级队列中,数据元素的优先级别依据优先数的大小进行判定,即优先数越小优先级别越大;
栈和队列的比较
相同点:
- 均为线性结构,数据元素间具有 “一对一” 的逻辑关系;
- 都有顺序存储和链式存储两种实现方式;
- 操作受限,插入操作均在表尾进行(优先级队列除外);
- 插入与删除操作都具有常数时间;
不同点:
- 栈具有后进先出特性;队列具有先进先出特性;
- 顺序栈可以实现多栈空间共享,而顺序队列则不行;
以上作为学习数据结构的笔记,供日后复习巩固;
在萌新的道路上慢慢探索吧
书籍:数据结构