今天介绍一下单链表的增删查改,外加三个小操作:反转单链表,逆序打印单链表结点以及合并两个有序链表,我这里是用单链表去实现存储水浒英雄人物的小案例,但其实单链表的核心操作就这几种:
首先创建一个结点类,存储英雄编号,姓名,昵称,里面包括了构造方法以及重写了toString方法
class HeroNode{
int no;
String name;
String nickname;
HeroNode next;//next默认为null,指向下一个结点
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 + "]";
}
}
创建一个头结点,不存放具体数据:
//先初始化一个头结点,头结点不动且不存放具体数据
private HeroNode head = new HeroNode(0, "", "");
添加结点:
这里给了两种方式,法一是直接添加至链表尾部,法二是根据编号来插入结点,都用到了辅助指针进行遍历,其中法二需利用辅助指针找到添加位置的前一个结点再进行插入
//添加结点,方法1,找到最后节点将next指向新节点
public void add(HeroNode heroNode) {
//头节点不能动,所以用辅助指针
HeroNode temp = head;
//遍历链表到最后
while(true) {
if(temp.next==null) {
break;
}
temp=temp.next;
}
temp.next = heroNode;
}
//添加节点方法二,关键是找到位于添加位置的前一个结点
public void addByOrder(HeroNode heroNode) {
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;
}
//判断flag
if(flag) {
System.out.printf("待插入的英雄编号%d已存在,不能插入\n",heroNode.no);
}
else {
heroNode.next=temp.next;
temp.next=heroNode;
}
}
删除结点:
利用辅助指针找到要删除的结点的前一个结点,直接越过待删除结点,让它成为垃圾
//删除节点,找到待删除结点的前一个结点
public void del(int no) {
HeroNode temp=head;
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结点不存在\n",no);
}
}
查找结点:
分为根据编号直接查找和查找倒数第index个结点,其中查找倒数第index个结点需要用for循环遍历到第length-index个结点,length又得另外写一个方法获取
//获取单链表的结点个数
public int getLength() {
HeroNode cur = head.next;
int length = 0;
while(cur!=null) {
length++;
cur=cur.next;
}
return length;
}
//根据编号直接查找
public HeroNode findIndexNode(int index) {
HeroNode temp = head;
boolean flag=false;//标识添加的编号是否被找到,默认false
while(true) {
if(temp.next==null) {//已在链表最后
break;
}
else if(temp.next.no==index) {
flag=true;
break;
}
temp = temp.next;
}
//判断flag
if(flag) {
return temp.next;
}
else {
System.out.printf("英雄编号%d不存在\n",index);
}
return null;
}
//查找倒数第index个结点
//先得到总长度,然后从第一个遍历length-index个
public HeroNode findLastIndexNode(int index) {
HeroNode cur = head.next;
if(cur==null) {
return null;
}
int length = getLength();
//先数据校验一下
if(index<=0||index>length) {
return null;
}
//for循环定位到index
for(int i=0;i<length-index;i++) {
cur=cur.next;
}
return cur;
}
修改结点信息:
传入新结点,类似于直接查找结点,找到就用新结点信息覆盖原来的结点
//修改节点信息,根据no编号来修改,即no编号不能改
public void update(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;
}
if(flag) {
temp.name=newHeroNode.name;
temp.nickname=newHeroNode.nickname;
}else {
System.out.printf("没有找到编号%d的结点,不能修改\n",newHeroNode.no);
}
}
反转单链表:
定义一个新头结点,遍历原来的链表,每取一个就放在前一个结点的前面,再将原来头结点覆盖新头结点实现反转
//反转单链表
public void reverse() {
HeroNode cur=head.next;
//链表为空或只有一个节点无需反转
if(cur==null||cur.next==null) {
return;
}
HeroNode next = null;//用于存cur的下一个节点
//新链表
HeroNode reverseHead = new HeroNode(0, "", "");
//遍历原来的链表,每遍历一个节点就将其取出放在新链表的最前端
while(cur!=null) {
next=cur.next;
cur.next=reverseHead.next;//cur的下一个结点指向新链表
reverseHead.next=cur;
cur=next;//cur后移
}
//将head.next指向reverseHead.next实现反转
head.next=reverseHead.next;
}
合并两个有序链表:
也是定义一个新的头结点,循环比较两条链表的编号大小,依次插入新的头结点,最后形成一条新链表,这里要注意遍历完一条链表后要将另一条继续遍历加进新链表
//合并两个有序链表
public SingleLinkedList mergeTwoLists(SingleLinkedList l1,SingleLinkedList l2)
{
SingleLinkedList s = new SingleLinkedList();
HeroNode h = s.head;
HeroNode h1 = l1.head.next;
HeroNode h2 = l2.head.next;
while(h1!=null&&h2!=null) {
if(h1.no<=h2.no) {
h.next=h1;
h1=h1.next;
h=h.next;
}else {
h.next=h2;
h2=h2.next;
h=h.next;
}
}
while(h1!=null) {
h.next=h1;
h1=h1.next;
h=h.next;
}
while(h2!=null) {
h.next=h2;
h2=h2.next;
h=h.next;
}
return s;
}
打印单链表结点:
这里分顺序打印和逆序打印,其中逆序打印利用了栈的先进后出来实现,可以去了解一下栈的一些push()和pop()方法
//显示链表
public void list() {
if(head.next==null) {
System.out.println("链表为空");
return;
}
HeroNode temp=head.next;
while(true) {
//判断是否到链表最后
if(temp==null) {
break;
}
//输出节点信息
System.out.println(temp);
//temp后移
temp=temp.next;
}
}
//逆序打印单链表
//利用栈将各个节点压入到栈,然后利用栈的先进后出特点实现逆序打印
public void reversePrint() {
HeroNode cur = head.next;
if(cur==null) {
System.out.println("链表为空");
return;
}
//创建一个栈
Stack<HeroNode> stack = new Stack<>();
//将链表所有结点压入栈
while(cur!=null) {
stack.push(cur);
cur=cur.next;
}
//将栈中结点进行打印
while(stack.size()>0) {
System.out.println(stack.pop());
}
}
以上便是带头结点的单链表的一些基本操作,关于测试可以自行实现