数据结构与算法系列笔记三:线性表

线性表

线性表:一个线性表是n个具有相同特性的数据元素的有限序列。

相关概念:

  • 前驱元素、后继元素
  • 头结点、尾结点

线性表分类:顺序表和链表

1、顺序表

顺序表:顺序表是在计算机内存中以数组的形式保存的线性表。

  • 例子:ArrayList
1.1 顺序表的实现
package com.example.algorithm.linear;

public class SequenceList<T> {
  //存储元素的数组
  private T[] eles;
  //记录当前顺序表中的元素个数
  private int N;

  public SequenceList(int capacity) {
    //初始化数组
    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) {
    //先把i索引及其后面的元素依次向后移动一位
    for (int index = N; index > i; index--) {
      eles[index] = eles[index - 1];
    }
    //再把t元素放到i索引处即可
    eles[i] = t;
    N++;
  }

  public T remove(int i) {
    T current = eles[i];
    for (int index = i; index < N - 1; index++) {
      eles[index] = eles[index + 1];
    }
    N--;
    return current;
  }

  public int indexOf(T t) {
    for (int i = 0; i < N; i++) {
      if (eles[i].equals(t)) {
        return i;
      }
    }
    return -1;
  }
}

1.2 顺序表的遍历

1.让SequenceList实现Iterable接口,重写iterator方法;
2.在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;

@Override
public Iterator<T> iterator() {
	return new SIterator();
}

private class SIterator implements Iterator {
private int cursor;

public SIterator() {
  this.cursor = 0;
}

@Override
public boolean hasNext() {
  return cursor < N;
}

@Override
public Object next() {
  return eles[cursor++];
}
}
1.3 顺序表容量可变
  //容量可变方法
  private 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;
  }

  public void insert(int i, T t) {
    if (N == eles.length) {
      resize(2 * eles.length);
    }
    //先把i索引及其后面的元素依次向后移动一位
    for (int index = N; index > i; i--) {
      eles[index] = eles[index - 1];
    }
    //再把t元素放到i索引处即可
    eles[i] = t;
    N++;
  }

  public T remove(int i) {
    T current = eles[i];
    for (int index = i; index < N - 1; index++) {
      eles[index] = eles[index + 1];
    }
    N--;
    if (N < eles.length / 4) {
      resize(eles.length / 2);
    }
    return current;
  }
1.4 顺序表的时间复杂度
  • get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
  • insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
  • remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);
  • 由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显
1.5 ArrayLsit实现

ArrayLsit集合底层是顺序表,使用数组实现。

2、链表

虽然顺序表的查询很快,时间复杂度为O(1),但是增删的效率是比较低的,因为每一次增删操作都伴随着大量的数据元素移动。这个问题有没有解决方案呢?有,我们可以使用另外一种存储结构实现线性表,链式存储结构。
链表:是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。

Node.java

public class Node<T> {
  //存储元素
  public T item;
  //指向下一个结点
  public Node next;

  public Node(T item, Node next) {
    this.item = item;
    this.next = next;
  }
  
}

生成链表

public static void main(String[] args) {
  //构建结点
  Node<Integer> first = new Node<>(11, null);
  Node<Integer> second = new Node<>(5, null);
  Node<Integer> third = new Node<>(8, null);
  Node<Integer> fourth = new Node<>(2, null);
  Node<Integer> fifth = new Node<>(74, null);
  //生成链表
  first.next = second;
  second.next = third;
  third.next = fourth;
  fourth.next = fifth;
}
2.1 单向链表

数据域+指针域

image-20210630104620416

单向链表的实现

package com.example.algorithm.linear;

public class LinkList<T> {
  //记录头结点
  private Node head;
  //记录链表的长度
  private int length;

  //结点类
  private class Node {
    //存储数据
    T item;
    //下一个结点
    Node next;

    public Node(T item, Node next) {
      this.item = item;
      this.next = next;
    }
  }

  public LinkList() {
    //初始化头结点
    this.head = new Node(null, null);
    //初始化元素个数
    this.length = 0;
  }

  public void clear() {
    this.head = null;
    this.length = 0;
  }

  public int size() {
    return length;
  }

  public boolean isEmpty() {
    return length == 0;
  }

