数据结构和算法--3链表(单向链表、双向链表、环形单向链表和约瑟夫问题)
链表
链表是以节点的方式来存储 每个节点包含data域和next域,指向下一个节点 链表的各个节点不一定是连续存储 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单向列表
最大特点是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查等功能。
单链表分为两种:有头链表和无头链表。
有头节点的增删改查
定义一个单链表的类:
//定义一个SingleLinkedList,单链表,管理HeroNode
class SingleLinkedList{
//初始化一个头节点,没有具体的值,只是表示是单链表的开头
private HeroNode head = new HeroNode(0, "", "");无序添加图解和思路:


代码实现:
//向单链表中添加节点
//1不考虑编号顺序时!!
//找到当前链表的末节点,末节点的next指向新节点
public void addNode(HeroNode heroNode) {
//头节点不能动,需要一个辅助节点去进行遍历,代表的是指向哪个节点
HeroNode temp = head;
//遍历链表,找到最后
while(true) {
//当头节点后面没有值,说明已经到达单链表末尾
if(temp.next == null) {
break;
}
//当没有到单链表末尾,每循环一次就把指针向后移动一位
temp = temp.next;
}
//当跳出循环,说明一定temp已经指向单链表末尾
//temp.next指向传入的heroNode节点
temp.next = heroNode;
}有序添加图解和思路:


代码实现:
//向单链表中添加节点
//2考虑编号顺序,根据排名把节点添加到指定位置!!
public void addOrder(HeroNode heroNode) {
//同样需要一个辅助节点来帮助找到要添加的位置
//因为是单链表,temp表示的是添加位置的前一个节点,然后把temp.next指向heroNode
HeroNode temp = head;
//flag标志要添加的编号在链表中是否已经存在
boolean flag = false;
while(true) {
//表示已经到链表的末尾
if(temp.next == null) {
break;
}
//如果辅助节点的下个节点的编号比新节点的大,新节点放在辅助节点的后面(辅助节点下一个节点的前面)
if(temp.next.no > heroNode.no) {
break;
//如果辅助节点的下一个节点编号与新节点相同,说明已存在
}else if(temp.next.no == heroNode.no){
flag = true;
break;
}
//如果没有找到,辅助节点向后移动一位
temp = temp.next;
}
//判断flag的值来向控制台输出语句
if(flag) {
System.out.printf("您输入的编号为%d的英雄已存在,不能重复添加\n",heroNode.no);
}else {
//把辅助节点的后一个节点绑定到新节点的后面
heroNode.next = temp.next;
//把新节点放到辅助节点的后一个节点上
temp.next = heroNode;
}
}修改节点信息的代码实现:
//修改节点信息,根据no编号来修改
public void updateNode(HeroNode heroNode) {
//判断是否为空
if(head.next == null) {
System.out.println("当前链表为空");
return;
}
//不为空时,通过辅助节点依次遍历
HeroNode temp = head.next;
//判断是否找到节点
boolean flag = false;
while(true) {
//如果辅助节点后面为空,说明已经到了链表末尾
if(temp.next == null) {
break;
}
//如果辅助节点编号等于给定节点的编号,找到了
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);
}
}删除节点思路:
1.我们先找到需要删除的这个节点的前一个节点 2.temp.next = temp.next.next 3.被删除的节点没有其他引用指向,会被垃圾回收机制回收
代码实现:
//删除节点
public void delete(int no) {
//头节点不能动,需要一个辅助节点找到要删除节点的前一个节点
HeroNode temp = head;
//是否找到要删除节点的前一个节点
boolean falg = false;
while(true) {
//表示已经遍历到链表末尾
if(temp.next == null) {
break;
}
//如果temp.next.no==no,此时temp节点就是要删除节点的前一个节点
if(temp.next.no == no) {
falg = true;
break;
}
temp = temp.next;
}
if(falg) {
temp.next = temp.next.next;
}else {
System.out.printf("要删除的%d节点不存在\n",no);
}
}遍历节点:
//显示单链表的数据,通过遍历
public void list() {
//如果头节点后面没有值,链表为空
if(head.next == null) {
System.out.println("当前链表为空");
return;
}
//通过辅助变量来遍历
HeroNode temp = head.next;
while(true) {
//当temp为空,说明已经到达单链表末尾
if(temp==null) {
break;
}
//输出节点的信息
System.out.println(temp);
//将temp后移一位
temp = temp.next;
}
}
}创建节点类
//定义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 + "]";
}对单向链表进行测试:
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 singleLinkedList = new SingleLinkedList();
//不考虑排名,直接放入节点
// singleLinkedList.addNode(hero1);
// singleLinkedList.addNode(hero2);
// singleLinkedList.addNode(hero3);
// singleLinkedList.addNode(hero4);
//考虑排名问题,按顺序添加
singleLinkedList.addOrder(hero4);
singleLinkedList.addOrder(hero2);
singleLinkedList.addOrder(hero1);
singleLinkedList.addOrder(hero3);
singleLinkedList.list();
//修改节点信息
HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.updateNode(newHeroNode);
// 遍历链表
singleLinkedList.list();
//删除某个节点
singleLinkedList.delete(1);
singleLinkedList.list();
}单向链表练习题
1获取单链表中有效节点的个数
代码实现
//获取单链表中有效节点的个数
//head 链表的头节点
public int getLength(HeroNode head) {
//如果头节点后面为空,则这个一个空链表
if(head.next == null) {
return 0;
}
int length = 0;
//需要一个辅助节点来帮忙遍历链表
HeroNode temp = head.next;
//如果辅助节点的下一个节点不为空,有效节点个数+1;
while(temp!= null) {
length++;
temp = temp.next;
}
return length;
}测试类:
//获取单链表中节点个数
//获取头节点
HeroNode head = singleLinkedList.getHead();
int length = singleLinkedList.getLength(head);
System.out.println("获取到的节点个数为"+length);2查找单链表中倒数第k个节点
思路:
1.编写一个方法,接收head及节点,同时接收index 2.index表示的是倒数第index节点 3.先把链表全部遍历一遍,得到有效的节点个数length(链表的长度) 4length-index就是这个倒数第k个节点的位置 5.找到就返回节点信息,找不到返回空
代码实现:
//查找单链表中倒数第k个节点
public HeroNode findLastIndexNode(HeroNode head,int index) {
//如果头节点后为空,这是一个空链表
if(head.next == null) {
return null;
}
//拿到链表中所有节点的个数(链表的长度)
int size = getLength(head);
//查找倒数第k个节点(size-index)的位置
//判断index是否是合法数据
if(index <= 0 || index > size) {
return null;
}
//通过辅助节点帮我们遍历到倒数第k个节点的位置,temp初始值为第一个有效节点
HeroNode temp = head.next;
//需要遍历的次数
for(int i = 0;i < (size-index);i++) {
temp = temp.next;
}
return temp;
}测试类
//查找单链表中倒数第k个节点
HeroNode res = singleLinkedList.findLastIndexNode(head, 2);
System.out.println("倒数的节点信息为"+res);3反转单链表
思路:
1.先定义一个节点 HeroNode reverseHead = new HeroNode() 2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端 3.原来的链表head.next = reverseHead.next
代码实现:
//单向链表反转
public void reverseList(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;
//然后现在再把新链表的后一个节点指向cur
reverseHead.next = cur;
//指针向后移动一位接着遍历
cur = next;
}
//当跳出while循环说明已经都反转了,再把原链表的后一个节点直接等于新链表的后一个节点,得出的数据还是在原链表下
head.next = reverseHead.next;
}测试类
HeroNode head = singleLinkedList.getHead();
singleLinkedList.reverseList(head);
singleLinkedList.list();4倒序打印单链表
思路:
方式一: 先将单链表进行反转操作,然后再进行遍历即可,但是这样会破坏原来单链表的访问,不推荐 方拾二: 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果
代码打印:
//逆序打印
//利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果
public 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;
}
//从栈中取值
while(stack.size() >0) {
System.out.println(stack.pop());
}
}测试类
//倒序打印链表
System.out.println("倒序打印链表");
singleLinkedList.reversePrint(head);5合并两个有序的单链表,合并之后的链表依然有序
双向列表
双向链表的遍历,添加,修改,删除的思路
添加节点(默认添加到双向链表的最后)
思路
先找到双向链表的最后节点 temp.next = newHeroNode newHeroNode.pre = temp
无序添加
//1不考虑编号顺序时!!
//找到当前链表的末节点,末节点的next指向新节点
public void addNode(HeroNode2 heroNode) {
//头节点不能动,需要一个辅助节点去进行遍历,代表的是指向哪个节点
HeroNode2 temp = head;
//遍历链表,找到最后
while(true) {
//当头节点后面没有值,说明已经到达单链表末尾
if(temp.next == null) {
break;
}
//当没有到单链表末尾,每循环一次就把指针向后移动一位
temp = temp.next;
}
//当跳出循环,说明一定temp已经指向单链表末尾
//temp.next指向传入的heroNode节点,heroNode的前一个节点是temp,反指向回temp,完成双向链接
temp.next = heroNode;
heroNode.pre = temp;
}有序添加
//2考虑编号顺序,根据排名把节点添加到指定位置!!
public void addOrder(HeroNode2 heroNode) {
//同样需要一个辅助节点来帮助找到要添加的位置
HeroNode2 temp = head;
//flag标志要添加的编号在链表中是否已经存在
boolean flag = false;
while(true) {
//表示已经到链表的末尾
if(temp.next == null) {
break;
}
//如果辅助节点的下个节点的编号比新节点的大,新节点放在辅助节点的后面(辅助节点下一个节点的前面)
if(temp.next.no > heroNode.no) {
break;
//如果辅助节点的下一个节点编号与新节点相同,说明已存在
}else if(temp.next.no == heroNode.no){
flag = true;
break;
}
//如果没有找到,辅助节点向后移动一位
temp = temp.next;
}
//判断flag的值来向控制台输出语句
if(flag) {
System.out.printf("您输入的编号为%d的英雄已存在,不能重复添加\n",heroNode.no);
}else {
// 把heroNode绑定到temp的后面
heroNode.next = temp.next;
if(temp.next != null) {
temp.next.pre = heroNode;
}
//双向链表,heroNode反向绑定
temp.next = heroNode;
heroNode.pre = temp;
}
}修改和遍历与单向链表思路一样
删除节点
思路:
双向链表可以实现自我删除,不用再去找它的前一个节点 直接找到要删除的节点temp temp.pre.next= temp.next temp.next.pre = temp.pre
//删除节点
public void delete(int no) {
//头节点不能动,需要一个辅助节点找到要删除节点的前一个节点
HeroNode2 temp = head.next;
if(temp == null) {
System.out.println("此双向链表是个空链表");
return;
}
//是否找到要删除节点的前一个节点
boolean falg = false;
while(true) {
//表示已经遍历到链表末尾
if(temp == null) {
break;
}
//如果temp.no==no,此时temp节点就是要删除的节点
if(temp.no == no) {
falg = true;
break;
}
temp = temp.next;
}
if(falg) {
temp.pre.next = temp.next;
//判断是否删除的是最后一个节点,因为最后一个节点的的temp.next为空没有pre属性,会报空指针
if(temp.next != null) {
temp.next.pre = temp.pre;
}
}else {
System.out.printf("要删除的%d节点不存在\n",no);
}
}环形单向链表

