使用带head头的单向链表实现–水浒英雄排行榜管理完成对英雄人物的增删改查操作,注:删除和修改,查找可以考虑学员独立完成,也可带学员完成
package com.sukang.linkedlist;
import java.util.Stack;
/**
* @description: 单链表模拟水浒英雄案例
* @author: sukang
* @date: 2019-12-30 10:43
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode heroNode1 = new HeroNode(1, "张三", "zs");
HeroNode heroNode2 = new HeroNode(2, "李四", "ls");
HeroNode heroNode3 = new HeroNode(3, "王五", "ww");
HeroNode heroNode4 = new HeroNode(4, "刘六", "ln");
SingleLinkedList singleLinkedList = new SingleLinkedList();
//增加在尾部
/* singleLinkedList.addHeroNode(heroNode3);
singleLinkedList.addHeroNode(heroNode1);
singleLinkedList.addHeroNode(heroNode2);
singleLinkedList.addHeroNode(heroNode4);*/
//按照顺序添加
singleLinkedList.addHeroNodeByOrder(heroNode3);
singleLinkedList.addHeroNodeByOrder(heroNode1);
singleLinkedList.addHeroNodeByOrder(heroNode2);
singleLinkedList.addHeroNodeByOrder(heroNode4);
singleLinkedList.list();
//删除一个节点
singleLinkedList.delHeroNode(1);
System.out.println("删除节点之后遍历");
singleLinkedList.list();
//修改一个节点
HeroNode heroNode5 = new HeroNode(2, "马七", "mq");
singleLinkedList.updateHeroNode(heroNode5);
System.out.println("修改节点之后遍历");
singleLinkedList.list();
//测试一下单链表中有效结点
System.out.println("单链表中有效结点个数为:" + SingleLinkedListDemo.getEffective(singleLinkedList.getHead()));
//查看链表的倒数第1个元素
System.out.println("查看链表的倒数第1个元素为:" + SingleLinkedListDemo.getHeroNode(singleLinkedList.getHead(), 1));
//测试链表反转
System.out.println("反转前链表遍历");
singleLinkedList.list();
SingleLinkedListDemo.linkedReversal(singleLinkedList.getHead());
System.out.println("反转后链表遍历");
singleLinkedList.list();
//测试链表反向打印
System.out.println("反向打印前链表遍历");
singleLinkedList.list();
System.out.println("反向打印链表");
SingleLinkedListDemo.linkedReversalPrint(singleLinkedList.getHead());
}
/**
* @description: 面试题,求一个链表中有效数据个数
* @param head
* @return: int
* @author: sukang
* @date: 2020/1/2 14:25
*/
public static int getEffective(HeroNode head){
//判断是否为空链表
if(head.next == null){
return 0;
}
int count = 0;
HeroNode temp = head.next;
while (true){
if(temp == null){
break;
}
count ++;
temp = temp.next;
}
return count;
}
/**
* @description: 面试题:单链表中倒数第k个节点
* @param head
* @param k
* @return:
* @author: sukang
* @date: 2020/1/2 14:36
*/
public static HeroNode getHeroNode(HeroNode head, int k){
//空链表
if(head.next == null){
return null;
}
//获取单链表中有效数据
int count = getEffective(head);
if(!(k > 0 && k < count)){
throw new RuntimeException("k传值超过有效范围");
}
HeroNode temp = head.next;
//从头遍历单链表遍历到(count - k),也就是倒数第k个节点
for (int i = 0; i < count - k ; i++) {
temp = temp.next;
}
return temp;
}
/**
* @description: 面试题链表反转
* @param
* @return: void
* @author: sukang
* @date: 2020/1/3 8:37
*/
public static void linkedReversal(HeroNode head){
//思路:首先申明一个新链表的头节点,然后遍历旧链表,按顺序插入新链表头节点之后
if(head.next == null || head.next.next == null){
System.out.println("链表为空或则链表的有效长度为1,不需要进行反转");
return;
}
//声明一个辅助节点,遍历单链表来用
HeroNode temp = head.next;
//声明一个当前节点指向temp节点
HeroNode cur = null;
//声明新链表的头节点
HeroNode newHead = new HeroNode(0,"","");
while (true){
//链表遍历到了最后一个节点
if(temp == null){
break;
}
cur = temp; //将当前节点指向单链表的辅助节点
temp = temp.next; //将辅助节点后移一个节点
cur.next = newHead.next;//然后将当前节点的后一个节点指向新链表的后一个节点
newHead.next = cur;//新链表的头结点的后一个节点指向当前节点
}
head.next = newHead.next;
}
/**
* @description: 面试题:单链表反向打印
* @param head
* @return: void
* @author: sukang
* @date: 2020/1/3 9:38
*/
public static void linkedReversalPrint(HeroNode head){
//思路:将单链表压入栈中,然后从栈中取出打印,利用了栈的新进后出的特性
if(head.next == null){
return;
}
Stack<HeroNode> stack = new Stack<>();
HeroNode temp = head;
while (true){
if(temp.next == null){
break;
}
stack.add(temp.next);
temp = temp.next;
}
while (stack.size() > 0){
System.out.println(stack.pop());
}
}
}
//链表类
class SingleLinkedList{
//声明一个头结点
private HeroNode head = new HeroNode(0,"","") ;
/**
* @description: 返回头结点
* @param
* @return:
* @author: sukang
* @date: 2020/1/2 14:28
*/
public HeroNode getHead(){
return head;
}
/**
* @description: 从链表尾处添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:18
*/
public void addHeroNode(HeroNode heroNode){
/* 思路:用一个辅助节点遍历链表,找到节点next为空及最后一个节点,
将其next赋给新节点*/
//申明一个过渡节点,因为head节点不能后移
HeroNode temp = head;
while (true){
//如果下一个节点为空的话,那么就在这个节点上添加新节点
if(temp.next == null){
break;
}
//如果没有到最后一个节点,那么节点往后移
temp = temp.next;
}
//把最后一个节点的下一个节点赋给新节点
temp.next = heroNode;
}
/**
* @description: 按照节点的顺序添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:31
*/
public void addHeroNodeByOrder(HeroNode heroNode){
/* 思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么辅助节点temp就应该为插入节点的上一个节点,另外如果链表中存在相同
大小的节点,那么将插入失败*/
//声明一个辅助节点
HeroNode temp = head;
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.next.id == heroNode.id){
System.out.printf("编号为%d的节点已经存在,不能添加", heroNode.id);
return;
}else if(temp.next.id > heroNode.id){
break;
}
temp = temp.next;
}
heroNode.next = temp.next;
temp.next = heroNode;
}
/**
* @description: 删除节点
* @param id
* @return: void
* @author: sukang
* @date: 2019/12/30 15:19
*/
public void delHeroNode(int id){
/*思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么如果辅助节点的下一个节点的id存在,那么就删除这个节点*/
HeroNode temp = head;
boolean flag = false;//是否找到删除节点标志
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.next.id == id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next = temp.next.next;
}else{
System.out.printf("要删除的节点%d,不存在", id);
}
}
/**
* @description: 更新节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 16:04
*/
public void updateHeroNode(HeroNode heroNode){
//思路:同样是通过辅助节点temp遍历找到和更新节点相同id的节点,然后进行更新
//声明辅助节点
HeroNode temp = head;
boolean flag = false; //判断是否找到更新的节点,默认为false
while (true){
if(temp == null){
break;
}
if(temp.next.id == heroNode.id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next.name = heroNode.name;
temp.next.nickName = heroNode.nickName;
}else{
System.out.printf("没有找到编号为 %d 的节点, 不能更新", heroNode.id);
}
}
/**
* @description: 遍历链表
* @param
* @return: void
* @author: sukang
* @date: 2019/12/30 16:15
*/
public void list(){
HeroNode temp = head;
if(temp.next == null){
System.out.println("链表为空!");
return;
}
while (true){
if(temp.next == null){
break;
}
System.out.println(temp.next);
temp = temp.next;
}
}
}
//链表节点
class HeroNode{
public int id; //唯一标志
public String name; //名字
public String nickName; //昵称
HeroNode next; //下一个节点
public HeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{id: "+ id+ ",name: "+ name +", nickName: "+ nickName +"}";
}
}
单链表的常见面试题有如下:
//方法:获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
/**
*
* @param head 链表的头节点
* @return 返回的就是有效节点的个数
*/
public static int getLength(HeroNode head) {
if(head.next == null) { //空链表
return 0;
}
int length = 0;
//定义一个辅助的变量, 这里我们没有统计头节点
HeroNode cur = head.next;
while(cur != null) {
length++;
cur = cur.next; //遍历
}
return length;
}
//查找单链表中的倒数第k个结点 【新浪面试题】
//思路
//1. 编写一个方法,接收head节点,同时接收一个index
//2. index 表示是倒数第index个节点
//3. 先把链表从头到尾遍历,得到链表的总的长度 getLength
//4. 得到size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到
//5. 如果找到了,则返回该节点,否则返回nulll
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断如果链表为空,返回null
if(head.next == null) {
return null;//没有找到
}
//第一个遍历得到链表的长度(节点个数)
int size = getLength(head);
//第二次遍历 size-index 位置,就是我们倒数的第K个节点
//先做一个index的校验
if(index <=0 || index > size) {
return null;
}
//定义给辅助变量, for 循环定位到倒数的index
HeroNode cur = head.next; //3 // 3 - 1 = 2
for(int i =0; i< size - index; i++) {
cur = cur.next;
}
return cur;
}
//将单链表反转
public static void reversetList(HeroNode head) {
//如果当前链表为空,或者只有一个节点,无需反转,直接返回
if(head.next == null || head.next.next == null) {
return ;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;// 指向当前节点[cur]的下一个节点
HeroNode reverseHead = new HeroNode(0, "", "");
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
//动脑筋
while(cur != null) {
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; //将cur 连接到新的链表上
cur = next;//让cur后移
}
//将head.next 指向 reverseHead.next , 实现单链表的反转
head.next = reverseHead.next;
}
import java.util.Stack;
//演示栈Stack的基本使用
public class TestStack {
public static void main(String[] args) {
Stack<String> stack = new Stack();
// 入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
// 出栈
// smith, tom , jack
while (stack.size() > 0) {
System.out.println(stack.pop());//pop就是将栈顶的数据取出
}
}
}
单链表的逆序打印代码:
//方式2:
//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
public static void reversePrint(HeroNode head) {
if(head.next == null) {
return;//空链表,不能打印
}
//创建要给一个栈,将各个节点压入栈
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
//将链表的所有节点压入栈
while(cur != null) {
stack.push(cur);
cur = cur.next; //cur后移,这样就可以压入下一个节点
}
//将栈中的节点进行打印,pop 出栈
while (stack.size() > 0) {
System.out.println(stack.pop()); //stack的特点是先进后出
}
}
使用带head头的双向链表实现 – 水浒英雄排行榜
管理单向链表的缺点分析:
双向链表的代码实现
package com.sukang.linkedlist;
/**
* @description:
* @author: sukang
* @date: 2020-01-03 10:43
*/
public class DoubleLinkedListDemo {
public static void main(String[] args) {
HeroNode2 heroNode1 = new HeroNode2(1, "张三", "zs");
HeroNode2 heroNode2 = new HeroNode2(2, "李四", "ls");
HeroNode2 heroNode3 = new HeroNode2(3, "王五", "ww");
HeroNode2 heroNode4 = new HeroNode2(4, "刘六", "ln");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
//增加在尾部
/* doubleLinkedList.addHeroNode(heroNode3);
doubleLinkedList.addHeroNode(heroNode1);
doubleLinkedList.addHeroNode(heroNode2);
doubleLinkedList.addHeroNode(heroNode4);
doubleLinkedList.list();*/
//按照顺序添加
doubleLinkedList.addHeroNodeByOrder(heroNode3);
doubleLinkedList.addHeroNodeByOrder(heroNode1);
doubleLinkedList.addHeroNodeByOrder(heroNode2);
doubleLinkedList.addHeroNodeByOrder(heroNode4);
doubleLinkedList.list();
//删除一个节点
doubleLinkedList.delHeroNode(1);
System.out.println("删除节点之后遍历");
doubleLinkedList.list();
//修改一个节点
HeroNode heroNode5 = new HeroNode(2, "马七", "mq");
doubleLinkedList.updateHeroNode(heroNode5);
System.out.println("修改节点之后遍历");
doubleLinkedList.list();
}
}
//双链表类链表类
class DoubleLinkedList{
//声明一个头结点
private HeroNode2 head = new HeroNode2(0,"","");
/**
* @description: 返回头结点
* @param
* @return:
* @author: sukang
* @date: 2020/1/2 14:28
*/
public HeroNode2 getHead(){
return head;
}
/**
* @description: 从链表尾处添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:18
*/
public void addHeroNode(HeroNode2 heroNode){
/* 思路:用一个辅助节点遍历链表,找到节点next为空及最后一个节点,
将其next赋给新节点*/
//申明一个过渡节点,因为head节点不能后移
HeroNode2 temp = head;
while (true){
//如果下一个节点为空的话,那么就在这个节点上添加新节点
if(temp.next == null){
break;
}
//如果没有到最后一个节点,那么节点往后移
temp = temp.next;
}
//把最后一个节点的下一个节点赋给新节点
temp.next = heroNode;
heroNode.pre = temp;
}
/**
* @description: 按照节点的顺序添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:31
*/
public void addHeroNodeByOrder(HeroNode2 heroNode){
/* 思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么辅助节点temp就应该为插入节点的上一个节点,另外如果链表中存在相同
大小的节点,那么将插入失败*/
//声明一个辅助节点
HeroNode2 temp = head;
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.next.id == heroNode.id){
System.out.printf("编号为%d的节点已经存在,不能添加", heroNode.id);
return;
}else if(temp.next.id > heroNode.id){
break;
}
temp = temp.next;
}
heroNode.next = temp.next;
temp.next = heroNode;
if(heroNode.next != null){
heroNode.next.pre = heroNode;
}
heroNode.pre = temp;
}
/**
* @description: 删除节点
* @param id
* @return: void
* @author: sukang
* @date: 2019/12/30 15:19
*/
public void delHeroNode(int id){
/*思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么如果辅助节点的下一个节点的id存在,那么就删除这个节点*/
HeroNode2 temp = head.next;
boolean flag = false;//是否找到删除节点标志
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.id == id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.pre.next = temp.next;
temp.next = temp.pre;
}else{
System.out.printf("要删除的节点%d,不存在", id);
}
}
/**
* @description: 更新节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 16:04
*/
public void updateHeroNode(HeroNode heroNode){
//思路:同样是通过辅助节点temp遍历找到和更新节点相同id的节点,然后进行更新
//声明辅助节点
HeroNode2 temp = head;
boolean flag = false; //判断是否找到更新的节点,默认为false
while (true){
if(temp == null){
break;
}
if(temp.next.id == heroNode.id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next.name = heroNode.name;
if(temp.next != null){
temp.next.nickName = heroNode.nickName;
}
}else{
System.out.printf("没有找到编号为 %d 的节点, 不能更新", heroNode.id);
}
}
/**
* @description: 遍历链表
* @param
* @return: void
* @author: sukang
* @date: 2019/12/30 16:15
*/
public void list(){
HeroNode2 temp = head;
if(temp.next == null){
System.out.println("链表为空!");
return;
}
while (true){
if(temp.next == null){
break;
}
System.out.println(temp.next);
temp = temp.next;
}
}
}
//双向链表节点
class HeroNode2{
public int id; //唯一标志
public String name; //名字
public String nickName; //昵称
HeroNode2 next; //下一个节点
HeroNode2 pre; //上一个节点
public HeroNode2(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode2{id: "+ id+ ",name: "+ name +", nickName: "+ nickName +"}";
}
}
Josephu(约瑟夫、约瑟夫环)问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
约瑟夫问题的示意图
Josephu问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示
用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
约瑟夫问题-创建环形链表的思路图解
约瑟夫问题-小孩出圈的思路分析图
Josephu问题的代码实现
package com.sukang.linkedlist;
/**
* @description: 约瑟夫问题(丢手帕)
* @author: sukang
* @date: 2020-01-04 13:48
*/
public class Josephu {
public static void main(String[] args) {
CircleLinkedList circleLinkedList = new CircleLinkedList();
circleLinkedList.create(5);
circleLinkedList.list();
circleLinkedList.outCircle(1,2,5);
}
}
//环形单链表
class CircleLinkedList{
//申明一个开始节点
Boy first = null;
/**
* @description: 根据传递的数量创建环形单链表
* @param num
* @return: void
* @author: sukang
* @date: 2020/1/4 14:41
*/
public void create(int num){
if(num < 1){
System.out.println("数量小于1,没法创建");
return;
}
Boy temp = null;
for (int i = 0; i < num; i++) {
Boy boy = new Boy(i+1);
if(i == 0){
first = boy;
temp = first;
}else{
temp.setNext(boy);
temp = boy;
}
}
temp.setNext(first);
}
/**
* @description: 遍历环形单链表
* @param
* @return: void
* @author: sukang
* @date: 2020/1/4 14:40
*/
public void list(){
if(first == null){
System.out.println("环形单链表为空");
return;
}
Boy temp = first;
while (true){
System.out.println(temp);
if(temp.getNext() == first){
System.out.println("遍历到最后了");
break;
}
temp = temp.getNext();
}
}
/**
* @description:
* @param startNo
* 表示从第几个小孩开始数
* @param countNum
* 表示数了几个数字
* @param nums
* 表示圈内一共有几个
* @return: void
* @author: sukang
* @date: 2020/1/4 15:32
*/
public void outCircle(int startNo, int countNum, int nums){
if(first == null || startNo < 1 || startNo > nums){
System.out.println("传入的数据有问题");
return;
}
//声明一个辅助节点,让其指向环形单链表的最后一个孩子节点
Boy temp = first;
while (true){
if(temp.getNext() == first){
break;
}
temp = temp.getNext();
}
//从第startNo元素开始数
for (int i = 0; i < startNo -1 ; i++) {
temp = temp.getNext();
first = first.getNext();
}
while (true){
if(temp == first){
break;
}
for (int i = 0; i < countNum -1 ; i++) {
temp = temp.getNext();
first = first.getNext();
}
System.out.println(first);
temp.setNext(first.getNext());
first = first.getNext();
}
System.out.println(first);
}
}
//孩子节点
class Boy{
private int index;
private Boy next;
public Boy(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
@Override
public String toString() {
return "Boy[index:"+index+"]";
}
}