一.单链表
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
结点结构
①数据域,②指针域
单链表就可以看作是多个结点相连
二.开始创建链表
1.创建结点类
/**
* @author Watching
* * @date 2022/10/24
* * Describe:单链表
* <p>
* 使用单链表存储水浒传108将
*/
public class SinglyLinkedListDemo {
//节点类
class Node {
public int number;//序号 数据域
public String name;//姓名 数据域
public String nickName;//昵称 数据域
public Node next;//next域 指向下一个节点 指针域
public Node() {
}
//构造器初始化节点
public Node(int no, String name, String nickName) {
this.name = name;
this.number = no;
this.nickName = nickName;
this.next = null;
}
@Override
public String toString() {
return "Node{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
2.创建链表类
/**
* @author Watching
* * @date 2022/10/24
* * Describe:单链表
* <p>
* 使用单链表存储水浒传108将
*/
public class SinglyLinkedListDemo {
//链表类
class SinglyLinkedList {
private Node node;
//头节点,不存放具体数据,只是单链表表头
Node head = new Node(0, "", "");
}
//节点类
class Node {
public int number;//序号
public String name;//姓名
public String nickName;//昵称
public Node next;//next域 指向下一个节点
public Node() {
}
//构造器初始化节点
public Node(int no, String name, String nickName) {
this.name = name;
this.number = no;
this.nickName = nickName;
this.next = null;
}
@Override
public String toString() {
return "Node{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
}
此时链表就已经创建完成了,现在我们需要给链表类编写增加节点,删除节点,修改节点,查找节点方法
2.1 添加节点
/**
* 添加节点(尾插法)
*
* @param node
*/
public void add(Node node) {
Node temp = head;//temp用于遍历链表
//找到链表的最后一个节点
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;//将新的节点添加到末尾
//TODO 为什么temp后面添加node会等于head后面添加node
//temp并不是一个新的节点,temp只是一个引用,它指向了head,所以在后面temp就是head
}
2.2 删除节点
/**
*从单链表中删除一个节点的思路
* 1.我们先找到需要删除的这个节点的前一个节点temp
* 2.删除操作:temp.next = temp.next.next
* 3.被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
*/
public void delete(int number){
Node temp = head;
while(true){
if(temp.next==null){
System.out.println("链表中没有对应的节点");
break;
}
if(temp.next.number == number){
temp.next = temp.next.next;
System.out.println("已删除:"+number);
break;
}
temp = temp.next;
}
}
2.3 修改节点
/**
* 根据id修改节点信息
*/
public void update(Node node){
Node temp = head;
boolean flag = false;
while(true){
if(temp.next==null){
System.out.println("未找到对应节点");
break;
}
if(temp.next.number == node.number){
temp.next.nickName = node.nickName;
temp.next.name = node.name;
flag = true;
System.out.println("修改成功:"+node.number);
break;
}
temp = temp.next;
}
if(!flag){
System.out.println("链表中没有对应的节点");
}
}
此外 还有按照顺序添加结点,可以通过代码理解一下
2.4 按照结点number的大小顺序添加结点
/**
* 按照顺序添加节点
*/
public void addByOrder(Node node) {
Node temp = head;//创建变量指向头节点
while (true) {
if (temp.next == null) {//如果已经到了链表末尾,则直接添加
temp.next = node;
break;
}
if (temp.next.number > node.number) {//如果遍历到的节点的后一个节点的number大于新添加的节点的number,则将新添加的number插入到其中
node.next = temp.next;
temp.next = node;
break;
}
if(temp.next.number == node.number){//如果已经有和新添加的节点一样的number则取消添加
System.out.println("节点已存在:"+ node.number);
break;
}
temp= temp.next;//遍历节点
}
}
2.5 反转链表
思路:
1.先定义一个节点reverseHead = new Node();
2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最后端
3.原来的链表的head.next =reverseHead.next
/**
* 反转链表
*
* @param head 被反转链表的头结点
* @return 思路:
*
* 1.先定义一个节点reverseHead = new Node();
* 2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最后端
* 3.原来的链表的head.next =reverseHead.next
*/
public Node reverse(Node head) {
if (head.next == null) {
System.out.println("链表为空");
}
Node reverseHead = new Node();
Node temp = head;
//获取有效结点个数,有多少个有效节点,就需要从原head移动结点到新reverseHead结点多少次
int num = 0;
while (temp.next != null) {
num++;
temp = temp.next;
}
int num1 = num;//num1用于遍历到head的最后一个结点
for (int k = 0; k < num; k++) {
temp = head;
//遍历到head的最后一个结点
for (int i = 0; i < num1; i++) {
temp = temp.next;
}
//将最后一个结点头插法到新的头结点reverseHead后面
//如果新的头结点后面无数据,则直接插入
if (reverseHead.next == null) {
reverseHead.next = temp;
num1--;
continue;
}
//如果新的头结点后面有数据,则插入末尾
Node t = reverseHead;
int l = k;
while (l != 0) {
t = t.next;
l--;
}
temp.next = null;
t.next = temp;
num1--;
}
head.next = reverseHead.next;
System.out.println("反转完成");
return head;
}
2.6 查找单链表中的倒数第k个结点
思路:
1.获取链表中有效结点个数
2.通过有效个数-倒数第k个+1=目标结点的正序序号 如 10个结点,倒数第2个即为9,10-2+1=9
3.遍历链表,获取目标节点
/**
* 查找单链表中的倒数第k个结点
*
* @param head 头结点
* @param k 倒数第k个
* @return 实现思路:
* ①获取链表中有效结点个数
* ②通过有效个数-倒数第k个+1=目标结点的正序序号 如 10个结点,倒数第2个即为9,10-2+1=9
* ③遍历链表,获取目标节点
*/
public Node getLastNode(Node head, int k) {
Node temp = head;
int i = 0;
//1.获取链表中有效结点个数
while (temp.next != null) {
temp = temp.next;
i++;
}
//2.获取目标节点的正序序号
int o = i - k + 1;
//3.遍历链表,获取目标节点
temp = head;
for (int j = 0; j < o; j++) {
temp = temp.next;
}
System.out.println(temp);
return temp;
}
三.完整代码
/**
* @author Watching
* * @date 2022/10/24
* * Describe:单链表
* <p>
* 使用单链表存储水浒传108将
*/
public class SinglyLinkedListDemo {
public static void main(String[] args) {
Node hero1 = new Node(1, "宋江", "及时雨");
Node hero2 = new Node(2, "卢俊义", "玉麒麟");
Node hero3 = new Node(3, "吴用", "智多星");
Node hero4 = new Node(4, "林冲", "豹子头");
SinglyLinkedList singlyLinkedList = new SinglyLinkedList();
// singlyLinkedList.add(hero1);
// singlyLinkedList.add(hero3);
// singlyLinkedList.add(hero2);
// singlyLinkedList.add(hero4);
singlyLinkedList.addByOrder(hero1);
singlyLinkedList.addByOrder(hero3);
singlyLinkedList.addByOrder(hero2);
singlyLinkedList.addByOrder(hero4);
singlyLinkedList.showList();
Node hero = new Node(4, "林冲~~", "豹子头~~");
singlyLinkedList.update(hero);
singlyLinkedList.showList();
// singlyLinkedList.delete(1);
// singlyLinkedList.delete(2);
// singlyLinkedList.delete(3);
// singlyLinkedList.delete(4);
// singlyLinkedList.showList();
System.out.println("获取第倒数第k个结点");
singlyLinkedList.getLastNode(singlyLinkedList.head, 1);
System.out.println("反转链表");
singlyLinkedList.reverse(singlyLinkedList.head);
singlyLinkedList.showList();
}
}
//链表类
class SinglyLinkedList {
private Node node;
//头节点,不存放具体数据,只是单链表表头
Node head = new Node(0, "", "");
/**
* 添加节点(尾插法)
*
* @param node
*/
public void add(Node node) {
Node temp = head;//temp用于遍历链表
//找到链表的最后一个节点
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;//将新的节点添加到末尾
//TODO 为什么temp后面添加node会等于head后面添加node
//temp并不是一个新的节点,temp只是一个引用,它指向了head,所以在后面temp就是head
}
/**
* 添加结点,头插法
*/
public void HeadAdd(Node node) {
//如果链表为null,则直接添加新结点到head结点后面
if (head.next == null) {
head.next = node;
return;
}
//如果链表不为null,则将新结点添加到头结点后面,其余结点前面
node.next = head.next;
head.next = node;
}
/**
* 按照顺序添加节点
*/
public void addByOrder(Node node) {
Node temp = head;//创建变量指向头节点
while (true) {
if (temp.next == null) {//如果已经到了链表末尾,则直接添加
temp.next = node;
break;
}
if (temp.next.number > node.number) {//如果遍历到的节点的后一个节点的number大于新添加的节点的number,则将新添加的number插入到其中
node.next = temp.next;
temp.next = node;
break;
}
if (temp.next.number == node.number) {//如果已经有和新添加的节点一样的number则取消添加
System.out.println("节点已存在:" + node.number);
break;
}
temp = temp.next;//遍历节点
}
}
/**
* 根据id修改节点信息
*/
public void update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
System.out.println("未找到对应节点");
break;
}
if (temp.next.number == node.number) {
temp.next.nickName = node.nickName;
temp.next.name = node.name;
flag = true;
System.out.println("修改成功:" + node.number);
break;
}
temp = temp.next;
}
if (!flag) {
System.out.println("链表中没有对应的节点");
}
}
/**
* 从单链表中删除一个节点的思路
* 1.我们先找到需要删除的这个节点的前一个节点temp
* 2.删除操作:temp.next = temp.next.next
* 3.被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
*/
public void delete(int number) {
Node temp = head;
while (true) {
if (temp.next == null) {
System.out.println("链表中没有对应的节点");
break;
}
if (temp.next.number == number) {
temp.next = temp.next.next;
System.out.println("已删除:" + number);
break;
}
temp = temp.next;
}
}
/**
* 展示链表
*/
public void showList() {
Node temp = head;
while (temp.next != null) {
System.out.println(temp.next);
temp = temp.next;
}
}
/**
* 查找单链表中的倒数第k个结点
*
* @param head 头结点
* @param k 倒数第k个
* @return 实现思路:
* ①获取链表中有效结点个数
* ②通过有效个数-倒数第k个+1=目标结点的正序序号 如 10个结点,倒数第2个即为9,10-2+1=9
* ③遍历链表,获取目标节点
*/
public Node getLastNode(Node head, int k) {
Node temp = head;
int i = 0;
//1.获取链表中有效结点个数
while (temp.next != null) {
temp = temp.next;
i++;
}
//2.获取目标节点的正序序号
int o = i - k + 1;
//3.遍历链表,获取目标节点
temp = head;
for (int j = 0; j < o; j++) {
temp = temp.next;
}
System.out.println(temp);
return temp;
}
/**
* 反转链表
*
* @param head 被反转链表的头结点
* @return 思路:
*
* 1.先定义一个节点reverseHead = new Node();
* 2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最后端
* 3.原来的链表的head.next =reverseHead.next
*/
public Node reverse(Node head) {
if (head.next == null) {
System.out.println("链表为空");
}
Node reverseHead = new Node();
Node temp = head;
//获取有效结点个数,有多少个有效节点,就需要从原head移动结点到新reverseHead结点多少次
int num = 0;
while (temp.next != null) {
num++;
temp = temp.next;
}
int num1 = num;//num1用于遍历到head的最后一个结点
for (int k = 0; k < num; k++) {
temp = head;
//遍历到head的最后一个结点
for (int i = 0; i < num1; i++) {
temp = temp.next;
}
//将最后一个结点头插法到新的头结点reverseHead后面
//如果新的头结点后面无数据,则直接插入
if (reverseHead.next == null) {
reverseHead.next = temp;
num1--;
continue;
}
//如果新的头结点后面有数据,则插入末尾
Node t = reverseHead;
int l = k;
while (l != 0) {
t = t.next;
l--;
}
temp.next = null;
t.next = temp;
num1--;
}
head.next = reverseHead.next;
System.out.println("反转完成");
return head;
}
}
//节点类
class Node {
public int number;//序号
public String name;//姓名
public String nickName;//昵称
public Node next;//next域 指向下一个节点
public Node() {
}
//构造器初始化节点
public Node(int no, String name, String nickName) {
this.name = name;
this.number = no;
this.nickName = nickName;
this.next = null;
}
@Override
public String toString() {
return "Node{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}