链表(Liked List)
一、链表的概念
- 链表是有序的列表,但是在内存中链表的各个节点不一定是连续的。
- 下图是链表在内存中的存储结构示意图
总结:
a.链表是以节点的方式存储,链式存储
b.每个节点包含了data域(存值)、next域(指向下一个节点)
c.链表的各个节点不一定是连续存储的
d.链表分带头节点的链表和没有带头节点的链表,根据实际的需求来确定
二、单链表
-
单链表(带头结点)逻辑结构示意图,实际并不连续
. -
单链表的创建,不按编号添加,头节点只写一次,思路分析如下
-
上述解释了如何创建链表,需要一个头节点(不能动),还有数据,还有next域。这时我们创建一个类,来代表单链表,然后再里面写上增删改查的方法,来实现一个完整的链表已经操控这个单链表
-
添加节点到单链表,不考虑编号
//添加节点到单向链表 当不考虑编号的顺序时
//1、找到当前链表的最后节点
//2、将最后这个节点的next,指向新的节点
public void add(HerNode herNode){
//因为head节点不能动,因此需要一个辅助变量temp
HerNode temp = head;
//遍历链表,找到最后
while (true){
//判断当前节点是否是最后一个
if (temp.next==null){
break;
}
//如果没有找到最后,将temp后移,查找后一个
temp=temp.next;
}
//当前退出while循环时,temp就指向链表最后
temp.next=herNode;
}
- 考虑编号时,向单链表中插入节点,思路分析
代码实现:
//考虑编号顺序时
public void addOrder(HerNode herNode){
//因为头节点不能动,我们需要通过一个辅助变量来帮助找到添加的位置
//因为是单链表,我们要找的temp,是位于添加位置的前一个节点,否则插入不了
HerNode temp =head;
boolean flag= false;//用来标志添加的编号是否已经存在,默认为false
while (true){
if (temp.next==null){//说明temp已经在链表最后
break;
}
if (temp.next.no>herNode.no){//找到位置,就在temp的后面插入
break;
}else if (temp.next.no==herNode.no){//说明希望添加的heroNode的编号已然存在
flag=true;
break;
}
temp=temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag){
System.out.printf("您的英雄%d已存在\n",herNode.no);
}else {
//插入到链表中,temp后面
herNode.next=temp.next;//当前插入的节点的指向temp的后一位
temp.next=herNode;//temp指向新的节点,这样就插到中间了。
}
}
- 修改节点,直接上代码
//修改节点
public void update(HerNode newNode){
//判断是否为空
if (head.next==null){
System.out.println("链表为空");
return;
}
//找到修改的节点通过遍历
HerNode temp=head.next;
boolean flag =false;//表示是否找到该节点
while (true){
if (temp==null){
break;//已经遍历完成
}
if (temp.no== newNode.no){
flag=true;
break;
}
temp=temp.next;
}
if (flag){
temp.name=newNode.name;
temp.nickName=newNode.nickName;
}else {
System.out.printf("没有找到编号%d 的节点",newNode.no);
}
}
- 删除节点
a.思路:
b.代码实现
//删除节点
//思路:head节点不能动,需要一个辅助变量帮助我们找到待删除的节点的前一个节点
// 在比较时是,temp.next.no和需要删除的节点no比较
public void delete(int no){
HerNode temp = head;
boolean flag = false;//表示是否找到待删除节点
while (true){
if (temp.next==null){//已经到链表最后
break;
}
if (temp.next.no==no){//找到待删除节点的前一个节点temp
flag=true;
break;
}
temp=temp.next;
}
if (flag){
//删除
temp.next=temp.next.next;
}else {
System.out.println("要删除的节点不存在。。。");
}
}
8.查询链表,根据id或者no查询链表中的节点数据,
a.思路:使用while遍历,知道后一个为止,切记遍历完一个必须指向下一个,接着遍历。
b.代码实现
//显示链表
public void list(){
//判断链表是否为空
if(head.next==null){
System.out.println("链表为空!");
return;
}
//如果不为空至少有一个节点,所以从第二个节点开始遍历,
// 头节点不能动,因此,需要一个辅助变量来遍历
HerNode temp = head.next;
while (true){
//是否到链表最后
if (temp==null){
System.out.println("遍历到最后跳出");
break;
}
//输出当前节点的信息
System.out.println(temp);
//将temp后移,判断下一个,输出
temp=temp.next;
}
}
c.根据no查询节点
//根据编码查询链表
public void getByNo(int no){
//判断链表是否为空
if(head.next==null){
System.out.println("链表为空!");
return;
}
//如果不为空至少有一个节点,所以从第二个节点开始遍历,
// 头节点不能动,因此,需要一个辅助变量来遍历
HerNode temp = head.next;
boolean flag=false;//判断是否找到
while (true){
//是否到链表最后
if (temp==null){
System.out.println("遍历到最后跳出");
break;
}
if (temp.no==no){
flag=true;//存在
break;
}
//将temp后移,判断下一个,输出
temp=temp.next;
}
if (flag){
System.out.println(temp);
}else {
System.out.println("您输入的编号不存在");
}
}
三、测试代码
package linkedlist;
import java.util.Stack;
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试
HerNode hero1 = new HerNode(1, "宋江", "及时雨");
HerNode hero2 = new HerNode(2, "卢俊义", "玉麒麟");
HerNode hero3 = new HerNode(3, "吴用", "智多星");
HerNode hero4 = new HerNode(4, "林冲", "豹子头");
HerNode hero5 = new HerNode(4, "林冲2", "豹子头2");
//创建一个链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
SingleLinkedList singleLinkedList3 = new SingleLinkedList();
//不按顺序加入
singleLinkedList.add(hero1);
singleLinkedList.add(hero3);
singleLinkedList2.add(hero2);
singleLinkedList2.add(hero4);
//合并为新的链表
HerNode merge = merge(singleLinkedList.getHead(), singleLinkedList2.getHead());
show(merge);
/*System.out.println("原来的链表");
singleLinkedList.list();
System.out.println("逆序打印后的链表");
reversePrint(singleLinkedList.getHead());
System.out.println("反转后的链表");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();*/
/*//按顺序加入
singleLinkedList.addOrder(hero1);
singleLinkedList.addOrder(hero3);
singleLinkedList.addOrder(hero4);
singleLinkedList.addOrder(hero2);
System.out.println("修改前");
singleLinkedList.list();
singleLinkedList.update(hero5);
System.out.println("修改后");
singleLinkedList.list();
System.out.println("删除后");
singleLinkedList.delete(4);
singleLinkedList.list();
System.out.println("根据编号查询后的记录");
singleLinkedList.getByNo(1);
System.out.println("单链表的有效个数为:"+ getLength(singleLinkedList.getHead()));
System.out.println("查找单链表中的倒数第k个结点:"+getNode(singleLinkedList.getHead(),1));*/
}
//通过一个头节点,来查询链表
public static void show(HerNode head) {
HerNode cur = head.next;
if (cur == null) {
System.out.println("链表为空~");
}
while (cur != null) {
System.out.println(cur);
cur = cur.next;
}
}
}
}
//定义一个SIngleLinkedList管理我们的英雄
class SingleLinkedList{
//初始化一个头节点,头节点不要动,不存放具体的数据
private HerNode head = new HerNode(0,"","");
//返回头节点
public HerNode getHead(){
return head;
}
//添加节点到单向链表 当不考虑编号的顺序时
//1、找到当前链表的最后节点
//2、将最后这个节点的next,指向新的节点
public void add(HerNode herNode){
//因为head节点不能动,因此需要一个辅助变量temp
HerNode temp = head;
//遍历链表,找到最后
while (true){
//判断当前节点是否是最后一个
if (temp.next==null){
break;
}
//如果没有找到最后,将temp后移,查找后一个
temp=temp.next;
}
//当前退出while循环时,temp就指向链表最后
temp.next=herNode;
}
//考虑编号顺序时
public void addOrder(HerNode herNode){
//因为头节点不能动,我们需要通过一个辅助变量来帮助找到添加的位置
//因为是单链表,我们要找的temp,是位于添加位置的前一个节点,否则插入不了
HerNode temp =head;
boolean flag= false;//用来标志添加的编号是否已经存在,默认为false
while (true){
if (temp.next==null){//说明temp已经在链表最后
break;
}
if (temp.next.no>herNode.no){//找到位置,就在temp的后面插入
break;
}else if (temp.next.no==herNode.no){//说明希望添加的heroNode的编号已然存在
flag=true;
break;
}
temp=temp.next;//后移,遍历当前链表
}
//判断flag的值
if (flag){
System.out.printf("您的英雄%d已存在\n",herNode.no);
}else {
//插入到链表中,temp后面
herNode.next=temp.next;//当前插入的节点的指向temp的后一位
temp.next=herNode;//temp指向新的节点,这样就插到中间了。
}
}
//修改节点
public void update(HerNode newNode){
//判断是否为空
if (head.next==null){
System.out.println("链表为空");
return;
}
//找到修改的节点通过遍历
HerNode temp=head.next;
boolean flag =false;//表示是否找到该节点
while (true){
if (temp==null){
break;//已经遍历完成
}
if (temp.no== newNode.no){
flag=true;
break;
}
temp=temp.next;
}
if (flag){
temp.name=newNode.name;
temp.nickName=newNode.nickName;
}else {
System.out.printf("没有找到编号%d 的节点",newNode.no);
}
}
//删除节点
//思路:head节点不能动,需要一个辅助变量帮助我们找到待删除的节点的前一个节点
// 在比较时是,temp.next.no和需要删除的节点no比较
public void delete(int no){
HerNode temp = head;
boolean flag = false;//表示是否找到待删除节点
while (true){
if (temp.next==null){//已经到链表最后
break;
}
if (temp.next.no==no){//找到待删除节点的前一个节点temp
flag=true;
break;
}
temp=temp.next;
}
if (flag){
//删除
temp.next=temp.next.next;
}else {
System.out.println("要删除的节点不存在。。。");
}
}
//显示链表
public void list(){
//判断链表是否为空
if(head.next==null){
System.out.println("链表为空!");
return;
}
//如果不为空至少有一个节点,所以从第二个节点开始遍历,
// 头节点不能动,因此,需要一个辅助变量来遍历
HerNode temp = head.next;
while (true){
//是否到链表最后
if (temp==null){
System.out.println("遍历到最后跳出");
break;
}
//输出当前节点的信息
System.out.println(temp);
//将temp后移,判断下一个,输出
temp=temp.next;
}
}
//根据编码查询链表
public void getByNo(int no){
//判断链表是否为空
if(head.next==null){
System.out.println("链表为空!");
return;
}
//如果不为空至少有一个节点,所以从第二个节点开始遍历,
// 头节点不能动,因此,需要一个辅助变量来遍历
HerNode temp = head.next;
boolean flag=false;//判断是否找到
while (true){
//是否到链表最后
if (temp==null){
System.out.println("遍历到最后跳出");
break;
}
if (temp.no==no){
flag=true;//存在
break;
}
//将temp后移,判断下一个,输出
temp=temp.next;
}
if (flag){
System.out.println(temp);
}else {
System.out.println("您输入的编号不存在");
}
}
}
//定义一个heroNode,每个node对象就是一个节点
class HerNode{
public int no;
public String name;
public String nickName;
public HerNode next;//指向下一个节点
//构造器
public HerNode(int no,String name,String nickName){
this.no=no;
this.name=name;
this.nickName=nickName;
}
public HerNode(){};
@Override
public String toString() {
return "HerNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
四、面试题
一、求单链表中有效节点的个数(如果时带头链表,不加入统计)
/**
* 求单链表中有效节点的个数(如果时带头链表,不加入统计)
* @param head 链表的头节点
* @return
*/
public static int getLength(HerNode head){
if (head.next==null){
return 0;
}
//定义一个辅助变量,没有统计头节点
HerNode temp = head.next;
int count=0;
while (temp!=null){
count++;
temp=temp.next;//遍历
}
return count;
}
二、查找单链表中的倒数第k个结点 【新浪面试题】
,里面的getLength长度,也就是上面的有效节点个数拉。
/**
* 查找单链表中的倒数第k个结点 【新浪面试题】
* @param head 头节点
* @param num 输入的值
* @return
*/
public static HerNode getNode(HerNode head, int num){
//判断是否为空
if (head.next==null){
return null;//没有找到
}
//第一次遍历得到的长度
int size = getLength(head);
//size-num 的位置就是我们倒数第K个节点
if (num<=0 || num>size){
return null;
}
//定义一个辅助变量,帮助我们遍历
HerNode cur =head.next;
for (int i = 0; i < size-num; i++) {
cur=cur.next;
}
return cur;
}
三、单链表的反转【腾讯面试题,有点难度】
/**
* 单链表的反转【腾讯面试题,有点难度】
* @param head
*/
public static void reversetList(HerNode head) {
//① 如果当前链表为空,或者只有一个节点,我们无需反转,直接返回
if (head.next==null || head.next.next==null){
return;
}
// ② 定义一个辅助变量,帮助我们遍历原来的链表
HerNode temp =head.next;
HerNode next = null;//指向当前节点【temp】的下一个节点
HerNode reverseHead= new HerNode(0,"","");
// ③遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
while (temp!=null){
next=temp.next;//暂时保存当前节点的下一个节点,后面需要使用
temp.next=reverseHead.next; //将temp的下一个节点,指向新的链表的最前端
reverseHead.next=temp; //将取出来的节点,接到新的链表上
temp=next;//让temp后移
}
//将head.next指向reverseHead.next,实现反转
head.next=reverseHead.next;
}
四、从尾到头打印单链表 【百度,要求方式1:反向遍历 (会破坏原来的单链表结构)。 方式2:Stack栈】
/**
* 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
* 使用方式二来实现方式二,stack先进后出
* @param head
*/
public static void reversePrint(HerNode head){
if (head.next==null){//空链表无法打印
return;
}
//创建一个栈,将节点压入栈中,再取出,就可以实现
Stack<HerNode> stack = new Stack<>();
HerNode cur = head.next;
//压入栈中
while (cur!=null){
stack.push(cur);
//后移压下一个
cur=cur.next;
}
//将栈中的节点进行打印,出栈
while (stack.size()>0){
System.out.println(stack.pop());
}
}
五、合并两个有序的单链表,合并之后的链表依然有序
/**
* 合并两个有序的单链表,合并之后的链表依然有序
*/
public static HerNode merge(HerNode herNode1, HerNode herNode2) {
if (herNode1.next == null) {
return herNode2;
} else if (herNode2.next == null) {
return herNode1;
}
HerNode newNode = new HerNode();
HerNode n1 = newNode;
HerNode l1 = herNode1.next;
HerNode l2 = herNode2.next;
while (l1 != null && l2 != null) {
if (l1.no < l2.no) {
n1.next = l1;
l1 = l1.next;
n1 = n1.next;
} else {
n1.next = l2;
l2 = l2.next;
n1 = n1.next;
}
}
if (l1 == null) {
n1.next = l2;
}
if (l2 == null) {
n1.next = l1;
}
return newNode;
}
五、单链表的缺点:
1、查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2、不能自我删除,需要靠辅助节点,而双向链表,则可以实现自我删除,删除单向链表节点时,需要通过辅助节点找到待删除节点的前一个,让前一个指向待删除节点的后一个,完成剔除。