链表
概论
wikipedia:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域(双向链表还有一个指向前驱的指针域)。
链表数据结构主要包含单向链表、双向链表及循环链表。
单向链表
链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
/**
* 单链表数据结构
*
* @param <T>
*/
public class SinglyLinkedList<T> {
//头节点
private Node head;
//节点数据结构类
private class Node {
//数据域
T data;
//指针域,节点引用指向下一个节点
Node next;
}
}
添加元素
单链表添加元素有两种方式,第一种链表头部添加,新添加的元素成为链表的head,还有一种添加到链表尾部,新添加元素变成链表最后一个元素。
//头部添加元素
public void addHead(T t) {
//新添加元素的next,指向填表之前头部,当head是null时也没问题,表示新添加的元素就是head后续无元素
Node node = new Node(t, head);
//新添加元素变成头部
head = node;
//链表长度+1
size++;
}
//尾部添加元素
public void addTail(T t) {
//当链表是null时,新添加元素变成head
if (head == null) {
head = new Node(t, null);
} else {
//当链表不为null时,需遍历链表找到最后一个元素,然后next指针指向新添加元素
Node curNode = head;
//遍历链表
while (curNode.next != null) {
curNode = curNode.next;
}
//while循环退出表示找到最后一个元素
curNode.next = new Node(t, null);
}
size++;
}
移除元素
移除链表中指定索引位置元素
- 找到链表中制定位置元素节点的前驱和后继节点
- 将指定索引位置元素的前后节点链接起来
- 返回移除节点元素值
T remove(int index) {
//size=0 空链表
if (index < 0 || index >= size || size == 0) {
throw new IndexOutOfBoundsException();
}
Node preNode = head;
//找到index之前的元素
for (int i = 0; i < index - 1; i++) {
preNode = preNode.next;
}
//获取当前要删除的节点
Node curNode = preNode.next;
//要删除元素的前一个节点指向删除元素的下一个节点
preNode.next = curNode.next;
size--;
return curNode.data;
}
移除链表中指定的元素
- 链表为空,直接返回false
- 如果删除的是头节点,新的head节点变成之前head.next
- 找到需要删除元素的前一个节点和后一个节点,将两者链接起来
//移除链表中指定的元素
boolean remove(T t) {
//空链表直接返回false
if (head != null) {
if (t.equals(head.data)) {
//删除头节点
head = head.next;
size--;
return true;
} else {
Node preNode = head;
Node curNode = preNode.next;
while (curNode != null) {
if (t.equals(curNode.data)) {
preNode.next = curNode.next;
size--;
return true;
}
//指针往后移动
preNode = curNode;
curNode = curNode.next;
}
}
}
return false;
}
返回指定元素在链表索引
返回指定元素所在链表的索引,元素不存在则返回-1,若存在多个相同元素,则返回第一次出现的索引下标
//遍历节点判断节点值与目标值是否相等
public int indexOf(T t) {
int index = 0;
for (Node curNode = head; curNode != null; curNode = curNode.next) {
if (t.equals(curNode.data)) {
return index;
}
index++;
}
return -1;
}
双向链
每个节点有两个连接:一个指向前一个节点,而另一个指向下一个节点。
数据结构
public class DoublyLinkedList<T> {
/**
* head 头节点 tail 尾节点
*/
private Node head;
private Node tail;
/**
* 链表长度
*/
private int size;
public DoublyLinkedList() {
this.head = null;
this.tail = null;
}
//节点结构
private class Node {
/**
* 前驱节点指针域
*/
DoublyLinkedList.Node pre;
/**
* 数据域
*/
T data;
/**
* 后继节点指针域
*/
DoublyLinkedList.Node next;
public Node(Node pre, T data, Node next) {
this.pre = pre;
this.data = data;
this.next = next;
}
}
}
查找元素
根据索引位置获取节点
- 判断索引位置是否合法,索引位置从0开始
- 获取链表的中心点位置,如果index在前半部分,从链表head开始搜索元素,index在后半部分从链表尾部开始搜索元素
public Node getByIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("index is error!!!");
}
//根据index与size/2大小比较,如果属于前半区间的位置从head开始搜素,如果属于后半区间位置从tail开始搜索,提高效率
if (index <= size / 2) {
//从head开始遍历
Node curNode = head;
for (int i = 0; i <= size / 2 && curNode != null; i++, curNode = curNode.next) {
if (i == index) {
return curNode;
}
}
} else {
//从tail开始遍历
Node curNode = tail;
for (int i = size - 1; i > size / 2 && curNode != null; i--, curNode = curNode.pre) {
if (i == index) {
return curNode;
}
}
}
return null;
}
查询指定元素在链表中首次出现位置索引,不存在返回-1
public int indexOf(T t){
// 从头结点开始搜索
Node curNode = head;
for (int i = 0; i < size && curNode != null; i++, curNode = curNode.next) {
if (curNode.data.equals(t)) {
return i;
}
}
return -1;
}
添加元素
头部添加元素
- 新增节点,节点的next节点为head
- 新节点成为新的head节点
- 如果是空链表,tail尾部节点等于头结点
public void addHead(T t) {
//新节点next指针指向原来的head
// Node node = new Node(null, t, head);
// this.head=node;
head = new Node(null, t, head);
if (head == null) {
//空链表
tail = head;
}
size++;
}
尾部添加元素
- 如果是空链表,新节点成为head节点,尾结点等于head节点
- 如果非空链表,创建新节点,新节点的pre节点为原来的tial节点
- 尾节点的next指针指向新节点
- 新节点成为尾结点
public void addTail(T t) {
if (head == null) {
//空链表
head = new Node(null, t, null);
tail = head;
} else {
//创建新节点,新节点的pre节点为原来的tial节点
Node node = new Node(tail, t, null);
//尾节点的next指针指向新节点
tail.next = node;
//新节点成为尾结点
tail = node;
}
size++;
}
指定位置插入节点
- 检测位置合法性
- 链表为空,头部插入节点
- 链表不为空获取index位置的前一个节点preNode
- 获取插入位置的后一个节点nextNode
- 创建插入节点,并指定pre,next
- 插入位置前一个节点next指向新节点
- 插入位置前后一个节点pre指向新节点
public void insert(T t, int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("index is error!!!");
}
if (head == null) {
//链表为空
addHead(t);
} else {
//获取index位置节点的前一个节点
Node preNode = getByIndex(index - 1);
Node nextNode = preNode.next;
Node curNode = new Node(preNode, t, nextNode);
preNode.next = curNode;
nextNode.pre = curNode;
size++;
}
}
移除元素
移除链表中指定位置元素
- 检查索引的合法性,从0开始
- 如果删除的是头结点,头结点的下一个节点成为新的头结点
- 如果删除的非头结点,获取删除元素的前一个节点
- 被删除节点的前一个节点next 指向删除节点下一个节点
- 如果删除节点的下一个节点不为null,删除元素的下一个节点的pre指向删除元素的pre节点
- 删除节点的pre,next以及删除节点置为null,gc更高效率回收
public T remove(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("index is error!!!");
}
Node result = null;
if (size == 0) {
result = head;
//删除head节点
head = head.next;
head.pre = null;
} else {
//获取删除节点前一个节点
Node preNode = getByIndex(index - 1);
Node delNode = preNode.next;
//被删除节点的前一个节点next 指向删除节点下一个节点。
preNode.next = delNode.next;
if (delNode.next != null) {
delNode.next.pre = preNode;
}
delNode.pre = null;
delNode.next = null;
result = delNode;
delNode = null;
}
size--;
return result.data;
}
移除链表中指定元素
- 循环遍历找到需要移除元素的索引
- 根据索引移除元素
public boolean remove(T t) {
try {
int i = indexOf(t);
remove(i);
return true;
} catch (Exception e) {
return false;
}
}
循环链表
头节点和尾节点被连接在一起的链表称为循环链表,这种方式在单向和双向链表中皆可实现。
单向循环链表
单向循环链表,尾节点有next节点指向head。
双向循环链表
双向循环链表,head节点的pre指向尾节点,尾节点next指向head。