链表 (Linked List)
1.概述:
1、 链表是以结点的方式来存储,是链式存储
2、 每个结点包含data域,next域;指向下一个结点
3、 链表的各个结点不一定是连续地址存放
4、链表分带头结点的链表和没有头结点的链表,根据实际的需求来确定
2.单向链表的创建、把结点添加到链表尾部(尾插法)
缺点:只能根据添加的前后顺序来输出显示
2.1 定义一个结点类
代码如下:
// 定义HeroNode,每个HeroNode的实例对象就是一个结点
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方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
2.2 定义链表类,以及添加和显示的功能
添加(创建):
1、先创建一个head头结点,作用是表示单链表的头
2、后面我们每添加一个结点,就直接加入到链表的最后
遍历:
通过一个辅助变量遍历,帮助遍历整个链表
代码如下:
// 定义SingleLinkedList管理我们的英雄
class SingleLinkedList{
// 先初始化一个头结点,头结点不要动,不存放具体的数据
private HeroNode head= new HeroNode(0,"","");
// 添加节点到单向链表
// 思路,当不考虑编号顺序时
// 1、找到当前链表的最后节点 2、将最后这个结点的next指向新的结点
public void add1(HeroNode heroNode){
// 因为head结点不能动,因此需要一个辅助遍历temp
HeroNode temp =head;
// 遍历链表,找到最后结点
while (true){
// 找到链表的最后
if (temp.next==null){
break;
}
// 如果没有找到最后,将temp后移
temp=temp.next;
}
// 当推出while循环时,temp就只想了链表的最后
temp.next=heroNode;
}
// 显示链表【遍历】
public void showlist(){
// 判断链表是否为空
if(head.next==null){
System.out.println("链表为空");
return;
}
// 因为头结点,不能动,因此要一个辅助遍历temp来遍历
HeroNode temp=head.next;
while (true){
// 判断是否到链表最后
if (temp==null){
break;
}
//输出结点信息
System.out.println(temp);
// 将temp后移,一定要小心
temp=temp.next;
}
}
}
2.3 测试自定义链表,以及添加和显示的功能
代码如下:
public class SingleLinkedListDemo {
public static void main(String[] args) {
// 进行测试
// 先创建结点
HeroNode sj = new HeroNode(1, "宋江", "及时雨");
HeroNode ljy = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode wy = new HeroNode(3, "吴用", "智多星");
HeroNode lc = new HeroNode(4, "林冲", "豹子头");
// 创建单向链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 加入
singleLinkedList.add1(sj);
singleLinkedList.add1(ljy);
singleLinkedList.add1(wy);
singleLinkedList.add1(lc);
// 显示
singleLinkedList.showlist();
}
}
3.单向链表的创建、把结点添加到链表中间(指定结点后插法)
优势:可以无视添加的先后顺序,根据自己定义的节点编号(no)大小顺序来显示
说明:
需要按照编号的顺序添加
1、首先找到新添加的结点的位置,是通过辅助变量temp(指针),遍历搞定。
2、(新的结点.next)=temp.next
3、将temp.next=新结点
代码改动如下:
// 第二种方式在添加结点时,根据顺序排名将结点插入到指定位置
// 如果有这个排名,则添加失败,并给提示
public void add2(HeroNode heroNode){
// 因为head结点不能动,因此需要一个辅助temp 遍历到要添加的位置
// 因为单链表,找的temp位于添加位置的前一个结点,否则插入不了(指定位置后插)
HeroNode temp =head;
boolean flag=false;// 表示添加的结点的编号是否存在,默认false
// 遍历到指定位置
while (true){
if (temp.next==null){ //到链表最后
break;
}
if (temp.next.no>heroNode.no){ // 位置找到,就在temp的后面
break;
}else if (temp.next.no==heroNode.no){ // 希望添加的结点已经存在
flag=true;
break;
}
temp=temp.next;
}
if (flag){ // flag为true时不可以添加
System.out.printf("结点的编号%d已存在不可以再添加 ",heroNode.no);
}else{
// 插入到链表中 temp后(指定位置后面)
heroNode.next=temp.next;
temp.next=heroNode;
}
}
测试代码改动如下:
// 第二种方式添加 (根据no来排列)
singleLinkedList.add2(sj);
singleLinkedList.add2(lc);
singleLinkedList.add2(wy);
singleLinkedList.add2(ljy);
4.—添加修改指定编号结点信息的方法
修改节点的信息,根据no编号来修改,即no编号不能改 说明:根据newHeroNode 的no来修改
添加代码如下:
// 修改节点的信息,根据no编号来修改,即no编号不能改
// 说明:根据newHeroNode 的no来修改
public void updata(HeroNode newHeroNode){
if(head.next==null){
System.out.println("链表为空");
return;
}
HeroNode 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的结点 ",newHeroNode.no);
}
}
测试代码如下:
// 测试修改节点的代码
HeroNode newheroNode = new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.updata(newheroNode);
// 显示
System.out.println("修改后的情况...");
singleLinkedList.showlist();
5.添加删除结点的方法
从单链表中删除一个结点的思路==
1、先找到需要删除的这个节点(根据no判断)的前一个结点temp
2、temp.next=temp.next,next
3、被删除的节点,将不会有其他引用指向,将会被垃圾回收机制回收。
添加代码如下:
//根据no值来判断删除
public void del(int no){
HeroNode temp=head;
boolean flag=false; // 标识是否找到
while (true){
if (temp.next==null){ //到最后
break;
}
if (temp.next.no==no) {
// 找到待删除结点的前一个结点temp
flag = true;
break;
}
temp=temp.next; // temp后移,继续遍历
}
if (flag){
temp.next=temp.next.next;
}else {
System.out.printf("要删除的编号%d的结点不存在\n",no);
}
}
测试代码如下:
// 删除一个结点
singleLinkedList.del(1);
System.out.println("删除后的情况...");
singleLinkedList.showlist();
6.单向链表面试题
6.1求单链表中有效结点的个数
概述:
方法:获取到单链表有效结点的个数(有头结点的要去掉)
head 链表的头结点 return是返回的有效结点的个数
代码如下:
public static int getLength(HeroNode head){
if(head.next==null){ // 如果头结点下一个为null返回0
return 0;
}
int length=0;
HeroNode temp=head;
while (true){
if (temp.next==null){
break;
}
temp=temp.next;
length++;
}
return length;
}
// 测试一下统计结点个数
System.out.println("有效的结点个数为:" + SingleLinkedList.getLength(singleLinkedList.getHead()));
6.2查找单链表中倒数第k个结点
思路说明:
1、编写一个方法,时接受一个index
2、index表示是倒数第index个结点
3、先把链表遍历一遍,得到链表长度m,然后再遍历(m-index) 使要查找的结点对应的next(下一个)就是要查找的倒数k个结点
实现代码如下:
public HeroNode findneedIndex(int index){
if(head.next==null){
return null; // 没有找到
}
int m=getLength(); // 获取链表长度
HeroNode temp=head;
if(index<=0 || index>m ){
return null;
}
for (int i=1;i<=(m-index);i++){
temp=temp.next;
}
return temp.next;
}
// 测试一下查找倒数第k个结点
System.out.println("输出的倒数第k个结点是:"+ singleLinkedList.findneedIndex(1));
6.3单链表的反转(头插法)
思路如下:
1、先定义一个头结点reverseHead=new HeroNode(0,“”,“”);—一个只有头结点的新链表
2、从头到尾遍历原来的链表,每遍历一个结点,就将其取出,并放在新的链表reverseHead的最前端。
next = temp.next;// 保存temp后的那个结点,后面移位要使用
temp.next = reversehead.next; // 取下的temp指向新链表头结点指向的结点(null)。
reversehead.next = temp; // 将新链表的头结点指向取下的temp结点
temp = next; // 把temp移到保存再next中它后面的那个结点
实现代码如下:
public void reverseHead(){
if (head.next==null || head.next.next==null){
return;
}
HeroNode temp=head.next;
HeroNode next =null; // 指向当前结点【cur】的下一个结点
HeroNode reversehead= new HeroNode(0,"","");
// 从头到尾遍历原来的链表,没遍历一个结点,就将其取出,
// 并放在新的链表reverseHead的最前端。
while (temp!=null) {
next = temp.next;// 保存temp后的那个结点,后面移位要使用
temp.next = reversehead.next; // 取下的temp指向新链表头结点指向的结点(null)。
reversehead.next = temp; // 将新链表的头结点指向取下的temp结点
temp = next; // 把temp移到保存再next中它后面的那个结点
}
// 将head.next指向reversehead.next,实现头结点的替换
head.next=reversehead.next;
}
测试如下:
// 测试单链表的反转功能
System.out.println("原来的单链表");
singleLinkedList.showlist();
System.out.println("反转后的单链表");
singleLinkedList.reverseHead();
singleLinkedList.showlist();
6.4从尾到头打印单链表(反向遍历 / stack栈)
思路分析:
方式一:先将单链表进行翻转操作,然后在遍历即可(问题是会破坏原来单链表的结构,不可取)
方式二:可以利用栈这个数据结构,将各个结点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果。