目录
1.什么是链表
2.单链表的基本结构
3.单链表的增删改查等功能
4.总结
一.什么是链表
在java中链表是一种数据结构,与数组连续的存储不同,链表在内存中是一种非连续的存储。
通过引用相互连接。
链表相较于其他数据结构,最大的优势是删除和添加节点(数据)比较方便,尤其是在需要频繁进行这些操作的场景中。
二.单链表的基本结构
如图:(带头节点的单链表)

链表是由一个一个的节点连接构成的一组线性结构,每一个节点是一个Node类,Node中有一个Node next 来连接下一个节点,尾节点的next=null;
代码:
class Node{
public int no; //编号
public String name; //姓名
public String nickname; //昵称
public Node next; //引用
//构造器(可以重载你需要的构造器)
public Node(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//toString方法,便于之后能够打印Node
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
根据上述代码链表最基本的结构已经完成。
三.单链表的增删改查
因为我所讲述的都是带头结点的单链表,所以先初始化一个头节点。
//初始化一个头节点
private Node head = new Node(0,"","");
此时我们有仅一个节点的链表
如图:

1.增加新节点
在单链表方法类中定义一个将数据添加到链表的方法。
主要步骤是:参数传来新节点,设置一个辅助指针指向头节点。然后遍历到尾节点,让为节点的next指向你要添加的节点。
如图:

代码如下:
public void add(Node newNode){
Node temp = head; //设置一个辅助指针指向头节点
while(true){
if (temp.next==null){ //遍历到temp.next为null时退出循环
break;
}
temp = temp.next; //辅助指针向后移动
}
//将最后这个节点的next指向新的节点
temp.next = newNode;
}
2.有序增加新节点
上述增加新节点是将新节点加入到链表末尾,当出现必须有序的需求时,比如Node中有int no这个变量,当需要no值有序插入,则可以换一种添加新节点的逻辑。
主要逻辑如下:
(1)遍历找到temp.next.no>newNode.no的那个节点,此时temp是需要插入位置的前一个节点。
当然还需要考虑没有比newNode.no还大的值,和newNode.no已经存在的情况。
(2)让newNode.next = temp.next; temp.next = newNode.next;这样就实现了插入。

代码:
//有序插入节点,插入节点数据不能重复
public void addByOrder(Node newNode){
//因为头节点不能动,我们通过辅助指针来找到节点
//单链表,temp是位于要找节点的前一个位置
Node temp = head;
boolean flag = false; //确认链表中是否存在要添加的顺序
while (true){
if (temp.next == null){
break;
}
if (temp.next.no > newNode.no){
//位置找到了,应该插入到temp后面
break;
}else if (temp.next.no == newNode.no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){//编号存在不能添加
System.out.printf("准备插入的节点编号%d已经存在,不能加入\n",newNode.no);
}else {
//插入到链表中,temp的后面(顺序不能调换)
newNode.next = temp.next;
temp.next = newNode;
}
}
2.修改节点数据
主要逻辑:通过一个辅助指针遍历找到newNode.no在链表中的编号,然后通过指针来修改该节点的信息。

代码:
//修改节点信息
public void update(Node newNode){
//判空
if (head.next == null){
System.out.println("链表为空~");
return;
}
//找到要修改的节点,需要no编号
//定义一个辅助变量
Node temp = head.next;
boolean flag = false;
while (true){
if (temp == null){
break;
}
if (temp.no == newNode.no){
flag = true;
break;
}
temp = temp.next;
}
//根据flag,判断是否找到要修改的节点
if (flag){
temp.name = newNode.name;
temp.nickname = newNode.nickname;
}else {
System.out.printf("没有找到编号%d的节点\n",newNode.no);
}
}
3.删除节点
主要逻辑:传入一个编号参数,遍历找到该参数的前一节点,然后让temp.next = temp.next.next;这样要删除的节点就从链表中断掉。
如图:

此时no=3的节点的next仍然指向no=4的节点,但是因为没有头节点无法遍历到该节点,说明该节点失效了。
这也是为什么要频繁使用辅助指针来遍历而不直接使用头指针,因为头节点不能改变,它是遍历的起点,如果头节点丢失,链表也就丢失。
代码如下:
//删除指定节点
public void delete(int no){
boolean flag = false;
Node temp = head;
while (true){
if (temp.next == null){
break;
}if (temp.next.no == no){
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
}else {
System.out.printf("没有找到编号为%d的节点\n",no);
}
}
4.查找某个节点
主要逻辑:传入你要查找的编号,根据编号遍历找到节点,然后输出对应节点的信息。
代码如下:
//查找
public void search(int no){
boolean flag = false;
Node temp = head;
while (true){
if (temp.next == null){
break;
}if (temp.next.no == no){
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("编号为%d的节点的姓名%s昵称%s\n",no,temp.name,temp.nickname);
}else {
System.out.printf("没有找到编号为%d的节点\n",no);
}
}
查找倒数第K个节点:
主要逻辑:先求出链表总长L,因此正数L-K次便是倒数第K个节点。
首先写出求链表总长的方法:
//方法:获取到单链表节点的个数
public static int getLength(Node head){
int Length = 0;
if (head.next == null){
Length = 0;
}
//定义一个辅助指针
Node cur = head.next;
while (cur != null){
length++;
//遍历
cur = cur.next;
}
return Length;
}
下面是查找倒数第k个节点的代码:
//找到倒数第K个节点
public Node searchK(Node head,int k) {
int length = SingleLinkedList.getLength(head); //SingleLinkedList是保存方法的类
if (k >= length || k <= 0) {
return null;
}
//设置辅助指针
Node temp = head;
//遍历次数
int count = length-k;
while (true){
if (count==0){
break;
}
temp = temp.next;
count--;
}
return temp;
}
5.链表翻转
主要思想:这段代码采用头插法,首先创建一个新的链表头节点,然后遍历原来链表的每一个节点,摘下来后根据头插法插入到新的节点上,就实现了链表翻转。
头插法特点:每次插入表头的后面。

根据这个方法,我们可以将原来链表一个个取出来,每次取出来放在头节点之后,原链表最后一个取出来的便是新链表的第一个,因此新链表就是原链表的逆序链表。

代码如下:
//链表反转
public void reverseLinked(Node head){
if (head.next==null||head.next.next==null){
return;
}
//创建一个新的头节点
Node endNode = new Node(0,"","");
//设置辅助指针遍历用于遍历
HeroNode temp = head.next;
//用于保存当前节点的下一个节点引用
HeroNode next = null;
while (temp!=null){
next = temp.next; //保存当前节点的下一节点
temp.next = endNode.next; //将当前节点的next指向新链表的头部的next
endNode.next = temp; //新链表头节点指向当前节点
temp = next; //辅助指针指向后一节点
}
head.next = endNode.next; //将原链表头节点下一节点指向新链表的头节点后一个
}
6.普通打印链表
基本原理:遍历到temp.next==null即可退出,每次以此输出temp指向的节点。
代码如下:
//显示链表
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); //因为重写了toString方法
//后移
temp = temp.next;
}
}
7.逆序打印
基本原理:逆序打印可以先实现逆序翻转再遍历打印,但是这样就改变了链表的结构,可以通过栈的特性:先进后出原则,先将链表压入一个栈中,再一个一个出栈输出即可实现逆序打印。
如图:

出栈的顺序是从上面出,依次向下。所以出栈的顺序是cba,进栈顺序是abc,实现了逆序。
代码如下:
//利用栈将各个节点压入栈中,然后利用栈的先进后出特点,实现逆序打印的效果
public static void reversePrint(Node head){
if (head.next==null){
System.out.println("空链表不能打印");
return;
}
//创建一个栈,将各个节点压入栈中
Stack<Node> stack = new Stack<>();
Node cur = head.next;
while (cur!=null){
stack.push(cur);//将cur指向的节点进栈
cur = cur.next;//让cur后移,能够压入下一个节点
}
while (stack.size()>0){
System.out.println(stack.pop()); //循环出栈,直到栈空
}
}
}
8.合并两个链表
如图:

根据所给的信息我们提出解题思路:
设置一个新的头节点,分别设置一个赋值节点current给这个新头节点,然后循环遍历比较值的大小,小的则连接到新头节点后面并且向后走一位,直到有一个链表为空。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode list = new ListNode(0); //设置新头节点
ListNode current = list; //新头节点的的辅助指针
while(list1!=null&&list2!=null){
if(list1.val>=list2.val){ //比较两个链表的值大小
current.next = list2; //list2比较小则连接到新链表上
list2 = list2.next; //list2向后移动
}else{
current.next = list1; //同理
list1 = list1.next;
}
current = current.next;
}
if(list1!=null){ //如果list2先遍历完,则list1剩下连接在后面
current.next=list1;
}else{ //同理
current.next=list2;
}
return list.next; //返回新链表
}
}
四:总结
根据链表的特性和链表的增删改查,想要熟练运用链表,就是要熟练修改引用(指针)的指向。当然遇到比如让链表逆序和逆序打印(不改变初始结构时),有更便捷的方法,头插法和压栈法。总体来说最重要的还是熟练掌握引用的修改以灵活使用链表。
1680

被折叠的 条评论
为什么被折叠?



