数据结构-链式存储结构(栈,队列,单向循环链表)Java实现

对于链式存储结构,在上一篇文章已经对单链表进行了讲解,文章链接(https://blog.csdn.net/master_1997/article/details/101024943)。今天来给大家讲解,采用链式存储结构的栈和队列,以及循环链表。

链式栈

对于栈,就是采用先进后出,后进先出的结构,之前已经讲过采用顺序存储结构的栈,本次要说的是采用链式存储的栈。
**通过LinkedList来进行进栈,出栈操作。**内容较简单,具体操作看如下代码。
类图
在这里插入图片描述

public class LinkedStack<E> implements Stack<E> {
	
	private LinkedList<E> list;	//通过链表来实现栈
	
	public LinkedStack() {	//创建无参构造
		list = new LinkedList<E>();
	}
	
	@Override
	public int getSize() {	//获取栈里的有效元素个数
		return list.getSize();	//通过链表的getSize()方法来获取
	}

	@Override
	public boolean isEmpty() {	//判断栈是否为空
		return list.isEmpty();	//直接调用链表的判空方法
	}

	@Override
	public void push(E e) {	//进栈
		list.addFirst(e);	//依据栈的特点,可以采用链表的头插法
	}

	@Override
	public E pop() {	//出栈
		return list.removeFirst();	//依据栈的特点,可以采用链表的删头法
	}

	@Override
	public E peek() {	//获取栈顶元素
		return list.getFirst();	//依据栈的先进后出的特点,说明栈顶元素在第一个
	}

	@Override
	public void clear() {	//清空栈
		list.clear();
	}
	//重写toString方法,便于数据的输出,直观对比
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("LinkedStack: size=" + getSize() + "\n");
		if(isEmpty()) {
			sb.append("[]");
		} else {
			sb.append('[');
			for(int i = 0; i < getSize(); i++) {
				sb.append(list.get(i));
				if(i == getSize() - 1) {
					sb.append(']');
				} else {
					sb.append(']');
				}
			}
		}
		return sb.toString();
	}
	//重写equals方法
	@Override
	public boolean equals(Object obj) {	
		if(obj == null) {	//如果是空,直接false
			return false;
		}
		if(obj == this) {	//如果是自己,直接输出true
			return true;
		}
		if(obj instanceof LinkedStack) {	//判断obj是否属于LinkedStack的子类或实例
			LinkedStack<E> stack = (LinkedStack<E>)obj;	//对obj进行强转
			if(getSize() == stack.getSize()) {	//判断两个对象的有效容量大小是否相等
				for(int i = 0; i < getSize(); i++) {	//对两个对象进行遍历
					if(list.get(i) != stack.list.get(i)) {	//如果有一个不一样则返回false
						return false;
					}
				}
				return true; // 如果整个遍历都没有返回false,说明相同
			} else {
				return false;
			}
		}
		return false;
	}

}

链式队列

链式队列也是先进先出,链式队列只允许在表的一端进行插入(入队)、删除(出队)操作。允许插入的一端称为队尾,允许删除的一端称为队头。 以上这些也同样适用于采用顺序存储结构的队列。
链式队列使用链表来实现,链表中的数据域用来存放队列中的元素,指针域用来存放队列中下一个元素的地址,同时使用队头指针指向队列的第一个元素和最后一个元素。
从下面的类图,可以清楚的看到链式队列(LinkedQueue)是基于LinkedList实现的。
类图
在这里插入图片描述

public class LinkedQueue<E> implements Queue<E> {

	private LinkedList<E> list;	//定义一个链表,来实现队列的操作
	
	public LinkedQueue() {	//创建一个无参构造
		list = new LinkedList<E>();	//对链表实例化
	}
	
	@Override
	public int getSize() {	//获取当前队列的元素个数
		return list.getSize();	//直接获取链表的getSIze()方法进行获取
	}

	@Override
	public boolean isEmpty() {	//判断队列是否为空
		return list.isEmpty();	//直接调用链表的判空方法
	}

