算法前置知识点

308 阅读20分钟

王争 algo

1. 如何分析,统计算法的执行效率和资源消耗

1. 什么是复杂度分析

  • 数据结构和算法是解决'如何让代码运行更快,更节省存储空间'.
  • 需要从执行时间和占用空间两个维度来评估数据结构和算法的性能.
  • 分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度.
  • 复杂度描述的是算法执行时间或空间占用与数据规模的增长关系.

2. 为什么要进行复杂度分析

  • 将代码跑一边,通过统计监控得到算法执行时间及内存占用,从而评估算法性能,称为事后统计法.
  • 和性能测试相比,复杂度分析不依赖于硬件环境和数据规模,效率高,指导性强,事前就能估计大致运行性能.
  • 能编写性能更优的代码

3. 如何进行复杂度分析:大O表示法

4. 大O表示法来源

  • 算法的执行时间与每行代码的执行总次数成正比: T(n) = O(f(n))
  • T(n) :算法执行总时间
  • f(n) :每行代码执行的总次数
  • n :往往表示数据的规模
  • O :表示代码的执行时间和每行代码执行次数成正比

5. 大O表示法特点

  • 以时间复杂度为例,时间复杂度描述的是算法执行时间与数据规模的增长变化趋势,结果中的 常量,低阶以及系数实际上对这种增长趋势不产生决定性影响,所以在做时间复杂度分析时忽略这些项.

6. 时间复杂度分析法则

  • 单段代码看高频: 比如循环,只看循环次数最多的那一段
  • 多段代码取最大: 比如一段代码有单层循环及多层循环,那么取多层循环的复杂度.
  • 嵌套代码求乘积: 比如递归,多重循环等.
  • 多个规模求加法: 比如方法中有2个参数控制两个循环的参数,复杂度就是2者复杂度相加.

7. 常见的复杂度量级

7.1.多项式量级
  • 随着数据规模的增长,算法执行的时间,按照多项式的比例增长.
  • 包括:常数阶(O(1)), 对数阶(O(logn)), 线性阶(O(n)), 线性对数阶(O(nlogn)), 平方阶(O(n^2)), 立方阶(O(n^3))
7.2.非多项式量级
  • 随着数据规模的增长,非多项式量级算法执行的时间会暴增,这类算法性能极差.
  • 包括:指数阶(O(2^n)), 阶乘阶(O(n!))

8.复杂度

  • 复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用于分析算法执行效率与数据规模之间的增长关系.
  • 越是高阶的复杂度的算法,执行效率越低.
  • 几乎所有数据结构和算法的复杂度都跑不出: O(1), O(n), O(logn), O(nlogn), O(n^2)

2. 浅析最好、最坏、平均、均摊时间复杂度

1. 最好情况时间复杂度

  • 在最理想情况下,执行一段代码的时间复杂度.

2. 最坏情况时间复杂度

  • 在最糟糕情况下,执行一段代码的时间复杂度.

3. 加权平均时间复杂度 或 期望时间复杂度

  • 将每种运行情况发生的概率考虑进去,得到的复杂度.

4. 大多数时候不需要区分最好,最坏,平均时间复杂度

  • 只有不同情况下,时间复杂度有量级的差距,才会使用3种复杂度进行区分.

5. 均摊时间复杂度

5.1.均摊时间复杂度是一种特殊的平均时间复杂度
5.2.使用场景:
  • 对一个数据结构进行一组连续的操作,大部分情况下时间复杂度都很低,只有个别情况时间复杂度比较高,而且这些操作间存在前后连贯的时序关系
  • 例如n-1个O(1)后面是1个O(n),则可以将O(n)平摊到n-1个O(1)上,则其均摊时间复杂度仍然是O(1)
5.3.在可以应用均摊复杂度的场景,一般均摊复杂度就是最好情况时间复杂度.
  • 例外情况:例如n-1个O(1)后面是1个O(n^2),则均摊时间复杂度是O(N)

3. 数组:为什么很多编程语言中数组都从0开始编号

1. 数组

1.1. 数组是一种线性表数据结构.它用一组连续的内存空间,来存储一组相同类型的数据
1.2. 线性表
  • 线性表就是数据排列成像一条线一样的结构.
  • 线性表上的数据,最多只有前后两个方向.
  • 数组,链表,队列,栈都是线性表结构
1.3. 非线性表
  • 非线性表中,数据之间并不是简单的前后关系
  • 如二叉树,堆,图 都是非线性表
1.4. 连续的内存空间和相同类型的数据
  • 这两个特性让数组可以随机访问
  • 随机访问指可以随机选择下标,进行数据访问
  • 数组做插入和删除非常低效,因为为了保证数组元素内存空间的连续性,需要做大量的搬移