构建单向环形链表
思路:
1.先创建一个节点,让first指向该节点,并形成环形 2后面每创建一个节点,就把该节点加入到已有的环形链表中
代码实现:
//创建一个小孩类
class Child{
//小孩编号
private int no;
//下一个小孩
private Child next;
//有参构造
public Child(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Child getNext() {
return next;
}
public void setNext(Child next) {
this.next = next;
}
@Override
public String toString() {
return "Child [no=" + no + ", next=" + next + "]";
}//创建一个first节点,后面会用到
private Child first = null;
//添加小孩节点,参数nums表示总共有多少个小孩
public void add(int nums) {
//判断nums的符合条件
if(nums < 1) {
System.out.println("您输入的数字不符合环形单向链表的条件");
return;
}
//因为第一个节点不能动,需要一个辅助节点帮忙遍历
Child cur = null;
//当输入的nums符合条件时,for循环创建环形单向链表
for(int i = 1;i <= nums;i++) {
Child child = new Child(i);
//如果是第一个节点
if(i == 1) {
first = child;//第一个节点不动
first.setNext(first);//环形需要自己指向自己
cur = first;//让辅助节点指向第一个节点
}else {
cur.setNext(child);//辅助节点就代表当前的那个节点
child.setNext(first);//每添加后都要指向第一个节点形成环形链表
cur = child;//辅助指针后移
}
}遍历环形链表
思路:
1先让辅助指针cur指向first节点 2通过while循环遍历该环形链表,当cur.next = first结束
代码实现:
//遍历环形链表
public void showChild() {
//判断链表是否为空
if(first == null) {
System.out.println("当前环形链表为空");
return;
}
//遍历时仍需借助辅助节点进行遍历
Child cur = first;
while(true) {
System.out.printf("小孩编号为%d\n",cur.getNo());
//判断是否是最后一个节点
if(cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
}约瑟夫问题
什么是约瑟夫问题?

设编号为1,2...n的n个人围坐一圈,约定编号k(1<= k <= n)的人从1开始报数,数到m的那个人出列,它的下一位从1开始报数,数到m的那个人再次出列,依次循环直到所有人出列为止,由此产生一个出队编号的序列
思路:
k=?代表从第几个人开始数数 m=?代表数几下 n=?代表总共有多少人 1需要创建一个辅助指针helper,事先应该让辅助指针指向环形链表的最后一个节点 2让first和helper移动k-1的位置,first表示从哪个人开始数,helper依旧在最后一个节点 3让first和helper同时移动m-1次, 4让first指向的人出圈 5需要把first向后移动一位,然后由helper指向first,这样要出圈的人没有人指向它,会被回收
代码实现:
//约瑟夫问题,小孩出圈,startNo从第几个人开始数数,countNum数几下,nums总多少人
public void outChild(int startNo,int countNum,int nums) {
add(nums);
//判断是否符合条件
if(startNo < 1 ||first == null || startNo > nums) {
return;
}
//1需要创建一个辅助指针helper,事先应该让辅助指针指向环形链表的最后一个节点
Child helper = first;
while(true) {
if(helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
//2让first和helper移动k-1的位置,first表示从哪个人开始数,helper依旧在最后一个节点
for(int j = 0;j < startNo-1;j++) {
first = first.getNext();
helper = helper.getNext();
}
//3让first和helper同时移动m-1次
while(true) {
//如果只剩下一个人
if(first == helper) {
break;
}
for(int j = 0;j < countNum - 1;j++) {
first = first.getNext();
helper = helper.getNext();
}
//4让first指向的人出圈
System.out.printf("小孩%d出圈\n",first.getNo());
//5.需要把first向后移动一位,然后由helper指向first,这样要出圈的人没有人指向它,会被回收
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的人是%d\n",first.getNo());
}。
相关推荐
koushr 2020-11-12
范范 2020-10-28
qiangde 2020-09-13
范范 2020-07-30
mingyunxiaohai 2020-07-19
OldBowl 2020-06-16
muhongdi 2020-05-19
zhaochen00 2020-10-13
Mars的自语 2020-09-27
steeven 2020-09-18
kka 2020-09-14
聚沙成塔积水成渊 2020-08-16
earthhouge 2020-08-15
aanndd 2020-08-12
bluetears 2020-07-28
horizonheart 2020-07-19
liushall 2020-07-18
bluetears 2020-07-05
fengshantao 2020-07-02