文章目录
前言
在学习 Java 的集合框架或数据结构课程时,我们经常会遇到 LinkedList。它看似是一个简单的容器类,却在底层通过链表(Linked List)这一经典数据结构实现了高效的插入与删除操作。为了更深入理解它的原理,我们不妨亲手实现一遍------从最基础的单向链表(Singly Linked List)开始,再扩展到能双向遍历的双向链表(Doubly Linked List)。在实现过程中,你会体会到指针(引用)操作的精妙,也能更清晰地理解为什么不同数据结构在时间复杂度和内存开销上有着截然不同的表现。
一、单链表的实现
java
import java.util.ListIterator;
public class SingleLinkedList {
public LinkLode head;
static class LinkLode {
public int val;
public LinkLode next;
public LinkLode(int val) {
this.val = val;
}
}
public void createList() {
head = null;
LinkLode node1 = new LinkLode(1);
LinkLode node2 = new LinkLode(2);
LinkLode node3 = new LinkLode(3);
LinkLode node4 = new LinkLode(4);
LinkLode node5 = new LinkLode(5);
LinkLode node6 = new LinkLode(6);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
head = node1;
}
public void showList() {
LinkLode node = head;
while (node != null) {
System.out.print(node.val + " ");
node = node.next;
}
}
//头插法
public void addFirst(int data) {
LinkLode node = new LinkLode(data);
node.next = head;
head = node;
}
//尾插法
public void addLast(int data) {
LinkLode node = new LinkLode(data);
LinkLode cur = head;
if (cur == null) {
head = node;
return;
}
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
if (index < 0 || index > size()) {
System.out.println("index error");
return;
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size() - 1) {
addLast(data);
return;
}
LinkLode node = new LinkLode(data);
LinkLode cur = searchByIndex(index);
node.next = cur.next;
cur.next = node;
}
//寻找index的前一个节点
private LinkLode searchByIndex(int index) {
if (index < 0 || index > size()) {
System.out.println("index error");
return null;
}
int count = 0;
LinkLode cur = head;
while (cur != null) {
if (count == index - 1) {
return cur;
}
count++;
cur = cur.next;
}
return cur;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
if (head == null) {
return false;
}
LinkLode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
if (head == null) {
System.out.println("head is null");
return;
}
LinkLode prev = findPrevNode(key);
if (prev == null) {
if (head.val == key) {
head = head.next;
}
} else {
LinkLode cur = prev.next;
prev.next = cur.next;
cur = null;
return;
}
}
private LinkLode findPrevNode(int key) {
if (head == null) {
return null;
}
LinkLode prev = head;
LinkLode cur = head.next;
while (cur != null) {
if (cur.val == key) {
return prev;
}
prev = cur;
cur = cur.next;
}
return null;
}
//删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {
return;
}
LinkLode prev = head;
LinkLode cur = head.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
if (head.val == key) {
head = head.next;
}
}
//得到单链表的长度
public int size() {
int count = 0;
LinkLode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
public void clear() {
LinkLode cur = head;
while (cur != null) {
LinkLode nextNode = cur.next;
cur.val = 0;
cur.next = null;
cur = nextNode;
}
head = null;
}
//递归做法
/*public LinkLode reverseList(LinkLode head) {
if(head==null){
return head;
}
if(head.next==null) {
return head;
}
LinkLode newHead = reverseList(head.next);
head.next.next=head;
head.next=null;
return newHead;
}*/
//循环做法
public LinkLode reverseList(LinkLode head) {
if (head == null) {
return null;
}
LinkLode cur = head.next;
head.next = null;
while (cur != null) {
LinkLode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
//方法一
public LinkLode middleNode(LinkLode head) {
int len = size();
LinkLode cur = head;
for (int i = 0; i < len; i++) {
cur=cur.next;
}
return cur;
}
//方法二
public LinkLode findMiddleNode(LinkLode head) {
if(head==null||head.next==null){
return head;
}
LinkLode fast=head;
LinkLode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
public int kthToLast(LinkLode head, int k) {
if(k<=0){
return -1;
}
int count=0;
LinkLode fast=head;
LinkLode slow=head;
while(count!=k-1){
if(fast==null){
return -1;
}
fast=fast.next;
count++;
}
while(fast.next!=null){
slow=slow.next;
fast=fast.next;
}
return slow.val;
}
public LinkLode partition(LinkLode pHead, int x) {
// write code here
LinkLode bs=null;
LinkLode be=null;
LinkLode as=null;
LinkLode ae=null;
LinkLode cur=pHead;
while(cur!=null){
if(cur.val<x){
if(bs==null&&be==null){
bs=be=cur;
}else{
be.next=cur;
be=be.next;
}
}else{
if(as==null&&ae==null){
as=ae=cur;
}else{
ae.next=cur;
ae=ae.next;
}
}
cur=cur.next;
}
if(bs==null){
return as;
}
be.next=as;
if(as!=null)
ae.next=null;
return bs;
}
}
二、双向链表的实现
java
public class MyLinkedList {
ListLode head;
ListLode last;
static class ListLode{
ListLode prev;
ListLode next;
int val;
public ListLode(ListLode prev, ListLode next, int val) {
this.prev = prev;
this.next = next;
this.val = val;
}
}
//头插法
public void addFirst(int data){
ListLode node=new ListLode(null,null,data);
if(head==null){
head=last=node;
return;
}
node.next=head;
head.prev=node;
head=node;
}
//尾插法
public void addLast(int data){
ListLode node=new ListLode(null,null,data);
if(head==null){
head=last=node;
return;
}
last.next=node;
node.prev=last;
last=node;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if(index<0||index>size()){
System.out.println("index out of bounds error");
}
if(index==0){
addFirst(data);
}else if(index==size()){
addLast(data);
}else{
ListLode cur=searchNodeByIndex(index);
ListLode node=new ListLode(null,null,data);
node.next=cur;
node.prev=cur.prev;
cur.prev.next=node;
cur.prev=node;
}
}
private ListLode searchNodeByIndex(int index){
ListLode cur=head;
while(index!=0){
cur=cur.next;
index--;
}
return cur;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListLode cur = head;
while(cur!=null){
if(cur.val==key){
return true;
}
cur=cur.next;
}
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
ListLode cur=head;
while(cur!=null){
if(cur.val==key){
if(cur==head){
head=head.next;
if(head==null){
head=last=null;
}
head.prev = null;
return;
}
if(cur==last){
last=last.prev;
last.next=null;
return;
}
cur.next.prev=cur.prev;
cur.prev.next=cur.next;
return;
}
cur=cur.next;
}
System.out.println("没有要删除的元素");
}
//删除所有值为key的节点
public void removeAllKey(int key){
ListLode cur=head;
while(cur!=null){
if(cur.val==key){
if(cur==head){
head=head.next;
if(head==null){
head=last=null;
cur=cur.next;
continue;
}
head.prev = null;
cur=cur.next;
continue;
}
if(cur==last){
last=last.prev;
last.next=null;
cur=cur.next;
continue;
}
cur.next.prev=cur.prev;
cur.prev.next=cur.next;
cur=cur.next;
continue;
}
cur=cur.next;
}
System.out.println("没有要删除的元素");
}
//得到单链表的长度
public int size(){
ListLode cur=head;
int count=0;
while(cur!=null){
count++;
cur=cur.next;
}
return count;
}
public void display(){
ListLode cur=head;
while(cur!=null){
System.out.println(cur.val);
cur=cur.next;
}
}
public void clear(){
ListLode cur=head;
while(cur!=null){
ListLode curN=cur.next;
cur.next=null;
cur.prev=null;
cur=curN;
}
head=last=null;
}
}
总结
通过手写单链表与双向链表,我们不仅复现了 LinkedList 的核心逻辑,还更直观地理解了链表在节点插入、删除、遍历时的运行机制。单链表结构简单、节省空间,但只能单向访问;双向链表虽多占用一些内存,却极大提升了遍历与删除效率。理解这些底层细节,不仅有助于掌握数据结构与算法的本质,也能让我们在使用 Java 集合类或设计复杂数据模型时更加得心应手。链表虽然古老,却是理解内存引用、指针操作、数据结构抽象思想的最佳起点。