1.5. 数组如何实现根据下标随机访问数组元素
  • 以长度为10的int数组为例. int[] a = new int[10];
  • 计算机给a,分配了一块连续的内存空间,比如从 1000 - 1039 (因为1个int占4个字节)
  • 内存块的首地址base_address为1000.
  • 当计算机需要访问数组中下标为i的元素,首先要计算出该元素的内存地址
    a[i]_address = base_address + data_type_size * i
    
    data_type_size表示数组中每个元素的大小,int类型数据每个是4字节.
    
  • 数组查找元素的时间复杂度,即使是排序好的数组,使用二分查找,其复杂度也是O(logn)
1.6. 数组低效的插入及改进
  • 如果数组是有序的,我们在位置K插入一个元素,为了将K腾出来,原始的位置K到N的元素都要向后搬移,时间复杂度是O(n)
  • 如果数组中存储的数据无规律,只是被当做存储数据的集合,为了提升性能,可以将原始K位置的元素搬移到数组末尾,并将新元素放到位置K,时间复杂度是O(1)
1.7. 数组低效的删除及改进
  • 删除位置K的元素,为了内存的连续性,也要进行搬移,时间复杂度是O(n)
  • 为了提升性能,可以将多次删除集中到一起执行.
    • 例如要删除a,b,c3个元素,每次删除并不真正删除数据,只是记录数据已被删除.
    • 当数组没有更多空间存储数据时,再触发一次真正的删除,这样就只执行1次数据搬移.
  • 删除性能的改进,类似JVM垃圾回收算法 中的 标记整理算法
    • 首先找到GC-ROOT,将非垃圾数据进行标记
    • 将垃圾数据清除,并将非垃圾数据进行搬移,使得内存紧凑

2. 数组越界问题

2.1. 不同的语言遇到数组越界,对应的编译器处理逻辑不同
  • 对于Java会抛异常,对于C不会
  • 对于C而言,只要数组通过偏移计算得到的内存地址可用,就不会报错
2.2. 很多计算机病毒就利用数组越界一样可以访问非法地址的漏洞来攻击系统.

3. 容器能否完全替代数组?

3.1. Java提供了ArrayList,底层就是数组.
  • ArrayList将数组插入,删除等操作进行了封装
  • ArrayList支持自动扩容
  • 扩容涉及到内存申请和数据搬移,性能低,如果事先能确定数据量,最好在开始时候就指定初始容量,避免后续多次扩容
3.2. ArrayList不能存储基本类型,比如int,long要封装为Integer,Long,而装箱拆箱会影响性能.
3.3. 如果数据量事先已知,且用不到ArrayList提供的大部分方法,可直接使用数组.
3.4. 表示多维数组,使用数组表示更直观:Integer[][] 好于 ArrayList<ArrayList>
3.5. 追求极致性能,就使用数组,性能要求不严格的场景使用容器类.

4. 数组下标为什么从0开始

4.1. 可能是历史原因,一开始的编程语言从0开始,后面的延续
4.2. 数组存储的内存模型决定,从0开始可以降低CPU的计算量
  • 数组是内存连续的多个同类型数据,下标的真正意义是相对于数组内存块首地址的偏移
  • 下标从0开始,下标为K的内存地址是:
    a[k]_address = base_address + k * type_size
    
  • 下标从1开始,下标为K的内存地址是:
    a[k]_address = base_address + (k-1) * type_size
    
  • 下标从1开始,每次随机访问数组元素,CPU都要多执行1次减法指令.

5. 二维数组的寻址(5行8列)

  • 以a[3][2]为例,第4行的第3个元素.其地址是数组内存块首地址 + 完整的3行 + 第4行的2列
  • a[3][2] = base_address + type_size * (8 * 3 + 2)

4. 链表(上):如何实现LRU缓存淘汰算法

1. 缓存是一种提高数据读取性能的技术,用空间换时间.

  1. 缓存的大小有限,当缓存空间被用满,哪些数据应该被清理,哪些应该被保留,需要缓存淘汰策略决定.

2. 缓存淘汰策略

  1. 先进先出策略/FIFO/First In,First Out
  2. 最少使用策略/LFU/Least Frequently Used
  3. 最近最少使用策略/LRC/Least Recently Used

3. 链表:通过指针将一组零散的内存块串联起来

  1. 最常见的链表: 单链表, 双链表, 循环链表
  2. 链表的第一个节点叫做头结点,最后一个节点叫做尾结点
  3. 头结点用于记录链表的基地址,通过头结点可以遍历得到整条链表
  4. 链表的删除,插入时间复杂度是O(1)
    • 不考虑查找定位到该节点的时间复杂度

