五、链表(Linked-List)
5.1 单链表
链表(linked list)有序列表,但是存储不连续
特点:
- 以节点形式存储
- 每个节点包括data域,next域:指向下一节点
- 链表的各个节点不一定时连续存放的
- 链表分带头节点的链表和无头节点的链表,根据实际需求确定
5.1.1 应用实例
使用带head节点的单向链表实现——水浒英雄排行榜管理
- 完成对英雄人物的增删改查操作
- 添加时两种方法,直接添加至链表尾部,或插入至指定位置
分析:
-
创建英雄节点(class,存放信息),创建头节点时,不存放数据表示单链表的头
/** * 定义一个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; } public HeroNode() { } @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
-
添加功能(不考虑顺序)
- 先创建head节点,作用是表示单链表的头
- 后面没添加一个节点,就直接加入到链表的尾部
/** * 添加节点到单项链表 * 思路当不考虑编号顺序是 * 1.找到链表的最后一个节点 * 2.将最后节点的next指向新节点 * @param heroNode */ public void add(HeroNode heroNode){ //定义一个辅助变量 HeroNode temp = head; //遍历链表,找到最后 while (true){ //找到链表最后 if (temp.next == null){ break; } //如果没有找到temp后移 temp = temp.next; } //退出循环找到最后的节点 //将最后这个节点指向新的节点 temp.next = heroNode; }
-
添加功能(考虑顺序)
- 首先找到新添加节点的位置,通过辅助变量
- 新的节点.next = temp.next
- 将temp.next = 新的节点
/** * 添加功能考虑顺序 * @param heroNode */ public void addOrder(HeroNode heroNode){ //定义辅助变量 HeroNode temp = head; //标志添加编号是否存在,默认为false boolean flag = false; while (true){ if (temp.next == null){//已经在链表最后 break; } if (temp.next.no>heroNode.no){ //位置找到了,在temp后插入 break; }else if (temp.next.no == heroNode.no){ //数据已存在 flag = true; break; } temp = temp.next;//后移,遍历当前链表 } //判断flag的值 if (flag == true){ //不能添加,编号存在 System.out.println("编号存在:"+temp.next.no+","+temp.next.name); }else { heroNode.next = temp.next; temp.next = heroNode; } }
-
**遍历:**通过辅助变量,遍历单链表
-
修改功能
编号不变,昵称等其他信息改变
/** * 根据根节点的id修改相应的值 * @param heroNode */ public void update(HeroNode heroNode){ HeroNode temp = head.next; while (true){ if (temp.no == heroNode.no){ break; } temp = temp.next; } temp.name = heroNode.name; temp.nickname = heroNode.nickname; }
-
删除节点
/** * 根据id删除节点 * @param id */ public void delete(int id){ HeroNode temp = head; while (true){ if (temp.next.no == id){ break; } temp = temp.next; } temp.next = temp.next.next; }
-
清空链表
/** * 清空链表 */ public void clear(){ HeroNode temp = head; temp = temp.next = null; }
5.1.2 单链表的面试题
-
求单链表中节点的个数
/** * 求单链表中节点的个数 * 遍历单链表 * 计数器 */ public void getLen(){ HeroNode temp = head; int count = 0; while (true){ if (temp.next==null){ break; } temp = temp.next; count++; } System.out.println("共有"+count+"个节点!"); }
-
查找单链表中倒数第k个节点
/** * 查找单链表倒数第k个节点 * 思路: * 链表 从头到尾遍历 * 链表长度-id,结尾查找的节点 * @param id */ public HeroNode selectByID(int id){ int len = getLen(); HeroNode temp = head.next; for (int i = 0; i < len-id; i++) { temp = temp.next; } return temp; }
-
单链表的反转
/** * 反转链表 * 思路: * 1.定义一个节点reverseHead = new HeroNode() * 2.从头到尾遍历原理啊的链表,每遍历一个节点,将其取出,并放在新的链表的最前端 * 3.原来的链表的head.next=reverseHead.next */ public void reverse(){ HeroNode reverseHead = new HeroNode(); HeroNode temp1 = head.next; //指向当前节点的下一节点 HeroNode next = null; if (temp1 == null || temp1.next == null){ return ; } //遍历原来的链表,每遍历一个节点,将其取出,并放在新的链表的最前端 while (temp1 != null){ next = temp1.next;//暂时保存当前节点的下一节点 temp1.next = reverseHead.next;//将temp1的下一节点指向新链表的最前端 reverseHead.next = temp1;//将temp1连接到新的链表上 temp1 = next;//后移一次 } //将head.next指向reverseHead.next,实现反转 head.next = reverseHead.next; } }
-
从尾到头打印单链表【要求方式1:反向遍历。方式2:Stack栈】
/** * 从尾到头打印单链表 * 思路一: * 先将单链表反转,然后遍历 * 缺点:破坏原来单链表的结构 * 思路二: * 利用栈,压栈后,利用栈先进后出的特点,实现逆序打印 * */ public void reversePrint(){ //创建栈 Stack<HeroNode> stack = new Stack<>(); HeroNode temp = head.next; if (temp == null){ return; } while (temp != null){ stack.push(temp); temp = temp.next; } while (stack.size()>0){ System.out.println(stack.pop()); } } }
-
合并两个有序的单链表,合并之后链表依然有序
5.2 双向链表
package com.why.linkedlist;
/**
* @Description TODO 双向链表创建,功能和测试类
* @Author why
* @Date 2020/10/19 10:23
* Version 1.0
**/
import java.util.List;
/**
* 测试类
*/
public class BidirectionalListDemo {
public static void main(String[] args) {
BidirectionalList bl = new BidirectionalList();
ListNode node1 = new ListNode(1,"lrx","123");
ListNode node2 = new ListNode(2,"cjl","123");
ListNode node3 = new ListNode(3,"why","123");
ListNode node4 = new ListNode(3,"wl","123");
bl.insert(node1);
bl.insert(node2);
bl.insert(node3);
System.out.println("原数据");
bl.list();
System.out.println("修改");
System.out.println();
bl.update(node4);
bl.list();
System.out.println("删除");
System.out.println();
bl.delete(3);
bl.list();
}
}
/**
* 功能实现类
*/
class BidirectionalList{
//建立头节点
ListNode head = new ListNode(0,"","");
/**
* 判空
* @return
*/
public boolean isEmpty(){
if (head.next == null){
System.out.println("链表为空");
return true;
}
return false;
}
/**
* 从头至尾遍历,显示
* @return
*/
public void list(){
boolean empty = isEmpty();
ListNode temp = head.next;
if (empty == true){
return;
}
while (temp!=null){
System.out.println(temp);
temp = temp.next;
}
}
/**
* 添加到双向链表的最后
* @param node
*/
public void insert(ListNode node){
ListNode temp = head;
while (temp.next != null){
temp = temp.next;
}
temp.next = node;
node.pre = temp;
}
/**
* 添加到指定位置之后
*/
public void insertSpecified(ListNode node,int id){
ListNode temp = head.next;
while (true){
if (temp.no == id){
node.next = temp.next;
temp.next.pre = node;
temp.next = node;
node.pre = temp;
break;
}
temp = temp.next;
}
}
/**
* 修改数据,根据no查找
* @param node
*/
public void update(ListNode node){
boolean empty = isEmpty();
if (empty){
return;
}
ListNode temp = head.next;
while (true){
if (temp.no == node.no){
break;
}
temp = temp.next;
}
temp.name = node.name;
temp.password = node.password;
}
/**
* 删除节点
* 对于双向链表可直接找到要删除的节点,
* 找到后自我删除即可
* @param id
*/
public void delete(int id){
//判断当前链表是否为空
if (isEmpty()){
System.out.println("不存在");
return;
}
ListNode temp = head.next;
while (true){
if (temp == null){
return;
}
if (temp.no == id){
break;
}
temp = temp.next;
}
temp.pre.next = temp.next;
//这里有问题,如果最后一个节点不需要执行,否则会出现空指针异常
if (temp.next !=null){
temp.next.pre = temp.pre;
}
}
}
/**
* 节点类
*/
class ListNode{
public int no;
public String name;
public String password;
public ListNode pre;
public ListNode next;
public ListNode(int no, String name, String password) {
this.no = no;
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "ListNode{" +
"no=" + no +
", name='" + name + '\'' +
", password='" + password + '\''+
'}';
}
}
5.3 环形链表和约瑟夫问题(约瑟夫环)
5.3.1 约瑟夫(Josephu)问题
设编号为1,2,…,n的n个人围坐一圈,约定编号weik(1<=k<=n)的人从1开始报数,疏导m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,依此类推,直到所有人出列为止,由此产生一个出队编号的序列
解决思路:
使用环形链表
5.3.2 环形列表
创建环形链表思路:
- 创建第一个节点,让first指向该节点,并形成环形
- 后面每创建一个新的节点,就把该节点加入到已有的环形链表中
遍历环形链表思路:
- 先让辅助指针temp,指向first节点
- 然后通过while循环遍历该环形链表即可
- 循环结束条件temp.next = =first
删除指定位置节点
- 根据用户输入id,删除
- 找到节点
- temp.pre.next = temp.next
- temp.next.pre = temp.pre
代码实现
package com.why.linkedlist;
import java.awt.*;
/**
* @Description TODO 环形链表节点,功能实现,测试类
* @Author why
* @Date 2020/10/20 10:07
* Version 1.0
**/
public class RingLinkedListDemo {
public static void main(String[] args) {
RingLinkedList rl = new RingLinkedList();
rl.add(20);
System.out.println("原数据");
rl.list();
rl.delete(5);
System.out.println("删除后:");
rl.list();
}
}
/**
* 功能实现类
*/
class RingLinkedList{
//创建first节点目前不要赋值,当前无编号
private RingNode first = new RingNode();
/**
* 添加节点
* @param nums
*/
public void add(int nums){
//nums做数据校验
if (nums<1){
System.out.println("数据不合法!!!");
return;
}
RingNode temp = null;//辅助指针帮助构建环形链表
//使用循环创建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号创建节点
RingNode node = new RingNode(i);
//第一个节点,赋值,,形成环状
if (i == 1){
first = node;
first.next = first;
first.pre = first;
temp = first;
}else {
temp.next = node;
first.pre = node;
node.pre = temp;
node.next = first;
temp = node;
}
}
}
/**
* 遍历打印
*/
public void list(){
//判空
if (first == null){
System.out.println("链表空");
return;
}
RingNode temp = first;
while (true){
System.out.println(temp);
if (temp.next == first){
break;
}
temp = temp.next;
}
}
public void delete(int id){
RingNode temp = first;
while (true){
if (id == temp.no){
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
System.out.println("出队的:"+temp);
break;
}
temp = temp.next;
}
}
}
/**
* 节点类
*/
class RingNode{
public int no;
public RingNode next;
public RingNode pre;
public RingNode(int no) {
this.no = no;
}
public RingNode() {
}
@Override
public String toString() {
return "RingNode{" +
"no=" + no +
'}';
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}