本文共 21669 字,大约阅读时间需要 72 分钟。
链表是有序的列表,但是它在内存中是存储如下
小结上图:使用带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; } Stackstack = 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. 如果找到了,则返回该节点,否则返回nulllpublic 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) { Stackstack = 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;//空链表,不能打印 } //创建要给一个栈,将各个节点压入栈 Stackstack = new Stack (); 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+"]"; }}
转载地址:http://eanti.baihongyu.com/