4. 循环列表

  1. 循环链表是一种特殊的单链表.
  2. 循环链表的优点是从链尾到链头比较方便.
  3. 当要处理的数据具有环形结构特征,就适合采用循环链表,如约瑟夫问题.没有看懂解决办法.

5. 双向链表

  1. 双向链表支持2个方向,每个结点不仅有后继指针next指向后面的结点,也有前驱指针prev指向前面的结点.
  2. 双向链表支持O(1)找到前驱结点prev,这一特性使某些情况下双向链表删除及插入性能高于单链表.

6. 双向链表删除

  1. 从链表中删除一个数据包含两种情况:
    • 删除链表中"值等于某个值的结点"
    • 删除链表中"特定指针指向的结点"
  2. 删除链表中"值等于某个值的结点"
    • 无论单链表还是双向链表,首先要遍历找到该结点,O(n)
    • 单纯删除时间复杂度是O(1)
    • 这种类型的删除,两者复杂度都是O(n)
  3. 删除链表中"特定指针指向的结点"
    • 这种情况下,要删除的结点已经确定,不需用遍历.
    • 该结点的前驱结点:
      • 单链表并不知道,依然要遍历,直至 p.next = target ,说明p就是target的前驱结点. 时间复杂度是O(n).
      • 双向链表直接找到target.prev. 时间复杂度是O(1).

7. 双向链表插入

  1. 双向链表在一个指定结点的前面插入1个结点,时间复杂度是O(1)
  2. 单链表则是O(n),因为要遍历查找指定结点的前一个节点

8. 对于一个有序链表,双向链表按值查询的效率,也比单链表要高

  1. 记录上次查找的结点P
  2. 现在要找T,比较P和T的大小关系,决定从P向前遍历还是向后遍历
  3. 而单链表只能从头结点向后遍历

9. 时间换空间 及 空间换时间

  1. 对于执行速度过慢的程序,可以通过消耗更多的存储/内存加快执行速
  2. 对于消耗内存过大的程序,可以通过增加执行时间降低存储占用

10. 数组和链表的比较

  1. 插入删除数组O(n),链表O(1)
  2. 随机访问数组O(1),链表O(n)
  3. 数组实际使用的是一段连续的内存空间,对CPU缓存机制友好,1个缓存行可以一次加载多个数组元素,访问性能更好;
    • 链表是多个散列的内存块通过指针串起来,内存不连续,对CPU缓存不友好,无法有效预读.
  4. 数组的缺点是大小固定,声明后要占用一段连续的内存空间,若声明的数组过大,系统没有足够的连续内存空间分配,会OOM.声明过小后面需要扩容申请更大内存空间,并需要进行数据搬移,性能很差.
    • 链表本身没有大小限制,天然的支持动态扩容/因为不是一段连续的内存空间
  5. 如果对内存的使用非常严格要使用数组,因为链表的每个节点都包含next指针,内存消耗会翻倍.
  6. 对链表进行频繁的插入删除,会频繁的申请内存和释放内存,容易造成内存碎片,Java中触发频繁GC.

11. 使用链表实现LRU缓存淘汰算法

  1. 当有1个新的数据要被访问,从头结点开始查找
  2. 若找到该结点,将其从原始位置删除,插入链表头部
  3. 若未找到该结点,分2种情况
    • 链表空间未满,直接插入到链表头部
    • 链表空间已满,首先将尾结点删除,然后将该结点插入链表头部

5.数组及链表部分代码

1. 实现int数组

public class Array {
	private int[] data;
	private int n;
	private int count;
	
	public Array(int capacity) {
		this.data = new int[capacity];
		this.n = capacity;
		this.count = 0;
	}

	public int find(int index) {
		if(index >= 0 && index < count) {
			return data[index];
		}
		return -1;
	}
	public boolean insert(int index,int value) {
		if(count == n) {
			System.out.println("数组已满");
			return false;
		}
		if(index < 0 || index > count) {
			System.out.println("索引值范围越界");
			return false;
		}
		for(int i = count; i > index; i--) {
			data[i] = data[i - 1];
		}
		data[index] = value;
		++count;
		return true;
	}
	public boolean delete(int index) {
		if(index < 0 || index > count - 1) {
			System.out.println("索引值范围越界");
			return false;
		}
		for(int i = index; i < count - 1; i++) {
			data[i] = data[i + 1];
		}
		--count;
		return true;
	}
	public void printAll() {
		for(int item : data) {
			System.out.print(item + " ");
		}
		System.out.println("");
	}
	public static void main(String[] args) {
		Array array = new Array(5);
                array.printAll();
                array.insert(0, 3);
                array.insert(0, 4);
                array.insert(1, 5);
                array.insert(3, 9);
                array.insert(3, 10);
                array.printAll();
	}
}

