一、单向链表
1.链表(Linked List)介绍
链表是有序的列表,但是它在内存中是存储如下
(1)链表是以节点的方式来存储,是链式存储。
(2)每个节点包含data域,next域:指向下一个节点。
(3)如图:发现链表的各个节点不一定连续。
(4)链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定。
(5)单链表(带头节点)逻辑结构示意图如下:
2.单链表的应用实例
使用带head头的单向链表实现–水浒英雄排行榜管理完成对英雄人物的增删改查操作。
(1)第一种方法在添加英雄时,直接添加到链表的尾部。
示例图
代码实现:
class SingleLinkedList{
//先初始化一个头节点,头节点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
/*
* 添加节点到链表中,添加到链表的最后
* 1.首先要遍历找到链表的最后一个节点
* 2.将最后节点的next域指向新的节点
*/
public void add(HeroNode node){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
//遍历,当节点的next域为null的时候表示链表到最后了
while (temp.getNext() != null){
temp = temp.getNext();
}
temp.setNext(node);
}
//遍历链表
public String showLinkedList(){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
StringBuffer container = new StringBuffer();
container.append("{");
if (temp.getNext() == null){
container.append("}");
return container.toString();
}
//当节点的next域为null的时候表示链表到最后了,停止遍历
while (temp.getNext() != null){
temp = temp.getNext();
container.append(temp.toString());
if (temp.getNext() == null){
container.append("}");
break;
}
container.append(",");
}
return container.toString();
}
}
//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
private Integer no;
private String name;
private String nickName;
private HeroNode next; //指向下一个节点
public HeroNode(){}
public HeroNode(Integer no,String name,String nickName){
this.no = no;
this.name = name;
this.nickName = nickName;
}
public HeroNode(Integer no,String name,String nickName,HeroNode next){
this.no = no;
this.name = name;
this.nickName = nickName;
this.next= next;
}
public Integer getNo() {
return no;
}
public void setNo(Integer 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;
}
public HeroNode getNext() {
return next;
}
public void setNext(HeroNode next) {
this.next = next;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName +
'}';
}
}
(2)第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
代码实现:
class SortableLinkedList{
//先初始化一个头节点,头节点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
/*
* 添加节点到链表中,按英雄排名顺序添加,如果有这个排名,这抛出异常
* 1.首先找到新添加的节点的位置
* 2.新节点.next=temp.next。
* 3.temp.next=新节点
*/
public void add(HeroNode node){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
//遍历,找到新添加节点的位置
while (temp.getNext() != null && (temp.getNext().getNo() <= node.getNo())){
if (temp.getNext().getNo() == node.getNo()){
throw new RuntimeException("已有英雄排名,无法插入");
}
temp = temp.getNext();
}
//新节点.next=temp.next。
node.setNext(temp.getNext());
//temp.next=新节点
temp.setNext(node);
}
//遍历链表
public String showLinkedList(){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
StringBuffer container = new StringBuffer();
container.append("{");
if (temp.getNext() == null){
container.append("}");
return container.toString();
}
//当节点的next域为null的时候表示链表到最后了,停止遍历
while (temp.getNext() != null){
temp = temp.getNext();
container.append(temp.toString());
if (temp.getNext() == null){
container.append("}");
break;
}
container.append(",");
}
return container.toString();
}
}
(3)修改英雄的名字和昵称
/*
* 修改节点的信息,根据no编号来修改,即no编号不能修改,只修改name和nickName
*
*/
public boolean update(HeroNode node){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
//判断链表是否为空
if (head.getNext() == null){
throw new RuntimeException("链表为空");
}
//遍历链表
while (temp.getNext() != null){
temp = temp.getNext();
//如果no值相等,则修改英雄的信息
if (temp.getNo() == node.getNo()){
temp.setName(node.getName());
temp.setNickName(node.getNickName());
return true;
}
}
//如果遍历完了,还没有匹配的英雄则抛出异常
throw new RuntimeException("没有匹配的英雄");
}
(4)删除英雄节点
//删除节点信息
public boolean delete(int no){
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode temp = head;
//判断链表是否为空
if (head.getNext() == null){
throw new RuntimeException("链表为空");
}
//遍历链表,我们先找到需要删除的这个节点的前一个节点temp
while (temp.getNext() != null){
//如果no值相等,则删除英雄的信息
if (temp.getNext().getNo() == no){
//temp.next=temp.next.next
temp.setNext(temp.getNext().getNext());
return true;
}
temp = temp.getNext();
}
//如果遍历完了,还没有匹配的英雄则抛出异常
throw new RuntimeException("没有匹配的英雄");
}
单链表面试种常见的面试题
//方法:计算单链表的节点的个数(如果是带头节点,需求不统计头节点)
public int size(HeroNode node) {
HeroNode temp = node;
int size = 0;
//首先,判断是否是带有头节点的链表
//带头节点
if (temp.getNo() == null) {
while (temp.getNext() != null) {
temp = temp.getNext();
size++;
}
return size;
}
//不带头节点
do {
size++;
temp = temp.getNext();
} while (temp != null);
return size;
}
//查找单链表中倒数第k个节点
public HeroNode findNode(HeroNode node, int k) {
HeroNode temp = node;
int size = size(node);
int index = size - k;
if (index < 0) {
throw new RuntimeException("超出链表中元素的个数");
}
//首先,判断是否是带有头节点的链表
//带头节点
if (temp.getNo() == null) {
while (index >= 0 && temp.getNext() != null) {
temp = temp.getNext();
index--;
}
return temp;
}
//不带头节点
do {
index--;
if (index < 0) {
return temp;
}
temp = temp.getNext();
} while (temp != null);
return temp;
}
//反转链表的第一种方法
public void reverseLinkedList(HeroNode head) {
int size = size(head);
HeroNode[] nodes = new HeroNode[size];
for (int i = 1; i <= size; i++) {
nodes[i-1] = findNode(head,i);
}
HeroNode node = nodes[0];
for (int i = 1; i < size; i++) {
node.setNext(nodes[i]);
node = nodes[i];
}
node.setNext(null);
head.setNext(nodes[0]);
}
//反转链表的第二种方法
public void reverseLinkedList2(HeroNode head) {
HeroNode reverseHead = new HeroNode();
HeroNode tempReverse = reverseHead;
HeroNode temp = head;
while (temp.getNext() != null) {
while (temp.getNext().getNext() != null) {
temp = temp.getNext();
}
tempReverse.setNext(temp.getNext());
tempReverse = temp.getNext();
temp.setNext(null);
temp = head;
}
tempReverse.setNext(null);
head.setNext(reverseHead.getNext());
}
//反转链表的第三种方法
public void reverseLinkedList3(HeroNode head) {
HeroNode reverseHead = new HeroNode();
HeroNode temp = head.getNext();
HeroNode next = null;
while (temp != null) {
next = temp.getNext();
temp.setNext(reverseHead.getNext());
reverseHead.setNext(temp);
temp = next;
}
head.setNext(reverseHead.getNext());
}
//逆向遍历单链表
public String reversePrint(HeroNode head){
int size = size(head);
HeroNode temp = head;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{");
if (temp.getNext() == null){
stringBuilder.append("}");
return stringBuilder.toString();
}
int j = -1;
for (int i = size-1; i >=0; i--) {
j = i;
while (j >= 0 && temp.getNext() != null){
temp = temp.getNext();
j--;
}
stringBuilder.append(temp.toString());
if (i == 0){
stringBuilder.append("}");
break;
}
stringBuilder.append(",");
temp = head;
}
return stringBuilder.toString();
}
二、双向链表
1.管理单链表的缺点分析:
1.单链表,查找的方向只能是一个方向,而双向链表可以向前或向后查找。
2.单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是找到需要删除节点的前一个节点。
2.双向链表的示例图:
3.双向链表的代码实现:
//双向链表
class DoubleLinkedList {
//初始化头节点
private HeroNode_D head = new HeroNode_D();
//遍历链表
public String showLinkedList() {
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode_D temp = head;
StringBuffer container = new StringBuffer();
container.append("{");
if (temp.getNext() == null) {
container.append("}");
return container.toString();
}
//当节点的next域为null的时候表示链表到最后了,停止遍历
while (temp.getNext() != null) {
temp = temp.getNext();
container.append(temp.toString());
if (temp.getNext() == null) {
container.append("}");
break;
}
container.append(",");
}
return container.toString();
}
//添加节点
public void add(HeroNode_D node) {
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode_D temp = head;
while (temp.getNext() != null) {
temp = temp.getNext();
}
temp.setNext(node);
node.setPre(temp);
}
//修改节点的信息,根据no编号来修改,即no编号不能修改,只修改name和nickName
public boolean update(HeroNode_D node) {
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode_D temp = head;
//判断链表是否为空
if (head.getNext() == null) {
throw new RuntimeException("链表为空");
}
//遍历链表
while (temp.getNext() != null) {
temp = temp.getNext();
//如果no值相等,则修改英雄的信息
if (temp.getNo() == node.getNo()) {
temp.setName(node.getName());
temp.setNickName(node.getNickName());
return true;
}
}
//如果遍历完了,还没有匹配的英雄则抛出异常
throw new RuntimeException("没有匹配的英雄");
}
//删除节点信息
public boolean delete(int no) {
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode_D temp = head;
//判断链表是否为空
if (head.getNext() == null) {
throw new RuntimeException("链表为空");
}
//遍历链表,我们先找到需要删除的节点
while (temp.getNext() != null) {
temp = temp.getNext();
//如果no值相等,则删除英雄的信息
if (temp.getNo() == no) {
temp.getPre().setNext(temp.getNext());
//要判断删除的节点是否是链表的最后一个节点,不然会发生空指针异常
if (temp.getNext() != null) {
temp.getNext().setPre(temp.getPre());
return true;
}
temp.setPre(null);
return true;
}
}
//如果遍历完了,还没有匹配的英雄则抛出异常
throw new RuntimeException("没有匹配的英雄");
}
/*
* 添加节点到链表中,按英雄排名顺序添加,如果有这个排名,这抛出异常
* 1.首先找到新添加的节点的位置
* 2.新节点.next=temp.next。
* 3.temp.next.pre=新节点
* 4.temp.next=新节点
* 5.新节点.pre=temp
*/
public void addS(HeroNode_D node) {
//因为head节点不能动,所有需要一个临时节点来遍历链表
HeroNode_D temp = head;
//遍历,找到新添加节点的位置
while (temp.getNext() != null && (temp.getNext().getNo() <= node.getNo())) {
if (temp.getNext().getNo() == node.getNo()) {
throw new RuntimeException("已有英雄排名,无法插入");
}
temp = temp.getNext();
}
//判断所添加的节点是否是第一个节点或者添加节点的位置是否在链表尾部
if (temp.getNext() == null){
temp.setNext(node);
node.setPre(temp);
return;
}
// 2.新节点.next=temp.next。
node.setNext(temp.getNext());
// 3.temp.next.pre=新节
temp.getNext().setPre(node);
// 4.temp.next=新节点
temp.setNext(node);
// 5. 新节点.pre = temp
node.setPre(temp);
}
}
//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode_D {
private Integer no;
private String name;
private String nickName;
private HeroNode_D next; //指向下一个节点
private HeroNode_D pre; //指向前一个节点
public HeroNode_D() {
}
public HeroNode_D(Integer no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
public Integer getNo() {
return no;
}
public void setNo(Integer 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;
}
public HeroNode_D getNext() {
return next;
}
public void setNext(HeroNode_D next) {
this.next = next;
}
public HeroNode_D getPre() {
return pre;
}
public void setPre(HeroNode_D pre) {
this.pre = pre;
}
@Override
public String toString() {
return "HeroNode_D{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
三、单向环形链表
1.单向环形链表的应用场景
Josephu(约瑟夫、约瑟夫环)问题:
josephu问题为:设编号为1,2,…n的n个人围成一圈,约定编号为k(1<=k<=n)的人开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
解决方法:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个节点的单向循环链表,然后由k节点从1开始计数,计到m时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除算法结束。
2.单向环形链表的示意图:
3.Josephu
约瑟夫问题的示意图:
约瑟夫问题-创建环形链表的思路图解:
约瑟夫问题-小孩出圈问题:
4.Josephu问题的代码实现
class Josephu {
//k=1,从第一个人开始报数
private static final Integer K = 1;
// m=2,数2下
private static final Integer M = 2;
//创建一个首节点
private HeroNode_J first = null;
//创建一个临时节点
private HeroNode_J curBoy = null;
/**
* 增加节点
* if (first == null){
* first = 插入的节点
* curBoy = 插入的节点
* 插入的节点.next = first
* }
* curboy.next = 插入节点
* curBoy = 插入节点
* 插入节点.next = first
*
* @param node
*/
public void add(HeroNode_J node) {
if (first == null) {
first = node;
curBoy = node;
node.setNext(first);
return;
}
curBoy.setNext(node);
curBoy = node;
node.setNext(first);
}
/**
* while(cur.next != first)
* 遍历链表
*
* @return
*/
public String showLinkedList() {
HeroNode_J cur = first;
StringBuffer container = new StringBuffer();
container.append("{");
if (cur == null) {
container.append("}");
return container.toString();
}
container.append(cur.toString());
//当cur.next=first,停止遍历
while (cur.getNext() != first) {
container.append(",");
cur = cur.getNext();
container.append(cur.toString());
}
container.append("}");
return container.toString();
}
/**
* 小孩出圈问题
*
* @return
*/
public String Josephu() {
HeroNode_J helper = first;
StringBuffer container = new StringBuffer();
container.append("{");
if (helper == null) {
container.append("}");
return container.toString();
}
//找到环形链表的最后一个节点
while (helper.getNext() != first) {
helper = helper.getNext();
}
//判断环形链表是否只剩最后一个节点
while (helper != first) {
//当小孩报数时,让first和helper指针同时的移动m-1次
for (int i = 0; i < (M - 1); i++) {
first = first.getNext();
helper = helper.getNext();
}
container.append(first.toString());
container.append(",");
/*
* 这时就可以将first指向小孩节点出圈。
* first = first.next
* helper.next = first
*/
first = first.getNext();
helper.setNext(first);
}
container.append(first.toString());
container.append("}");
first = null;
helper = null;
return container.toString();
}
}
class HeroNode_J {
private Integer no;
private String name;
private String nickName;
private HeroNode_J next; //指向下一个节点
public HeroNode_J() {
}
public HeroNode_J(Integer no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
public HeroNode_J(Integer no, String name, String nickName, HeroNode_J next) {
this.no = no;
this.name = name;
this.nickName = nickName;
this.next = next;
}
public Integer getNo() {
return no;
}
public void setNo(Integer 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;
}
public HeroNode_J getNext() {
return next;
}
public void setNext(HeroNode_J next) {
this.next = next;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName +
'}';
}
}