  public T get(int i) {
    //通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
    Node n = head.next;
    for (int index = 0; index < i; index++) {
      n = n.next;
    }
    return n.item;
  }

  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
    length++;
  }

  public void insert(T t, int i) {
    //找到i位置前一个结点
    Node pre = head;
    for (int index = 0; index < i; index++) {
      pre = pre.next;
    }
    //找到i位置结点
    Node curr = pre.next;
    //创建新结点,并且新结点需要指向原来i位置的结点
    Node newNode = new Node(t, curr);
    //原来i位置前一个结点指向新结点即可
    pre.next = newNode;
    //元素个数+1
    length++;
  }

  public T remove(int i) {
    //找到i位置前一个结点
    Node pre = head;
    for (int index = 0; index < i; index++) {
      pre = pre.next;
    }
    //找到i位置的结点
    Node curr = pre.next;
    //找到i位置的下一个结点
    Node nextNode = curr.next;
    //前一个结点指向下一个结点
    pre.next = nextNode;
    //元素个数-1
    length--;
    return curr.item;
  }

  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 Itr();
  }

  private class Itr implements Iterator {
    private Node n;

    public Itr() {
      this.n = head;
    }

    @Override
    public boolean hasNext() {
      return n.next != null;
    }

    @Override
    public Object next() {
      n = n.next;
      return n.item;
    }
  }
2.2 双向链表

例子:LinkedList:使用双向链表实现

一个数据域+两个指针域

image-20210630135718403

双向链表的实现

package com.example.algorithm.linear;

import java.util.Iterator;

public class TwoWayLinkList<T> implements Iterable<T> {
  //首结点
  private Node head;
  //最后一个结点
  private Node last;
  //链表的长度
  private int length;

  @Override
  public Iterator iterator() {
    return new Itr();
  }

  private class Itr implements Iterator<T> {
    private Node n;

    public Itr() {
      this.n = head;
    }

    @Override
    public boolean hasNext() {
      return n.next != null;
    }

    @Override
    public T next() {
      n = n.next;
      return n.item;
    }
  }

  //结点类
  private class Node {
    //存储数据
    public T item;
    //指向上一个结点
    public Node pre;
    //指向下一个结点
    public Node next;

    public Node(T item, Node pre, Node next) {
      this.item = item;
      this.pre = pre;
      this.next = next;
    }
  }

  public TwoWayLinkList() {
    //初始化头结点、尾结点
    this.head = new Node(null, null, null);
    this.last = null;
    //初始化元素个数
    this.length = 0;
  }

  public void clear() {
    this.head.next = null;
    this.last = null;
    this.length = 0;
  }

  public int size() {
    return length;
  }

  public boolean isEmpty() {
    return length == 0;
  }

  public T getFirst() {
    return isEmpty() ? null : head.next.item;
  }

  public T getLast() {
    return isEmpty() ? null : last.item;
  }

  public void insert(T t) {
    if (isEmpty()) {
      last = new Node(t, head, null);
      head.next = last;
    } else {
      Node oldLast = last;
      Node newNode = new Node(t, oldLast, null);
      oldLast.next = newNode;
      last = newNode;
    }
    length++;
  }

  public void insert(T t, int i) {
    Node pre = head;
    for (int index = 0; index < i; index++) {
      pre = pre.next;
    }
    Node next = pre.next;
    Node newNode = new Node(t, pre, next);
    pre.next = newNode;
    next.pre = newNode;
    length++;
  }

  public T get(int i) {
    Node n = head.next;
    for (int index = 0; index < i; index++) {
      n = n.next;
    }
    return n.item;
  }

  public int indexOf(T t) {
    Node n = head;
    for (int i = 0; n.next != null; i++) {
      n = n.next;
      if (n.next.equals(t)) {
        return i;
      }
    }
    return -1;
  }

  public T remove(int i) {
    Node pre = head;
    for (int index = 0; index < i; index++) {
      pre = pre.next;
    }
    Node curr = pre.next;
    Node nextNode = curr.next;
    pre.next = nextNode;
    nextNode.pre = pre;
    length--;
    return curr.item;
  }
}

2.3 链表的应用场景

1、单链表反转

1、递归反转

递归反转法在反转当前节点之前先反转后续节点。这样从头结点开始,层层深入直到尾结点才开始反转指针域的指向。简单的说就是从尾结点开始,

public static Node reverse(Node head) {
  if (head == null || head.next == null) {
    return head;
  }
  Node pre = reverse(head.next);
  head.next.next = head;
  head.next = null;
  return pre;
}