运行结果:
0 0 0 0 0 
4 5 3 10 9 

2. 实现可自动扩容的数组

public class GenericArray<T> {
	private T[] data;
	private int size;

	public GenericArray(int capacity) {
		data = (T[]) new Object[capacity];
		size = 0;
	}

	public GenericArray() {
		this(10);
	}

	public int getCapacity() {
		return data.length;
	}

	public int count() {
		return size;
	}

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

	private void checkIndex(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("索引值不合法!");
		}
	}

	private void checkIndexAdd(int index) {
		if (index < 0 || index > size) {
			throw new IllegalArgumentException("索引值不合法!");
		}
	}

	public void set(int index, T e) {
		checkIndex(index);
		data[index] = e;
	}

	public T get(int index) {
		checkIndex(index);
		return data[index];
	}

	public boolean contains(T e) {
		for (int i = 0; i < data.length; i++) {
			if (data[i].equals(e)) {
				return true;
			}
		}
		return false;
	}

	public int indexOf(T e) {
		for (int i = 0; i < size; i++) {
			if (data[i].equals(e)) {
				return i;
			}
		}
		return -1;
	}

	private void resize(int capacity) {
		T[] newData = (T[]) new Object[capacity];
		for (int i = 0; i < size; i++) {
			newData[i] = data[i];
		}
		data = newData;
	}

	public void add(int index, T e) {
		checkIndexAdd(index);
		if (size == data.length) {
			resize(size);
		}
		for (int i = size; i > index; i--) {
			data[i] = data[i - 1];
		}
		data[index] = e;
		++size;
	}

	public void addFirst(T e) {
		add(0, e);
	}

	public void addLast(T e) {
		add(size, e);
	}

	public T remove(int index) {
		checkIndex(index);
		T ret = data[index];
		for (int i = size - 1; i > index; i--) {
			data[i - 1] = data[i];
		}
		data[size - 1] = null;
		--size;
		if (size == data.length / 4 && data.length / 2 != 0) {
			resize(data.length / 2);
		}
		return ret;
	}

	public void removeFirst() {
		remove(0);
	}

	public void removeLast() {
		remove(size - 1);
	}

	public void removeElement(T e) {
		int index = indexOf(e);
		if (index != -1) {
			remove(index);
		}
	}
}

3. 使用自定义单链表,校验字符串是否是回文字符串

public class SinglyLinkedList {
	class Node {
		private int data;
		private Node next;

		public Node(int data, Node next) {
			this.data = data;
			this.next = next;
		}

		public int getData() {
			return data;
		}
	}

	private Node head;

	public Node createNode(int data, Node next) {
		return new Node(data, next);
	}

	public void insertBefore(Node p, int data) {
		insertBefore(p, createNode(data, null));
	}

	public void insertBefore(Node p, Node newNode) {
		if (p == null) {
			System.out.println("目标位置结点为空!");
			return;
		}
		if (newNode == null) {
			System.out.println("新结点为空!");
			return;
		}
		if (p == head) {
			newNode.next = head;
			head = newNode;
			return;
		}
		Node curr = head;
		while (curr.next != null && curr.next != p) {
			curr = curr.next;
		}
		if (curr.next == p) {
			newNode.next = p;
			curr.next = newNode;
		}
	}

	public void insertAfter(Node p, int data) {
		insertAfter(p, createNode(data, null));
	}

	public void insertAfter(Node p, Node newNode) {
		if (p == null) {
			System.out.println("目标位置结点为空!");
			return;
		}
		if (newNode == null) {
			System.out.println("新结点为空!");
			return;
		}
		newNode.next = p.next;
		p.next = newNode;
	}

	public void insertToTail(int data) {
		insertToTail(createNode(data, null));
	}

	public void insertToTail(Node newNode) {
		if (head == null) {
			head = newNode;
			return;
		}
		Node curr = head;
		while (curr.next != null) {
			curr = curr.next;
		}
		curr.next = newNode;
	}

	public void insertToHead(int data) {
		insertToHead(createNode(data, null));
	}

	public void insertToHead(Node newNode) {
		if (head == null) {
			head = newNode;
		} else {
			newNode.next = head;
			head = newNode;
		}
	}

