单链表
链表(Linked List)介绍
链表是有序的列表,但是它在内存中是存储如下
总结:
- 链表是以节点的方式来存储, 是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 如图:发现 链表的各个节点不一定是连续存储.
- 链表分 带头节点的链表和 没有头节点的链表,根据实际的需求来确定
单链表(带头结点) 逻辑结构示意图如下
应用实例
使用带 head 头的单向链表实现: 水浒英雄排行榜管理完成对英雄人物的增删改查操作
- 添加结点
- 方式1:在添加英雄时,直接添加到链表的尾部
- 方式2:在添加英雄时, 根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
- 修改结点
- 删除结点
代码实现:
//单向链表CRUD:添加顺序不考虑结点的编号no
//并提供优化方法-->按结点的编号no作为添加顺序
public class SingleLinkedListDemo {
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.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);*/
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero3);
singleLinkedList.addByOrder(hero2);
System.out.println("修改前:");
singleLinkedList.list();
singleLinkedList.update(new HeroNode(2,"小卢","玉麒麟~~~"));
System.out.println("修改后:");
singleLinkedList.list();
singleLinkedList.delete(2);
System.out.println("删除后:");
singleLinkedList.list();
}
}
class SingleLinkedList {
HeroNode first = new HeroNode(0, "", "");
//添加结点方式一:不考虑添加顺序-->添加顺序即为打印顺序
public void add(HeroNode hn) {
HeroNode temp=first;//创建辅助指针
while (true) {
if (temp.next == null) {//此时temp为代表尾结点
temp.next=hn;//尾部接上结点
break;
}
temp= temp.next;//每次使用后赋值更迭
}
}
//添加结点方式二:按结点的编号no作为添加顺序
//因为添加的位置是在结点的后面,所以比较编号也是同下一个结点比较
public void addByOrder(HeroNode hn) {
HeroNode temp = first;
boolean flag = false;//该变量表示:默认待加入英雄的排名在表中不存在
while (true) {
if (temp.next == null) {//遍历到尾部,hn的编号为最大
break;
}
if (temp.next.no > hn.no) {//该结点的下一个编号比hn大
break;
} else if (temp.next.no == hn.no) {//还hn的标号已存在
flag = true;
break;
}
temp = temp.next;//迭代
}
if (flag) {
System.out.println("排名存在,添加失败~");
}
//先将temp的下一个结点挂在hn后面-->再将hn按排序结果插入
// -->否则每次在链表中间插入结点后面的链表会断开丢弃
hn.next = temp.next;
temp.next = hn;
}
//修改结点
public void update(HeroNode newhn) {
boolean flag = false;//该变量表示:默认待修改英雄的排名在表中不存在
HeroNode temp = first;
if (temp.next == null) {
System.out.println("链表为空~");
}
while (true) {
if (temp == null) {//没找到对应编号
break;
}
if (temp.no == newhn.no) {//找到对应编号
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("修改成功~");
//提示:此处不能直接将结点直接赋值,否则链表断裂(next属性未处理)
temp.nickName= newhn.nickName;
temp.name = newhn.name;
} else {
System.out.println("没找到该编号,修改失败...");
}
}
//删除结点
public void delete(int no) {
boolean flag = false;//该变量表示:默认待删除英雄的排名在表中不存在
HeroNode temp = first;
if (temp.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) {
System.out.println("删除成功~");
//改变指向待删除结点的next指针即可-->指向待删除的下一个结点
temp.next = temp.next.next;
} else {
System.out.println("没找到该编号,删除失败...");
}
}
//遍历方法:
public void list() {
HeroNode temp = first;
if (temp.next == null) {
System.out.println("链表为空~");
}
while (true) {
if (temp.next == null) {
break;
}
temp= temp.next;
System.out.println(temp);
}
}
}
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;
}
//提示:toString方法不可以返回next属性,否则每个结点打印都会打印由其开始的一条链表
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
单链表面试题
求单链表中有效节点的个数
//获得有效结点的个数
public int getLength(HeroNode head) {
if (head.next == null) {
return 0;
}
int len = 0;
HeroNode temp=head.next;
while (temp != null) {
len++;
temp=temp.next;
}
return len;
}
【新浪面试题】
题目:查找单链表中的倒数第 k 个结点
思路:
- 编写一个方法,接收 head 节点,同时接收一个 lastIndex
- lastIndex 表示是倒数第 index 个节点
- 先把链表从头到尾遍历,得到链表的总的长度 getLength
- 得到 size 后,我们从链表的第一个开始遍历 index=(size-lastIndex)个,就可以得到
- 如果找到了,则返回该节点,否则返回 nulll
//查找单链表中的倒数第 k 个结点
public HeroNode findLastIndexNode(HeroNode head, int lastIndex) {
int index = this.getLength(first) - lastIndex;//正序索引为index
if (head.next == null) {//链表为空
return null;
}
if (lastIndex < 1 || lastIndex > this.getLength(first)) {//校验
return null;
}
HeroNode temp = head.next;//第一个有效结点
for (int i = 0; i < index; i++) {//验证:当index==1,表示定位到第二个有效结点
temp = temp.next;//迭代一次,正确
}
return temp;
}
【腾讯面试题】
题目:单链表的反转
思路:
- 先定义一个结点 reverseHead=new HeroNode();
- 从头到尾遍历原来的链表,每遍历一个结点就将其取出,并放在新的链表reverseHead的最前端
- 原来的链表的head.next=reverseHead.next
//反转链表
public void reverseList(HeroNode head) {
HeroNode reverseHead = new HeroNode(0, "", "");
HeroNode temp = head.next;
HeroNode next;
while (temp!= null) {
next = temp.next;//这里提取辅助变量后面的链表是为了使用完辅助变量后该可以保持迭代
temp.next=reverseHead.next;//将新链表(头结点排除)先挂在待添加结点后面
reverseHead.next = temp;//再将加入结点后的链表挂在头结点后,此时完成一次按序加入
temp =next;//辅助结点后移
}
head.next = reverseHead.next;//新链表(头结点排除)挂在head后面
}
【百度面试题】
题目:从尾到头打印单链表
思路:
前置工作:了解并使用栈(Stack),演示如下:
public class TestStack{
public static void main(String[] args){
Stack<String> stack=new Stack();
//入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
//出栈(先进后出,即取出栈顶的数据)
while(stack.size()>0){
System.out.println(stack.pop);
}
}
}
编写题目的实现方法:
//逆序打印
public void reversePrint(HeroNode head) {
if (head.next == null) {
System.out.println("链表为空!");
}
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode temp = head.next;
while (temp != null) {
stack.add(temp);
temp = temp.next;
}
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
补充练习
题目:合并两个有序的单链表,合并之后的链表依然有序
思路:
- 分别遍历两条链表,将其结点存放在栈中(入栈)
- 取出所有结点(出栈),将其有序添加到新的第三条链表,并返回新链表
提示:
addByOrder()方法操作结点后会改变该节点.next的指向
–>本题中由原来的指向该节点所在链表的结点改为指向待加入链表的插入位置后面的一个结点
–>因此遍历链表时,因该方法会导致迭代语句失效(temp=temp.next)
–>具体说明:next改为指向待加入链表的有效结点中排名大于它的结点,若加入结点的排名数字最大,next指向null,
–>因而禁止迭代时使用该方法
–>引入栈对象来暂存结点
//合并两个有序的单链表,合并之后的链表依然有序
//收集两个链表的所有结点,按序添加到新链表
public static SingleLinkedList combine01(HeroNode first, HeroNode head) {
if (first.next == null && head.next == null) {
System.out.println("两个空链表");
}
SingleLinkedList combineList01 = new SingleLinkedList();
Stack<HeroNode> stack = new Stack<>();
HeroNode temp01=first.next;
while (temp01 != null) {
//addByOrder()方法操作结点后会改变next的指向
//-->由原来改为指向待加入链表的插入位置后面的一个结点
//-->因此遍历链表时,因该方法会导致迭代语句失效(temp=temp.next)
//-->禁止迭代时使用该方法
//combineList01.addByOrder(temp01);
stack.add(temp01);//入栈
temp01 = temp01.next;
}
HeroNode temp02=head.next;
while (temp02 != null) {
stack.add(temp02);
temp02 = temp02.next;
}
while (stack.size() > 0) {
combineList01.addByOrder(stack.pop());//出栈-->有序添加到新链表
}
return combineList01;
}