单链表的操作:逻辑控件是连续的,实际的内存非陪并不是连续的
使用带head头的单向链表实现 - 水浒英雄排行榜管理完成对英雄人物的增删改查操作,注:删除和修改,查找可以考虑学员独立完成,也可带学员完成
//定义HeroNode,每个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;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
//定义SingleLinkedList 管理我们的水浒英雄
class SingleLinkedList {
//定义头节点,头节点的位置不可改变,不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//返回头节点
public HeroNode getHead() {
return head;
}
//添加节点到单项链表
//思路,当我们不考虑编号时
//1.找到当前链表的最后节点
//2.将最后这个节点的next 指向新的节点
public void add(HeroNode heroNode) {
//因为head节点不能动,一次我们需要一个辅助遍历
HeroNode temp = head;
while (true) {
//找到链表的最后
if (temp.next == null) {
break;
}
//如果没有找到,就将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
//将最后这个节点的next指向新的节点
temp.next = heroNode;
}
//有序插入:添加英雄到单链表,根据英雄排名插入到指定的位置
//如果又这个排名,则添加失败,并给出提示
public void addByOrder(HeroNode heroNode) {
//因为头节点不可改变,定义临时变量temp进行转换
HeroNode temp = head;
//定义查看是否已经插入过该排名的变量
boolean flag = false;
while (true) {
//查找到链表的最后
if (temp.next == null) {
break;
}
//找到插入节点的位置temp(必须使用temp.next进行对比寻找,不然用temp直接寻找可能会出现位置不准确)
if (temp.next.no > heroNode.no) { //位置找到,就在temp的后面插入
break;
} else if (temp.next.no == heroNode.no) { //说明希望添加的heroNode的编号已然存在
flag = true;
break;
}
//将节点向后移,否则会出现死循环
temp = temp.next;
}
//找到插入节点的对应位置
if (flag) {
System.out.printf("插入失败,%d英雄编号已存在~\n",heroNode.no);
} else {
//将数据插入到对应的temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改对应编号的节点信息
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//因为头节点变量不能动,定义一个辅助变量temp
HeroNode temp = head.next;
//flag表示是否找到该节点
boolean flag = false;
while (true) {
//已经完成遍历链表,找到尾节点
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) { //找到该节点
flag = true;
break;
}
//temp移动到下一节点的位置
temp = temp.next;
}
//根据flag的值判断链表中是否有该节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
System.out.printf("没有找到编号为 %d的节点\n",newHeroNode.no);
}
}
//根据编号n查找对应编号的信息
public HeroNode findByNum(int n) {
HeroNode temp = head;
if (temp.next == null) {
System.out.println("链表为空~~");
}
while (true) {
if (temp.next == null) {
throw new RuntimeException("未找到编号为 "+n+"的英雄信息");
}
if (temp.next.no == n) { //已找到
return temp.next;
}
temp = temp.next;
}
}
//删除对应编号的信息
public void delete(int n) {
//使用辅助变量进行遍历查询链表
HeroNode temp = head;
if (temp.next == null) {
System.out.println("链表为空~");
}
//flag表示是否找到对应的编号n
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == n) { //找到编号为n的英雄信息在temp的下一变量
flag = true;
break;
}
temp = temp.next;
}
//根据flag的值来进行判断是否找到n编号的英雄
if (flag) {
// HeroNode hero = findByNum(n);
// temp.next = hero.next;
temp.next = temp.next.next;
} else {
System.out.printf("删除失败,没有找到编号为 %d的英雄信息",n);
}
}
//显示链表【遍历】
public void list() {
if (head.next == null) {
System.out.println("链表为空");
}
//因为头节点不能动,因此我们需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true) {
//当循环到最后一个节点时,跳出循环体
if (temp == null) {
break;
}
System.out.println(temp);
//将next进行后移
temp = temp.next;
}
}
}
/**
*方法:获取到单链表的节点的个数(如果是带头节点的链表,记得不统计头节点)
* @param head 链表的头节点
* @return 返回的就是有效节点的个数
*/
public static int getLinkedListLength(HeroNode head) {
int count = 0; //计算有效节点的个数
HeroNode temp = head;
if (temp.next == null) {
return 0;
}
while (temp.next != null) {
count ++;
temp = temp.next;
}
return count;
}
/**
* 方法:查找单链表中倒数第k个节点
* @param head 查找的那个头节点
* @param index 倒数的那个节点
* @return
* 思路:
* 1.编写一个方法,接收head节点,同时接收一个index
* 2.index表示是倒数第index个节点
* 3.先把链表从头到尾遍历,得到链表的总的长度getLength
* 4.得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
* 5.如果找到了,则返回该节点,否则返回null
*/
public static HeroNode findLastIndexNode(HeroNode head,int index) {
HeroNode temp = head;
int count = 0; //计算总结点的个数
if (temp.next == null) {
System.out.println("该链表为空链表");
}
//先进行遍历该链表,求出总链表的个数
while (temp.next != null) {
count ++;
temp = temp.next;
}
//判断索引值是否在链表的个数范围内
if (index <=0 || index > count) {
//如果不在范围内
System.out.println(index+"索引值已超出链表范围~");
} else { //该索引值在搜索范围内
temp = head.next; //temp代表第一个有效的节点
for (int i = 0; i < count - index; i++) {
temp = temp.next; //count-index就是指针的移动次数
}
return temp;
}
return null;
}
/**
* 方法:将链表进行反转
* 思路:
* 1.先定义一个节点reverseHead = new HeronNode();
* 2.从头到尾遍历原来的链表,就将其取出,并放在新的链表reverseHead的最前端
* 3.原来的链表的head.next = reverseHead.next
*/
public static void reverseLinkedListNode(HeroNode head) {
//判断节点是否为空或者只有一个节点,无需反转,直接返回
if (head.next == null || head.next.next == null) {
System.out.println("该链表为空~");
return;
}
//定义新的链表头节点
HeroNode reverseHead = new HeroNode(0,"","");
//定义辅助节点temp
HeroNode cur = head.next;
HeroNode next = null; //指向当前节点cur[cur]的下一个节点
while (cur != null) {
//进行遍历链表,到链表的尾部
next = cur.next; //先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next; //将cur的下一个节点保存到新的链表的最前端
reverseHead.next = cur; //将cur链接到新的链表上
cur = next; //让cur后移
}
head.next = reverseHead.next;
}
要求方式:1.反向遍历 2.Stack栈
/**
* 将链表进行倒序打印
* 思路:
* 1.将链表进行遍历
* 2.将每个链表的数据添加到栈中
*/
public static void reversePrint(HeroNode head) {
if (head.next == null) {
System.out.println("该链表为空~~");
return;
}
//定义一个辅助变量
HeroNode temp = head.next;
//定义一个栈
Stack<HeroNode> heroNodeStack = new Stack<>();
while (temp != null) {
heroNodeStack.add(temp);
temp = temp.next;
}
//将栈中的节点元素进行打印,pop出栈
while (heroNodeStack.size() > 0 ){
System.out.println(heroNodeStack.pop()); //先进后出的原则
}
}
/**
* 合并两个有序的单链表,合并之后的链表依然有序
* 思路:
* 1.判断链表是否为空
* 2.定义辅助变量temp,将最终的有序链表存储到temp
*/
public static HeroNode OrderTwoNode(HeroNode head1,HeroNode head2) {
if (head1 == null && head2 == null) {
System.out.println("该链表为空~~");
return null;
}
if (head2 == null) {
return head1;
}
if (head1 == null) {
return head2;
}
//如果这两个链表均不为空
//合并后的链表
// 方法一:使用递归的方式
HeroNode temp = null;
if (head1.no < head2.no) { //将小的数据节点插入到temp后面
//把head较小的节点给头节点
temp = head1;
//继续递归head1
temp.next = OrderTwoNode(head1.next, head2);
} else {
temp = head2;
temp.next = OrderTwoNode(head1, head2.next );
}
return temp;
}
Josephu(约瑟夫、约瑟夫环)问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
class Boy {
public int no; //小孩节点的编号
public Boy next; //指向下一个节点的指针
public Boy(int no) {
this.no = no;
}
}
//创建环形单向链表
class CircleSingleLinkedList {
private Boy first = null; //创建头节点,当前没有编号
//添加小孩节点,构建成一个环形的链表
public void add(int num) {
//做一个数据校验
if (num < 1) {
System.out.println("num的值不正确");
return;
}
//创建一个辅助指针curBoy,因为头指针的位置不能发生改变
Boy curBoy = null;
//使用for循环来创建我们的环形链表
for (int i = 1; i <= num; i++) {
//根据小孩编号,创建小孩节点
Boy boy = new Boy(i);
if (i == 1) {
first = boy;
first.next = first;
curBoy = first; //让curBoy指向第一个小孩
} else {
curBoy.next = boy;
boy.next = first;
curBoy = curBoy.next; //将curBoy的指针移向下一位置
}
}
}
//遍历当前的环形链表
public void showList() {
//判断链表是否为空
if (first == null) {
System.out.println("该环形链表为空~~");
return;
}
//设置辅助指针,因为头指针不能移动
Boy curBoy = first;
while (true) {
System.out.printf("小孩的编号为:%d\n",curBoy.no);
if (curBoy.next == first) {
break;
}
curBoy = curBoy.next;
}
}
}
/**
* 根据用户的输入,计算小孩出圈的顺序:helper指向要丢出的数的前一个指针,first指向的节点,就是要出圈的小孩节点
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少个小孩在圈中
*/
public void circleGame(int startNo,int countNum,int nums) {
//开始进行数据校验
if (startNo < 1 || startNo > nums || first == null) {
System.out.println("参数输入有误,请重新输入");
return;
}
//创建一个辅助指针helper,帮助小孩出圈
Boy helper = first;
//1. 一开始将helper指针环形链表的最后
while (true) {
if (helper.next == first) {
break;
}
helper = helper.next;
}
//2. 将helper指针移向startNo的前一位,first指针移向startNo位置(移动startNo下)因为从自己开始算,要减一
for (int i = 0; i < startNo-1; i++) {
helper = helper.next;
first = first.next;
}
//3. 这里是一个循环操作,直至圈中只有一个节点
while (true) {
if (helper == first) {
//helper == first说明只剩下了一个小孩,跳出循环(终止条件)
break;
}
//4. 将helper指针移向出圈位置的前一位,first指针移向出圈位置(移动countNum-1下),因为从自己开始读,要减一
for (int i = 0; i < countNum-1; i++) {
helper = helper.next;
first = first.next;
}
System.out.printf("小孩%d出圈\n",first.no);
//5. 将出圈的小孩踢掉(单链表删除节点的操作)
first = first.next; //将first指向下一位置
helper.next = first; //将helper指向first(也就是指向的下一个节点)
}
System.out.printf("最后出圈的小孩是 %d\n",first.no);
}
使用带head头的双向链表实现-水浒英雄排行榜管理单向链表的缺点分析:
对上图的说明:
分析双向链表的遍历,添加,修改,删除的操作思路=》代码实现
class HeroNode2 {
public int no; //英雄的编号
public String name; //英雄名称
public String nickname; //英雄昵称
public HeroNode2 pre; //前置节点,默认为null,无需赋值
public HeroNode2 next; //后置节点,默认为null,无需赋值
public HeroNode2(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode2{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
/**
* 双链表的增删改查(部分代码与单链表的操作相识)
*/
class DoubleLinkedList {
//定义头节点,头节点的位置不可改变,不存放具体的数据
private HeroNode2 head = new HeroNode2(0,"","");
//添加一个节点到双链表的最后
public void add(HeroNode2 heroNode) {
//定义一个辅助节点
HeroNode2 temp = head;
while (true) {
//遍历链表,找到链表的最后位置
if (temp.next == null) {
break;
}
temp = temp.next;
}
//当while退出循环时,temp就指向了链表的最后
temp.next = heroNode;
heroNode.pre = temp;
}
//有序插入:添加英雄到单链表,根据英雄排名插入到指定的位置
//如果又这个排名,则添加失败,并给出提示
public void addByOrder(HeroNode2 heroNode) {
//定义一个赋值变量temp
HeroNode2 temp = head;
//判断该链表中是否已经存在该英雄编号的值
boolean flag = false;
while(true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) { //找到插入该节点的位置,在temp的后面进行插入即可
break;
}
if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
//根据flag的值来进行判断该英雄的编号是否已经存在
if (flag) {
System.out.printf("插入失败,%d节点的英雄信息已存在\n",heroNode.no);
} else {
//将该节点插入到指定位置上(在temp的下一位置),双链表和单链表不同,要双重前后绑定
heroNode.next = temp.next;
temp.next = heroNode;
heroNode.pre = temp;
}
}
//删除某个节点的英雄信息
public void delete(int no) {
//设置辅助变量,直接指向第一个数据域的相关信息
HeroNode2 temp = head.next;
if (temp == null) {
System.out.println("该链表为空~~");
return;
}
//返回是否找到该节点的值
boolean flag = false;
while (temp != null) {
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
//根据flag的值来判断对应是否找到该节点
if (flag) {
//temp节点就是找到的no编号的那个指定节点
temp.pre.next = temp.next; //经测试按照单链表的方式进行顺序插入,temp.pre会出现空指针异常
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("该链表中不含有编号为 %d的英雄信息\n",no);
}
}
//修改双链表中的某个节点
public void update(HeroNode2 heroNode) {
HeroNode2 temp = head;
if (temp.next == null) {
System.out.println("该链表为空~~");
return;
}
//是否找到对应n编号的节点
boolean flag = false;
while (temp.next != null) {
if (temp.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
//根据flag的值来进行判断是否找到对应的节点
if (flag) {
temp.name = heroNode.name;
temp.nickname = heroNode.nickname;
} else {
System.out.printf("没有找到编号为 %d英雄节点\n",heroNode.no);
}
}
//查找某个节点的英雄信息
public HeroNode2 findByNo(int no) {
HeroNode2 temp = head;
if (head.next == null) {
System.out.println("该链表为空~~");
return null;
}
boolean flag = false;
while (temp.next != null) {
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
return temp;
} else {
System.out.printf("该链表中不存在节点为 %d的相关信息\n",no);
}
return null;
}
//遍历整个双链表
public void showList() {
HeroNode2 temp = head;
if (temp.next == null) {
System.out.println("该链表为空~~");
return;
}
while (temp.next != null) {
System.out.println(temp.next); //temp时头节点,而temp.next才是指向的第一个数据
temp = temp.next;
}
}
}