	public void deleteNode(Node node) {
		if (head == null) {
			System.out.println("头结点为空!");
			return;
		}
		if (node == null) {
			System.out.println("目标节点为空!");
			return;
		}
		if (node == head) {
			head = head.next;
			return;
		}
		Node curr = head;
		while (curr.next != null && curr.next != node) {
			curr = curr.next;
		}
		if (curr.next == node) {
			curr.next = curr.next.next;
		}
	}
	
	private boolean compareTwoLinkedList(Node head1,Node head2) {
		if(head1 == null || head2 == null) {
			return false;
		}
		//
		Node n1 = head1;
		Node n2 = head2;
		while(n1 != null) {
			System.out.print(" | n1:" + n1.data);
			n1 = n1.next;
		}
		while(n2 != null) {
			System.out.print(" | n2:" + n2.data);
			n2 = n2.next;
		}
		//
		while(head1 != null && head2 != null && head1.data == head2.data) {
			head1 = head1.next;
			head2 = head2.next;
		}
		if(head1 == null && head2 == null) {
			return true;
		}
		return false;
	}
	private Node inverseLinkedList(Node tail) {
		Node curr = head;
		Node next = null;
		while(curr.next != tail) {
			next = curr.next;
			curr.next = curr.next.next;
			next.next = head;
			head = next;
		}
		curr.next = null;
		tail.next = head;
		head = tail;
		return head;
	}
	public boolean palindrome() {
		if(head == null) {
			System.out.println("头结点为空!");
			return false;
		}
		if(head.next == null) {
			System.out.println("只有头结点!");
			return true;
		}
		if(head.next.next == null) {
			if(head.data == head.next.data) {
				return true;
			}else {
				return false;
			}
		}
		Node s = head;
		Node f = head;
		while(f.next != null && f.next.next != null){
			s = s.next;
			f = f.next.next;
		}
		if(f.next == null) {
			//一共有奇数个结点
			Node right = s;
			Node left = inverseLinkedList(s);
			return compareTwoLinkedList(left,right);
		}else {
			//一共有偶数个结点
			Node right = s.next;
			Node left = inverseLinkedList(s);
			return compareTwoLinkedList(left,right);
		}
	}
	private void printAll() {
		Node curr = head;
		while(curr != null) {
			System.out.print("" + curr.data + ";");
			curr = curr.next;
		}
	}
	
	public static void main(String[] args) {
		/*
		 * int[] d1 = new int[] {1,2,3,4,4,3,2,1}; int[] d2 = new int[] {1,2,3,3,2,1};
		 * int[] d3 = new int[] {1,2,2,1}; int[] d4 = new int[] {1,0,0,1};
		 */
		int[] d1 = new int[] {1,2,3,4,3,2,1};
		int[] d2 = new int[] {1,2,3,2,1};
		int[] d3 = new int[] {1,2,2,1};
		int[] d4 = new int[] {1,0,1,0};
		SinglyLinkedList list1 = new SinglyLinkedList();
		for(int item:d1) {
			list1.insertToTail(item);
		}
		System.out.println("list1 is:");
		list1.printAll();
		System.out.println("list1 is palindrome:" + list1.palindrome());
		SinglyLinkedList list2 = new SinglyLinkedList();
		for(int item:d2) {
			list2.insertToTail(item);
		}
		System.out.println("list2 is:");
		list2.printAll();
		System.out.println("list2 is palindrome:" + list2.palindrome());
		SinglyLinkedList list3 = new SinglyLinkedList();
		for(int item:d3) {
			list3.insertToTail(item);
		}
		System.out.println("list3 is:");
		list3.printAll();
		System.out.println("list3 is palindrome:" + list3.palindrome());
		SinglyLinkedList list4 = new SinglyLinkedList();
		for(int item:d4) {
			list4.insertToTail(item);
		}
		System.out.println("list4 is:");
		list4.printAll();
		System.out.println("list4 is palindrome:" + list4.palindrome());
	}
}

list1 is:
1;2;3;4;3;2;1; | n1:4 | n1:3 | n1:2 | n1:1 | n2:4 | n2:3 | n2:2 | n2:1list1 is palindrome:true
list2 is:
1;2;3;2;1; | n1:3 | n1:2 | n1:1 | n2:3 | n2:2 | n2:1list2 is palindrome:true
list3 is:
1;2;2;1; | n1:2 | n1:1 | n2:2 | n2:1list3 is palindrome:true
list4 is:
1;0;1;0; | n1:0 | n1:1 | n2:1 | n2:0list4 is palindrome:false

4. 使用数组实现LRU算法