	@Override
	public void clear() {	//清空队列
		list.clear();
	}

	@Override
	public void enqueue(E e) {	//进队
		list.addLast(e);	//队列只能从一端进,一端出,等同于链表的尾插
	}

	@Override
	public E dequeue() {	//出队
		return list.removeFirst();	//依据队列的特性,等同于链表的删除头元素
	}

	@Override
	public E getFront() {	//获取头元素
		return list.getFirst();
	}

	@Override
	public E getRear() {	//获取队尾元素
		return list.getLast();	//队列的队尾,等同于链表的表尾,所以采用链表的getLast方法
	}
	//重写toString方法,方便数据输出,更加的直观
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("LinkedQueue: size=" + getSize() + "\n");
		if(isEmpty()) {
			sb.append("[]");
		} else {
			sb.append('[');
			for(int i = 0; i < getSize(); i++) {
				sb.append(list.get(i));
				if(i == getSize() - 1) {
					sb.append(']');
				} else {
					sb.append(',');
				}
			}
		}
		return sb.toString();
	}
	//重写equals方法
	@Override
	public boolean equals(Object obj) {
		if(obj == null) {	//判断传入的对象是否为空
			return false;
		}
		if(obj == this) {	//判断传入的对象是否是自己
			return true;	//如果是自己就肯定顶相等
		}
		if(obj instanceof LinkedQueue) {	//判断obj是否属于LinkedQueue的子类或实例
			LinkedQueue queue = (LinkedQueue)obj;	//对传入的对象进行强转,进行对比
			if(this.list.equals(queue.list)){	//调用LinkedList的 equals方法进行判断
				return true;
			} else {
				return false;
			}
		}
		return false;
	}
}

单向循环链表

将单链表中的尾节点的指针域由NULL改为指向头结点,使整个单链表形成一个环,这种头尾相接的单链表就可以称之为单循环链表,简称单向循环链表。
采用虚拟头结点的单向循环链表
一个结点
在这里插入图片描述
多个结点
在这里插入图片描述
采用真实头结点
在这里插入图片描述
具体代码

public class LoopSingle<E> implements List<E> {

	private class Node{	
		E data; //数据域
		Node next; //指针域
		public Node() {
			this(null,null);
		}
		public Node(E data,Node next) {
			this.data = data;
			this.next = next;
		}
		
		@Override
		public String toString() {
			return data.toString();
		}
	}
	
	private Node head;	//创建头指针
	private Node rear;	//创建尾指针
	private int size;	//创建元素个数
	
	public LoopSingle() {	//无参构造
		head = null;	//默认为空
		rear = null;
		size = 0;	//元素个数默认为0
	}
	
	public LoopSingle(E[] arr) {	//有参构造
		this();
		for(E e: arr) {	//对数组遍历
			addLast(e);	//调用addLast方法尾插元素
		}
	}
	
	public Node getHead() {	//获取头指针
		return head;	
	}
	
	public Node getRear() {	//获取尾指针
		return rear;
	}
	
	@Override
	public int getSize() {	//获取元素个数
		return size;
	}

	@Override
	public boolean isEmpty() {	//判断是否为空
		return size == 0 && head == null && rear == null;	//元素个数为0且头指针和尾指针同时为空
	}

插入方法
单向循环链表插入元素分四种情况

