title: Day29-数据结构与算法-线性表
date: 2020-10-16 10:20:47
tags:
- 常用的经典数据结构(例如:二叉树、哈希表、Trie等)
- 更高级的数据结构(例如:图、并查集、跳表、布隆过滤器等)与各种算法(例如:排序、KMP、贪心、分治、动态规划等)
- 刷题LeetCode和算法真题(海量数据处理、字符串处理)
常用的经典数据结构
复杂度
- 求第n个斐波那契数
package com.zimo.复杂度;
import com.zimo.utils.TimeTool;
import com.zimo.utils.TimeTool.Task;
public class Fibonacci {
/**
* 斐波那契数列: 0 1 1 2 3 5 8 13
* @param args
*/
public static void main(String[] args) {
TimeTool.check("递归",new Task(){
@Override
public void execute() {
fib_recursion(45);
}
});
TimeTool.check("公式",new Task(){
@Override
public void execute() {
fib(45);
}
});
}
// 递归 当n过大,导致栈空间溢出
public static int fib_recursion(int n){
if (n <= 1) return n;
return fib_recursion(n-1) + fib_recursion(n -2);
}
public static int fib(int n){
if (n <= 1) return n;
int first = 0;
int second = 1;
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;
second = sum;
}
return second;
}
}
-
斐波那契数的线性代数解法 - 特征方程
public static int fibFunction(int n){
double c = Math.sqrt(5);
return (int)((Math.pow((1 + c) / 2, n) - Math.pow((1-c) / 2, n)) / c);
}
-
复杂度分析
- 最好情况复杂度
- 最坏情况复杂度
- 平均情况复杂度
-
评估算法的优劣:
- 正确性、可读性、健壮性(对不合理输入的反应能力)
- 时间复杂度:估算程序指令的执行次数(执行时间)
- 空间复杂度:估算所需占用的存储空间
大O表示法
- 一般用大O表示法来描述复杂度,它表示的是数据规模n对应的复杂度
- 忽略常数,系数,低阶
- 对数阶一般省略底数 log2n = log29 * log9n,所以log2n、log9n统称为logn
**注意:**大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率
执行次数 | 复杂度 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n + 3 | O(n) | 线性阶 |
4n² + 2n + 6 | O(n²) | 平方阶 |
4log2 + 25 | O(logn) | 对数阶 |
3n + 2nlog3n + 15 | O(nlogn) | nlogn阶 |
4n³ + 3n² + 22n + 100 | O(n³) | 立方阶 |
2n | O(2n) | 指数阶 |
- O1 < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2n) < O(n!) < O(nn)
算法的优化方向:
- 尽量少的存储空间
- 尽量少的执行步骤(执行时间)
- 根据情况,可以:空间换时间,时间换空间
线性表
数据结构是计算机存储、组织数据的方式:
- 线性结构:数组、链表、栈、队列、哈希表(散列表)等
- 树形结构:二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集等
- 图形结构:邻接矩阵、邻接表等
根据实际应用场景选择最合适的数据结构
线性表是具有n个相同类型元素的有限序列(n >= 0)
数组Array List
数组是一种顺序存储的线性表,所有元素的内存地址是连续的,数组的致命缺陷,一旦确定无法扩容
**泛型:**使用泛型计数可以让动态数组更加通用,可以存放任何数据类型
-
数组动态扩容:手撸ArrayList,使用泛型存在内存管理问题
package com.zimo.线性表.数组; import java.util.Objects; /** * 线性表 - 动态数组 * 接口设计: * 1.元素的数量:int size(); * 2.是否为空:boolean isEmpty(); * 3.是否包含某个元素:boolean contains(E element); * 4.添加元素到最后面:void add(E element); * 5.返回index位置对应的元素:E get(int index); * 6.设置index位置的元素:E set(int index, E element); * 7.往index位置添加元素:void add(int index, E element); * 8.删除index位置对应的元素:E remove(int index); * 9.查看元素的位置:int indexOf(E element); * 10.清楚所有元素:void clear(); * 11.删除index对应的元素:E remove(E element); * * @author Liu_zimo * @version v0.1 by 2020-10-16 13:59:56 */ public class AutoArray <E> { public static void main(String[] args) { AutoArray<Integer> array = new AutoArray<>(); array.add(10); array.add(12); array.add(14); array.add(15); System.out.println(array.toString()); array.remove(2); System.out.println(array.toString()); } private int size = 0; // 元素的数量 private E[] elements; // 所有的元素 private static final int DEFAULT_CAPACITY = 10; private static final int ELEMENT_NOT_FOUND = -1; public AutoArray() { this(DEFAULT_CAPACITY); } public AutoArray(int capacity) { capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity; this.elements = (E[]) new Object[capacity]; } /** * 清楚所有元素 */ public void clear(){ // 为了支持泛型,需要将数据清空begin for (int i = 0; i < elements.length; i++) { elements[i] = null; } // end size = 0; } /** * 元素的数量 * @return 【out】返回元素的数量 */ public int size(){ return size; } /** * 是否为空 * @return 【out】返回数组是否为空 */ public boolean isEmpty(){ return size == 0; } /** * 是否包含某个元素 * @param element 【in】要校验的元素 * @return 【out】校验结果 */ public boolean contains(E element){ return indexOf(element) != ELEMENT_NOT_FOUND; } /** * 添加元素到尾部 * @param element 【in】要添加的元素 */ public void add(E element){ add(size, element); } /** * 添加元素到index * @param index 【in】要插入的位置 * @param element 【in】要插入的元素 */ public void add(int index, E element){ //if (element == null) return; // 是否允许存空值(null) // 允许等于size rangeCheckForAdd(index); ensuerCapacity(size + 1); for (int i = size; i > index; i--) { elements[i] = elements[i - 1]; } elements[index] = element; size++; } /** * 获取index位置的元素 * @param index 【in】要获取元素的位置 * @return 【out】返回元素值,如果没有返回null */ public E get(int index){ rangeCheck(index); return elements[index]; } /** * 设置index位置的元素 * @param index 【in】要设置元素的位置 * @param element 【in】要设置的元素 * @return 【out】原来的元素 */ public E set(int index, E element){ rangeCheck(index); E old = elements[index]; elements[index] = element; return old; } /** * 删除index位置的元素 * @param index 【in】要删除元素的位置 * @return 【out】返回被删除的元素 */ public E remove(int index){ rangeCheck(index); E old = elements[index]; for (int i = index + 1; i < size; i++) { elements[i - 1] = elements[i]; } size--; // 为了支持泛型,需要将最后一个引用清空 elements[size] = null; return old; } public E remove(E element){ return remove(indexOf(element)); } /** * 查看元素的索引 * @param element 【in】要查找的元素 * @return 【out】元素所在位置 */ public int indexOf(E element){ // 如果允许null,那么要特殊处理 if (element == null){ for (int i = 0; i < elements.length; i++) { if (elements[i] == null) return i; } }else { for (int i = 0; i < elements.length; i++) { // if (element == elements[i]) return i; // 泛型的改进 if (element.equals(elements[i])) return i; } } return ELEMENT_NOT_FOUND; } /** * 要保证有capacity的容量 * @param capacity 【in】至少需要的容量 */ private void ensuerCapacity(int capacity) { int oldCapacity = elements.length; // 旧数组的长度 if (oldCapacity >= capacity) return; // 新容量为旧容量的1.5倍 int newCapacityLength = oldCapacity + (oldCapacity >> 1); // 等价于 oldCapacity * 1.5 E[] newElements = (E[]) new Object[newCapacityLength]; for (int i = 0; i < size; i++){ newElements[i] = elements[i]; } elements = newElements; } private void outOfBounds(int index){ throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size); } private void rangeCheck(int index){ if (index < 0 || index >= size){ outOfBounds(index); } } private void rangeCheckForAdd(int index){ if (index < 0 || index > size){ outOfBounds(index); } } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("size = ").append(size).append(", ["); for (int i = 0; i < size; i++) { if (i != 0) stringBuilder.append(", "); stringBuilder.append(elements[i]); // if (i != size - 1){ // stringBuilder.append(", "); // } } stringBuilder.append("]"); return stringBuilder.toString(); } }
优化:增加一个头节点指针,使数组变成环形数组,增强性能,减少增删移动次数
-
对象数组
动态数组的缩容
如果内存使用比较紧张,动态数组有比较多的剩余空间,可以考虑进行缩容操作
如果扩容倍数、缩容时机设计不得当,有可能会导致复杂度震荡(扩容倍数 x 缩容倍数 = 1)
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
size--;
// 为了支持泛型,需要将最后一个引用清空
elements[size] = null;
trim(); // 增加缩容操作
return old;
}
/**
* 缩容:剩余空间占总容量的一半时,就进行缩容
*/
private void trim() {
int capacity = elements.length;
int newCapacity = capacity >> 1;
if (size >= newCapacity || capacity <= DEFAULT_CAPACITY) {
return;
}
// 缩容:剩余空间还很多
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
链表Linked List
- 动态数组有个明显的缺点:可能造成内存空间的大量浪费
- 链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的
链表的大部分接口和动态数组是一致,所以可以抽取公共方法:
package com.zimo.线性表;
/**
* 线性表 - 公共的方法抽取
*
* @author Liu_zimo
* @version v0.1 by 2020-10-19 16:30:00
*/
public interface List<E> {
/**
* 清楚所有元素
*/
void clear();
/**
* 元素的数量
* @return 【out】返回元素的数量
*/
int size();
/**
* 是否为空
* @return 【out】返回数组是否为空
*/
boolean isEmpty();
/**
* 是否包含某个元素
* @param element 【in】要校验的元素
* @return 【out】校验结果
*/
boolean contains(E element);
/**
* 添加元素到尾部
* @param element 【in】要添加的元素
*/
void add(E element);
/**
* 添加元素到index
* @param index 【in】要插入的位置
* @param element 【in】要插入的元素
*/
void add(int index, E element);
/**
* 获取index位置的元素
* @param index 【in】要获取元素的位置
* @return 【out】返回元素值,如果没有返回null
*/
E get(int index);
/**
* 设置index位置的元素
* @param index 【in】要设置元素的位置
* @param element 【in】要设置的元素
* @return 【out】原来的元素
*/
E set(int index, E element);
/**
* 删除index位置的元素
* @param index 【in】要删除元素的位置
* @return 【out】返回被删除的元素
*/
E remove(int index);
E remove(E element);
/**
* 查看元素的索引
* @param element 【in】要查找的元素
* @return 【out】元素所在位置
*/
int indexOf(E element);
}
package com.zimo.线性表;
/**
* 线性表 - 抽象类
*
* @author Liu_zimo
* @version v0.1 by 2020-10-19 16:40:00
*/
public abstract class AbstractList<E> implements List<E> {
protected int size; // 元素的数量
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
public void add(E element) {
add(size, element);
}
public E remove(E element){
return remove(indexOf(element));
}
protected void outOfBounds(int index){
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
protected void rangeCheck(int index){
if (index < 0 || index >= size){
outOfBounds(index);
}
}
protected void rangeCheckForAdd(int index){
if (index < 0 || index > size){
outOfBounds(index);
}
}
}
单向链表
package com.zimo.线性表.链表;
import com.zimo.线性表.AbstractList;
import com.zimo.线性表.List;
import org.w3c.dom.Node;
/**
* 线性表 - 单向链表
* 链表的大部分接口和动态数组是一致
*
* @author Liu_zimo
* @version v0.1 by 2020-10-19 16:30:00
*/
public class LinkList<E> extends AbstractList<E> {
// 测试
public static void main(String[] args) {
LinkList<Integer> list = new LinkList<>();
list.add(20);
list.add(0, 10);
list.add(30);
list.add(list.size(),40);
list.remove(1);
System.out.println(list);
}
private Node<E> firstNode;
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
firstNode = null;
}
@Override
public void add(int index, E element) {
if (index == 0){
firstNode = new Node<>(element, firstNode);
}else {
Node<E> previous = node(index - 1);
previous.next = new Node<>(element, previous.next);
}
size++;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E remove(int index) {
Node<E> node = firstNode;
if (index == 0){
firstNode = firstNode.next;
}else {
Node<E> prev = node(index - 1);
node = prev.next;
prev.next = prev.next.next;
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
Node<E> node = this.firstNode;
if(element == null){
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 根据index脚标获取索引对象
* @param index
* @return Node
*/
private Node<E> node(int index){
rangeCheck(index);
Node<E> node = this.firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("size = ").append(size).append(", [");
Node<E> node = this.firstNode;
for (int i = 0; i < size; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(node.element);
node = node.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
练习:反转链表(递归、非递归)、链表是否有环(快慢指针:[slow:1,fast:2])
虚拟头节点
package com.zimo.线性表.链表;
import com.zimo.线性表.AbstractList;
/**
* 线性表 - 链表 - 虚拟头节点
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 10:20:30
*/
public class LinkListVirtualHead<E> extends AbstractList<E> {
private Node<E> firstNode;
// 创建就要有一个虚拟空节点
public LinkListVirtualHead() {
this.firstNode = new Node<>(null, null);
}
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
firstNode = null;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
Node<E> previous = index == 0 ? firstNode : node(index - 1);
previous.next = new Node<>(element, previous.next);
size++;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> prev = index == 0 ? firstNode : node(index - 1);
Node<E> node = prev.next;
prev.next = prev.next.next;
size--;
return node.element;
}
@Override
public int indexOf(E element) {
Node<E> node = this.firstNode;
if (element == null) {
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
private Node<E> node(int index) {
rangeCheck(index);
Node<E> node = this.firstNode.next;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("size = ").append(size).append(", [");
Node<E> node = this.firstNode.next;
for (int i = 0; i < size; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(node.element);
node = node.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
双向链表
package com.zimo.线性表.链表;
import com.zimo.线性表.AbstractList;
/**
* 线性表 - 双向链表
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 13:51:00
*/
public class DupLinkList<E> extends AbstractList<E> {
public static void main(String[] args) {
DupLinkList<Integer> list = new DupLinkList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);
list.add(0,55);
list.add(2,66);
list.add(list.size(), 77);
list.remove(0);
list.remove(2);
list.remove(list.size() -1);
System.out.println(list);
}
private Node<E> firstNode; // 头节点
private Node<E> lastNode; // 尾节点
private static class Node<E> {
E element; // 元素
Node<E> prev; // 前节点
Node<E> next; // 后节点
public Node(E element, Node<E> prev, Node<E> next) {
this.element = element;
this.prev = prev;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
firstNode = null;
lastNode = null;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == size) {
// 往最后添加
Node<E> oldLast = lastNode;
lastNode = new Node<>(element, oldLast, null);
if (oldLast == null) {
firstNode = lastNode;
} else {
oldLast.next = lastNode;
}
} else {
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> newNode = new Node<>(element, prev, next);
next.prev = newNode;
if (prev == null) {
firstNode = newNode;
} else {
prev.next = newNode;
}
}
size++;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> node = node(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
if (prev == null) {
firstNode = next;
} else {
prev.next = next;
}
if (next == null) {
lastNode = prev;
} else {
next.prev = prev;
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
Node<E> node = this.firstNode;
if (element == null) {
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 根据index脚标获取索引对象
*
* @param index
* @return Node
*/
private Node<E> node(int index) {
rangeCheck(index);
if (index < (size >> 1)) {
// 如果index 小于容量的一半,从左查找
Node<E> node = this.firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
} else {
// 如果index 大于容量的一半,从右查找
Node<E> node = this.lastNode;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("size = ").append(size).append(", [");
Node<E> node = this.firstNode;
for (int i = 0; i < size; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(node.element);
node = node.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
单向链表VS双向链表
- 粗略对比一下删除的操作数量
- 单向链表: 1 + 2 + 3 + … + n = (1 + n) * n / 2 = (n / 2) + (n² / 2),除以n平均一下是1/2 + n/2
- 双向链表:(1 + 2 + 3 + … + n/2) * 2 = [ (1 + n/2) * n/2 ] / 2 * 2 = n/2 + n²/4
- 操作数量缩减了近一半
动态数组VS双向链表
动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容解决)
双向链表:开辟、销毁内存空间的次数相对较多,但不会造成内存空间浪费
- 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择
- 如果频繁在头部进行添加、删除操作,建议选择使用双向链表
- 如果有频繁的(在任意位置)进行添加、删除操作,建议选择使用双向链表
- 如果有频繁的查询操作(随机访问操作),建议选择使用动态数组
有了双向链表,单向链表是否就没有任何用处了?
并非如此,在哈希表的设计中就用到了单链表
单向循环链表
// 单项链表改进
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == 0){
Node<E> newFirst = new Node<>(element, firstNode);
// 拿到最后节点
Node<E> last = size == 0 ? newFirst : node(size - 1);
last.next = newFirst;
firstNode = newFirst;
}else {
Node<E> previous = node(index - 1);
previous.next = new Node<>(element, previous.next);
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> node = firstNode;
if (index == 0){
if(size == 1) {
firstNode = null;
} else {
Node<E> last = node(size - 1); // 在改变之前拿到最后一个节点
firstNode = firstNode.next;
last.next = firstNode;
}
}else {
Node<E> prev = node(index - 1);
node = prev.next;
prev.next = prev.next.next;
}
size--;
return node.element;
}
双向循环链表
将循环链表发挥最大威力:增设一个成员变量,3个方法
- current:用于指向某个节点
- void reset():让current指向头节点first
- E next():让current往后走一步,也就是current = current.next
- E remove():删除current指向的节点,删除成功后让current指向下一个节点
package com.zimo.线性表.链表;
import com.zimo.线性表.AbstractList;
/**
* 线性表 - 双向循环链表
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 16:15:00
*/
public class DupLoopLinkList<E> extends AbstractList<E> {
public static void main(String[] args) {
// 练习:解决约瑟夫问题
DupLoopLinkList<Integer> list = new DupLoopLinkList<>();
for (int i = 1; i < 9; i++) {
list.add(i);
}
// 指向头节点
list.reset();
while (!list.isEmpty()){
list.next();
list.next();
System.out.println(list.remove());
}
}
private Node<E> firstNode; // 头节点
private Node<E> lastNode; // 尾节点
private Node<E> current; // 用于指向某个节点
private static class Node<E> {
E element; // 元素
Node<E> prev; // 前节点
Node<E> next; // 后节点
public Node(E element, Node<E> prev, Node<E> next) {
this.element = element;
this.prev = prev;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
firstNode = null;
lastNode = null;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == size) {
// 往最后添加
Node<E> oldLast = lastNode;
lastNode = new Node<>(element, oldLast, firstNode);
if (oldLast == null) { // 链表的第一个元素
firstNode = lastNode;
firstNode.next = firstNode;
firstNode.prev = firstNode;
} else {
oldLast.next = lastNode;
firstNode.prev = lastNode;
}
} else {
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> newNode = new Node<>(element, prev, next);
next.prev = newNode;
prev.next = newNode;
if (index == 0) { // 往0插入节点 next == firstNode条件也可以
firstNode = newNode;
}
}
size++;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E remove(int index) {
rangeCheck(index);
return remove(node(index));
}
private E remove(Node<E> node) {
if (size == 1){
firstNode = null;
lastNode = null;
}else {
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if (node == firstNode) { // index == 0 或 node == firstNode
firstNode = next;
}
if (node == lastNode) { // index == size - 1
lastNode = prev;
}
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
Node<E> node = this.firstNode;
if (element == null) {
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 让current指向头节点first
*/
public void reset(){
current = firstNode;
}
/**
* 让current往后走一步,也就是current = current.next
* @return 当前current所指向的元素
*/
public E next() {
if (current == null) return null;
this.current = current.next;
return current.element;
}
/**
* 删除current指向的节点,删除成功后让current指向下一个节点
* @return 删除节点的元素
*/
public E remove(){
if (current == null) return null;
Node<E> next = current.next;
E remove = remove(current);
current = size == 0 ? null : next;
return remove;
}
/**
* 根据index脚标获取索引对象
*
* @param index
* @return Node
*/
private Node<E> node(int index) {
rangeCheck(index);
if (index < (size >> 1)) {
// 如果index 小于容量的一半,从左查找
Node<E> node = this.firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
} else {
// 如果index 大于容量的一半,从右查找
Node<E> node = this.lastNode;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("size = ").append(size).append(", [");
Node<E> node = this.firstNode;
for (int i = 0; i < size; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(node.element);
node = node.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
静态链表
- 之前学习的链表,是依赖指针(引用)实现的
- 有些语言没有指针,如早期的BASIC、FORTRAN语言
- 没有指针情况下如何实现链表
- 可以通过数组来模拟链表,称为静态链表
- 数组的每个元素存放两个数据:值、下个元素的索引
栈(stack)
- 栈是一种特殊的线性表,只能在一端进行操作
- 往栈中添加元素的操作,一般叫做push,入栈
- 从栈中移除元素的操作,一般叫做pop,出栈(只能移除栈顶元素,也叫做:弹出栈顶元素)
- 后进先出的原则,Last In First Out,LIFO
- 注意:这里说的“栈”与内存中的“栈空间”是两个不同的概念
- 接口设计:
int size();
元素的数量boolean isEmpty();
是否为空void push(E element);
入栈E pop();
出栈E top();
获取栈顶元素
package com.zimo.线性表.栈;
import com.zimo.线性表.List;
import com.zimo.线性表.数组.AutoArray;
import com.zimo.线性表.链表.DupLoopLinkList;
/**
* 线性表 - 栈
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 17:36:50
*/
// 可以使用动态数组或者链表实现,如果使用继承,暴露父类接口,选择组合方式比较合理
public class MyStack<E> {
public static void main(String[] args) {
MyStack<Integer> stack = new MyStack<>();
for (int i = 1; i <= 5; i++) {
stack.push(i);
System.out.println("入栈元素" + i);
}
while (!stack.isEmpty()){
System.out.println("弹出元素为:" + stack.pop() + "剩余元素:" + stack.size());
}
}
private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现
// private AutoArray<E> list = new AutoArray<>(); // 可以改为动态数组
/**
* 获取栈元素数量
* @return 栈元素数量
*/
public int size(){
return list.size();
}
/**
* 判断栈是否为空
* @return 是否为空结果
*/
public boolean isEmpty(){
return list.isEmpty();
}
/**
* 往栈中添加元素
* @param element 要添加的元素
*/
public void push(E element){
list.add(element);
}
/**
* 从栈顶弹出元素
* @return 弹出的元素
*/
public E pop(){
return list.remove(list.size() - 1);
}
/**
* 获取栈顶元素
* @return 栈顶元素
*/
public E top(){
return list.get(list.size() - 1);
}
}
应用:浏览器的前进和后退、撤销和恢复(两个栈实现),括号匹配
队列(Queue)
- 队列是一种特殊的线性表,只能在头尾两端进行操作
- 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
- 队头(front):只能从队头移除元素,一般叫做deQueue,出队
- 先进先出原则,First In First Out,FIFO
- 接口设计
int size();
元素的数量boolean isEmpty();
是否为空void enQueue(E element);
入队E deQueue();
出队E front();
获取队列的头元素void clear();
清空
package com.zimo.线性表.队列;
import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;
/**
* 线性表 - 队列
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 10:22:12
*/
public class MyQueue<E> {
public static void main(String[] args) {
MyQueue<Integer> queue = new MyQueue<>();
for (int i = 1; i <= 5; i++) {
queue.enQueue(i);
System.out.println("入队元素" + i);
}
while (!queue.isEmpty()){
System.out.println("弹出元素为:" + queue.deQueue() + "剩余元素:" + queue.size());
}
}
private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现
/**
* 获取队列元素数量
* @return 队列元素数量
*/
public int size(){
return list.size();
}
/**
* 判断队列是否为空
* @return 是否为空结果
*/
public boolean isEmpty(){
return list.isEmpty();
}
/**
* 往队列中添加元素
* @param element 要添加的元素
*/
public void enQueue(E element){
list.add(0, element);
}
/**
* 从队头弹出元素
* @return 弹出的元素
*/
public E deQueue(){
return list.remove(list.size() - 1);
}
/**
* 获取队头元素
* @return 队头元素
*/
public E front(){
return list.get(list.size() - 1);
}
/**
* 清空队列元素
*/
public void clear(){
list.clear();
}
}
练习:用栈模拟队列(inStack,outStack)
双端队列(Deque)
- 双端队列是能在头尾两端添加、删除的队列,英文deque是double ended queue的简称
- 接口设计:
int size();
元素的数量boolean isEmpty();
是否为空void enQueueRear(E element);
队尾入队void enQueueFront(E element);
队头入队E deQueueRear();
队尾出队E deQueueFront();
队头出队E front();
获取队列的头元素E rear();
获取队列的尾元素void clear();
清空
package com.zimo.线性表.队列;
import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;
/**
* 线性表 - 双端队列
*
* @author Liu_zimo
* @version v0.1 by 2020-10-20 10:22:12
*/
public class MyDeque<E> {
public static void main(String[] args) {
MyDeque<Integer> deque = new MyDeque<>();
deque.enQueueFront(11);
deque.enQueueFront(22);
deque.enQueueRear(33);
deque.enQueueRear(44);
while (!deque.isEmpty()){
System.out.println("弹出元素为:" + deque.deQueueFront() + "剩余元素:" + deque.size());
}
}
private List<E> list = new DupLoopLinkList<>(); // 组合双向环形链表实现
/**
* 获取队列元素数量
* @return 队列元素数量
*/
public int size(){
return list.size();
}
/**
* 判断队列是否为空
* @return 是否为空结果
*/
public boolean isEmpty(){
return list.isEmpty();
}
/**
* 往队列头/尾添加元素
* @param element 要添加的元素
*/
public void enQueueFront(E element){ list.add(0, element); }
public void enQueueRear(E element){ list.add(element); }
/**
* 从队尾/头弹出元素
* @return 弹出的元素
*/
public E deQueueRear(){ return list.remove(list.size() - 1); }
public E deQueueFront(){ return list.remove(0); }
/**
* 获取队头/尾元素
* @return 队头元素
*/
public E front(){ return list.get(0); }
public E rear(){ return list.get(list.size() - 1); }
/**
* 清空队列元素
*/
public void clear(){
list.clear();
}
}
循环队列(环形队列)
- 循环队列底层使用数组实现
package com.zimo.线性表.队列.循环队列;
/**
* 线性表 - 环形队列(数组实现)
*
* @author Liu_zimo
* @version v0.1 by 2020/10/21 11:13:00
*/
public class CircleQueue<E> {
public static void main(String[] args) {
CircleQueue<Integer> queue = new CircleQueue<Integer>();
for (int i = 0; i < 10; i++) {
queue.enQueue(i);
}
for (int i = 0; i< 5; i++) {
queue.deQueue();
}
System.out.println(queue);
for (int i = 15; i < 20; i++){
queue.enQueue(i);
}
System.out.println(queue);
while (!queue.isEmpty()) {
System.out.println(queue.deQueue());
}
}
private int size; // 元素大小
private E[] elements; // 元素
private int front; // 对头游标
private static final int DEFAULT_CAPACITY = 10;
public CircleQueue() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void enQueue(E element){
ensuerCapacity(size + 1);
int index = index(size);
elements[index] = element;
size ++;
}
public E deQueue(){
E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
}
public E front(){
return elements[front];
}
public void clear(){
for (int i = 0; i < size; i++) {
elements[index(i)] = null;
}
size = 0;
front = 0;
}
// 环形脚标映射
private int index(int index){
// return (front + index) % elements.length;
// 优化
index += front;
return index - (index >= elements.length ? elements.length : 0);
}
// 动态扩容
private void ensuerCapacity(int capacity) {
int oldCapacity = elements.length; // 旧数组的长度
if (oldCapacity >= capacity) return;
// 新容量为旧容量的1.5倍
int newCapacityLength = oldCapacity + (oldCapacity >> 1); // 等价于 oldCapacity * 1.5
E[] newElements = (E[]) new Object[newCapacityLength];
for (int i = 0; i < size; i++){
newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("capacity = ").append(elements.length).append(", front = ").append(front)
.append(", size = ").append(size).append(", array:[");
for (int i = 0; i < elements.length; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(elements[i]);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
循环双端队列
可以进行两端添加、删除操作的循环队列
package com.zimo.线性表.队列.循环队列;
import com.zimo.线性表.List;
import com.zimo.线性表.链表.DupLoopLinkList;
/**
* 线性表 - 循环双端队列
*
* @author Liu_zimo
* @version v0.1 by 2020-10-21 11:55:29
*/
public class CircleDeque<E> {
public static void main(String[] args) {
CircleDeque<Integer> deque = new CircleDeque<>();
for (int i = 0; i < 10; i++){
deque.enQueueFront(i + 1);
deque.enQueueRear(i + 100);
}
System.out.println(deque);
for (int i = 0; i < 3; i++){
deque.deQueueFront();
deque.deQueueRear( );
}
deque.enQueueFront(11);
deque.enQueueFront(12);
System.out.println(deque);
while (!deque.isEmpty()){
System.out.println(deque.deQueueFront());
}
}
private int size; // 元素大小
private E[] elements; // 元素
private int front; // 对头游标
private static final int DEFAULT_CAPACITY = 10;
public CircleDeque() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/**
* 头/尾入队
* @param element
*/
public void enQueueFront(E element){
ensuerCapacity(size + 1);
front = index(-1);
elements[front] = element;
size++;
}
public void enQueueRear(E element){
ensuerCapacity(size + 1);
int index = index(size);
elements[index] = element;
size ++;
}
/**
* 头/尾出队
* @return
*/
public E deQueueFront(){
E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
}
public E deQueueRear(){
int rearIndex = index(size - 1);
E rear = elements[rearIndex];
elements[rearIndex] = null;
size--;
return rear;
}
/**
* 获取头/尾元素
* @return
*/
public E front(){ return elements[front]; }
public E rear(){ return elements[index(size - 1)]; }
/**
* 清空队列
*/
public void clear(){
for (int i = 0; i < size; i++) {
elements[index(i)] = null;
}
size = 0;
front = 0;
}
// 环形脚标映射
private int index(int index){
index += front;
if (index < 0){
return index + elements.length;
}
// 优化前 return index % elements.length;
// 优化
return index - (index >= elements.length ? elements.length : 0);
}
// 动态扩容
private void ensuerCapacity(int capacity) {
int oldCapacity = elements.length; // 旧数组的长度
if (oldCapacity >= capacity) return;
// 新容量为旧容量的1.5倍
int newCapacityLength = oldCapacity + (oldCapacity >> 1); // 等价于 oldCapacity * 1.5
E[] newElements = (E[]) new Object[newCapacityLength];
for (int i = 0; i < size; i++){
newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("capacity = ").append(elements.length).append(", front = ").append(front)
.append(", size = ").append(size).append(", array:[");
for (int i = 0; i < elements.length; i++) {
if (i != 0) stringBuilder.append(", ");
stringBuilder.append(elements[i]);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}
- 已知 n >= 0, m > 0 n % m 等价于 n - (m > n ? 0 : m)的前提条件:n < 2m