public class LRUBasedArray<T> {
	private static int DEFAULT_CAPACITY = 1 << 3;
	private int count;
	private int capacity;
	private T[] data;
	private Map<T, Integer> holder;

	public LRUBasedArray() {
		this(DEFAULT_CAPACITY);
	}

	public LRUBasedArray(int capacity) {
		this.count = 0;
		this.capacity = capacity;
		this.data = (T[]) new Object[capacity];
		this.holder = new HashMap<T, Integer>(capacity);
	}

	public boolean contains(T t) {
		return holder.containsKey(t);
	}

	public boolean isFull() {
		return count == capacity;
	}

	public void access(T t) {
		if(t == null) {
			throw new IllegalArgumentException("不允许访问null");
		}
		if (contains(t)) {
			// 说明之前已缓存t
			Integer index = holder.get(t);
			transferToRight(index);
			addToHead(t);
		} else {
			// 说明之前未缓存t
			if (isFull()) {
				// 已满:
				//
				holder.remove(data[capacity - 1]);
				transferToRight(capacity - 1);
				addToHead(t);
			} else {
				// 未满:
				//
				transferToRight(count);
				addToHead(t);
				count++;
			}
		}
	}

	public void transferToRight(int endIndex) {
		for (int index = endIndex; index > 0; index--) {
			data[index] = data[index - 1];
			holder.put(data[index], index);
		}
	}

	public void addToHead(T t) {
		data[0] = t;
		holder.put(t, 0);
	}
	@Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append(data[i]);
            sb.append(" ");
        }
        return sb.toString();
    }
	
	public static void main(String[] args) {
		testDefaultConstructor();
        testSpecifiedConstructor(4);
        testWithException();
	}
	private static void testWithException() {
		System.out.println("测试非法访问!");
        LRUBasedArray<Integer> lru = new LRUBasedArray<Integer>();
        lru.access(null);
    }

    public static void testDefaultConstructor() {
        System.out.println("测试默认构造器");
        LRUBasedArray<Integer> lru = new LRUBasedArray<Integer>();
        lru.access(1);
        lru.access(2);
        lru.access(3);
        lru.access(4);
        lru.access(5);
        System.out.println(lru);
        lru.access(6);
        lru.access(7);
        lru.access(8);
        lru.access(9);
        System.out.println(lru);
    }

    public static void testSpecifiedConstructor(int capacity) {
        System.out.println("测试指定构造参数构造器");
        LRUBasedArray<Integer> lru = new LRUBasedArray<Integer>(capacity);
        lru.access(1);
        System.out.println(lru);
        lru.access(2);
        System.out.println(lru);
        lru.access(3);
        System.out.println(lru);
        lru.access(4);
        System.out.println(lru);
        lru.access(2);
        System.out.println(lru);
        lru.access(4);
        System.out.println(lru);
        lru.access(7);
        System.out.println(lru);
        lru.access(1);
        System.out.println(lru);
        lru.access(2);
        System.out.println(lru);
    }
}

测试默认构造器
5 4 3 2 1 
9 8 7 6 5 4 3 2 
测试指定构造参数构造器
1 
2 1 
3 2 1 
4 3 2 1 
2 4 3 1 
4 2 3 1 
7 4 2 3 
1 7 4 2 
2 1 7 4 
测试非法访问!
Exception in thread "main" java.lang.IllegalArgumentException: 不允许访问null
	at ***.LRUBasedArray.access(LRUBasedArray.java:34)
	at ***.LRUBasedArray.testWithException(LRUBasedArray.java:88)
	at ***.LRUBasedArray.main(LRUBasedArray.java:83)

5. 使用单链表实现LRU算法

public class LRUBaseLinkedList<T> {
	class Node<T>{
		public T data;
		public Node next;
		public Node(T data, Node next) {
			super();
			this.data = data;
			this.next = next;
		}
	}
	public Node createNode(T data) {
		Node node = new Node(data,null);
		return node;
	}
	