2、遍历反转

递归反转法是从后往前逆序反转指针域的指向,而遍历反转法是从前往后反转各个结点的指针域的指向。

基本思路是:将当前节点cur的下一个节点 cur.getNext()缓存到temp后,然后更改当前节点指针指向上一结点pre。也就是说在反转当前结点指针指向前,先把当前结点的指针域用tmp临时保存,以便下一次使用.

public static Node reverse(Node head) {
  if (head == null || head.next == null) {
    return head;
  }
  Node pre = null;
  Node next = null;
  while (head != null) {
    next = head.next;
    head.next = pre;
    pre = head;
    head = next;
  }
  return pre;
}

2、快慢指针

快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍

2.1中间值问题

如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。

image-20210701104249136

/**
 * @param first 链表的首结点
 * @return 链表的中间结点的值
 */
public static String getMid(Node<String> first) {
  //定义两个指针
  Node<String> fast = first;
  Node<String> slow = first;
  //使用两个指针遍历链表,当快指针指向的结点没有下一个结点,就结束;结束后,慢指针指向的结点就是中间值
  while (fast != null && fast.next != null) {
    fast = fast.next.next;
    slow = slow.next;
  }

  return slow.item;
}

2.2 单向链表是否有环问题

image-20210701110439329

  /**
   * 判断链表中是否有环
   *
   * @param first 链表首结点
   * @return ture为有环,false为无环
   */
  public static boolean isCircle(Node<String> first) {
    //定义快慢指针
    Node<String> fast = first;
    Node<String> slow = first;
    //遍历链表,如果快慢指针指向头一个结点,那么证明有环
    while (fast != null && fast.next != null) {
      fast = fast.next.next;
      slow = slow.next;
      if (fast.equals(slow)) {
        return true;
      }
    }
    return false;
  }

2.3 有环链表入口问题

当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现。

image-20210701111853523

  /**
   * 查找有环链表中环的入口结点
   *
   * @param first 链表首结点
   * @return 环的入口结点
   */
  public static Node getEntrance(Node<String> first) {
    //定义快慢指针
    Node<String> fast = first;
    Node<String> slow = first;
    Node<String> temp = null;
    //遍历链表,先找到环(快慢指针相遇)。准备一个临时指针,指向链表的首结点,继续遍历,直到慢指针和临时指针相遇,那么相遇时所指向的结点就是环的入口。
    while (fast != null && fast.next != null) {
      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)) {
        break;
      }
    }
    return temp;
  }

3、循环链表

循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。

image-20210701150331771

4、约瑟夫问题

循环链表解决约瑟夫问题

问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。

问题转换
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。

1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。

package study.algorithm.sort;

public class Joseph {

  public static void main(String[] args) {
    JosephMethod(41, 3);

  }

  public static void JosephMethod(int totalNum, int countNum) {
    //1、构建单向循环链表
    //first用来记录首结点,pre记录上一个结点
    Node<Integer> pre = null;
    Node<Integer> first = null;
    for (int i = 1; i <= totalNum; i++) {
      if (i == 1) {
        first = new Node<>(i, null);
        pre = first;
        continue;
      }
      //如果不是第一个结点
      Node newNode = new Node(i, null);
      pre.next = newNode;
      pre = newNode;
      //如果是最后一个结点,那么需要让最后一个结点的下一个结点变为first,变为循环链表了
      if (i == totalNum) {
        pre.next = first;
      }
    }
    //2、需要count计数器,模拟报数
    int count = 0;
    //3、遍历循环链表
    //记录每次遍历拿到的结点,默认从首结点开始
    Node<Integer> n = first;
    //记录当前结点的上一个结点
    Node<Integer> before = null;
    while (n != n.next) {
      //模拟报数
      count++;
      if (count == totalNum) {
        before.next = n.next;
        System.out.print(n.item + ",");
        count = 0;
      } else {
        before = n;
      }
      n = n.next;
    }
    //打印最后一个元素
    System.out.print(n.item);

  }

  private static class Node<T> {
    public T item;
    public Node next;

    public Node(T item, Node next) {
      this.item = item;
      this.next = next;
    }
  }
}

3、栈

线性表两种特殊数据结构:

  • 顺序栈
    • 两栈共享空间
  • 链栈

队列

  • 顺序队列
    • 循环队列
  • 链队列

