Day29-数据结构与算法-线性表

it2024-11-15  14


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表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率

执行次数复杂度非正式术语12O(1)常数阶2n + 3O(n)线性阶4n² + 2n + 6O(n²)平方阶4log2 + 25O(logn)对数阶3n + 2nlog3n + 15O(nlogn)nlogn阶4n³ + 3n² + 22n + 100O(n³)立方阶2nO(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指向头节点firstE next():让current往后走一步,也就是current = current.nextE 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
最新回复(0)