	private Node head;
	private static int DEFAULT_CAPACITY = 8;
	private int capacity = DEFAULT_CAPACITY;
	private int count;
	public LRUBaseLinkedList(int capacity) {
		this.capacity = capacity;
	}
	public boolean isFull() {
		return count == capacity;
	}
	public void access(T t) {
		if(t == null) {
			throw new IllegalArgumentException("访问元素不能为空!");
		}
		if(head == null) {
			head = createNode(t);
			count++;
			return;
		}
		if(head.data.equals(t)) {
			return;
		}
		Node curr = head;
		while(curr.next != null && !curr.next.data.equals(t)) {
			curr = curr.next;
		}
		if(curr.next != null && curr.next.data.equals(t)) {
			//要访问的元素已存在
			Node next = curr.next;
			if(next.next == null) {
				curr.next = null;
				next.next = head;
				head = next;
			}else {
				curr.next = next.next;
				next.next = head;
				head = next;
			}
		}else {
			//要访问的元素不存在
			if(isFull()) {
				//元素已经满了
				//1:删除链表尾部元素
				Node c = head;
				while(c != null && c.next != null && c.next.next != null) {
					c = c.next;
				}
				c.next = null;
				//2:将t添加到链表头部
				addHead(t);
			}else {
				//元素未满
				//1:直接将t添加到链表头部
				addHead(t);
				count++;
			}
		}
	}
	public void addHead(T t) {
		Node newNode = createNode(t);
		newNode.next = head;
		head = newNode;
	}
	private void printAll() {
		System.out.println("当前数据为:\n");
		Node curr = head;
		while(curr != null) {
			System.out.print(" -> " + curr.data);
			curr = curr.next;
		}
	}
	public static void main(String[] args) {
		LRUBaseLinkedList<Integer> list = new LRUBaseLinkedList<>(6);
		list.access(1);
		list.access(3);
		list.access(5);
		list.access(7);
		list.access(9);
		list.access(2);
		list.access(4);
		list.access(6);
		list.access(8);
		list.access(10);
		list.printAll();
	}
}

当前数据为:

 -> 10 -> 8 -> 6 -> 4 -> 2 -> 9

6.几个单链表问题

  1. 单链表反转
  2. 单链表中环的检测
  3. 两个有序的单链表的合并
  4. 删除单链表倒数第K个结点
  5. 求单链表的中间节点
public class LinkedListSample {
	class Node {
		public int data;
		public Node next;

		public Node(int data, Node next) {
			super();
			this.data = data;
			this.next = next;
		}
	}

	/**
	 * 单链表反转
	 * 
	 * @param list
	 * @return
	 */
	public Node reverse(Node list) {
		if (list == null) {
			throw new IllegalArgumentException("参数不能为空!");
		}
		if (list.next == null) {
			return list;
		}
		Node curr = list;
		Node pre = list;
		Node next;
		while (curr.next != null) {
			next = curr.next;
			curr.next = curr.next.next;
			next.next = pre;
			pre = next;
		}
		return pre;
	}

