1、认识链表
链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。每个节点都是独立的对象,通过指针连接在一起形成一个链式结构。
链表的特点是灵活的插入和删除操作,相对于数组来说,链表的大小可以动态变化。由于节点之间通过指针连接,因此在内存中可以非连续地存储。
链表可以分为以下几种类型:
单向链表(Singly Linked List):每个节点包含数据和指向下一个节点的指针。
双向链表(Doubly Linked List):每个节点包含数据、指向下一个节点的指针和指向前一个节点的指针。
循环链表(Circular Linked List):最后一个节点指向第一个节点或者其他形成循环的节点。
双向循环链表(Doubly Circular Linked List):既是双向链表又是循环链表,即最后一个节点指向第一个节点,同时第一个节点也指向最后一个节点。
2、链表的创建
2.1 创建单向链表
public class BasicLinked {
public static void main(String[] args) {
int[] array = {1, 2, 3, 88, 99, 100};
//创建链表
Node head = initLinkedList(array);
System.out.println(BasicLinked.toString(head));
}
/**
* 初始化链表
* @param array
* @return
*/
private static Node initLinkedList(int[] array) {
//记录头节点,防止链表丢失
Node head = null;
//记录当前节点
Node cur = null;
for (int i = 0; i < array.length; i++) {
Node newNode = new Node(array[i]);
if (i == 0) {
head = newNode;
} else {
cur.next = newNode;
}
cur = newNode;
}
return head;
}
/**
* 输出链表
* @param head 头节点
* @return
*/
public static String toString(Node head) {
Node currNode = head;
StringBuilder sb = new StringBuilder();
while (currNode != null) {
sb.append(currNode.val).append("\t");
currNode = currNode.next;
}
return sb.toString();
}
static class Node {
public int val;
public Node next;
Node(int x) {
val = x;
next = null;
}
}
}
得到链表效果如下:
3、链表的插入与删除
3.1 单向链表
3.1.1 单向链表的插入
单链表的插入操作需要考虑三种情况:首部、中部和尾部。
当在链表的表头插入时注意head需要重新指向表头。创建一个新的结点newNode,执行newNode.next = head就添加成功了,这时head需要重新指向newNode让head = newNode即可,或者是直接返回一个newNode就行。
在链表中间插入时,必须先遍历找到插入的位置,获取到目标结点的前一个位置的结点(避免丢失前面结点的信息)。
在尾部插入时只需要将尾结点指向新结点就行了。
/**
* 链表增加节点
*
* @param head 链表头节点
* @param insertNode 增加的节点
* @param position 增加节点的位置--从1开始
* @return
*/
public static Node insertNode(Node head, Node insertNode, int position) {
//做安全性校验
if (head == null) { //不做插入处理
return insertNode;
}
int likedListLength = getLinkedListLength(head);
if (position > likedListLength + 1 || position < 1) {
System.out.println("位置参数输入有误");
return head;
}
//当从表头插入
if (1 == position) {
insertNode.next = head;
//return insertNode; 当前的insertNode已经是符合条件的链表了
head = insertNode;
return head;
}
//当从其他位置插入
Node currNode = head; //当前节点--一步一步往后移(用来存被替换位置节点的上一个节点)
int currIndex = 1;
while (currIndex < position - 1) { //遍历链表,在遍历到链表目标位置的上两个位置时最后一次进入循环
currNode = currNode.next; //最后一次循环获取到目标位置的上一个节点
currIndex++;
}
insertNode.next = currNode.next; //先把被挤掉位置的节点接入到新加节点的next上,防止丢失,然后再让上一个节点指向新加的节点(顺序不能颠倒)
currNode.next = insertNode;
return head;
}
/**
* 遍历链表获取到链表的长度
*
* @param head 头节点
* @return
*/
public static int getLinkedListLength(Node head) {
int length = 0;
Node curr = head;
while (curr != null) {
length++;
curr = curr.next;
}
return length;
}
/**
* 如果链表是单调的,链表增加节点保证链表仍保持调单调
*
* @param head 链表头节点
* @param insertNode 增加的节点
* @return
*/
public static Node insertNodeKeepAsc(Node head, Node insertNode) {
//做安全性校验
if (head == null) { //不做插入处理
System.out.println("链表为空,请检查!");
return null;
}
int insertVal = insertNode.val;
Node currNode = head;
//当增加的节点值小于头节点值时
if (insertVal <= currNode.val) {
insertNode.next = head;
return insertNode;
}
//找到符合条件的上一个位置
while (currNode.next != null && insertVal > currNode.next.val) {
currNode = currNode.next;
}
insertNode.next = currNode.next; //先把被挤掉位置节点的地址存起来,防止丢失
currNode.next = insertNode;
return head;
}
3.1.2 单向链表的删除
删除同样分为删除头部、中间和尾部元素。
删除头部元素一般只要执行head=head.next就行。
删除中间节点时需要找到被删除结点的上一个结点curr,然后让被删除节点的上一个结点指向被删除结点的下一个结点就行,即curr.next = curr.next.next。
删除最后一个结点时也是需要找到要删除的结点的前驱结点curr,然后让curr.next = null即可。
/**
* 根据给定位置删除链表节点
*
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
//安全性校验
if (head == null) {
return null;
}
int linkedListLength = getLinkedListLength(head);
if (position > linkedListLength || position < 1) {
System.out.println("位置参数输入错误,请检查");
return head;
}
if (position == 1) { //删除头节点
return head.next;
} else { //删除其他节点
Node currNode = head; //当前节点
int count = 1;
while (count < position - 1) { //获取到被删除节点的上一个位置节点
currNode = currNode.next;
count++;
}
Node delNode = currNode.next; //得到被删除节点的信息
currNode.next = delNode.next; //把被删除节点排除在链表之外
}
return head;
}