单链表
单链表的结构特点
- 链表的结构特点
- 链表是有序的列表,在内存中的1存储如下图 ,是以节点的方式来存储的!
- 每个节点包含data域,next域:指向下一节点
- 如下图可以发现链表的各个节点不一定是连续存储的
- 链表分带头节点的链表和没有头节点的链表,根据实际需求来
链表在逻辑结构上来看属于线性结构
单链表的增删改查
单链表构建以及显示
/**
* @Date:2021/5/23
* @Author:GuoHeLong
*
* 单链表,实现水浒英雄排名
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试,先创建几个节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//加入链表
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.add(hero1);
linkedList.add(hero2);
linkedList.add(hero3);
linkedList.add(hero4);
//显示一把
linkedList.list();
}
//定义一个单链表来管理英雄
static class SingleLinkedList{
//先初始化一个头节点不存放具体数据,头节点不能动
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
/*
当不考虑编号顺序时
1:找到当前链表的最后节点
2:将最后这个节点的next指向新的节点
*/
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助遍历节点temp。
// head一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了
HeroNode temp = head;
while (true){
//找到链表的最后
if (temp.next == null){
break;
}
//若没有找到将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后。此时将temp指向加入的节点
temp.next = heroNode;
}
//显示链表
public void list(){
//首先判断是否需要循环
if (head.next == null){
System.out.println("链表为空");
return;
}
//定义辅助变量,因为头节点不能动
HeroNode temp = head.next;
while (true){
//判断是否到链表的最后了
if (temp == null){
break;
}
System.out.println(temp);
//temp后移指向下一节点
temp = temp.next;
}
}
}
//定义HeroNode,每个Hero对象就是一个节点
static 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 + '\'' +
", next=" + next +
'}';
}
}
}
插入链表,需求:在添加英雄时根据英雄排名插入到指定位置(若有这个排名这添加失败,并给出提示)
public static void main(String[] args) {
//测试,先创建几个节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//加入链表
SingleLinkedList linkedList = new SingleLinkedList();
//非顺序插入
linkedList.addByOrder(hero3);
linkedList.addByOrder(hero1);
linkedList.addByOrder(hero4);
linkedList.addByOrder(hero2);
linkedList.addByOrder(hero2);
//显示一把
linkedList.list();
}
public void addByOrder(HeroNode heroNode){
//因为头节点不能动,因此我们仍然通过定一个辅助指针来帮忙找到添加的位置
//找到temp一定是位于添加位置的前一节点
HeroNode temp = head;
boolean flag = true;
while (true){
if (temp.next == null || temp.next.no > heroNode.no){
//代表head下一节点就是要插入的位置
break;
}else if (temp.next.no == heroNode.no){
flag = false;
break;
}
temp = temp.next; //节点后移
}
if (!flag){
System.out.println("====不能添加,该英雄编号已经存在"+heroNode.no);
}else {
//此时temp是要插入位置的上一节点
heroNode.next = temp.next;
temp.next = heroNode;
}
}
单链表修改节点操作
public static void main(String[] args) {
//测试,先创建几个节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//加入链表
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.add(hero1);
linkedList.add(hero2);
linkedList.add(hero3);
linkedList.add(hero4);
//显示一把
linkedList.list();
System.out.println("==============");
HeroNode edit = new HeroNode(4, "公孙胜", "入云龙");
linkedList.update(edit);
linkedList.list();
}
//修改指定节点信息 根据no编号来修改,即no编号不能改.
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("===链表为空");
return;
}
//同样的思路定义辅助节点
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
System.out.println("已遍历全表");
}
if (temp.no == newHeroNode.no){
flag = true;
break;
}
temp = temp.next;
}
if (!flag){
System.out.println("未找到要修改的节点");
return;
}
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
}
单链表删除节点:找到要删除的no直接temp.next = temp.next.next即可,跳过的节点会被GC自动回收
public void del(int no){
//如何删除?temp = temp.next.next 即可删除 temp.next 节点,该节点没有引用指向它,会被垃圾回收机制回收
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){
System.out.println("已找到链表最后");
break;
}
if (temp.next.no == no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
//跳过要删除的节点指向下一个节点,该节点会被GC回收
temp.next = temp.next.next;
list();
}else {
System.out.println("未找到要删除的节点");
}
}
单链表面试题
【新浪】面试题1:计算出链表中有效节点的个数(不包含头节点)
public int getLength(HeroNode head) {
//初始化数量
int count = 0;
if (head.next == null) {
return count;
}
HeroNode temp = head.next;
while (temp != null) {
temp = temp.next;
count++;
}
return count;
}
【新浪】面试题2: 查找单链表中倒数第n个节点
思路:链表有效节点的长度-n=要找的节点
public HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
int length = getLength(head);
if (length == 0 || index > length || index < 0) {
return null;
}
HeroNode temp = head.next;
for (int i = 0; i < length - index; i++) {
temp = temp.next;
}
return temp;
}
【腾讯】将单链表反转
如:1-2-3反转为3-2-1
思路:
1定义临时链表,
2循环原链表将循环到的节点的next指向临时链表的首个有效节点
3把临时链表head的next指向步骤3所得链表
4将原链表head的next指向临时链表head的next
代码实现(个人写法)
public void reversetList(HeroNode head) {
if (head.next == null || head.next.next == null){
return;
}
HeroNode reverHead = new HeroNode(0, "", "");
HeroNode temp = head.next;
while (temp != null) {
//每次循环取到当前节点temp的value,定义一个临时的节点来存储
HeroNode upNode = new HeroNode(temp.no, temp.name, temp.nickName);
upNode.next = reverHead.next; //临时节点的next指向当前反转链表head的next。
reverHead.next = upNode; //再将反转链表head的next指向临时节点
temp = temp.next; //原链表节点后移
}
head.next = reverHead.next;
}
尚硅谷老师写法)
//【腾讯】单链表反转(尚硅谷老师写法)
public void reversetListBySGG(HeroNode head) {
if (head.next == null || head.next.next == null){
return;
}
//定义一个辅助指针帮助遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;//指向当前节点的下一节点
HeroNode reverseHead = new HeroNode(0,"","");
while (cur != null){
next = cur.next; //先将遍历节点的下一节点存起来
cur.next = reverseHead.next; //当前节点的next指向反转链表head的next
reverseHead.next = cur; //反转链表的head的next再指向cur就完成了换位
cur = next; //cur后移
}
head.next = reverseHead.next; //最后将原链表head的next指向反转链表head的next
}
【百度】将链表逆向打印
思路
利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点
public void reversePrint(HeroNode head){
//利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点
if (head.next == null){
return;
}
Stack<HeroNode> stack = new Stack<>();
HeroNode cur = head.next;
while (cur != null){
stack.push(cur);
cur = cur.next;
}
while (stack.size() > 0){
HeroNode pop = stack.pop();
System.out.println(pop);
}
}
单链表缺点分析
- 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
- 单项链表不能自我删除,需要依靠辅助接点,而双向链表则可以自我删除
双向链表
总结:多出了pre上一节点,灵活变通即可
双向链表的增删改查
代码示例
package com.ghl.likendList;
/**
* @Date:2021/5/31
* @Author:GuoHeLong 双向链表
*/
public class DoubleLinkedListDemo {
public static void main(String[] args) {
//测试,先创建几个节点
HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
//加入链表
DoubleLinkendList linkendList = new DoubleLinkendList();
//=======================测试顺序插入===============================================================
//顺序插入
linkendList.add(hero1);
linkendList.add(hero2);
linkendList.add(hero3);
linkendList.add(hero4);
linkendList.list();
//linkendList.del(2);//删除
linkendList.update(new HeroNode2(1,"宋老黑","解绑星"));
linkendList.update(new HeroNode2(4,"林教头","进军教头"));
}
//创建一个双向链表类
static class DoubleLinkendList {
//先初始化一个头节点不存放具体数据,头节点不能动
private HeroNode2 head = new HeroNode2(0, "", "");
//返回头节点
public HeroNode2 getHead() {
return head;
}
//添加一个节点到双向链表最后
public void add(HeroNode2 heroNode) {
HeroNode2 temp = head;
while (true) {
//找到最后节点
if (temp.next == null) {
break;
}
temp = temp.next;
}
//要加入的节点的pre指向链表最后一个节点
heroNode.pre = temp;
//链表的最后一个节点指向新加节点
temp.next = heroNode;
}
//插入
public void addByOrder(HeroNode2 hero) {
System.out.println("插入节点");
HeroNode2 temp = head;
boolean flag = false;
while (true) {
if (temp.next == null || temp.next.no > hero.no) {
flag = true;
break;
}
if (temp.next.no == hero.no) {
System.out.println("已有相同的不能插入");
break;
}
temp = temp.next;
}
if (flag) {
HeroNode2 heroNode2 = temp.next; //要插入位置的下一节点
hero.pre = temp;
hero.next = heroNode2;
temp.next = hero;
if (heroNode2 != null) {
hero.next.pre = hero;
}
}
list();
}
//修改双向链表的一个节点
public void update(HeroNode2 upHeroNode) {
System.out.println("================显示链表======================");
HeroNode2 temp = head;
if (temp.next == null) {
System.out.println("要修改的链表为空~~~~");
return;
}
boolean flag = false;
while (true) {
if (temp == null) {
System.out.println("已经遍历全表");
}
if (temp.no == upHeroNode.no) {
flag = true;
break;
}
temp = temp.next; //后移
}
if (!flag) {
System.out.println("未找到要删除的节点");
return;
}
temp.name = upHeroNode.name;
temp.nickName = upHeroNode.nickName;
System.out.println("修改成功");
list();
}
/*
del=要删除节点
删除节点。
找到del的上一节点,然后上一节点的next指向del的下一节点,
若del的下一节点不为null则下一节点的pre指向del的上一节点
删除完毕,GC自动清理del
*/
public void del(int no) {
System.out.println("=================删除节点===========================");
HeroNode2 temp = head;
if (temp.next == null) {
System.out.println("链表为空~~~");
return;
}
boolean flag = false;
while (temp.next != null) {
if (temp.next.no == no) {
flag = true; //找到了del,此时指针停留在上一节点
break;
}
temp = temp.next;
}
if (!flag) {
System.out.println("未找到要删除节点");
return;
}
HeroNode2 delNext = temp.next.next; //先拿到要del的下一节点
if (delNext != null) { //防止最有一个节点是null导致nullPoint
delNext.pre = temp; //del的下一节点指向del的上一节点
}
temp.next = delNext; //del的上一节点的next指向del的下一节点
System.out.println("删除成功");
list();
}
//遍历双向链表
public void list() {
System.out.println("================显示链表======================");
if (head.next == null) {
System.out.println("链表为空~~~~");
}
HeroNode2 temp = head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
}
//定义HeroNode,每个Hero对象就是一个节点
static class HeroNode2 {
public int no; //英雄编号
public String name;//英雄名
public String nickName; //英雄昵称
public HeroNode2 next; //指向下一节点,默认为null
public HeroNode2 pre; //指向上一节点,默认为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 + '\'' +
'}';
}
}
}
约瑟夫环问题
package com.ghl.likendList;
/**
* @Date:2021/6/5
* @Author:GuoHeLong
*
* 约瑟夫环实现
*/
public class JosepFu {
public static void main(String[] args) {
CircleSingleLekinList lekinList = new CircleSingleLekinList();
lekinList.addBoy(5);
lekinList.list();
lekinList.countBoy(1, 2, 3); // 2->4->1->5->3
}
//创建一个环形的单向链表
static class CircleSingleLekinList{
//先创建一个firest节点,当前没有编号
private Boy firest = null;
//添加小孩节点,构建成一个环形链表
public void addBoy(int nums){
if (nums < 1){
System.out.println("nums值不正确");
return;
}
Boy curBoy = null;
for (int i = 1; i <= nums; i++) {
Boy boy = new Boy(i);
if (i == 1){
firest = boy;
firest.setNext(firest); //先构成环
curBoy = firest; //让curBoy指向第一个小孩
}else {
curBoy.setNext(boy);
boy.setNext(firest);
curBoy = boy;
}
}
}
//遍历环形链表
public void list(){
if (firest == null){
System.out.println("没有小孩");
return;
}
Boy curBoy = firest;
while (true){
System.out.println("编号为 :"+curBoy.getNo());
if (curBoy.getNext() == firest){
break;
}
curBoy = curBoy.getNext();
}
}
/**
*
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少小孩在圈中
*/
public void countBoy(int startNo, int countNum, int nums) {
// 先对数据进行校验
if (firest == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入有误, 请重新输入");
return;
}
// 创建要给辅助指针,帮助完成小孩出圈
Boy helper = firest;
// 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点
while (true) {
if (helper.getNext() == firest) { // 说明helper指向最后小孩节点
break;
}
helper = helper.getNext();
}
// 小孩报数前,先让 firest 和 helper 移动 k - 1次(firest指针指向开始节点,helper也是)
for (int j = 0; j < startNo - 1; j++) {
firest = firest.getNext();
helper = helper.getNext();
}
// 当小孩报数时,让firest 和 helper 指针同时 的移动 m - 1 次, 然后出圈
// 这里是一个循环操作,直到圈中只有一个节点
while (true) {
if (helper == firest) { // 说明圈中只有一个节点
break;
}
// 让 firest 和 helper 指针同时 的移动 countNum - 1(-1是因为自己也要数,也就是节点进位是countNum-1)
for (int j = 0; j < countNum - 1; j++) {
firest = firest.getNext();
helper = helper.getNext();
}
// 这时firest指向的节点,就是要出圈的小孩节点
System.out.printf("小孩%d出圈\n", firest.getNo());
// 这时将firest指向的小孩节点出圈
firest = firest.getNext();
helper.setNext(firest);
}
System.out.printf("最后留在圈中的小孩编号%d \n", firest.getNo());
}
}
static class Boy{
private int no;
private Boy next;
public Boy() {
}
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
}