部分转自https://www.jianshu.com/p/6782f3d96471
单链表
什么是单链表
链表(Linked list)是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的引用(Pointer)。链表并不像数组那样将数组存储在一个连续的内存地址空间里,所以较之数组来说这是一个优势。
单链表的特点
- 链表增删元素的时间复杂度为O(1),查找一个元素的时间复杂度为 O(n);
- 单链表不用像数组那样预先分配存储空间的大小,避免了空间浪费;
- 单链表不能进行回溯操作,如:只知道链表的头节点的时候无法快读快速链表的倒数第几个节点的值。
单链表的基本操作
获取单链表的长度
对于一个链表的长度我们只能一次去遍历链表的节点,直到找到某个节点的下一个节点为空的时候得到链表的总长度:
public int getLength(Node head){
if(head == null){
return 0;
}
int len = 0;
while(head != null){
len++;
head = head.next;
}
return len;
}
查询指定索引的节点值或指定节点值的索引
查询指定索引的节点值:
public int getValueOfIndex(Node head, int index) throws Exception {
if (index < 0 || index >= getLength(head)) {
throw new Exception("角标越界!");
}
if (head == null) {
throw new Exception("当前链表为空!");
}
Node targetHead = head;
while (targetHead.next != null && index > 0) {
targetHead = targetHead.next;
index--;
}
return targetHead.value;
}
查询指定节点值的索引:
public int getNodeIndex(Node head, int value) {
int index = -1;
Node targetHead= head;
while (targetHead!= null) {
index++;
if (targetHead.value == value) {
return index;
}
targetHead= targetHead.next;
}
return -1;
}
添加一个元素
1、在已有链表头部插入一个节点
public Node addAtHead(Node head, int value){
Node newHead = new Node(value);
newHead.next = head;
return newHead;
}
2、在已有链表的尾部插入一个节点:
public void addAtTail(Node head, int value){
Node node = new Node(value);
Node targetHead= head;
while( targetHead.next != null){
targetHead= targetHead.next;
}
targetHead.next = node;
}
3、在指定位置添加一个节点
public Node insertElement(Node head, int value, int index) throws Exception {
//为了方便这里我们假设知道链表的长度
int length = getLength(head);
if (index < 0 || index >= length) {
throw new Exception("角标越界!");
}
if (index == 0) {
return addAtHead(head, value);
}
else if (index == length - 1) {
addAtTail(head, value);
}
else {
Node pre = head;
Node cur = head.next;
while (pre != null && index > 1) {
pre = pre.next;
cur = cur.next;
index--;
} //循环结束后 pre 保存的是索引的上一个节点 而 cur 保存的是索引值当前的节点
Node node = new Node(value);
pre.next = node;
node.next = cur;
}
return head;
}
在指定位置添加一个节点,首先我们应该找到这个索引所在的节点的前一个,以及该节点,分别记录这两个节点,然后将索引所在节点的前一个节点的 next 指针指向新节点,然后将新节点的 next 指针指向插入节点即可。与其他元素并没有什么关系,所以单链表插入一个节点时间复杂度为 O(1)。
而数组插入元素就不一样了如果将一个元素插入数组的指定索引位置,那么该索引位置以后元素的索引位置(内存地址)都将发生变化,所以一个数组的插入一个元素的时间复杂度为 O(n);所以链表相对于数组插入的效率要高一些,删除同理。
删除一个元素
1、 删除头部节点
public Node deleteHead(Node head) throws Exception {
if (head == null) {
throw new Exception("当前链表为空!");
}
return head.next;
}
2、 删除尾节点:
public void deleteTail(Node head) throws Exception {
if (head == null) {
throw new Exception("当前链表为空!");
}
Node targetHead = head;
while (targetHead.next != null && targetHead.next.next != null) {
targetHead= targetHead.next;
}
targetHead.next = null;
}
3、 删除指定索引的节点:
public Node deleteElement(Node head, int index) throws Exception {
int size = getLength(head);
if (index < 0 || index >= size) {
throw new Exception("角标越界!");
}
if (index == 0) {
return deleteHead(head);
}
else if (index == size - 1) {
deleteTail(head);
}
else {
Node pre = head;
while (pre.next != null && index > 1) {
pre = pre.next;
index--;
}
//循环结束后 pre 保存的是索引的上一个节点 将其指向索引的下一个元素
if (pre.next != null) {
pre.next = pre.next.next;
}
}
return head;
}
由单链表的增加删除可以看出,链表的想要对指定索引进行操作(增加,删除),的时候必须获取该索引的前一个元素。