背包、队列、栈
本节主要是记录对于的背包、队列、和链表的Java实现代码
背包
定义
背包就是一种不支持从中删除元素的数据类型——它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素。
链表存储方式的实现
package com.alg.fundamentals;
import java.util.Iterator;
import java.util.Scanner;
/**
* 背包是一种不支持删除操作的集合数据类型 * 目的就是帮助用例收集元素并迭代遍历所有收集到的元素 * * 以下是范型可迭代 链表背包集合实现 * Created by shuds on 2022/4/27 **/public class Bag<Item> implements Iterable<Item> {
private Node first; // 链表的首结点
private int N; // 背包元素的个数
private class Node{
private Item item; // 结点值
private Node next; // 下一结点引用
public Node(Item item) {
this.item = item;
}
}
public int size() {
return N;
}
public boolean isEmpty() {
return first == null;
}
/**
* 头插法创建链表 * 和栈的里面的元素特征一样,呈现先进入元素在链表最后面 * 和栈不同的是,背包的数据结构不提供元素抛出操作,因此,这里大可不必在意元素的插入顺序 * */ public void add(Item item) {
Node oldfirst = first;
first = new Node(item);
first.next = oldfirst;
N++;
}
/**
* 需要实现可迭代集合,必须要重写Iterable接口返回一个迭代器对象 * 主要是要重写Iterator中的hashNext方法,用于访问当前的集合的元素 */ @Override
public Iterator<Item> iterator() {
return new ListIterator();
}
private class ListIterator implements Iterator<Item> {
private Node current = first;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
}
/**
* 背包的典型测试用例 * 随机输入一些数字,然后计算平均值,计算平均值的平方根并打印 */ public static void main(String[] args) {
Bag<Double> numbers = new Bag<Double>();
Scanner sc = new Scanner(System.in);
while (!sc.hasNext("#")) {
numbers.add(sc.nextDouble());
}
int N = numbers.size();
Double sum = 0.0;
for (Double e : numbers) {
sum += e;
}
Double mean = sum / N;
Double std = Math.sqrt(mean);
System.out.printf("Mean: %.2f\n", mean);
System.out.printf("Std dev: %.2f\n", std);
}
}
栈
定义
栈作为基础抽象数据类型,它本质是一种只能在一端进行操作的集合抽象类呀。它的特点是元素先进后出。可以有两种基本存储结构的实现——顺序存储和链式存储。
定容栈的实现
/**
* 定容栈 * 接下来将从四个方向优化它:泛型、动态调整数组、对象游离、迭代数组 * Created by shuds on 2022/5/13 **/public class FixedCapacityStackOfString {
String[] a;
int N;
public FixedCapacityStackOfString(int n) {
this.a = new String[n];
N = -1;
}
private void push(String e) {
if(N == a.length-1)
return;
a[++N] = e;
}
private String pop() {
if (isEmpty()) return "";
return a[N--];
}
private int size() {
return N+1;
}
private boolean isEmpty() {
return N == -1;
}
public static void main(String[] args) {
FixedCapacityStackOfString fixedCapacityStackOfString = new FixedCapacityStackOfString(5);
fixedCapacityStackOfString.push("hello");
fixedCapacityStackOfString.push("halo");
fixedCapacityStackOfString.push("Thanks");
System.out.println(fixedCapacityStackOfString.size());
System.out.println(fixedCapacityStackOfString.pop());
System.out.println(fixedCapacityStackOfString.size());
}
}
定容栈的优化
主要在以下四个方面进行了优化:
- 泛型:当前的栈只能操作
String
类型元素,使用Java语言的泛型机制,就可以实现使用它们存 储任意类型的数据。
⚠️注意:创建泛型数组在Java中是不允许的,需要使用到类型转换
a = new Item[N]; // X
a = (Item[]) new Item[N]; //√
-
调整数组大小:使用固定数组大小,在元素少时浪费空间,元素多时出现溢出。因此,需要实现动态调整大小的数组
-
对象游离:Java的内存管理机制是扫描并清楚没有引用指向的对象,因此需要将删除的元素引用置为
null
。 -
迭代:集合数据类型的基本操作之一就是,能够对集合使用
foreach
语句通过迭代遍历所有集合元素。
实现迭代的方法:
1)集合数据类型必须实现Iterable
接口,来覆写iterator()方法并返回一个iterator对象;
2)Iterator类必须包含两个方法:hasNext()(返回一个布尔值)next()(返回集合中一个泛型元素)。
链式存储栈的实现
/**
* 链式栈的实现 * Created by shuds on 2022/3/16 **/public class LinkListStack<Item> implements Iterable<Item> {
private Node first;
private int N;
// 这里的Node尽管没有使用Static标注,它也是一个内部类
private class Node{
private Item item;
private Node next;
}
/**
* 判空操作 * @return boolean
*/ public boolean isEmpty(){
return first == null;
}
/**
* 栈的元素大小 * @return int
*/ public int size() {
return N;
}
/**
* 进栈操作:头插法 * 1、进栈元素加在栈头;2、栈的长度需要加1 * @param item 泛型元素对象
*/ public void push(Item item){
Node oldFirst = first;
first = new Node();
first.item = item;
first.next = oldFirst;
N++;
}
/**
* 出栈操作 * 1、出栈的是当前的是首节点,2、出栈之后需要重置栈的长度 * 需要注意的是,这里不需要像C、C++一样自行管理内存的操作 * 我们只需要改变了首节点的引用之后,JVM将会自行发现没有被引用的对象并进行清理操作 * @return
*/
public Item pop(){
Node item = first;
first = first.next;
N--;
return item.item;
}
@Override
public Iterator<Item> iterator() {
return new LinkListStackIterator();
}
private class LinkListStackIterator implements Iterator<Item> {
private Node current = first;
@Override
public void remove() {
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
}
public static void main(String[] args) {
LinkListStack<String> stack = new LinkListStack<>();
stack.push("helle");
stack.push("my");
stack.push("LinkListStack");
stack.push("!");
// System.out.println(stack.size());
// System.out.println(stack.pop());
for (String item : stack) {
System.out.println(item);
}
}
队列
定义
队列是一种双端操作受限,只允许在一端进行操作的线性表结构。以下主要记录分别使用数组和链表存储结构实现的队列。
顺序存储队列的实现
public class ResizingQueueofStrings {
private String[] a;
private int first;
private int last;
/**
* 初始化构造函数 */ public ResizingQueueofStrings(int n) {
a = new String[n];
}
public int size() {
return last - first + 1;
}
public boolean isEmpty(){
return first == last;
}
public void enqueue(String s){
if (size() == a.length) {
resize(2 * a.length);
}
a[last++] = s;
}
public String dequeue(){
if (size() < a.length / 2) {
resize(a.length / 2);
}
return a[first++];
}
public void resize(int length) {
String[] temp = new String[length];
for (int i = 0; i < length; i++) {
temp[i] = a[i];
}
a = temp;
}
}
链式存储队列的实现
/**
* 链式队列 * Created by shuds on 2022/3/16 **/public class LinkListQueue<Item> implements Iterable<Item>{
/**
* 指向队列第一个节点 */ private Node first;
/**
* 指向队列最后一个节点 */ private Node last;
private int N; // Int初始化默认值为0
private class Node{
private Item item;
private Node next;
}
/**
* 判空操作 * @return boolean
*/ public boolean isEmpty() {
return first == null;
}
/**
* 队列元素大小 * @return int
*/ public int size() {
return N;
}
/**
* 进栈操作 * 1、声明一个新的指针oldLast指向目前的最后一个节点 * 2、last节点用于赋予新的值,由于它是最后一个节点,因此它的next需要置为null * 3、校验当前队列是否为空 * 空,这是添加的一个元素,因此first指针也需要指向这第一个元素 * 不为空,只需要将之前的最后元素和当前的新元素联系起来即可 * 4、最后,执行完成队列的新增操作,N++ * @param item
*/
public void enqueue(Item item) {
Node oldLast = last;
last = new Node();
last.item = item;
last.next = null;
if(isEmpty()) first = last;
else oldLast.next = last;
N++;
}
/**
* 出栈操作 * 1、取得当前首节点的元素 * 2、first引用指向下一个节点 * 3、校验指向下一个节点之后是否队列已经为空 * 空,则需要将last也置为null * 4、N-- * @return Item
*/ public Item dequeue() {
Item item = first.item;
first = first.next;
if(isEmpty()) last = null;
N--;
return item;
}
@Override
public Iterator<Item> iterator() {
return new LinkListQueueIterator();
}
private class LinkListQueueIterator implements Iterator<Item> {
private Node current = first;
@Override
public void remove() {
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
}
public static void main(String[] args) {
LinkListQueue<String> q = new LinkListQueue<>();
q.enqueue("hello");
q.enqueue("my");
q.enqueue("linkListQueue");
q.enqueue("!");
// System.out.println(q.size());
// System.out.println(q.dequeue());
for (String e : q) {
System.out.println(e);
}
}}
小结
深入理解抽象数据类型的原因
1)基础数据类型是构造高级数据结构的基础
2)基础数据类型展示了数据结构和算法的关系
3)实现算法的重点就是需要其中的抽象数据类型能够支持对对象集合的强大操作
基础数据结构区别
数组 VS 链表
数据结构 | 优点 | 缺点 |
---|---|---|
数组 | 通过索引可以直接访问任意元素 | 在初始化时就需要知道元素数量 |
链表 | 使用的空间大小和元素数量成正比 | 需要通过引用访问任意元素 |
研究一个新的数据结构遵循的步骤
1)定义API
2)根据特定的应用场景开发用例代码
3)描述一种数据结构(一组值的表示),并在API所对应的抽象数据类型的实现中根据它定义类的实例变量
4)描述算法(实现一组操作的方式),并根据它实现类中的实例方法
5)分析算法的性能特点