总结:
1、链表中每个结点的next引用都相当于一个指针指向另一个结点
2、在单链表中通常使用head引用指向单链表的首结点,由head引用完成对整个链表中所有结点的访问
3、单链表的一个重要特性就是只能通过前驱结点找到后续结点,不能从后续结点找到前驱结点
单链表的增、删、改、查实现:
// 根据条件排序添加
/**
* 单链表的实现
*/
public class SingleLinkdListDemo {
public static void main(String[] args) {
SingleLinkdList singleLinkdList = new SingleLinkdList();
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
// singleLinkdList.add(hero1);
// singleLinkdList.add(hero2);
// singleLinkdList.add(hero3);
// singleLinkdList.add(hero4);
singleLinkdList.add2(hero1);
singleLinkdList.add2(hero4);
singleLinkdList.add2(hero2);
singleLinkdList.add2(hero3);
// 修改
//singleLinkdList.upDataNode(new HeroNode(4, "吴用1111", "智多星1111"));
// 删除
//singleLinkdList.deleteNode(5);
singleLinkdList.showLinkList(singleLinkdList.head);
singleLinkdList.size();
singleLinkdList.reversetLog(singleLinkdList.head);
// 经典面试
//1、查询链表有效个数
// singleLinkdList.getLength();
//2、查询链表的倒数第几个
//singleLinkdList.getEndIndex(2);
//3、反转链表
// singleLinkdList.invertsLink(singleLinkdList.head);
//singleLinkdList.showLinkList(singleLinkdList.head);
}
}
/**
* 单链表
*/
class SingleLinkdList{
// 头节点
public HeroNode head = new HeroNode(1,"","");
/**
* 添加数据 1 无序添加(直接加在链表的尾部)
* 1. 找到当前链表的最后节点
* 2. 将最后这个节点的 next 指向 新的节点
*/
public void add(HeroNode node){
if(node == null) {
System.out.println("传入数据为空~~~");
return;
}
// 头节点不能变,所以备份变量
HeroNode data = head;
while (true) {
// 找到最后一个元素,next为null,赋值即可
if(data.next == null) {
data.next = node;
break;
}
data = data.next;
}
}
/**
* 添加数据 2 (有序添加,根据no变量值插入)
* 1、找到前节点,然后指向新节点,然后新节点指向后节点
* 2、如果排行相等 就不加入
*/
public void add2(HeroNode node){
if(node == null) {
System.out.println("传入数据为空~~~");
return;
}
// 因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
HeroNode data = head;
// flag标志添加的编号是否存在,默认为false
boolean isFlag = false;
while (true){
if(data.next == null) {
break;
}
// 判断下一个节点的排名大于当前的排名
if(data.next.no > node.no) {
break;
}else if(data.next.no == node.no) {
isFlag = true;
break;
}
data = data.next;
}
if(isFlag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", node.no);
}else {
// 新节点指向后一节点
node.next = data.next;
// 上一节点指向新节点
data.next = node;
}
}
/**
* 修改节点的信息, 根据no编号来修改,即no编号不能改.
* 修改某一个节点
*/
public void upDataNode(HeroNode node){
if(head.next == null) {
System.out.println("链表为空~~~");
return;
}
HeroNode data = head;
while (true){
if(data.next == null && data.no != node.no) {
System.out.println("没有找到您要修改的数据");
break;
}
if(data.next.no == node.no) {
// 找到修改的数据
data.next.setNickName(node.getNickName());
data.next.setName(node.getName());
break;
}
data = data.next;
}
}
/**
* 链表大小,没有头节点
*/
public void size(){
if(head.next == null) {
System.out.println("链表为空~~~");
return;
}
int num = 0;
HeroNode data = head;
while (true){
if(data.next == null) {
break;
}
num++;
data = data.next;
}
System.out.println("链表size~~~" + num);
}
/**
* 查询链表数据
*/
public void showLinkList(HeroNode head){
if(head.next == null) {
System.out.println("链表为空~~~");
return;
}
HeroNode data = head;
while (true){
if(data.next == null) {
break;
}
data = data.next;
System.out.println(data);
}
}
/**
* 删除链表数据
* 1、找到前一个节点,然后指向删除节点的下一个节点
* 2、通过no排名条件,删除节点
*/
public void deleteNode(int no){
if(head.next == null) {
System.out.println("链表为空~~~");
return;
}
HeroNode data = head;
while (true){
if(data.next == null && data.no != no) {
System.out.println("没有找到删除的数据~~~");
break;
}
// 找到对应排名的数据
if(data.next.no == no) {
System.out.println("删除成功~~~" + data.next);
// 前节点指向 删除节点的后一节点
data.next = data.next.next;
break;
}
data = data.next;
}
}
}
/**
* 链表节点
*/
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;
}
public HeroNode getNext() {
return next;
}
public void setNext(HeroNode next) {
this.next = next;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
几个单链表相关的面试题:
1、求单链表中有效节点的个数
/**
* 求单链表中有效节点的个数
*/
public int getLength(){
if(head.next == null) {
System.out.println("有效数量为0.....");
return 0;
}
int length = 0;
HeroNode data = head;
while (data.next != null) {
length++;
data = data.next;
}
System.out.println("有效数量为....." + length);
return length;
}
2、查找链表的倒数第几个
/**
* 查找链表的倒数第几个
*/
public void getEndIndex(int index){
if(head.next == null) {
System.out.println("没有找到");
return;
}
if(index <= 0 || index > getLength()) {
System.out.println("查找的位置异常。。。");
return;
}
HeroNode data = head;
// 有效个数 - 倒数第几个。 比如有效长度 4 倒数第2 0 1 2 3
for(int i = 0; i <= getLength() - index; i++) {
data = data.next;
}
System.out.println("查找的倒数第" + index + ": " + data);
}
3、反转链表
/**
* 反转链表
*/
public void invertsLink(HeroNode head){
if(head == null || head.next == null || head.next.next == null) {
System.out.println("您反转的链表为空~~~" + "head.next.next == null单链表1个不需要反转");
return;
}
/**
* 思路:
* 1、创建新反转的头节点
* 2、然后在旧的头取出数据放在新头节点后面,
* 3、同时是永远都要放在新头节点的后面,之前节点的前面(新链表的最前端)
* */
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
// 指向当前节点[cur]的下一个新节点(备份下一个新节点)
HeroNode next = null;
// 创建新的头结点
HeroNode newHead = new HeroNode(0, "", "");
while (cur != null){
// 新链表下个要操作的节点
next = cur.next;
// 取出来的节点指向新的反转头结点最前端
cur.next = newHead.next;
// 新的反转头节点指向取出来的节点
newHead.next = cur;
// 新链表往后移动
cur = next;
}
// 之前的头结点指向 反转头节点的后面
head.next = newHead.next;
}
4、逆向打印链表 (还可以通过链表反转在打印等等)
/**
* 逆向打印链表
* 方式: 通过栈
*/
public void reversetLog(HeroNode head){
if(head == null || head.next == null) {
System.out.println("链表为空");
return;
}
Stack<HeroNode> stack = new Stack<>();
HeroNode cur = head.next;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (stack.size() > 0){
System.out.println(stack.pop());
}
}
双链表的实现:
分析 双向链表的遍历,添加,修改,删除的操作思路===》代码实现
- 遍历 方和 单链表一样,只是可以向前,也可以向后查找
- 添加 (默认添加到双向链表的最后)
(1) 先找到双向链表的最后这个节点
(2) temp.next = newHeroNode
(3) newHeroNode.pre = temp; - 修改 思路和 原来的单向链表一样.
- 删除
(1) 因为是双向链表,因此,我们可以实现自我删除某个节点
(2) 直接找到要删除的这个节点,比如 temp
(3) temp.pre.next = temp.next
(4) temp.next.pre = temp.pre;
public static void main(String[] args) {
TwoLinkdList twoLinkdList = new TwoLinkdList();
HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
twoLinkdList.add(hero1);
twoLinkdList.add(hero2);
twoLinkdList.add(hero3);
twoLinkdList.add(hero4);
twoLinkdList.showTowLinkData();
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");
HeroNode2 hero5 = new HeroNode2(6, "林冲111", "豹子头111");
twoLinkdList.upData(hero5);
twoLinkdList.showTowLinkData();
// twoLinkdList.deleteLink(3);
// twoLinkdList.showTowLinkData();
}
class TwoLinkdList{
// 指向上面的指针
public HeroNode2 pre;
// 指向下面的指针
public HeroNode2 next;
// 头节点
public HeroNode2 head = new HeroNode2(0,"","");
public void add(HeroNode2 node){
if(node == null) {
System.out.println("传入的数据异为null");
return;
}
HeroNode2 data = head;
while (true){
// 找到最后一个节点
if (data.next == null){
break;
}
data = data.next;
}
data.next = node;
node.pre = data;
}
/**
* 修改节点
*/
public void upData(HeroNode2 node){
if(head == null || head.next == null) {
System.out.println("链表为空~~~");
return;
}
HeroNode2 data = head.next;
// 是否匹配到修改的标识
boolean isSelecte = false;
while (true){
if(data.next == null && data.no != node.no) {
System.out.println("没有找到匹配对象~~~");
break;
}
if(data.no == node.no) {
isSelecte = true;
break;
}
data = data.next;
}
if(isSelecte) {
data.setName(node.getName());
data.setNickName(node.getNickName());
}
}
/**
* 查询双链表数据
*/
public void showTowLinkData(){
if(head == null || head.next == null) {
System.out.println("链表为空~~~");
return;
}
HeroNode2 data = head.next;
while (data != null){
System.out.println(data);
data = data.next;
}
}
/**
* 双向链表删除某一个
*/
public void deleteLink(int no){
System.out.println("~~~~~~~~~~~~~~~~~~~~~");
if(head == null || head.next == null) {
System.out.println("双链表为空哦~~~");
return;
}
HeroNode2 data = head.next;
while (data != null){
if(data.no == no) {
// 说明删除的这个节点肯定是最后一个
if(data.next == null) {
data.pre.next = null;
}else {
// 正常删除
data.pre.next = data.next;
data.next.pre = data.pre;
}
break;
}
data = data.next;
}
}
}
/**
* 链表节点
*/
class HeroNode2{
/** 排名*/
public int no;
/** 英雄人物*/
public String name;
/** 英雄代号*/
public String nickName;
public com.example.suanfa.lianbiao.HeroNode2 next;
public com.example.suanfa.lianbiao.HeroNode2 pre;
public HeroNode2 getPre() {
return pre;
}
public void setPre(HeroNode2 pre) {
this.pre = pre;
}
public HeroNode2(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
public com.example.suanfa.lianbiao.HeroNode2 getNext() {
return next;
}
public void setNext(com.example.suanfa.lianbiao.HeroNode2 next) {
this.next = next;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode2{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
约瑟夫问题:
/**
* Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,
* 数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,
* 依次类推,直到所有人出列为止,由此 产生一个出队编号的序列。
*
*
* 用一个不带头结点的循环链表来处理 Josephu 问题:先构成一个有 n 个结点的单循环链表,
* 然后由 k 结点起从 1 开 始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个
*/
public static void main(String[] args) {
SingLink singLink = new SingLink();
// 约瑟夫问题
BoyNode head = singLink.creatRingLink(5);
singLink.outQueue(1,2,head,5);
singLink.showRingLink(head);
}
class SingLink{
/**
* 环形链表 约瑟夫问题
* 创建环形链表 num: 环形链表的数量
* 没有头节点
*/
public BoyNode creatRingLink(int num){
if(num <= 0) {
System.out.println("创建的环形队列长度异常~~");
return null;
}
// 定义一个当前执行的节点,比较好操作
BoyNode curNode = null;
BoyNode head = null;
for(int i = 0; i < num; i++) {
BoyNode boy = new BoyNode(i + 1);
if(i == 0) {
// 当前的节点
curNode = boy;
// 标记第一个
head = boy;
// 如果是一个环形指向自己
curNode.setNext(boy);
}else {
// 当前的节点指向心节点
curNode.setNext(boy);
// 新节点指向第一个
boy.next = head;
// 更新当前节点
curNode = boy;
}
}
return head;
}
/**
* 遍历环形链表
*/
public void showRingLink(BoyNode node){
if(node == null || node.next == null) {
System.out.println("您输出的为空环形链表~~");
return;
}
BoyNode head = node;
while (head.next != null){
System.out.println(head);
if(head.next == node) {
// 最后一个链表的下一个指向的是头,就退出
break;
}
head = head.next;
}
}
/**
* 出队列
* num: 出队列的数字
* start: 第几个人开始数
* sum: 圈里坐的人数
* 思路:
* 1、先创建一个辅助指针,helper指针,指向最后一个节点
* 2、然后将first和helper指针移动到start - 1第几个人开始数
* 3、让first和helper移动 num - 1 次
* 4、让first 指向 下一个节点 , helper再次指向first
* first = first.next helper.next = frist
*/
public void outQueue(int start,int num, BoyNode first, int sum){
if(num <= 0 || start < 1 || start > sum) {
System.out.println("输入出队的数字不对");
return;
}
if(first == null || first.next == null) {
System.out.println("链表为空~~~");
return;
}
// 1、创建辅助指针,并指向最后一个节点
BoyNode helperNode = null;
BoyNode cur = first;
while(cur.next != null) {
if(cur.next == first) {
break;
}
cur = cur.next;
}
helperNode = cur;
// 2、移动到要数的节点位置
for(int i = 0; i < start - 1; i++) {
first = first.next;
helperNode = helperNode.next;
}
// 3、移动num - 1步,然后变换指针
while (true){
if(first == helperNode) {
System.out.println("只剩下一个...");
break;
}
for(int i = 0; i < num - 1; i++) {
first = first.next;
helperNode = helperNode.next;
}
System.out.printf("小孩%d 出圈\n", first.getNo());
// 变换指针
first = first.next;
helperNode.next = first;
}
System.out.printf("最后留在圈中的小孩编号%d \n", first.getNo());
}
}
class BoyNode{
public int no;
public BoyNode next;
public int getNo() {
return no;
}
public BoyNode(int no) {
this.no = no;
}
public void setNo(int no) {
this.no = no;
}
public void setNext(BoyNode next) {
this.next = next;
}
@Override
public String toString() {
return "BoyNode{" +
"no=" + no +
'}';
}
}