	/**
	 * 检测链表是否存在环
	 * 
	 * @param list
	 * @return
	 */
	public boolean checkCircle(Node list) {
		if (list == null) {
			return false;
		}
		Node slow = list;
		Node fast = list;
		while (fast.next != null && fast.next.next != null) {
			fast = fast.next.next;
			slow = slow.next;
			if (fast == slow) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 合并两个有序单链表
	 * 
	 * @param l1
	 * @param l2
	 * @return
	 */
	public Node mergeTwoSortedList(Node l1, Node l2) {
		if (l1 == null) {
			return l2;
		}
		if (l2 == null) {
			return l1;
		}
		// 引入哨兵结点
		Node soldier = new Node(0, null);
		Node curr = soldier;
		while (l1 != null && l2 != null) {
			if (l1.data <= l2.data) {
				curr.next = l1;
				l1 = l1.next;
			} else {
				curr.next = l2;
				l2 = l2.next;
			}
			curr = curr.next;
		}
		if (l1 != null) {
			curr.next = l1;
		} else {
			curr.next = l2;
		}
		return soldier.next;
	}

	/**
	 * 删除单链表的倒数第k个结点
	 * 
	 * @param list
	 * @param k
	 */
	public Node deleteLastK(Node list, int k) {
		if (list == null) {
			throw new IllegalArgumentException("单链表不能为空!");
		}
		if (k < 1) {
			throw new IllegalArgumentException("k必须>=1!");
		}
		Node fast = list;
		int i = 0;
		// 快的结点先向前走k次
		while (fast.next != null && i < k) {
			fast = fast.next;
			i++;
		}
		if (i < k) {
			throw new IllegalArgumentException("单链表长度不够!");
		}
		Node slow = list;
		while (slow.next != null && fast.next != null) {
			slow = slow.next;
			fast = fast.next;
		}
		// 待删除的结点,就是solw.next
		Node next = slow.next;
		slow.next = next.next;
		next.next = null;
		return list;
	}

	/**
	 * 求单链表的中心结点
	 * 
	 * @return
	 */
	public Node findMiddleNode(Node list) {
		if (list == null) {
			throw new IllegalArgumentException("单链表不能为空!");
		}
		Node slow = list;
		Node fast = list;
		while (fast.next != null && fast.next.next != null) {
			fast = fast.next.next;
			slow = slow.next;
		}
		return slow;
	}

	public void printAll(Node list) {
		if (list == null) {
			throw new IllegalArgumentException("单链表不能为空!");
		}
		Node curr = list;
		System.out.println("单链表:");
		while (curr != null) {
			System.out.print("当前结点:" + curr.data + " ; ");
			curr = curr.next;
		}
	}

	public static void main(String[] args) {
		LinkedListSample test = new LinkedListSample();
		System.out.println("\n测试单链表翻转");
		test.testReverse();
		System.out.println("\n测试单链表是否成环");
		test.testCircle();
		System.out.println("\n测试合并2个已排序的单链表");
		test.testMergeTwoSortedList();
		System.out.println("\n测试删除单链表倒数第K个元素");
		test.testDeleteLastK();
		System.out.println("\n测试寻找单链表的中心结点");
		test.testFindMiddleNode();
	}

	private void testReverse() {
		Node node1 = new Node(5, null);
		Node node2 = new Node(4, null);
		Node node3 = new Node(3, null);
		Node node4 = new Node(2, null);
		Node node5 = new Node(1, null);
		node1.next = node2;
		node2.next = node3;
		node3.next = node4;
		node4.next = node5;
		Node result = reverse(node1);
		printAll(result);
	}

	private void testCircle() {
		Node circle = new Node(100, null);
		Node node1 = new Node(5, null);
		Node node2 = new Node(4, null);
		Node node3 = new Node(3, null);
		Node node4 = new Node(2, null);
		Node node5 = new Node(1, null);
		node1.next = node2;
		node2.next = node3;
		node3.next = circle;
		circle.next = node4;
		node4.next = node5;
		node5.next = circle;
		System.out.println("该单链表是否有环:" + checkCircle(node1));
	}

	private void testMergeTwoSortedList() {
		Node node1 = new Node(3, null);
		Node node2 = new Node(7, null);
		Node node3 = new Node(8, null);
		Node node4 = new Node(20, null);
		Node node5 = new Node(30, null);
		node1.next = node2;
		node2.next = node3;
		node3.next = node4;
		node4.next = node5;

		Node node21 = new Node(1, null);
		Node node22 = new Node(2, null);
		Node node23 = new Node(13, null);
		Node node24 = new Node(40, null);
		Node node25 = new Node(50, null);
		node21.next = node22;
		node22.next = node23;
		node23.next = node24;
		node24.next = node25;

		Node result = mergeTwoSortedList(node1, node21);
		printAll(result);
	}
	
	private void testDeleteLastK() {
		Node node1 = new Node(3, null);
		Node node2 = new Node(7, null);
		Node node3 = new Node(8, null);
		Node node4 = new Node(20, null);
		Node node5 = new Node(30, null);
		Node node6 = new Node(1, null);
		Node node7 = new Node(2, null);
		Node node8 = new Node(13, null);
		node1.next = node2;
		node2.next = node3;
		node3.next = node4;
		node4.next = node5;
		node5.next = node6;
		node6.next = node7;
		node7.next = node8;
		Node result = deleteLastK(node1, 5);
		printAll(result);
	}
	
	private void testFindMiddleNode() {
		Node node1 = new Node(3, null);
		Node node2 = new Node(7, null);
		Node node3 = new Node(8, null);
		Node node4 = new Node(20, null);
		Node node5 = new Node(30, null);
		Node node6 = new Node(1, null);
		Node node7 = new Node(2, null);
		Node node8 = new Node(13, null);
		node1.next = node2;
		node2.next = node3;
		node3.next = node4;
		node4.next = node5;
		node5.next = node6;
		node6.next = node7;
		node7.next = node8;
		Node result = findMiddleNode(node1);
		System.out.println("中间节点:" + result.data);
	}
}

测试单链表翻转
单链表:
当前结点:1 ; 当前结点:2 ; 当前结点:3 ; 当前结点:4 ; 当前结点:5 ; 
测试单链表是否成环
该单链表是否有环:true

测试合并2个已排序的单链表
单链表:
当前结点:1 ; 当前结点:2 ; 当前结点:3 ; 当前结点:7 ; 当前结点:8 ; 当前结点:13 ; 当前结点:20 ; 当前结点:30 ; 当前结点:40 ; 当前结点:50 ; 
测试删除单链表倒数第K个元素
单链表:
当前结点:3 ; 当前结点:7 ; 当前结点:8 ; 当前结点:30 ; 当前结点:1 ; 当前结点:2 ; 当前结点:13 ; 
测试寻找单链表的中心结点
中间节点:20