目录
概念
什么是链表?
为什么链表的头节点不能动,或者不能操作?
链表和数组的区别是什么?
实现
节点
单链表
末尾添加
遍历
按编号添加:
修改节点
删除
面试题
求单链表的长度
求单链表倒数第K个节点
求两个链表的公共节点,如果有多个,返回第一个。
单链表的反转
从尾到头打印单链表的节点
合并两个有序的单链表,合并后依然有序
概念
什么是链表?
链表是一种含有数据域和指针域的数据结构,以节点的方式来存储,在内存中是不连续的,分为带头节点的和不带头节点的。
为什么链表的头节点不能动,或者不能操作?
链表的操作都要基于头节点,如果头节点丢失,整个链表会丢失。
链表和数组的区别是什么?
链表在内存中是不连续的, 数组是连续的。
链表的插入和删除,效率要优于数组,数组的遍历和修改优于链表。
链表在空间上消耗两倍的数组内存。
链表支持动态扩容,数组在初始化的时候长度就固定了。
实现
节点
实现单链表之前,先构造一个链表节点HeroList,这里我们以漫威英雄为例,英雄角色有编号no,姓名name,性别sex,我们将no,name和sex定义为数据域,no为主键,唯一不可更改,方便我们进行修改和查找等操作。还需要定义一个next指针域,用来指向下一个节点。
下面我们通过Java语言实现节点:
//链表节点类
class HeroList {
int no;
String name;
boolean sex;
HeroList next;
public HeroList(int no, String name, boolean sex) {
this.no = no;
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "HeroList{" +
"no=" + no +
", name='" + name + '\'' +
", sex=" + sex +
'}';
}
}
单链表
有了节点,我们可以通过next指针,将一个个节点串联,组装为一个单链表。这个单链表应该含有一个头节点,表示起始,以及各种操作函数,可以对这个单链表进行查找、修改、遍历、删除、新增等。
末尾添加
下面我们通过Java语言实现单链表的添加:
class SingleLinkedList {
//我们需要定义一个head节点,表示链表的开始,head节点不能动,数据域不存放数据,丢失head节点,链表则会丢失
HeroList head = new HeroList(0, "", false);
//在末尾添加节点
public void addAtEnd(HeroList hero) {
//因为头节点,不能动,我们需要一个临时节点来操作
HeroList temp = head;
//需要遍历,找到末尾节点
while (true) {
if (temp.next == null) //到末尾了
break;
//temp后移
temp = temp.next;
}
//循环结束,temp就是末尾节点
temp.next = hero;
}
}
遍历
Java语言实现单链表的遍历:
//链表的遍历
public void listLinked() {
//如果链表为null,无须遍历
if (head.next == null) {
System.out.println("链表为null");
return;
}
//链表不为null,通过一个临时节点遍历链表
HeroList temp = head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
按编号添加:
Java语言实现单链表的按位添加:
//按编号添加节点,如果编号已存在,添加失败
public void addByNo(HeroList hero){
//1:需要一个临时节点temp来操作
HeroList temp = head;
//待添加的节点是否存在,默认不存在
boolean flag = false;
//2:遍历单链表
while (true){
if(temp.next==null) {//找到末尾,没找到,插入到末尾
break;
}
if(temp.next.no>hero.no)//找到待添加的位置了,是temp的后面
{
break;
}
if(temp.next.no==hero.no){//已存在,添加失败
flag = true;
break;
}
//temp后移
temp = temp.next;
}
if(flag){
System.out.printf("待添加的节点%d已存在\n",hero.no);
}else{
//此时,将hero.next指向temp.next;
hero.next = temp.next;
//再将temp.next指向hero
temp.next = hero;
}
}
修改节点
Java语言实现单链表的修改:
这里的修改指的是根据编号,修改节点的数据域。
//修改节点
public void updateLinked(HeroList hero)
{
HeroList temp = head;
//是否找到待修改的节点
boolean flag = false;
while (true){
if(temp.next == null){//遍历到末尾了,还没找到
break;
}
if(temp.next.no == hero.no){//找到了,temp.next节点
flag = true;
break;
}
//temp后移
temp = temp.next;
}
if(flag){
temp.next.name = hero.name;
temp.next.sex = hero.sex;
}else
System.out.println("待修改的节点不存在");
}
删除
Java语言实现单链表的删除:
根据节点编号删除单链表,如果单链表为null,则删除失败。
//根据编号删除节点
public void delByNo(int no){
HeroList temp = head;
if(temp.next==null){
System.out.println("链表为null,无法删除");
return;
}
//是否找到待删除的节点,默认没找到
boolean flag = false;
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.printf("待删除的节点不存在%d",no);
}
面试题
求单链表的长度
//1:求单链表的长度,或者单链表的有效长度
//给定一个单链表的头节点,返回单链表的长度
public static int getLength(HeroList head){
int length = 0;
if(head.next==null){
return length;
}
//开始遍历
HeroList temp = head;
while (temp.next!=null){
length++;
temp = temp.next;
}
return length;
}
求单链表倒数第K个节点
//2:求单链表单数第k个结点
//如果节点存在,返回节点,不存在返回null
public static HeroList getKNode(HeroList head,int k){
//求单链表倒数第K个节点,就是遍历到length-K位置的节点,用一个for循环
//k的范围应该大于0,小于length
int size = getLength(head);
if(k>size||k<0){
return null;
}
HeroList temp = head.next;
for(int i = 0;i<size-k;i++){
temp = temp.next;
}
return temp;
}
}
求两个链表的公共节点,如果有多个,返回第一个。
//给定2个单链表的头节点,求这两个单链表的公共部分,并且返回,如果没有公共部分,返回null
public HeroList getPublicPart(HeroList head1,HeroList head2){
//要找两个单链表的公共部分,首先要遍历,如何一次遍历,就找到公共节点呢?
//这里的思路是链表A遍历到末尾后,指向链表B的头节点,链表B遍历到末尾后,指向链表A的头节点,这样子,2个链表的
//速度一样,路程一样,当两个指针相遇的时候,就是公共部分的起始节点。如果循环,结束,没有相遇的指针,p1和p2都在末尾,
//为null,直接返回即可。
//代码如下:
HeroList p1 = head1;
HeroList p2 = head2;
while (p1!=p2){
p1 = (p1==null)?p2:p1.next;
p2 = (p2==null)?p1:p2.next;
}
return p1;
}
单链表的反转
//3:单链表的反转
public static void reverseLinked(HeroList head){
//思路,
//如果单链表为null或者节点数目为1,无需反转
if(head==null||head.next==null)
return;
// 需要三个指针,第一个辅助节点cur,用来遍历头节点,指向head.next
HeroList cur = head.next;
//第二个指针,next,用来保存/备份cur节点的下一个节点,在操作过程中,不丢失原链表
HeroList next;
//第三个指针,reverse,新链表的头节点
HeroList reverse = new HeroList(0,"",false);
//开始遍历原链表
while (cur!=null){
//备份cur.next
next = cur.next;
//断开cur,并放到reverse.next的前面
cur.next = reverse.next;
//将cur链接到reverse的后面
reverse.next = cur;
//cur后移
cur = next;
}
//原链表的头,指向reverse的下一个节点
head.next = reverse.next;
}
从尾到头打印单链表的节点
方式一:将单链表反转,然后遍历输出,不建议,会改变原有链表的结构
方式二:遍历原链表,压入栈,使其出栈。
//4:从尾到头,打印单链表
public static void printEtoFLinked(HeroList head){
if(head.next==null)
return;
Stack<HeroList> stack = new Stack<>();
HeroList temp = head.next;
while (temp!=null){
stack.push(temp);
temp = temp.next;
}
while (stack.size()>0){
System.out.println(stack.pop());
}
}
合并两个有序的单链表,合并后依然有序
//合并2个有序的单链表,合并后依然有序
public static HeroList mergeLinked(HeroList hero1,HeroList hero2){
HeroList temp1 = hero1.next;
HeroList temp2 = hero2.next;
//1第一步先判断
if (temp1 == null && temp2 == null)
return null;
if (temp1 == null)
return temp2;
if (temp2 == null)
return temp1;
//2定义新链表的firstNode,LastNode;
HeroList firstNode;
HeroList lastNode;
//3比较待合并链表的值,设置新链表的首节点和尾节点,并且temp后移
if(temp1.no<temp2.no){
firstNode = temp1;
lastNode = temp1;
temp1 = temp1.next;
}else {
firstNode = temp2;
lastNode = temp2;
temp2 = temp2.next;
}
//4开始循环,循环终止条件是temp1并且temp2不为null
while (temp1!=null && temp2!=null){
//循环的目的依次比较,将较小的节点添加到新链表的lastNode
if(temp1.no<temp2.no){
lastNode.next = temp1;
lastNode = temp1;
temp1 = temp1.next;
}else{
lastNode.next = temp2;
lastNode = temp2;
temp2 = temp2.next;
}
}
//5循环结束,判断temp1或者temp2是否为null,将一方不为null的节点保存到新链表的lastNode
if(temp1==null){
lastNode.next = temp2;
}else{
lastNode.next = temp1;
}
return firstNode;
}