单链表
- 链表依然属于线性结构.只不过它不像数组那样在内存中是连续存储的.它在内存中并不一定是连续存放的.
- 单链表就是一个结点指向下一个节点.
- 节点包含了存放数据的域.和指向下一个节点的域.
- 这个域也可以理解为变量.只是我看那是一块一块的区域.
- 节点类,这个类我单独抽离出来了.其实他是在整个单链表类里面的一个静态内部类.只服务于单链表类.所以作为一个静态内部类比较合适.
这里普及一下嵌套类的概念以及应用场景:定义在一个类内部的类,它的存在仅仅只是为了他的外围类提供服务.
否则如果该嵌套类会用于其他的某个环境中,它应该是顶层类.
如果声明的成员类不要求访问外部类的实例,就要始终将它定义为静态static的.
如果声明的是非静态的内部类.那么每个内部类的实例中都将会包含一个额外的指向外围对象的引用.保存这份引用需要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却任然得以保留.由此而造成的内存泄露常常难以发现.因为这个额外的引用并不常见.
至于为何是私有的.因为这个Node是作为单链表类的一个组件.并不需要外部访问.我们只在内部进行访问.
所以最后应该是私有的静态成员类
/**
* Node Class.
*/
private static class Node<E>{
E item;
/**
* Point to next node.
*/
private Node<E> next;
Node(E element, Node<E> next) {
this.item = element;
this.next = next;
}
}
操作
常见的增删改查,这里直接上代码了.整体是仿照LinkedList的源码来写的.看懂这个,其实LinkedList源码.你也是看了一部分的.这里你要耐心的一点一点捋.其实并不难.
package name.dancer.linkedlist;
import java.util.NoSuchElementException;
/**
* @author dancer
* @date 2019-09-26
* @see java.util.LinkedList
**/
@SuppressWarnings("unused")
public class SingleLinkedList<E>{
private int size = 0;
/**
* Pointer to first node.
*/
private Node<E> first;
/**
* Point to last node.
*/
private Node<E> last;
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
private void linkLast(E e) {
// 保存尾节点的状态
Node<E> l = last;
// 将当前元素.组装为一个Node节点
Node<E> newNode = new Node<>(e, null);
// 为尾节点last赋新值(要添加的节点)
last = newNode;
// 如果fist为null,将当前节点赋值给头节点
if (null == first) {
first = newNode;
} else {
/*
* 将原头节点l. 的下一个节点指向当前新的头节点;
* 思考一个问题:
* 为什么JDK不用l.next = last.
* 而是用l.next = newNode.
*/
l.next = newNode;
// l.next = last;
}
size++;
}
/**
* Inserts element e before non-null Node succ.
*/
private void linkBefore(E e, Node<E> succ) {
if (null == succ) {
Node<E> next = first;
first = new Node<>(e, next);
} else {
Node<E> next = succ.next;
succ.next = new Node<>(e, next);
}
size++;
}
/**
* Before returns the Node as specified element index
*/
private Node<E> node(int index) {
if (index == 0) {
return null;
}
Node<E> x = first;
for(int i = 0, end = index - 1; i < end; i++) {
x = x.next;
}
return x;
}
/**
* Inserts the specified element at the specified position in this list.
*/
public void add(int index, E element) {
// 判断添加的位置是否越界
checkElementIndex(index);
// 等于size添加到链表尾
if (index == size) {
linkLast(element);
// 添加到指定索引位置
} else {
linkBefore(element, node(index));
}
}
private void checkElementIndex(int index) {
if (! (index >= 0 && index <= size)) {
throw new IndexOutOfBoundsException("Index: "+index+",Size: "+size);
}
}
/**
* Replaces the element at the specified position in this list with the specified element.
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x= node(index);
if (null == x) {
E oldVal = first.item;
first.item = element;
return oldVal;
}
E oldVal = x.next.item;
x.next.item = element;
return oldVal;
}
public E remove() {
return removeFirst();
}
private E removeFirst() {
Node<E> f = first;
if (null == f) {
throw new NoSuchElementException();
}
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
E element = f.item;
Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (null == next) {
last = null;
}
size--;
return element;
}
/**
* Return all nodes as Array
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size) {
// 构建指定类型和大小的数组集合
a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
}
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next) {
result[i++] = x.item;
}
// 如果传递过来的集合大小大于原本的长度.将索引size作为一个隔离层,隔离层之外的是读取不到的;
if (a.length > size) {
a[size] = null;
}
return a;
}
/**
* Returns the number of elements in this list
*/
public int size() {
return size;
}
/**
* Node Class.
*/
private static class Node<E>{
E item;
/**
* Point to next node.
*/
private Node<E> next;
Node(E element, Node<E> next) {
this.item = element;
this.next = next;
}
}
}
测试
package name.dancer.linkedlist;
/**
* 单链表
* @author dancer
* @date 2019-08-30
**/
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList<Object> linkedList = new SingleLinkedList<>();
linkedList.add("1");
linkedList.add("2");
linkedList.add("3");
linkedList.add(0,"0");
linkedList.set(3, "100");
linkedList.remove();
linkedList.remove();
linkedList.remove();
linkedList.add(0,"0");
linkedList.add("3");
String[] strings = linkedList.toArray(new String[linkedList.size()]);
for (String string : strings) {
System.out.println(string);
}
}
}
面试题
求单链表中有效节点的个数
如果是带头结点的链表,不统计头结点
遍历所有的有效节点即可
/**
* Returns the number of elements in this list
*/
public int size() {
return size;
}
查找链表中倒数第K个节点(新浪)
正数第size-k个节点
/**
* 查找单链表中倒数第index个节点
*/
public Node<E> reverserNode(int index) {
checkElementIndex(index);
Node<E> x = first;
for(int i = 0, v = size - index; i < v ; i++) {
x = x.next;
}
return x;
}
单链表的反转(腾讯)
从尾到头打印单链表(百度)
- 反转单链表,打印.这样做会破坏原来单链表的结构
- 利用栈,将各个结点压入到栈中.利用栈的先进后出,实现逆序打印java.util.Stack
合并两个有序的单链表,合并之后仍然有序.
思路和反转单链表很像.将两个链表进行对比.小的那个加入新的链表中.