  • 链表为空,让头指针指向新插入的元素,然后后移尾指针
  • 从头插入元素,插入角标为0,新插入元素指向头指针,再将头指针前移,最后将尾指针指向新头指针
  • 从尾插入元素,插入角标尾size ,先让新插入元素指向头指针,然后让尾指针指向新插入元素,最后后移尾指针
  • 其他情况,一般插入,先循环遍历找到插入角标处前一个元素,新插入元素的next指向前一个元素的next,最后将前一个元素的next指向新插入元素。
@Override
	public void add(int index, E e) {
		if(index < 0 && index > size) {	//判断角标是否合法
			throw new IllegalArgumentException("插入角标非法");
		}
		Node n = new Node(e,null);
		if(isEmpty()) {	//特殊情况
			head = n;
			rear = n;
			rear.next = head;
		} else if(index == 0) {	//头插
			n.next = head;
			head = n;
			rear.next = head;
		} else if(index == size) {	//尾插
			n.next = head;
			rear.next = n;
			rear = rear.next;
		} else {	//其他情况
			Node p = head;
			for(int i = 0; i < index - 1; i++) {
				p = p.next;
			}
			n.next = p.next;
			p.next = n;
		}
		size++;
	}

	@Override
	public void addFirst(E e) {	//表头插入元素
		add(0,e);	//角标0处插入
	}

	@Override
	public void addLast(E e) {	//表尾插入元素
		add(size,e);	//角标size(元素个数)插入
	}

删除方法
分四种情况

  • 删除链表就一个元素
  • 删除链表表头元素
  • 删除链表表尾元素
  • 删除中间元素
	@Override
	public E remove(int index) {
		if(index < 0 || index >= size) {
			throw new IllegalArgumentException("删除角标非法");
		}
		E res = null;
		if(size == 1) {	//如果就一个元素,则直接置空
			res = head.data;
			head = null;
			rear = null;
		} else if(index == 0) {	//下标为0,则将头指针后移一位,尾指针指向新的头指针
			res = head.data;
			head = head.next;
			rear.next = head;
		} else if(index == size - 1) {	//小标为最后一个,循环得到倒数第二个元素,将它指向头指针
			Node p = head;
			res = rear.data;
			while(p.next != rear) {
				p = p.next;
			}
			p.next = rear.next;
			rear = p;
		} else {	//删除中间元素
			Node p = head;
			for(int i = 0; i < index - 1; i++) {	//遍历得到当前指定下标元素
				p = p.next;
			}
			Node del = p.next;
			res = del.data;
			p.next = del.next;
			
		}
		size--;
		return res;
	}

	@Override	
	public E removeFirst() {	//删除表头元素
		return remove(0);
	}

	@Override
	public E removeLast() {	//删除表尾元素
		return remove(size - 1);
	}

	@Override
	public void removeElement(E e) {	//删除指定元素
		remove(find(e));	//find方法找到下标remove方法通过下标删除
	}

查找方法
分三种情况

  • 表头元素
  • 表尾元素
  • 中间元素
@Override
	public E get(int index) {
		if(index < 0 || index >= size) {	//判断角标是否合法
			throw new IllegalArgumentException("获取角标非法");
		}
		if(index == 0) {	//表头位置
			return head.data;	//返回头指针数据
		} else if(index == size - 1) {	//表尾位置
			return rear.data;	//返回表尾数据
		} else {
			Node  p = head;	//创建新指针
			for(int i = 0; i < index; i++) {	//遍历到指定角标元素
				p = p.next;	//此时p指针指向index位置的元素
			}
			return p.data;
		}
	}

	@Override
	public E getFirst() {	//获取表头元素
		return get(0);	//调用get方法,传入index = 0
	}

	@Override
	public E getLast() {	//获取表尾元素
		return get(size - 1);	//传入size - 1获取表尾元素
	}
	//通过元素找角标
	@Override
	public int find(E e) {
		if(isEmpty()) {	//判断链表是否为空
			return -1;
		} 
		Node p = head;
		int index = 0;
		while(p.data != e) {	//对链表遍历
			p = p.next;
			index++;
			if(p == head) {	//如果遍历了一圈还没找到,说明无此元素,返回-1
				return -1;
			}
		}
		return index;
	}
	//判断指定元素是否存在于链表中
	@Override
	public boolean contains(E e) {
		int index = find(e);	//调用上面的find方法,返回-1,说明无,否则说明有
		if(index == 0) {
			return true;
		} else {
			return false;
		}
	}

以上代码非Java内置的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值