1、链表(Linked List)介绍
- 链表是以接地那的方式来存储的
- 每个结点包含 data域(存放数据),next域(指向下一个节点)
- 链表的各个结点不一定是连续存储
- 链表分带头节点的链表和不带头节点的链表
2、单链表的创建
package linkedlist;
public class SingleLinkedListDemo{
public static void main(String[] args) {
//测试
//创建节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(1, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(1, "吴用", "智多星");
HeroNode hero4 = new HeroNode(1, "林冲", "豹子头");
//添加节点到链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.showList();
}
}
//定义SingleLinkedList 管理英雄
class SingleLinkedList {
//先初始化一个头结点
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
public void add(HeroNode heroNode){
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向新的节点
//因为head节点不能动,因此需要一个辅助变量 temp
//辅助变量指向指针
HeroNode temp = head;
//遍历链表,找到最后
while(true){
//找到链表的最后
if(temp.next == null){
break;
}
//如果没有找到最后,将temp后移
temp = temp.next;
}
//将最后这个节点指向新的节点
temp.next = heroNode;
}
//显示链表(遍历链表)
public void showList(){
//判断链表是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,所以需要一个辅助变量
HeroNode temp = head.next;
while(true){
//判断是否到链表的最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp.toString());
//将temp后移
temp = temp.next;
}
}
}
//定义HeroNode
class HeroNode{
public int no;
public String name;
public String nickname;
public HeroNode next; //指向下一个节点
//构造器
public HeroNode(int no,String name,String nickname){
this.no = no;
this.name =name;
this.nickname = nickname;
}
//重写toString()
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
3、按照编号的顺序添加
- 首先找到新添加的节点的位置,通过辅助变量(指针)
- 新的节点.next = temp.next
- temp.next = 新的节点
//按编号顺序添加节点
public void addByOrder(HeroNode heroNode){
//头结点不能动,需要辅助变量
//找的temp是位于添加位置的前一个节点否则添加不了
HeroNode temp = head;
boolean flag = false; //flag标志添加的编号是否存在,默认为false
while (true){
if (temp.next == null){ //temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no){ //新结点插入位置temp 和 temp.next 之间
break;
}else if(temp.next.no == heroNode.no){ //添加的新节点的编号已经存在
flag = true;
break;
}
temp = temp.next; //后移
}
if (flag){ //flog == true 编号已经存在 不能添加
System.out.println("准备插入英雄的编号已经存在");
}else{
//新节点插入到链表中
heroNode.next = temp.next;
temp.next = heroNode;
}
}
4、修改指定节点信息
//修改节点信息
public void update(HeroNode newHeroNode){
//判断链表是否为空
if (head.next ==null){
System.out.println("链表为空");
return;
}
//找到需要修改的结点,根据no编号
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false; //是否找到该节点
while (true){
if (temp == null){
break; //已经遍历完链表
}
if(temp.no == newHeroNode.no){ //找到需要修改的节点
flag = true;
break;
}
temp = temp.next; //指向下一个节点
}
//根据flag判断是否找到结点
if (flag){
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
}else{
//没有找到
System.out.println("没有找到编号为"+newHeroNode.no+"的结点,不能修改");
}
}
5、删除单链表一个节点
- 先找到需要删除的前一个结点
- temp.next = temp.next.next
- 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收
//删除节点
public void delete(int no){
//判断链表是否为空
if(head.next == null ){
System.out.println("链表为空");
return;
}
//辅助变量
HeroNode temp = head;
boolean flag = false; //是否找到该节点
while (true){
if (temp.next == null){ //已经遍历完链表
break;
}
if (temp.next.no == no){ //找到节点
flag = true;
break;
}
temp = temp.next; //temp后移
}
//根据flag判断是否找到结点
if (flag){
temp.next = temp.next.next; //删除temp.next结点
}else{
//没有找到
System.out.println("没有找到编号为"+no+"的结点,不能删除");
}
}
6、单链表的反转
//单链表的反转
public static void reversal(HeroNode head){
//如果当前单链表为空,或者只有一个节点,无需反转,直接返回
if (head.next == null ||head.next.next == null){
return;
}
//定义一个辅助变量
HeroNode temp = head.next;
HeroNode next = null; //指向当前节点temp的下一个节点
HeroNode reversalHead = new HeroNode(0,"","");
//遍历原来的节点,每遍历一个节点,就将其取出,并放在新的链表reversalHead的最前端
while (temp != null){
next = temp.next;
temp.next = reversalHead.next;
reversalHead.next = temp;
temp = next;
}
//将head.next指向reversalHead.next,实现单链
head.next = reversalHead.next;
}
7、双向链表
- 单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找
- 单向链表不能自我删除,需要靠辅助节点,而双向链表可以自我删除
- 遍历和单向链表一样,只是可以向前,也可以向后查找
- 添加(添加到双向链表的最后)
- 先找到双向链表的最后一个节点
- temp.next = newNode
- newNode.pre = temp
- 修改和单向链表一样
- 删除
- 找到需要删除的节点temp
- temp.pre.next = temp.next
- temp.next.pre = temp.pre
package linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
//测试
System.out.println("********双向链表的测试********");
//创建节点
Node node01 = new Node(1, "cml01", "youmo01");
Node node02 = new Node(2, "cml02", "youmo02");
Node node03 = new Node(3, "cml03", "youmo03");
Node node04 = new Node(4, "cml04", "youmo04");
//创建双向链表并添加节点
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(node01);
doubleLinkedList.add(node02);
doubleLinkedList.add(node03);
doubleLinkedList.add(node04);
//遍历链表
System.out.println("********遍历双向链表********");
doubleLinkedList.list();
//修改节点
System.out.println("********修改节点********");
Node updateNode = new Node(4,"updateNode","updateNode");
doubleLinkedList.update(updateNode);
doubleLinkedList.list();
//删除节点
System.out.println("********删除节点********");
doubleLinkedList.delete(4);
doubleLinkedList.list();
}
}
//定义双链表
class DoubleLinkedList{
//初始化头结点
private Node head = new Node(0,"","");
//遍历双向链表
public void list(){
//判断链表是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}
//定义辅助变量temp
Node temp = head.next;
while(true){
//判断是否到链表最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp.toString());
//temp 后移
temp = temp.next;
}
}
//添加节点到链表最后
public void add(Node node){
//定义辅助变量
Node temp = head;
//遍历链表
while (true){
if (temp.next == null){ //遍历到链表最后
break;
}
//temp向后移
temp = temp.next;
}
//添加新的节点
temp.next = node;
node.pre = temp;
}
//修改节点的内容
public void update(Node node){
//判断链表是否为空
if (head.next == null){
return;
}
//定义辅助变量temp
Node temp = head.next;
boolean flag = false; //表示是否找到需要修改的节点
while (true){
if (temp == null){
return;
}
if (temp.no == node.no){
flag=true;
break;
}
//temp后移
temp = temp.next;
}
//根据flag判断是否找到需要修改的节点
if (flag){ //找到
temp.name = node.name;
temp.nickname = node.name;
}else{ //没有找到
System.out.println("没有找到需要修改的结点"+node.no);
}
}
//删除节点
public void delete(int no){
//判断链表是否为空
if (head.next ==null) {
System.out.println("链表为空");
return;
}
//定义辅助变量
Node temp = head.next;
boolean flag = false; //表示是否找到需要删除的节点
while (true){
if (temp == null){ //链表遍历到最后
break;
}
if(temp.no == no){ //找到需要删除的节点
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到需要删除的节点
if (flag){ //找到
//删除节点temp
temp.pre.next = temp.next;
//如果是最后一个节点,不需要执行下面一步操作
if (temp.next != null){
temp.next.pre = temp.pre;
}
}else{
System.out.println("没有找到节点"+no);
}
}
}
//定义Node节点
class Node{
public int no;
public String name;
public String nickname;
public Node pre; //指向前一个节点,默认为null
public Node next; //指向后一个节点,默认为null
//构造器
public Node(int no,String name,String nickname){
this.no = no;
this.name =name;
this.nickname = nickname;
}
//重写toString()
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
8、单向环形链表(约瑟夫问题)
- 单向环形链表应用场景
- Josephu(约瑟夫、约瑟夫环)问题
- Josephu 问题为:设编号为 1,2,... n 的 n 个人围坐一圈,约定编号为 k(1 <= k <= n)的人从 1 开始报数,数到 m 的那个人出列,他的下一为又从 1 开始报数,数到 m 的那个人又出列,一次类推,直到所有人出列为止,由此产生一个出队编号的序列
- 用一个不带头节点的循环链表来处理Josephu问题,先构成一个由n个节点的单循环链表,然后由 k 节点开始从 1 开始计数,记到 m 时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从 1 开始计数,直到最后一个节点从链表中删除算法结束
- 构造单向环形链表思路
- 先创建第一个节点,让frist指向该结点
- 当我们每创建一个新的节点,就把该结点加入到已有的环形链表中即可
- 遍历环形链表
- 先让一个辅助变量 temp 指向frist结点
- 通过while循环遍历该环形链表
- 当 temp.next = frist 遍历结束
package linkedlist;
public class Josephu {
public static void main(String[] args) {
//测试
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5); //加入5个小孩节点
circleSingleLinkedList.list();
circleSingleLinkedList.countBoy(1,2,5);
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点,为空
public Boy first = null;
//添加节点,构成一个环形的链表 num 小孩节点个数
public void addBoy(int num){
if (num < 1){
System.out.println("num < 1 ");
return;
}
//创建赋值变量temp
Boy temp = null;
for (int i=1;i<=num;i++){
//根据编号创建小孩节点
Boy boy = new Boy(i);
//如果是第一个小孩
if(i==1){
first = boy;
first.next = first; //first next 指向自己构成环状
temp = first; //让temp变量指向first
}else {
temp.next = boy; //temp 指向 新节点
boy.next = first; //新节点指向first节点 形成环状
temp = temp.next; //temp 后移
}
}
}
//遍历当前环形链表
public void list(){
//判断链表是否为空
if (first == null){
System.out.println("链表为空");
return;
}
//创建赋值变量temp
Boy temp = first;
while (true){
System.out.println(temp.no);
if(temp.next == first){ //已经遍历完成
break;
}
temp = temp.next;
}
}
//根据用户的输入,计算出小孩出圈的顺序
/**
* @param startNo 表示从第几个小孩可是数数
* @param countNum 表示数几下
* @param num 表示最初有多少小孩在圈中
* */
public void countBoy(int startNo,int countNum,int num){
//先对数据进行校验
if (first == null ||startNo <1 || startNo >num){
System.out.println("输入有误");
return;
}
//创建辅助变量temp,指向最后一个节点
Boy temp = first;
while (true){
if (temp.next == first){
break;
}
temp = temp.next;
}
//小孩报数前,先让first 和 temp 移动 startNo-1 次
//使first指向开始数数的孩子节点,temp指向开始数数的孩子节点的上一个节点
for (int i = 0;i<startNo -1;i++){
first = first.next;
temp = temp.next;
}
while (true){
if (temp == first){ //说明圈中只有一个孩子节点
break;
}
//让first 和 temp 指针同时移动 countNum - 1
for (int i= 0;i< countNum -1;i++){
first = first.next;
temp = temp.next;
}
//这时first指向的节点就是出圈的小孩节点
System.out.println("小孩"+first.no+"出圈");
//小孩出圈,删除该小孩节点
first = first.next;
temp.next = first;
}
System.out.println("最后留在圈中的小孩:"+temp.no);
}
}
//创建Boy节点类
class Boy{
public int no; //编号
public Boy next; //指向下一个节点默认null
public Boy(int no){
this.no = no;
}
}