栈:先进后出。只能在一端进行插入和删除操作的特殊线性表。

栈既可以通过链式数据结构实现,也可以用顺序数据结构实现

栈的实现

image-20210701161226369

package com.example.algorithm.linear;
import java.util.Iterator;

public class Stack<T> implements Iterable<T> {
  //记录首结点
  private Node head;
  //栈中元素的个数
  private int N;

  @Override
  public Iterator<T> iterator() {
    return new Itr();
  }

  private class Itr implements Iterator<T> {
    private Node n;

    public Itr() {
      this.n = head;
    }

    @Override
    public boolean hasNext() {
      return n.next != null;
    }

    @Override
    public T next() {
      n = n.next;
      return n.item;
    }
  }

  private 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;
    //元素个数+1
    N++;
  }

  //弹栈
  public T pop() {
    //找到首结点指向的第一个结点
    Node oldFirst = head.next;
    //让首结点指向原来第一个结点的下一个结点
    if (oldFirst == null) {
      return null;
    }
    head.next = oldFirst.next;
    //元素个数-1
    N--;
    return oldFirst.item;
  }

}

数组实现栈

package com.example.algorithm.linear;

import java.util.Iterator;

public class Stack2<T> implements Iterable<T> {
  private T[] items;
  private int N;

  public Stack2(int capacity) {
    this.items = (T[]) new Object[capacity];
    this.N = 0;
  }

  public boolean isEmpty() {
    return N == 0;
  }

  public int size() {
    return N;
  }

  public void push(T t) {
    if (N == items.length) {
      resize(2 * items.length);
    }
    items[N++] = t;
  }

  public T pop() {
    T pop = items[--N];
    items[N] = null;
    if (N < items.length / 4) {
      resize(items.length / 2);
    }
    return pop;
  }

  private void resize(int capacity) {
    T[] temp = items;
    items = (T[]) new Object[capacity];
    for (int i = 0; i < N; i++) {
      items[i] = temp[i];
    }
  }

  @Override
  public Iterator<T> iterator() {
    return new Itr();
  }

  private class Itr implements Iterator<T> {
    private int cursor;

    public Itr() {
      this.cursor = 0;
    }

    @Override
    public boolean hasNext() {
      return cursor < N;
    }

    @Override
    public T next() {
      return items[cursor++];
    }
  }
}
栈的应用

1、括号匹配问题

问题描述:

给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。
例如:
	"(上海)(长安)":正确匹配
	"上海((长安))":正确匹配
	"上海(长安(北京)(深圳)南京)":正确匹配
	"上海(长安))":错误匹配
	"((上海)长安":错误匹配
  /**
   * 判断str中的括号是否匹配
   *
   * @param str 括号组成的字符串
   * @return 如果匹配,返回true,如果不匹配,返回false
   */
  public static boolean isMatch(String str) {
    String[] strings = str.split("");
    Stack<String> stack = new Stack<>();
    for (String string : strings) {
      if ("(".equals(string)) {
        stack.push(str);
      } else if (")".equals(string)) {
        String pop = stack.pop();
        if (pop == null) {
          return false;
        }
      }
    }
    return stack.size() == 0;
  }

2、逆波兰表达式求值问题

中缀表达式
中缀表达式就是我们平常生活中使用的表达式,例如:1+3*2,2-(1+3)等等,中缀表达式的特点是:二元运算符总是置于两个操作数中间
逆波兰表达式(后缀表达式)
后缀表达式的特点:运算符总是放在跟它相关的操作数之后

image-20210701170538162

需求
给定一个只包含加减乘除四种运算的逆波兰表达式的数组表示方式,求出该逆波兰表达式的结果。

package com.example.algorithm.linear;

public class ReversePolishNotation {
  public static void main(String[] args) {
    //中缀表达式3*(17-15)+18/6的逆波兰表达式如下
    String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
    int result = calculate(notation);
    System.out.println("逆波兰表达式的结果为:" + result);
  }

