简介
-
链表
1、线性表的一种,物理存储结构上非连续,是链式的存储结构。
2、动态分配内存,链表在内存中是不连续的。
3、插入和删除的效率高,查找的效率低。
- 插入或删除不需要移动其他元素
- 查询通过遍历定位元素,时间复杂度是O(n) -
数组
1、把所有元素按次序依次存储,是顺序的存储结构。
2、静态分配内存,数组在内存中是连续的。
3、插入和删除的效率低,查找的效率高。
- 插入和删除需要移动其他元素,时间复杂度是O(n)
- 查询利用下标定位,时间复杂度是O(1)
一、定义
-
链表结构示例
-
链表节点定义
public class ListNode {
public int val;
public ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
二、增删操作
1. 删除链表节点
-
删除操作:
1、遍历找到要待删除的节点,或者节点位置。
2、记录待删除节点 curNode,前置节点 preNode。
3、执行删除 preNode.next = curNode.next。
如上图从上到下的过程。删除Node(4),执行:Node(3).next = Node(5)
2. 添加链表节点
-
添加操作:
1、遍历找到要待插入节点 node 的位置。
2、记录待插入位置 curNode,前置节点 preNode。
3、执行添加 node.next = curNode; preNode.next = node。
如上图从下到上的过程。新增Node(4),执行 :Node(3).next = Node(4); Node(4).next = Node(5);
3. 增删案例
通过一段代码例子,来体验一下链表的增删操作。自定义一个链表类 LinkedNodeList。
- 实现:
/*
自定义链表
*/
public class LinkedNodeList {
private int size;
private ListNode head;
public LinkedNodeList() {
this.size = 0;
this.head = null;
}
/**
* 添加操作
*/
public void addToHead(ListNode node) {
// 处理头节点
node.next = this.head;
this.head = node;
// 处理节点个数
this.size++;
}
public void addToTail(ListNode node) {
if (head == null) {
this.addToHead(node);
return;
}
ListNode curNode = head;
while (curNode.next != null) { // 循环结束后,curNode指向最后一个节点
curNode = curNode.next;
}
curNode.next = node;
size++;
}
/*
定义: 插入节点到指定下标
示例: 1->2->3
操作: insert -> Node(7), 1
结果: 1->7->2->3
*/
public void insert(ListNode node, int index) {
if (index == 0) { // 添加到头部
this.addToHead(node);
return;
}
if (index == size) { // 添加到尾部
this.addToTail(node);
return;
}
if (index < -1 || index > size) {
throw new IllegalArgumentException(String.format("[error: index is out of range.][size: %s][index: %s]", size, index));
}
ListNode preNode = head;
ListNode curNode = head;
// 从0走到下标index需要走index步,走到index前一个节点需要index-1步
while (index > 0) { // 循环index curNode移动到index位置
preNode = curNode;
curNode = curNode.next;
index--;
}
node.next = curNode;
preNode.next = node;
size++;
}
/**
* 删除操作
*/
public void removeHead() {
if (size == 0) {
throw new NullPointerException("当前链表为空,不支持删除操作");
}
head = head.next;
size--;
}
public void removeTail() {
if (size == 0) {
throw new NullPointerException("当前链表为空,不支持删除操作");
}
if (size == 1) {
removeHead();
return;
}
ListNode preNode = head;
ListNode curNode = head;
while (curNode.next != null) {
preNode = curNode;
curNode = curNode.next; // 循环结束,curNode是最后一个节点
}
preNode.next = null;
size--;
}
public void removeByIndex(int index) {
if (index == 0) { // 删除链表头节点
this.removeHead();
return;
}
if (index == size - 1) { // 删除链表尾节点
this.removeTail();
return;
}
if (index < -1 || index >= size) {
throw new IllegalArgumentException(String.format("[error: index is out of range.][size: %s][index: %s]", size, index));
}
// 从0到下标index需要走index步
ListNode preNode = head;
ListNode curNode = head;
if (index > 0) { // 循环index curNode移动到index位置
preNode = curNode;
curNode = curNode.next;
index--;
}
// System.out.println("delete element: " + curNode.val);
preNode.next = curNode.next;
size--;
}
public ListNode head() {
return this.head;
}
public int size() {
return this.size;
}
public boolean empty() {
return this.size == 0;
}
public void show() {
ListNode curNode = head;
while (curNode != null) {
System.out.print(curNode.val + " ");
curNode = curNode.next;
}
System.out.println();
}
}
- 测试入口
public static void main(String[] args) {
LinkedNodeList list = new LinkedNodeList();
// add to head
list.addToHead(new ListNode(1));
list.addToHead(new ListNode(2));
list.addToHead(new ListNode(3));
System.out.print("添加节点到链表头部后结果: ");
list.show();
// insert by index
list.insert(new ListNode(7), 1);
System.out.print("插入节点 Node(7) 到下标1的位置到链表头部后结果: ");
list.show();
// add to tail
list.addToTail(new ListNode(8));
list.addToTail(new ListNode(9));
System.out.print("添加节点到链表尾部后结果: ");
list.show();
System.out.println("-----------------------------");
// delete head
list.removeHead();
System.out.print("删除链表头节点后结果: ");
list.show();
// delete tail
list.removeTail();
System.out.print("删除链表尾节点后结果: ");
list.show();
// delete by index
list.removeByIndex(1);
System.out.print("删除链表下标为1的节点后结果: ");
list.show();
System.out.println("最终链表长度为: " + list.size());
}
-
结果预览
添加节点到链表头部后结果: 3 2 1 插入节点 Node(7) 到下标1的位置到链表头部后结果: 3 7 2 1 添加节点到链表尾部后结果: 3 7 2 1 8 9 ----------------------------- 删除链表头节点后结果: 7 2 1 8 9 删除链表尾节点后结果: 7 2 1 8 删除链表下标为1的节点后结果: 7 1 8 最终链表长度为: 3
三、常用基础操作
- 示例:
1. 寻找链表中间节点(快慢指针)
当节点个数为奇数没什么需要讨论,当为偶数时。会存在两个节点都可以称为中间节点.
public ListNode getMidListNode(ListNode head) {
if (head == null) {
return null;
}
// 偏右
ListNode fast = head;
// 偏左
// ListNode fast = head.next;
ListNode slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
2. 反转链表(原址改变指向)
/*
input: 1->2->3->4->5
left right
// null 1->2->3->4->5
1->null 2->3->4->5
2->1->null 3->4->5
3->2->1->null 4->5
4->3->2->1->null 5
5->4->3->2->1->null null
*/
private static ListNode reverse(ListNode head) {
if (head == null) {
return null;
}
ListNode left = null;
ListNode right;
while (head != null) {
right = head.next;
head.next = left;
left = head;
head = right;
}
return left;
}
3. 巩固
通过一道题目来加强对链表基础操作的使用。
-
描述:给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
-
思路:
1、将链表分成左右两个部分;
2、对右半部分做翻转;
3、比较左右两个链表是否相同(注意奇偶因素)。 -
实现:
/*
1、找出链表的中间节点
2、对后半段做 链表反转
3、比较前后半段
4、恢复链表
*/
public static boolean isPalindrome(ListNode head) {
// 结果
boolean flag = true;
ListNode midNodeList = findMidNodeList(head);
// 右半段
ListNode rightNode = midNodeList.next;
ListNode reversedRightNode = reverseListNode(rightNode);
ListNode left = head;
ListNode right = reversedRightNode;
while (right != null) {
if (right.val != left.val) {
flag = false;
break;
}
right = right.next;
left = left.next;
}
// 还原
midNodeList.next = reverseListNode(reversedRightNode);
return flag;
}
/*
寻找链表的中间节点
1->2->3->4 // 奇数返回2
1->2->3->4->5 // 偶数返回3
*/
private static ListNode findMidNodeList(ListNode head) {
if (head == null) {
return null;
}
// 虚节点,也可以使用上述的方法
ListNode dummy = new ListNode(0, head);
ListNode fast = dummy;
ListNode slow = dummy;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
/*
链表反转
1->2->3->4->5->null
left right
null 1->2->3->4->5->null
1->null 2->3->4->5->null
2->1->null 3->4->5->null
*/
private static ListNode reverseListNode(ListNode head) {
ListNode left = null;
ListNode right;
ListNode curNode = head;
while (curNode != null) {
right = curNode.next;
curNode.next = left;
left = curNode;
curNode = right;
}
return left;
}