一、基本介绍
链表是有序列表,但它在内存中各节点却不一定连续,单链表在内存中的存储如下:
由上图可知:
- 链表以节点方式进行存储,是链式存储。
- 每个节点包含一个data域和next域,data域用来存储数据,next域则用来指向下一个节点。
- 链表的各个节点不一定连续存储。
一般的,链表可分为带头节点的和不带头节点的,具体使用要根据实际情况来分析。链表的逻辑结构如下:
二、链表的使用
1、首先需要定义一个链表结构
class Node {
/*
这里的data域值一系列想要存储的数据,一个节点可以存储多个数据,这里只写了一个data,可根据需要定义多个data
例如:
public int data1;
public int data2;
public String data3;
public Node next;
*/
//这里为了快速实现就将变量定义为public
public int data;
public int data1;
public Node next;
//空参构造器
public Node() {
}
//有参构造器
public Node(int data,int data1) {
this.data = data;
this.data1 = data1;
}
//重写toString()方法
@Override
public String toString() {
return "Node{" + "data=" + data + ", data1=" + data1 + '}';
}
}
2、添加增删改查基本操作,这里我们再定义一个类来实现
class SingleLinkedList2 {
//初始化一个头节点,不存放具体数据
private Node head = new Node();
//添加节点到单向链表,这里默认data唯一,不可重复添加
public void add(Node node) {
//辅助变量,用于遍历
Node temp = head;
boolean flag = false;
//遍历链表,找到最后一个节点
while (true) {
//找到链表最后
if (temp.next == null) {
break;
}
if (temp.next.data == node.data) {
flag = true;
break;
}
//如果没有找到,就将temp后移
temp = temp.next;
}
if (flag) {
System.out.printf("节点发%d已经存在\n",node.data);
} else {
//当退出while循环时,temp就指向链表的最后
temp.next = node;
}
}
//按插入大小排序,这里默认data唯一,不可重复添加
public void addByOrder(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {//说明temp已经到链表最后
break;
}
if (temp.next.data > node.data) {//位置找到,在temp后面添加
break;
} else if (temp.next.data == node.data) {
flag = true;
break;
}
temp = temp.next;//后移
}
//判断flag的值
if (flag) {//已存在,添加失败
System.out.println("该英雄已经存在,不能重复添加");
} else {
node.next = temp.next;
temp.next = node;
}
}
//删除节点
public void delete(int no) {
//辅助变量,用于遍历
Node temp = head;
//查找节点标志,fales表示未找到要删除的节点,true表示找到要删除的节点
boolean flag = false;
//遍历
while (true) {
//找到链表最后
if (temp.next == null) {
break;
}
//找到要删除的节点
if (no == temp.next.data) {
flag = true;
break;
}
//如果没有找到,就将temp后移
temp = temp.next;
}
if (flag) {//说明已经找到要删除的节点
temp.next = temp.next.next;
} else {
System.out.println("未找到该英雄");
}
}
//修改链表
public void update(Node node) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
//定义辅助变量
Node temp = head.next;
boolean flag = false;
//遍历
while (true) {
//找到链表最后
if (temp.next == null) {
break;
}
//找到要修改的节点
if (temp.data == node.data) {
flag = true;
break;
}
//如果没有找到,就将temp后移
temp = temp.next;
}
if (flag) {
temp.data1 = node.data1;
} else {
System.out.println("未找到该英雄");
}
}
//显示链表
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//使用辅助变量遍历
Node temp = head.next;
while (true) {
//判断是否到最后
if (temp == null) {
break;
}
//输出节点的信息
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
}
3、写一个Demo来进行测试
public class SingleLinkedListExer {
public static void main(String[] args) {
SingleLinkedList2 list = new SingleLinkedList2();
Node node1 = new Node(1,10);
Node node2 = new Node(2,20);
Node node3 = new Node(3,30);
Node node4 = new Node(4,40);
// 按自定义顺序进行添加
// list.add(node1);
// list.add(node3);
// list.add(node2);
// list.add(node4);
// System.out.println("链表为:");
// list.list();
//按data大小进行排序添加
list.addByOrder(node1);
list.addByOrder(node3);
list.addByOrder(node2);
list.addByOrder(node4);
System.out.println("原来链表为:");
list.list();
Node node5 = new Node(3,50);
list.update(node5);
System.out.println("修改后链表为:");
list.list();
list.delete(2);
System.out.println("删除后链表为:");
list.list();
}
}
测试结果:
三、常用操作
这里要用到head节点,所以我们在SingleLinkedList2类中加上:
public Node getHead() {
return head;
}
来获取头节点
1、求单链表中有效节点的个数
这里没有另外定义类来写,直接写到public类的方法里,所以使用static修饰,下同。
/**
*获取单链表有效节点的个数(如果带头节点,需不计头节点)
* @param head 链表的头节点
* @return 有效节点的个数
*/
public static int getLength(Node head) {
if (head.next == null) {
return 0;
}
int length = 0;
Node cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
2、查找单链表中的倒数第k个节点
/**
*
* @param head 头节点
* @param index 倒数第index个
* @return 倒数第index个节点
*/
public static Node getIndex(Node head, int index) {
if (head.next == null) {
return null;
}
int size = getLength(head);
if (index <= 0 || index > size) {
return null;
}
Node temp = head.next;
for (int i = 0; i < size - index; i++) {
temp = temp.next;
}
return temp;
}
3、单链表的反转
/**
* 将单链表反转
* @param head
*/
public static void reverseLinkedList(Node head) {
if (head.next == null || head.next.next == null) {//为空或只有一个节点
return;
}
//辅助节点,用来遍历原来的链表
Node cur = head.next;
Node next = null;//指向cur的下一个节点
Node reverseHead = new Node();
//遍历原来的链表
while (cur != null) {
next = cur.next;//暂时保存当前节点的下一个节点
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的头部
reverseHead.next = cur;
cur = next;
}
head.next = reverseHead.next;
}
4、逆序打印单链表
/**
* 逆序打印单链表
* @param head
*/
public static void reversePoint(Node head) {
if (head.next == null) {
return;
}
Stack<Node> stack = new Stack<>();
Node cur = head.next;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
//打印栈中节点
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
5、测试方法
public class SingleLinkedListExer {
public static void main(String[] args) {
SingleLinkedList2 list = new SingleLinkedList2();
Node node1 = new Node(1,10);
Node node2 = new Node(2,20);
Node node3 = new Node(3,30);
Node node4 = new Node(4,40);
list.addByOrder(node1);
list.addByOrder(node3);
list.addByOrder(node2);
list.addByOrder(node4);
System.out.println("原来链表为:");
list.list();
System.out.println("有效节点个数为:" + getLength(list.getHead()));
int index = 2;
System.out.printf("倒数第%d个节点是:%s\n",index,getIndex(list.getHead(), index));
reverseLinkedList(list.getHead());
System.out.println("反转后的链表为:");
list.list();
System.out.println("逆序打印链表:");
reversePoint(list.getHead());
}