线性表概念
基本概念
- 线性表是最基本,最简单,也是最常用的一种数据结构。
- 一个线性表是n个具有相同特性的数据元素的有限序列。
-
-
前驱元素 / 后继元素
-
A元素在B元素前面,则称A为B的前驱元素(
B为A的后继元素
)
-
A元素在B元素前面,则称A为B的前驱元素(
-
-
线性表的特征:数据元素之间具有一种“一对一”的逻辑关系
-
第一个元素成为
头结点
最后一个元素为尾节点
除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继
-
第一个元素成为
线性表的分类
- 线性表中存储方式
可以是顺序存储,也可以是链式存储
,按照存储方式不同,可以将线性表分为线性表
和链表
顺序表
- 顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即
通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系
代表有数组形式。
API设计
package linear;
/**
* @ClassName SequenceListTest
* @Authoc 孙少聪
* @Date 2022/7/28 10:04:58
*/
public class SequenceList<T> {
// 存储元素的数据
private T[] eles;
// 记录当前顺序表中的元素个数
private int N;
// 构造方法
public SequenceList(int capacity){
// 初始化数据 Object也可以存储任意类型的数据
this.eles = (T[]) new Object[capacity];
// 初始化长度
this.N = 0;
}
// 清空线性表置为空
public void clear(){
this.N = 0;
}
// 线性表是否为空
public boolean isEmpty(){
return N==0;
}
// 获取线性表的长度
public int length(){
return N;
}
public T get(int i){
return eles[i];
}
// 在线性表最后加一个元素
public void insert(T t){
eles[N++] = t;
}
// 在线性表任意位置加一个元素
public void insert(int i,T t){
for (int j = N-1; j > i; j--) {
eles[j] = eles[j-1];
}
eles[i] = t;
}
// 删除指定索引位置,并返回该位置元素
public T remove(int i){
T current = eles[i];
for (int j = i; j < N-1; j++) {
eles[j] = eles[j+1];
}
// 元素个数减一
N--;
return current;
}
// 第一次出现元素的索引
public int indexOf(T t){
for (int i = 0; i < N; i++) {
if(eles[i].equals(t)){
return i;
}
}
// 未出现返回-1
return -1;
}
}
遍历
- 一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式。
-
-
在java中,遍历集合的方式一般都是用的是foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:
-
- 让SequenceList实现Iterable接口,重写iterator方法;
- 在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
-
public class SequenceList<T> implements Iterable<T>{
@Override
public Iterator<T> iterator() {
SIterator sIterator = new SIterator();
return sIterator;
}
private class SIterator implements Iterator{
private int cusor;
public SIterator(){
this.cusor = 0;
}
@Override
public boolean hasNext() {
return cusor < N;
}
// 获取当前元素下一位元素
@Override
public Object next() {
return eles[cusor++];
}
}
}
顺序表容量可变
- 问题,数据长度过短造成数据越界
public static void main(String[] args) {
SequenceList<String> sl = new SequenceList<>(3);
sl.insert("张三");
sl.insert("李四");
sl.insert("王五");
sl.insert("赵六");
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
-解决
// 根据参数newSize,重置eles的大小
public void resize(int newSize){
// 定义一个临时数组,指向原数组
T[] temp = eles;
// 创建新数组
eles =(T[]) new Object[newSize];
// 把原数组的数组拷贝到新数组
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}
添加元素
- 如果容量不够则创建新的更大的容量,一般是原数组容量两倍的新数组存储元素。
// 在线性表最后加一个元素
public void insert(T t){
if(N == eles.length){
resize(2*eles.length);
}
eles[N++] = t;
}
移除元素
- 如果发现数据元素的数量不足数组容量的1/4,则创建一个原数组容量1/2的新数组存储元素
// 在线性表任意位置加一个元素
public void insert(int i,T t){
if(N == eles.length){
resize(2*eles.length);
}
for (int j = N; j > i; j--) {
eles[j] = eles[j-1];
}
eles[i] = t;
// 元素个数加1
N++;
}
- 测试
public static void main(String[] args) {
SequenceList<String> sl = new SequenceList<>(3);
sl.insert("张三");
sl.insert("李四");
sl.insert("王五");
sl.insert("赵六");
for (String s:sl
) {
System.out.println(s);
}
System.out.println(sl.length());
}
时间复杂度分析
get(i)
,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);insert(int i,T t)
:每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);remove(int i)
:每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);- 在操作的过程中涉及到了
容器扩容操作
。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显
ArrayList的实现
- 是否用数组实现
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 有没有扩容操作
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 有没有提供遍历(内部类)
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
链表
顺序表查询比较快,增删效率比较低,每次增删操作伴随着大量的数据元素移动
- 链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
单向链表
- 单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
节点API设计
单向链表API
package linear;
import java.util.Iterator;
/**
* @ClassName LinkList
* @Authoc 孙少聪
* @Date 2022/7/30 15:18:21
*/
public class LinkList<T> implements Iterable<T> {
// 记录头结点
private Node head;
// 记录链表的长度
private int N;
public class Node {
// 存储元素
public T item;
// 指向下一个元素
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public LinkList(){
// 初始化头结点
this.head = new Node(null,null);
// 初始化元素个数
this.N = 0;
}
// 清空链表
public void clear(){
// 断开头节点
head.next = null;
this.N = 0;
}
// 清空链表
public int length(){
return N;
}
// 判断链表是否为空
public boolean isEmpty(){
return N==0;
}
// 获取指定位置i处的元素
public T get(int i){
// 从头结点开始循环i次
Node n = head.next;
for (int j = 0; j < i; j++) {
n = n.next;
}
return n.item;
}
// 像链表中添加元素t
public void insert(T t){
// 找到当前最后一个节点
Node n = head;
while(n.next != null){
n = n.next;
}
// 创建新节点,保存元素t
Node newNode = new Node(t, null);
// 让当前最后一个节点指向新节点
n.next = newNode;
// 元素个数+1
N++;
}
// 向链表指定位置中添加元素t
public void insert(int i,T t){
// 找到i位置前一个节点
Node pre = head;
for (int j = 0; j <= i-1; j++) {
pre = pre.next;
}
// 找到i位置的节点
Node curr = pre.next;
// 创建新节点,并且新节点需要指向原来的i位置的结点
Node newNode = new Node(t, curr);
pre.next = newNode;
// 元素的个数+1
N++;
}
// 删除指定位置i处的元素,并返回被删除的元素
public T remove(int i){
// 找到i位置前一个节点
Node pre = head;
for (int index = 0; index <= i-1; index++) {
pre = pre.next;
}
// 找到i位置的节点
Node curr = pre.next;
// 找打i位置的下一个节点
Node after = curr.next;
// 前一个节点指向下一个节点
pre.next = after;
// 元素个数-1
N--;
return curr.item;
}
// 查找元素t在链表中第一次出现的位置
public int indexOf(T t){
Node pre = head;
int i = 0;
for (int j = 0; pre.next != null; j++) {
pre = pre.next;
if(pre.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
测试
package linear.test;
import linear.LinkList;
import linear.SequenceList;
/**
* @ClassName SequenceListTest
* @Authoc 孙少聪
* @Date 2022/7/28 10:19:59
*/
public class LinkListTest1 {
public static void main(String[] args) {
// 创建顺序表对象
LinkList<String> sl = new LinkList<>();
// 测试插入
sl.insert("姚明");
sl.insert("科比");
sl.insert("麦迪");
sl.insert(1,"詹姆斯");
for (String s : sl) {
System.out.println(s);
}
// 测试获取
String s = sl.get(1);
System.out.println("索引1的元素"+s);
// 测试删除
String remove = sl.remove(0);
System.out.println("删除索引0的元素"+remove);
// 测试清空
sl.clear();
System.out.println("线性表的长度"+sl.length());
}
}
双向链表
- 双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
节点API设计
双向链表API设计
package linear;
import java.util.Iterator;
/**
* @ClassName TwoWayLinkList
* @Authoc 孙少聪
* @Date 2022/7/30 15:18:21
*/
public class TwoWayLinkList<T> implements Iterable<T> {
// 记录头结点
private Node head;
// 记录尾结点
private Node last;
// 记录链表的长度
private int N;
public class Node {
// 存储元素
public T item;
// 指向下一个元素
public Node pre;
// 指向下一个元素
public Node next;
public Node(T item, Node pre, Node next) {
this.item = item;
this.next = next;
this.pre = pre;
}
}
public TwoWayLinkList(){
this.head = new Node(null,null,null);
this.last = null;
this.N = 0;
}
// 清空链表
public void clear(){
this.head.next = null;
this.head.pre = null;
this.head.item = null;
this.last = null;
this.N = 0;
}
// 清空链表
public int length(){
return N;
}
// 判断链表是否为空
public boolean isEmpty(){
return N==0;
}
// 获取第一个元素
public T getFirst(int i){
if(isEmpty()){
return null;
}
// 扎到头结点下一个,因为头节点不存储数据
return head.next.item;
}
// 获取最后一个元素
public T getLast(int i){
if(isEmpty()){
return null;
}
// 最后一个节点的item返回
return last.item;
}
// 获取指定位置i处的元素
public T get(int i){
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
// 像链表中添加元素t
public void insert(T t){
// 链表为空
if(isEmpty()){
Node newNode = new Node(t, head, null);
last = newNode;
head.next = last;
}else { // 链表不为空
// 记录尾节点
Node oldLast = last;
// 创建新节点
Node newNode = new Node(t, oldLast, null);
// 将老的尾节点下一位指向新节点
oldLast.next = newNode;
// 更新尾节点数据
last = newNode;
}
// 链表长度+1
N++;
}
// 向链表指定位置中添加元素t
public void insert(int i,T t){
// 找到i位置的前一个节点
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
// 找到i位置的节点
Node curr = pre.next;
// 创建新节点
Node newNode = new Node(t,pre,curr);
// 更新一下数据
// 让i位置的前一个节点的下一个节点变为新节点
pre.next = newNode;
// 让i位置的前一个节点变为新节点
curr.pre = newNode;
// 元素个数+1
N++;
}
// 删除指定位置i处的元素,并返回被删除的元素
public T remove(int i){
// 找到前一个节点
Node pre = head;
for (int j = 0; j < i; j++) {
pre = pre.next;
}
Node curr = pre.next;
Node after = curr.next;
pre.next = after;
after.pre = pre;
return curr.item;
}
// 查找元素t在链表中第一次出现的位置
public int indexOf(T t){
Node n = head;
for (int i = 0; n.next != null; i++) {
n = n.next;
if(n.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
LinkedList底层源码
- 底层是否用双向链表实现
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
- 节点类是否有三个域(两个指针域一个数据域)
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
时间复杂度分析
get(int i)
:每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)insert(int i,T t)
:每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);remove(int i)
:每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)- 相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它
不需要预先指定存储空间大小
,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换
。 - 相比较顺序表,
链表的查询操作性能会比较低
。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表。
链表反转(单链表)
- public void reverse():对整个链表反转
- public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回
- 使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
public void reverse(){
// 空链表结束运行,如果是空链表结束运行,如果不是则递归调用reverse方法进行反转
if(isEmpty()){
return;
}
reverse(head.next);
}
// 反转指定的结点curr,并把反转后的节点返回
public Node reverse(Node curr){
if(curr.next == null){
head.next = curr;
return curr;
}
// 递归的反转当前节点curr的下一个结点,返回值就是链表反转后当前结点的上一个结点
Node pre = reverse(curr.next);
// 返回的结点的下一个结点变为当前节点curr
pre.next = curr;
// 当前结点下一个节点变为null
curr.next = null;
return curr;
}
快慢指针
- 快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。
一般情况下,快指针的移动步长为慢指针的两倍
中间值问题
public static void main(String[] args) {
// 创建结点
Node<String> first = new Node<String>("aa",null);
Node<String> second = new Node<String>("bb",null);
Node<String> third = new Node<String>("cc",null);
Node<String> fourth = new Node<String>("dd",null);
Node<String> fifth = new Node<String>("ee",null);
Node<String> six = new Node<String>("ff",null);
Node<String> seven = new Node<String>("gg",null);
// 形成链表
first.next =second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
String mid = getMid(first);
System.out.println(mid);
}
// 快慢指针获取中间值
public static String getMid(Node<String> first){
// 定义两个指针
Node<String>fast = first;
Node<String> slow = first;
// 使用两个指针遍历列表,当快指针指向的结点没有下一个结点就结素,返回慢指针的结点就是中间值
while(fast.next != null && fast != null){
// 变化fast的值和slow的值
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
// 内部类
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
单向链表是否有环问题
public static void main(String[] args) {
// 创建结点
Node<String> first = new Node<String>("aa",null);
Node<String> second = new Node<String>("bb",null);
Node<String> third = new Node<String>("cc",null);
Node<String> fourth = new Node<String>("dd",null);
Node<String> fifth = new Node<String>("ee",null);
Node<String> six = new Node<String>("ff",null);
Node<String> seven = new Node<String>("gg",null);
// 形成链表
first.next =second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
// 产生环
seven.next = third;
Boolean circle = isCircle(first);
System.out.println(circle);
}
public static Boolean isCircle(Node<String> first){
// 定义两个指针
Node<String>fast = first;
Node<String> slow = first;
// 如果快慢指针指向同一个节点,证明有欢
while(fast.next != null && fast != null){
// 变化fast的值和slow的值
fast = fast.next.next;
slow = slow.next;
// 如果快指针遇到慢指针
if(fast.equals(slow)){
return true;
}
}
return false;
}
// 内部类
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
有环链表的入口问题
package linear.test;
/**
* @ClassName FastSlowTest
* @Authoc 孙少聪
* @Date 2022/8/1 15:56:09
*/
public class FastSlowTest {
public static void main(String[] args) {
// 创建结点
Node<String> first = new Node<String>("aa",null);
Node<String> second = new Node<String>("bb",null);
Node<String> third = new Node<String>("cc",null);
Node<String> fourth = new Node<String>("dd",null);
Node<String> fifth = new Node<String>("ee",null);
Node<String> six = new Node<String>("ff",null);
Node<String> seven = new Node<String>("gg",null);
// 形成链表
first.next =second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
// 产生环
seven.next = fifth;
String entrance = getEntrance(first);
System.out.println(entrance);
}
public static String getEntrance(Node<String> first){
if (!isCircle(first)){
return "无环";
}
// 定义两个指针
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
// 如果快慢指针指向同一个节点,证明有欢
while(fast.next != null && fast != null){
// 变化fast的值和slow的值
fast = fast.next.next;
slow = slow.next;
if(fast.equals(slow)){
temp = first;
break;
}
}
while (temp != null){
temp = temp.next;
slow = slow.next;
if(temp.equals(slow)){
return temp.item;
}
}
return null;
}
public static Boolean isCircle(Node<String> first){
// 定义两个指针
Node<String>fast = first;
Node<String> slow = first;
// 如果快慢指针指向同一个节点,证明有欢
while(fast.next != null && fast != null){
// 变化fast的值和slow的值
fast = fast.next.next;
slow = slow.next;
if(fast.equals(slow)){
return true;
}
}
return false;
}
// 内部类
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
循环链表
public static void main(String[] args) {
// 创建结点
Node<String> first = new Node<String>("aa",null);
Node<String> second = new Node<String>("bb",null);
Node<String> third = new Node<String>("cc",null);
Node<String> fourth = new Node<String>("dd",null);
Node<String> fifth = new Node<String>("ee",null);
Node<String> six = new Node<String>("ff",null);
Node<String> seven = new Node<String>("gg",null);
// 形成链表
first.next =second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
// 产生环
seven.next = first;
}
约瑟夫问题
- 比较有意思的一个问题
package linear.test;
/**
* @ClassName JosephTest
* @Authoc 孙少聪
* @Date 2022/8/1 17:32:30
*/
public class JosephTest {
public static void main(String[] args) {
// 创建一个循环链表,包含41个结点,分别存储1-41之间的值
Node<Integer> first = null; // 记录首节点
Node<Integer> pre = null; // 用来记录前一个节点
for (int i = 1; i <= 41; i++) {
// 是第一个节点
if(i==1){
first = new Node(i,null);
pre = first;
continue;
}
// 不是第一个节点
Node<Integer> newNode = new Node<>(i, null);
pre.next = newNode;
pre = newNode;
// 是最后一个节点,吧下一个节点变成头结点
if(i==41){
pre.next = first;
}
System.out.println(pre.item);
}
// count计数器
int count = 0;
// 遍历循环链表
Node<Integer> n = first; // 记录每次遍历拿到的节点
Node<Integer> before = null; // 记录每次遍历拿到的节点的上一个节点
while(n!=n.next){
// 模拟报数
count++;
// 如果是3,则把当前节点删除调用,打印当前节点,重置count,让当前的n后移
if(count==3){
before.next = n.next;
System.out.print(n.item+",");
count = 0;
n = n.next;
}else {// 如果不是3让before变为当前节点,让当前节点后移
before = n;
n = n.next;
}
}
System.out.println(n.item);
System.out.println(1+",");
}
public static class Node<T>{
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
栈
- 栈是一种基于
先进后出
(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。 - 数据进入到栈的动作为
压栈
,数据从栈中出去的动作为弹栈
。
栈的API设计
package linear;
import java.util.Iterator;
/**
* @ClassName Stack
* @Authoc 孙少聪
* @Date 2022/8/2 09:36:34
*/
public class Stack<T> implements Iterable<T>{
// 记录首节点
private Node head;
// 栈中元素的个数
private int N;
public class Node{
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Stack(){
this.head = new Node(null,null);
this.N = 0;
}
// 判断是否为空
public boolean isEmpty(){
return N==0;
}
// 判断栈的大小
public int size(){
return N;
}
public void push(T t){
// 存下第一个元素
Node oldFirst = head.next;
Node newNode = new Node(t, null);
head.next = newNode;
newNode.next = oldFirst;
N++;
}
public T pop(){
Node oldFirst = head.next;
if(oldFirst == null){
return null;
}
head.next = oldFirst.next;
N--;
return oldFirst.item;
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
public class SIterator implements Iterator{
private Node n;
public SIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
- 测试压栈和弹栈
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("a");
stack.push("b");
stack.push("c");
stack.push("d");
stack.push("e");
for (String item: stack) {
System.out.println(item);
}
System.out.println("==============弹栈测试==================");
String result = stack.pop();
System.out.println(result);
System.out.println(stack.size());
}
案例
括号匹配问题
- 给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。
“(上海)(长安)”:正确匹配
“上海((长安))”:正确匹配
“上海(长安(北京)(深圳)南京)”:正确匹配
“上海(长安))”:错误匹配
“((上海)长安”:错误匹配
public static void main(String[] args) {
String str = "((上海(长安)()))";
boolean match = isMatch(str);
System.out.println(match);
}
// 是否括号匹配,遇到左括号实行压栈,遇到右括号弹出一个左括号,遍历完之后如果有剩余的左括号或者右括号没有左括号与之对应则表示不匹配
private static boolean isMatch(String str) {
Stack<String> stack = new Stack<>();
for (int i = 0; i < str.length(); i++) {
String currChar = str.charAt(i)+"";
if(currChar.equals("(")){
stack.push(currChar);
}else if(currChar.equals(")")){
String pop = stack.pop();
if(pop==null){
return false;
}
}
}
if (stack.size()==0){
return true;
}
return false;
}
逆波兰
-
-
需求:
- 给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。
public static void main(String[] args) {
String[] notation = {"3", "17", "15", "-", "*","20", "6","/","+"};
int result = caculate(notation);
System.out.println("逆波兰表达式的结果为:"+result);
}
private static int caculate(String[] notation) {
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < notation.length; i++) {
Integer one;
Integer two;
switch (notation[i]){
case "+":
one = stack.pop();
two = stack.pop();
stack.push(two+one);
break;
case "-":
one = stack.pop();
two = stack.pop();
stack.push(two-one);
break;
case "*":
one = stack.pop();
two = stack.pop();
stack.push(two*one);
break;
case "/":
one = stack.pop();
two = stack.pop();
stack.push(two/one);
break;
default:
stack.push(Integer.valueOf(notation[i]));
break;
}
}
int result = stack.pop();
return result;
}
队列
- 队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来。
队列API设计
package linear;
import linear.Stack;
import java.io.FileReader;
import java.util.Iterator;
/**
* @ClassName Queue
* @Authoc 孙少聪
* @Date 2022/8/2 15:14:49
*/
public class Queue<T> implements Iterable<T>{
// 记录首节点
private Node head;
// 记录尾节点
private Node last;
// 栈中元素的个数
private int N;
public class Node{
public T item;
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Queue(){
this.head = new Node(null,null);
this.last = null;
this.N = 0;
}
// 判断是否为空
public boolean isEmpty(){
return N==0;
}
// 判断栈的大小
public int size(){
return N;
}
public void enqueue(T t){
if(last == null){
last = new Node(t,null);
head.next = last;
}else{
Node oldLast = last;
last = new Node(t,null);
oldLast.next = last;
}
N++;
}
public T dequeue(){
if(isEmpty()){
return null;
}
Node oldFirst = head.next;
head.next = oldFirst.next;
N--;
if(isEmpty()){
last = null;
}
return oldFirst.item;
}
@Override
public Iterator<T> iterator() {
return new QIterator();
}
public class QIterator implements Iterator{
private Node n;
public QIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
- 测试
public static void main(String[] args) {
// 创建对象
Queue<String> q = new Queue<>();
q.enqueue("a");
q.enqueue("b");
q.enqueue("c");
q.enqueue("d");
for (String str:q
) {
System.out.println(str);
}
System.out.println("=========================");
String r = q.dequeue();
System.out.println(r);
System.out.println(q.size());
}