  /**
   * @param notaion 逆波兰表达式的数组表示方式
   * @return 逆波兰表达式的计算结果
   */
  public static int calculate(String[] notaion) {
    Stack<Integer> operators = new Stack<>();
    for (String s : notaion) {
      Integer o1 = 0;
      Integer o2 = 0;
      switch (s) {
        case "+":
          o1 = operators.pop();
          o2 = operators.pop();
          operators.push(o2 + o1);
          break;
        case "-":
          o1 = operators.pop();
          o2 = operators.pop();
          operators.push(o2 - o1);
          break;
        case "*":
          o1 = operators.pop();
          o2 = operators.pop();
          operators.push(o2 * o1);
          break;
        case "/":
          o1 = operators.pop();
          o2 = operators.pop();
          operators.push(o2 / o1);
          break;
        default:
          operators.push(Integer.parseInt(s));
          break;
      }
    }
    return operators.pop();
  }
}

前缀、后缀、中缀表达式

例:

中缀表达式:a+b*c+(d*e+f)*g
前缀表达式:++a*bc*+*defg
后缀表达式:abc*+de*f+g*+

中缀表达式便于人们的理解与计算,但是前缀、后缀表达式更方便计算机的运算

中缀转前缀、后缀的三种方式

1、基于堆栈的算法(比较复杂,用于计算机实现)

2、括号法

3、语义树

括号法:

  1. 按照运算符的优先级对所有的运算单位加括号

    ((a+(b*c))+(((d*e)+f)*g))
    
  2. 转换成前缀表达式与后缀表达式

    • 前缀:把运算符号移动到对应的括号前面
    • 后缀:把运算符号移动到对应的括号后面

语义树

img

  • 中缀:先左,后中,再右
  • 前缀:先中,后左,再右
  • 后缀:先左,后右,在中

两栈共享空间

对于数组实现栈来说,如果是两个相同数据类型的栈,可以使用数组的两端作栈底的方法来让两个栈共享数据,就可以最大化的利用数组的空间。

  • 两栈共享空间,通常是在两个栈的空间需求有相反关系时,也就是一个栈增长,另一个栈在缩短的情况。这样使用两栈共享空间才有意义。
package com.example.algorithm.linear;

import org.omg.CORBA.Object;

public class TwoStack<T> {
  private T[] items;
  private int n1;
  private int n2;

  public TwoStack(int capacity) {
    this.items = (T[]) new Object[capacity];
    this.n1 = 0;
    this.n2 = capacity - 1;
  }

  public void push(T t, int stackNum) {
    if (stackNum != 1 && stackNum != 2 || n1 == n2) {
      return;
    }
    if (stackNum == 1) {
      items[n1++] = t;
    } else {
      items[n2--] = t;
    }
  }

  public T pop(int stackNum) {
    if (stackNum != 1 && stackNum != 2) {
      return null;
    }
    if (stackNum == 1) {
      if (n1 == 0) {
        return null;
      } else {
        return items[--n1];
      }
    } else {
      if (n2 == items.length - 1) {
        return null;
      } else {
        return items[++n2];
      }
    }
  }


}
4、队列

队列:先进先出。是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表。

即可以顺序数据结构实现,也可以链式数据结构实现

image-20210701172356532

队列的实现
package com.example.algorithm.linear;

import java.util.Iterator;

public class Queue<T> implements Iterable<T> {
  //记录首结点
  private Node head;
  //记录最后一个结点
  private Node last;
  //记录队列中元素的个数
  private int N;

  @Override
  public Iterator<T> iterator() {
    return new Itr();
  }

  private class Itr implements Iterator<T> {
    private Node n;

    public Itr() {
      this.n = head;
    }

    @Override
    public boolean hasNext() {
      return n.next != null;
    }

    @Override
    public T next() {
      n = n.next;
      return n.item;
    }
  }

  private 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;
  }

  //向队列中插入元素t
  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--;
    return oldFirst.item;
  }
}

循环队列相关问题(C语言,数组实现)
  • front指向队头元素,rear指向队尾元素的下一个位置(在Java中,即front=0,rear=arr.length)
    • 空队列时,front=rear=0

循环队列

队列“假溢出”时,就再从头开始,头尾相接的循环。即假溢出时,rear=1

此时问题来了:空队列时,front=rear,那么满队列时,front=rear。如何判断队满还是队空?

  1. 方法一:设置一个变量flag。当front=rear,flag=0时队列为空;front=rear,flag=1时队列为满
  2. 方法二:修改队满条件,保留一个元素空间。即:队列满时,数组中还有一个空闲单元。即front+rear+1==queueSize
    • (rear+1)%queueSize==front
    • 队列通用计算公式:(rear-front+queueSize)%queueSize
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值