1.链表(Linked List)介绍
链表是有序的列表,但是它在内存存储结构如下:
2.特点:
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 链表的各个节点不一定是连续存储.
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
3.单链表介绍
单链表(带头结点) 逻辑结构示意图如下:
4.应用示例:
使用带head头的单向链表实现 –水浒英雄排行榜管理,完成对英雄人物的增删改查操作
第一种方法在添加英雄时,直接添加到链表的尾部
思路分析
添加(创建)
- 先创建一个head头节点,表示单链表的头
- 在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
遍历
- 通过一个辅助变量遍历,帮助遍历整个链表。
删除节点
链表数据结构实现:
class HeroNode{
public int id; //唯一标志
public String name; //名字
public String nickName; //昵称
HeroNode next; //下一个节点
public HeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{id: "+ id+ ",name: "+ name +", nickName: "+ nickName +"}";
}
}
辅助管理链表类:
需要注意,链表维护的表头节点head 不能动,因此处理时需要一个temp辅助节点找到待删除节点的前一个节点
//链表类
class SingleLinkedList{
//声明一个头结点
private HeroNode head = new HeroNode(0,"","") ;
/**
* @description: 返回头结点
* @param
* @return:
* @author: sukang
* @date: 2020/1/2 14:28
*/
public HeroNode getHead(){
return head;
}
/**
* @description: 从链表尾处添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:18
*/
public void addHeroNode(HeroNode heroNode){
/* 思路:用一个辅助节点遍历链表,找到节点next为空及最后一个节点,
将其next赋给新节点*/
//申明一个过渡节点,因为head节点不能后移
HeroNode temp = head;
while (true){
//如果下一个节点为空的话,那么就在这个节点上添加新节点
if(temp.next == null){
break;
}
//如果没有到最后一个节点,那么节点往后移
temp = temp.next;
}
//把最后一个节点的下一个节点赋给新节点
temp.next = heroNode;
}
/**
* @description: 按照节点的顺序添加节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 14:31
*/
public void addHeroNodeByOrder(HeroNode heroNode){
/* 思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么辅助节点temp就应该为插入节点的上一个节点,另外如果链表中存在相同
大小的节点,那么将插入失败*/
//声明一个辅助节点
HeroNode temp = head;
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.next.id == heroNode.id){
System.out.printf("编号为%d的节点已经存在,不能添加", heroNode.id);
return;
}else if(temp.next.id > heroNode.id){
break;
}
temp = temp.next;
}
heroNode.next = temp.next;
temp.next = heroNode;
}
/**
* @description: 删除节点
* @param id
* @return: void
* @author: sukang
* @date: 2019/12/30 15:19
*/
public void delHeroNode(int id){
/*思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
那么如果辅助节点的下一个节点的id存在,那么就删除这个节点*/
HeroNode temp = head;
boolean flag = false;//是否找到删除节点标志
while (true){
//链表已经遍历到最后
if(temp.next == null){
break;
}
if(temp.next.id == id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next = temp.next.next;
}else{
System.out.printf("要删除的节点%d,不存在", id);
}
}
/**
* @description: 更新节点
* @param heroNode
* @return: void
* @author: sukang
* @date: 2019/12/30 16:04
*/
public void updateHeroNode(HeroNode heroNode){
//思路:同样是通过辅助节点temp遍历找到和更新节点相同id的节点,然后进行更新
//声明辅助节点
HeroNode temp = head;
boolean flag = false; //判断是否找到更新的节点,默认为false
while (true){
if(temp == null){
break;
}
if(temp.next.id == heroNode.id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next.name = heroNode.name;
temp.next.nickName = heroNode.nickName;
}else{
System.out.printf("没有找到编号为 %d 的节点, 不能更新", heroNode.id);
}
}
/**
* @description: 遍历链表
* @param
* @return: void
* @author: sukang
* @date: 2019/12/30 16:15
*/
public void list(){
HeroNode temp = head;
if(temp.next == null){
System.out.println("链表为空!");
return;
}
while (true){
if(temp.next == null){
break;
}
System.out.println(temp.next);
temp = temp.next;
}
}
}
【面试题】单链表的反转
思路:
- 先定义一个节点reverseHead = new HeroNode();
- 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
- 原来的链表的head.next = reverseHead.net
代码实现
/**
* @description: 面试题链表反转
* @param
* @return: void
* @author: sukang
* @date: 2020/1/3 8:37
*/
public static void linkedReversal(HeroNode head){
//思路:首先申明一个新链表的头节点,然后遍历旧链表,按顺序插入新链表头节点之后
if(head.next == null || head.next.next == null){
System.out.println("链表为空或则链表的有效长度为1,不需要进行反转");
return;
}
//声明一个辅助节点,遍历单链表来用
HeroNode temp = head.next;
//声明一个当前节点指向temp节点
HeroNode cur = null;
//声明新链表的头节点
HeroNode newHead = new HeroNode(0,"","");
while (true){
//链表遍历到了最后一个节点
if(temp == null){
break;
}
cur = temp; //将当前节点指向单链表的辅助节点
temp = temp.next; //将辅助节点后移一个节点
cur.next = newHead.next;//然后将当前节点的后一个节点指向新链表的后一个节点
newHead.next = cur;//新链表的头结点的后一个节点指向当前节点
}
head.next = newHead.next;
}
【面试题】从尾到头打印单链表(要求方式1:反向遍历,方式2:Stack栈)
思路:
- 方式1:先将单链表进行反转操作,然后再遍历即可,这样做的问题是会破坏原来的单链表结构,不建议
- 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果。
方式2代码实现
/**
* @description: 面试题:单链表反向打印
* @param head
* @return: void
* @author: sukang
* @date: 2020/1/3 9:38
*/
public static void linkedReversalPrint(HeroNode head){
//思路:将单链表压入栈中,然后从栈中取出打印,利用了栈的新进后出的特性
if(head.next == null){
return;
}
Stack<HeroNode> stack = new Stack<>();
HeroNode temp = head;
while (true){
if(temp.next == null){
break;
}
stack.add(temp.next);
temp = temp.next;
}
while (stack.size() > 0){
System.out.println(stack.pop());
}
}