文章目录
链表介绍
链表是有序的列表,但是它在内存中是如下存储的:
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含data域,next域:指向下一个节点
- 链表的各个节点不一定是连续存储
- 链表分带头节点的链表和没有头结点的链表
单链表(带头节点)逻辑结构示意图如下:
1. 单链表应用实例
使用带头的单向链表实现 -带编号的英雄人物的增删改查操作。
1.1 实现思路
1.add方法添加英雄时,直接添加到链表尾部
2.addByOrder方法添加英雄时,根据英雄编号将英雄插入到指定的位置
3.update方法修改节点功能。
1)通过遍历根据no找到该节点,没找到就后移temp=temp.next
2)替换name和nickName
4.删除节点
1.2 代码实现
package com.zh.LinkedList;
public class SingleLinkedListDemo01 {
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 list = new SingleLinkedList();
// list.add(hero1);
// list.add(hero2);
// list.add(hero3);
// list.add(hero4);
// list.listTraverse();
list.addByOrder(hero1);
list.addByOrder(hero4);
list.addByOrder(hero3);
list.addByOrder(hero2);
list.listTraverse();
HeroNode heroNew = new HeroNode(2,"佐伊","暮光星灵");
list.update(heroNew);
System.out.println("-------------------修改后------------------");
list.listTraverse();
list.delete(1);
System.out.println("-------------------删除后------------------");
list.listTraverse();
}
}
//创建链表
class SingleLinkedList{
//创建一个头节点,头节点不能动,不存放具体的数据
HeroNode head = new HeroNode(0,"","");
/*当不考虑编号顺序时
1.找到当前链表的最后节点
2.将最后这个节点的next指向新的节点
*/
public void add(HeroNode heroNode){
//因为头节点不能动,所以创建一个辅助节点temp
HeroNode temp = head;
while (true){
//找到链表的最后节点
if (temp.next==null){
break;
}else {
//如果没有找到链表的最后,后移一位
temp = temp.next;
}
}
//将最后这个节点的next指向新的节点
temp.next=heroNode;
}
//按英雄编号来添加
public void addByOrder(HeroNode heroNode){
HeroNode temp = head;
boolean flag = false;//判断添加的节点是否存在
while (true){
if (temp.next==null){
//链表为空
break;
}
if (temp.next.no > heroNode.no){
//如果temp指向的下一个节点编号大于添加的编号,说明新节点位于temp与temp.next之间
break;
}else if (temp.next.no == heroNode.no){
//说明添加的节点编号已经存在
flag = true;
break;
}
//节点后移
temp = temp.next;
}
if (flag){
System.out.println("要添加的节点已存在,编号为"+heroNode.no);
}else {
//插入到链表中,将新节点的next指向temp.next,temp.next指向新节点
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//链表节点修改
public void update(HeroNode heroNode){
//判断是否为空
if (head.next==null){
System.out.println("链表为空");
return;
}
//根据no找到要修改的节点
//定义辅助变量
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true){
if (temp==null){
//已经遍历完
break;
}
if (temp.no == heroNode.no){
flag = true;
break;
}
//没找到就后移
temp = temp.next;
}
if (flag) {
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
}else {
System.out.println("没有找到编号为"+ heroNode.no+"的节点,不能进行修改");
}
}
//删除节点
public void delete(int no){
HeroNode temp = head;
boolean flag = false;
if (head.next==null){
System.out.println("链表为空");
}
while (true){
if (temp.next==null){
//已经遍历完
break;
}
if (temp.next.no==no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.next = temp.next.next;
}else {
System.out.println("没有找到编号为"+no+"的节点,无法删除");
}
}
//链表遍历
public void listTraverse(){
//创建一个辅助节点遍历
HeroNode temp = head.next;
//判断链表是否为空
if (temp==null){
System.out.println("链表为空");
return;
}else {
while (true){
//判断链表是否到最后
if (temp == null){
break;
}else{
//输出节点信息
System.out.println(temp);
//将节点后移
temp=temp.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;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
2.单链表常见面试题
2.1 求单链表中有效节点的个数
/**获取单链表有效节点的个数
*
* @param head
* @return
*/
public int getLength(HeroNode head){
int length = 0;
HeroNode temp = head;
if (head.next==null){
return 0;
}
while (temp.next!=null){
length++;
temp = temp.next;
}
return length;
}
2.2 查找单链表中倒数第K个节点
/**
* 查找链表中倒数第K个节点
* @param head
* @param index
* @return
*/
public HeroNode findLastIndexNode(HeroNode head,int index){//index表示倒数第index个节点
//获取链表长度
int length = getLength(head);
HeroNode temp = head.next;
if (head.next==null){
return null;
}
//从第一个开始遍历length - index就可以得到倒数第index个节点
int signal = length - index;
if (index <= 0 || index > length){
return null;
}
for (int i = 0; i <signal ; i++) {
temp=temp.next;
}
return temp;
}
2.3 单链表的反转
思路:
- 定义一个新的头结点reverseHead.
- 遍历原来的链表,每遍历一个节点就将其取出,放在新链表最前端
- 原来链表的head.next = reverseHead
/**
* 单链表的反转
* @param head
*/
public void reverseList(HeroNode head){
//如果链表为空或只有一个节点,那么直接返回
if (head.next==null || head.next.next==null){
return;
}
//创建一个新的头结点
HeroNode reverseHead = new HeroNode(0,"","");
HeroNode cur = head.next;
HeroNode next = null;
while (cur!=null){
next =cur.next;//保存当前节点的下一个节点
//将cur的下一个节点指向链表的最前端
cur.next = reverseHead.next;
//将cur连接到cur的下一个节点和头结点之间。
reverseHead.next = cur;
//后移
cur = next;
}
//将头结点指向反转后的头结点,实现反转
head.next = reverseHead.next;
}
2.4 逆序打印单链表
要求:反向遍历,用栈实现
/**
* 单链表逆序输出(用栈实现)
* @param head
*/
public void reversePrint(HeroNode head){
if (head.next==null){
return;
}
HeroNode cur = head.next;
Stack<HeroNode> stack = new Stack<>();
while (cur!=null){
stack.add(cur);//将链表的节点压入栈
cur = cur.next;
}
while (stack.size()>0){
System.out.println(stack.pop());//先进后出输出
}
}
2.5 合并两个单链表,合并后依然有序
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode head = null;
if (l1.val <= l2.val){
head = l1;
head.next = mergeTwoLists(l1.next, l2);
} else {
head = l2;
head.next = mergeTwoLists(l1, l2.next);
}
return head;
}
3. 双链表应用实例
3.1 单链表缺点
管理单向链表的缺点分析:
- 单向链表,查找的只能是一个方向,而双向链表可以向前或者向后查找
- 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面单链表删除,总是找到temp,temp是待删除节点的前一个节点。
下面用双链表来实现上面的增删改查功能。
3.2 实现思路
遍历:和单链表一样,但是既可以向前遍历也可以向后遍历
添加(默认添加到双向链表最后): 先找到双向链表最后的一个节点
temp.next = heroNode
heroNode.pre = temp;
修改 :和单链表思路一样
删除:因为是双向链表,所以可以自我删除,直接找到要删除的节点
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
3.3 代码实现
public class DoubleLinkedListDemo {
public static void main(String[] args) {
HeroNode2 hero1 = new HeroNode2(1,"伊泽瑞尔","冒险家");
HeroNode2 hero2 = new HeroNode2(2,"拉克丝","光辉女郎");
HeroNode2 hero3 = new HeroNode2(3,"亚索","疾风剑豪");
HeroNode2 hero4 = new HeroNode2(4,"卢锡安","圣枪游侠");
HeroNode2 hero2New = new HeroNode2(2,"佐伊","暮光星灵");
DoubleLinkedList list = new DoubleLinkedList();
//添加节点
list.add(hero1);
list.add(hero2);
list.add(hero3);
list.add(hero4);
System.out.println("----------------遍历-----------------");
list.traverse();
System.out.println("----------------修改后-----------------");
list.update(hero2New);
list.traverse();
System.out.println("----------------删除后-----------------");
list.del(3);
list.traverse();
}
}
class DoubleLinkedList{
//创建一个头节点,头节点不能动,不存放具体的数据
private HeroNode2 head = new HeroNode2(0,"","");
public void setHead(HeroNode2 head) {
this.head = head;
}
public HeroNode2 getHead() {
return head;
}
/**
* 在链表的最后添加节点
*/
public void add(HeroNode2 heroNode){
//头结点不能动,创建一个辅助节点
HeroNode2 temp = head;
//找到最后一个节点
while (temp.next!=null){
temp = temp.next;
}
//将新节点添加到节点的最后
temp.next = heroNode;
heroNode.pre = temp;
}
/**
* 遍历链表
*/
public void traverse(){
if (head.next == null){
System.out.println("链表为空");
return;
}
HeroNode2 temp = head.next;
while (temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
/**
* 修改链表节点
*/
// 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样
// 只是 节点类型改成 HeroNode2
public void update(HeroNode2 newHeroNode) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
// 找到需要修改的节点, 根据no编号
// 定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) {
break; // 已经遍历完链表
}
if (temp.no == newHeroNode.no) {
// 找到
flag = true;
break;
}
temp = temp.next;
}
// 根据flag 判断是否找到要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else { // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);
}
}
/**
* 删除节点
*/
public void del(int no) {
// 判断当前链表是否为空
if (head.next == null) {// 空链表
System.out.println("链表为空,无法删除");
return;
}
HeroNode2 temp = head.next; // 辅助变量(指针)
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp == null) { // 已经到链表的最后
break;
}
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next; // temp后移,遍历
}
// 判断flag
if (flag) { // 找到
// 可以删除
// temp.next = temp.next.next;[单向链表]
temp.pre.next = temp.next;
// 这里我们的代码有问题?
// 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要删除的 %d 节点不存在\n", no);
}
}
}
class HeroNode2{
public int no;//序号
public String name;
public String nickName;
public HeroNode2 next;//指向下一个节点
public HeroNode2 pre;//指向前一个节点
public HeroNode2(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 + '\'' +
'}';
}
}