一、单链表的头插法:
头插法原理图:
代码演示头插法:
package com.fan.linkedlist;
public class SingleLInkedListDemo3 {
public static void main(String[] args) {
SingleLInkedList3 singlelist3 = new SingleLInkedList3();
System.out.println(singlelist3.headInsert(1));
System.out.println(singlelist3.headInsert(2));
System.out.println(singlelist3.headInsert(3));
System.out.println("显示链表:");
singlelist3.show();
//测试尾插法1:向单链表增加数据
System.out.println("\n测试尾插法1:");
SingleLInkedList3 singlelist4 = new SingleLInkedList3();
System.out.println(singlelist4.tailInsert1(4));
System.out.println(singlelist4.tailInsert1(5));
System.out.println(singlelist4.tailInsert1(6));
singlelist4.show();
//测试尾插法2:向单链表增加数据
System.out.println("\n测试尾插法2:");
SingleLInkedList3 singlelist5 = new SingleLInkedList3();
System.out.println(singlelist5.tailInsert2(7));
System.out.println(singlelist5.tailInsert2(8));
System.out.println(singlelist5.tailInsert2(9));
singlelist5.show();
}
}
//2.创建链表类,并初始化头结点/尾结点,设置其的指针域为空.
class SingleLInkedList3{//链表带泛型
//一个单链表是由多个节点构成的。这里我们设置头节点和尾部节点
//声明头节点,并设置其指针域为空即headNode ---> null
Node3 headNode = new Node3();
//声明尾部节点,用来演示尾插法而设置的变量
Node3 tailNode = new Node3();
public Node3 getHeadNode() {
return headNode;
}
public void setHeadNode(Node3 headNode) {
this.headNode = headNode;
}
public Node3 getTailNode() {
return tailNode;
}
public void setTailNode(Node3 tailNode) {
this.tailNode = tailNode;
}
//使用空的构造方法创建单链表,当一个空链表创建好了之后就要有头或者尾节点
//此构造方法很重要
public SingleLInkedList3() {
//初始化头节点的next指针域为空,并设置其指针域为空即headNode ---> null
headNode.setNext(null);//一个空链表的头节点是指向null的
//初始化尾节点的next指针域也为空,采用头插法就没必要设置tailNode
tailNode.setNext(null);
//headNode永远存储第一个节点的地址,tailNode永远存储最后一个节点的地址
headNode = tailNode ;//也为了show方法的遍历
}
//头插法,即头节点不动,其后的指针每次进行拆散,并将新节点插入到头节点紧邻后边
/*头插法一般也用于单链表模拟栈,我们用头插法能实现先入后出*/
/*向单链表增加数据*/
public boolean headInsert(Object obj){
Node3 newNode = new Node3(obj);//将数据封装成一个新节点
newNode.setNext(headNode.getNext());//设置新节点的后驱节点为 头节点的下一个节点
headNode.setNext(newNode);//设置新节点的前驱节点为 头节点,即头节点不动
//如果头节点紧邻后是新节点,证明头插入成功
return headNode.getNext() == newNode;
}
//尾插法 ====第一种实现:使用从头节点遍历找尾节点并插入===
/*向单链表增加数据*/
public boolean tailInsert1(Object obj){
Node3 newNode = new Node3(obj);
Node3 curr = headNode;//临时节点从头节点开始遍历,
while(true){
if(curr.getNext() == null){
break;//到了链表结尾
}
curr = curr.getNext();//临时节点后移
}//end-while
//出循环后就证明到了链表结尾,直接添加新节点,curr此时指向了最后一个有效节点
curr.setNext(newNode);
newNode.setNext(null);
return newNode.getNext() == null;
}
//尾插法====第二种实现,使用尾节点移动来实现
public boolean tailInsert2(Object obj){
Node3 newNode = new Node3(obj);
//1.将尾指针的指针域指向 刚刚插入的新节点newNode
tailNode.setNext(newNode);
//刷新尾节点,现在newNode变成了最后一个元素了,也就是新的尾节点
//2.tailNode永远存储最后一个节点
tailNode = newNode;//尾节点指向新添加的节点,即尾节点后移,类似于i++
//将最终的尾节点指针域赋值为空
tailNode.setNext(null);
return tailNode.getNext()==null;
}
//链表元素的显示
public void show(){
//定义一个临时变量curr来遍历链表,不能使用头节点,因为头节点不能动
Node3 curr = headNode.getNext();//curr指向头节点下一个,或者说将头节点地址赋值给curr临时节点
while(true){
if(curr == null){//下一个有效元素位空,则到了链表的结尾
break;
}
System.out.print(curr+"\t-->");//打印当前有效节点
curr = curr.getNext();//指针后移,相当于i++
}
}
//链表元素的显示
}
//1.创建节点类,也可以将此类设置成内部类
class Node3{
//数据域,可以是多个数据如name,id,age等
private Object object;//
//指针域,单链表有数据域和next指针这两项
private Node3 next;//指向下驱节点
public Node3(Object object) {
this.object = object;
}
public Node3() {
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Node3 getNext() {
return next;
}
public void setNext(Node3 next) {
this.next = next;
}
@Override
public String toString() {
return "Node3{" +
"object=" + object +
'}';
}
}
测试结果:
二、单链表的尾插法:
尾插法: 尾插法相对于头插法有些许不同 因为要返回头 头不能动 所以需要一个tailNode来记录最后一个值 tailNode右移
尾插法原理图:
开始tailNode–>null是指向null的。
代码:
//尾插法 ====第一种实现:使用从头节点遍历找尾节点并插入===
/*向单链表增加数据*/
public boolean tailInsert1(Object obj){
Node3 newNode = new Node3(obj);
Node3 curr = headNode;//临时节点从头节点开始遍历,
while(true){
if(curr.getNext() == null){
break;//到了链表结尾
}
curr = curr.getNext();//临时节点后移
}//end-while
//出循环后就证明到了链表结尾,直接添加新节点,curr此时指向了最后一个有效节点
curr.setNext(newNode);
newNode.setNext(null);
return newNode.getNext() == null;
}
//尾插法====第二种实现,使用尾节点移动来实现
public boolean tailInsert2(Object obj){
Node3 newNode = new Node3(obj);
//1.将尾指针的指针域指向 刚刚插入的新节点newNode
tailNode.setNext(newNode);
//刷新尾节点,现在newNode变成了最后一个元素了,也就是新的尾节点
//2.tailNode永远存储最后一个节点
tailNode = newNode;//尾节点指向新添加的节点,即尾节点后移,类似于i++
//将最终的尾节点指针域赋值为空
tailNode.setNext(null);
return tailNode.getNext()==null;
}
测试代码:
public class SingleLInkedListDemo3 {
public static void main(String[] args) {
SingleLInkedList3 singlelist3 = new SingleLInkedList3();
System.out.println(singlelist3.headInsert(1));
System.out.println(singlelist3.headInsert(2));
System.out.println(singlelist3.headInsert(3));
System.out.println("显示链表:");
singlelist3.show();
//测试尾插法1:向单链表增加数据
System.out.println("\n测试尾插法1:");
SingleLInkedList3 singlelist4 = new SingleLInkedList3();
System.out.println(singlelist4.tailInsert1(4));
System.out.println(singlelist4.tailInsert1(5));
System.out.println(singlelist4.tailInsert1(6));
singlelist4.show();
//测试尾插法2:向单链表增加数据
System.out.println("\n测试尾插法2:");
SingleLInkedList3 singlelist5 = new SingleLInkedList3();
System.out.println(singlelist5.tailInsert2(7));
System.out.println(singlelist5.tailInsert2(8));
System.out.println(singlelist5.tailInsert2(9));
singlelist5.show();
}
}
测试结果:
总结头插法和尾插法:
三、单链表顺序添加:
需求,我们有一批购房的摇号者,我们让摇号者的编号小的越先获取购房资格,请乱序添加5个摇号者的编号,添加到链表中后是从小到大排好序的。
分析:单链表的按照次序添加,区别于头插法和尾插法:
//按照编号顺序添加
public void addByOrder(ConsumerNode newNode){
ConsumerNode curr = headNode;//头节点不能动,所以设置临时节点
boolean flag = false;//默认编号不存在
while(true){
//遍历到链表结尾的时候
if(curr.getNext() == null){
break;
}
//判断编号的插入位置
if(newNode.getNo() < curr.getNext().getNo()){
break;//头插
}else if(newNode.getNo() == curr.getNext().getNo()){
flag = true;//编号相同的特殊情况,单独处理
break;
}
curr = curr.getNext();
}
if(!flag){//当编号不存在的时候我们才添加
newNode.setNext(curr.getNext());
curr.setNext(newNode);
}else{
System.out.println("编号相同,不能插入---");
}
}
四、按照值查找节点:
//根据数据域编号查找该节点的所有信息
public void findByNo(Integer key){
ConsumerNode curr =headNode;
boolean flag =false;
while(true){
if(curr.getNext() == null){//到链表结尾,退出循环
break;
}
if(curr.getNext().getNo() == key){
flag = true;//找到查找的节点了,退出
break;
}
curr = curr.getNext();//临时节点后移进行遍历
}
if(flag){
System.out.println(curr.getNext());
}else{
System.out.println("没有找到要查找的节点信息----");
}
}
五、删除节点:
//删除某一个节点,按照编号
public void delByNo(Integer no){
ConsumerNode curr = headNode;
boolean flag = false;//设置是否找到要删除的节点
while(true){
//需要判断是否到链表的结尾处
if(curr.getNext() == null){
break;
}
if(curr.getNext().getNo() == no){
flag = true;
break;
}
curr = curr.getNext();//没有找到的话,后移继续找
}
if(flag){
curr.setNext(curr.getNext().getNext());
}else{
System.out.println("没有此节点要删除---");
}
}
六、修改节点:
//修改某一个节点,根据节点编号,修改
public void update(ConsumerNode newNode){
//思路,就是替换编号 相同的节点,即插入新节点
ConsumerNode curr = headNode;
boolean flag = false;//设置是否找到要修改的节点
while(true){
//需要先判断是否到链表的结尾处
if(curr.getNext() == null){
break;//到链表的结尾
}
if(curr.getNext().getNo()== newNode.getNo() ){
flag = true;
break;
}
curr = curr.getNext();
}
if(flag){
newNode.setNext(curr.getNext().getNext());
curr.setNext(newNode);
}else{
System.out.println("输入的编号没找到对应节点---");
}
}
七、求单链表的表长:
//获取链表的长度
public int getSize(){
int count = 0;
ConsumerNode curr = headNode;
while(true){
if(curr.getNext() == null){
break;
}
count++;
curr = curr.getNext();
}
return count;
}
最后的总代码:
package com.fan.linkedlist;
public class SingleLinkedlistDemo4 {
public static void main(String[] args) {
//创建一个空链表
ConsumerLinkedlist consumerLinkedlist = new ConsumerLinkedlist();
/*consumerLinkedlist.addFirst(new ConsumerNode(1,"小一"));
consumerLinkedlist.addFirst(new ConsumerNode(2,"小二"));
consumerLinkedlist.addFirst(new ConsumerNode(3,"张三"));*/
/*consumerLinkedlist.addLast(new ConsumerNode(1,"小一"));
consumerLinkedlist.addLast(new ConsumerNode(2,"小二"));
consumerLinkedlist.addLast(new ConsumerNode(3,"张三"));
consumerLinkedlist.addLast(new ConsumerNode(4,"李四"));*/
consumerLinkedlist.addByOrder(new ConsumerNode(4,"小一"));
consumerLinkedlist.addByOrder(new ConsumerNode(2,"小二"));
consumerLinkedlist.addByOrder(new ConsumerNode(1,"张三"));
consumerLinkedlist.addByOrder(new ConsumerNode(3,"李四"));
consumerLinkedlist.addByOrder(new ConsumerNode(5,"李四"));
System.out.println("显示添加后的顺序链表:");
consumerLinkedlist.show();
System.out.println("\n链表长度:"+ consumerLinkedlist.getSize());
//consumerLinkedlist.delByNo(3);
consumerLinkedlist.delByNo(11);
System.out.println("删除后显示:");
consumerLinkedlist.show();
System.out.println("\n修改节点:===");
consumerLinkedlist.update(new ConsumerNode(3,"我是大山"));//测试的此编号不存在
consumerLinkedlist.show();
System.out.println("\n查找节点========");
//consumerLinkedlist.findByNo(8);
consumerLinkedlist.findByNo(3);
}
}
//链表类
class ConsumerLinkedlist{
//一个单链表要有头节点或者尾节点
private ConsumerNode headNode = new ConsumerNode();
private ConsumerNode tailNode = new ConsumerNode();
//构造方法
public ConsumerLinkedlist(){
headNode.setNext(null);
tailNode.setNext(null);
//头节点和尾节点开始时是重合的,都是指向null的
//headNode永远存储第一个节点的地址,tailNode永远存储最后一个节点的地址
headNode = tailNode ;//头尾重合,两个指针重合
}
//按照标号添加到链表中
public void addFirst(ConsumerNode newNode){
//头插法,不需要循环,头节点不动
//注意,必须先处理新节点的后驱节点
newNode.setNext(headNode.getNext());
headNode.setNext(newNode);
}
//尾插法,还是头结点不动,尾节点移动
public void addLast(ConsumerNode newNode){
//注意,tailNode此时相当于一个临时的节点指针,相当一个指向节点的一个箭头
/* if(tailNode.getNext() == null){
//直接添加新节点到尾节点的后边
tailNode.setNext(newNode);
//让尾节点成为最后一个节点
tailNode = tailNode.getNext();
//让尾节点的指针为null
tailNode.setNext(null);
}*/
//直接添加新节点到尾节点的后边
tailNode.setNext(newNode);
tailNode = tailNode.getNext();//让尾节点成为最后一个节点
tailNode.setNext(null);//让尾节点指向null
}
//按照编号顺序添加
public void addByOrder(ConsumerNode newNode){
ConsumerNode curr = headNode;//头节点不能动,所以设置临时节点
boolean flag = false;//默认编号不存在
while(true){
//遍历到链表结尾的时候
if(curr.getNext() == null){
break;
}
//判断编号的插入位置
if(newNode.getNo() < curr.getNext().getNo()){
break;//头插
}else if(newNode.getNo() == curr.getNext().getNo()){
flag = true;//编号相同的特殊情况,单独处理
break;
}
curr = curr.getNext();
}
if(!flag){//当编号不存在的时候我们才添加
newNode.setNext(curr.getNext());
curr.setNext(newNode);
}else{
System.out.println("编号相同,不能插入---");
}
}
//删除某一个节点,按照编号
public void delByNo(Integer no){
ConsumerNode curr = headNode;
boolean flag = false;//设置是否找到要删除的节点
while(true){
//需要判断是否到链表的结尾处
if(curr.getNext() == null){
break;
}
if(curr.getNext().getNo() == no){
flag = true;
break;
}
curr = curr.getNext();//没有找到的话,后移继续找
}
if(flag){
curr.setNext(curr.getNext().getNext());
}else{
System.out.println("没有此节点要删除---");
}
}
//修改某一个节点,根据节点编号,修改
public void update(ConsumerNode newNode){
//思路,就是替换编号 相同的节点,即插入新节点
ConsumerNode curr = headNode;
boolean flag = false;//设置是否找到要修改的节点
while(true){
//需要先判断是否到链表的结尾处
if(curr.getNext() == null){
break;//到链表的结尾
}
if(curr.getNext().getNo()== newNode.getNo() ){
flag = true;
break;
}
curr = curr.getNext();
}
if(flag){
newNode.setNext(curr.getNext().getNext());
curr.setNext(newNode);
}else{
System.out.println("输入的编号没找到对应节点---");
}
}
//根据数据域编号查找该节点的所有信息
public void findByNo(Integer key){
ConsumerNode curr =headNode;
boolean flag =false;
while(true){
if(curr.getNext() == null){//到链表结尾,退出循环
break;
}
if(curr.getNext().getNo() == key){
flag = true;//找到查找的节点了,退出
break;
}
curr = curr.getNext();//临时节点后移进行遍历
}
if(flag){
System.out.println(curr.getNext());
}else{
System.out.println("没有找到要查找的节点信息----");
}
}
//获取链表的长度
public int getSize(){
int count = 0;
ConsumerNode curr = headNode;
while(true){
if(curr.getNext() == null){
break;
}
count++;
curr = curr.getNext();
}
return count;
}
//单链表的遍历
public void show(){
//判断链表是否为空,只有一个头节点的话直接结束
if(headNode.getNext() == null){
System.out.println("链表为空");
return;
}
ConsumerNode curr = headNode.getNext();//从有效节点开始遍历
while(true){
if(curr == null){//到链表末尾时退出
break;
}
System.out.print(curr+"-->");
curr = curr.getNext();
}
}
}
//消费者节点,消费者将来需要被叫号排到链表中,可以用lambook
class ConsumerNode {
//数据域
private Integer no;
private String name;
//指针域,单链表只有一个next指针域,用于指向下一个节点
private ConsumerNode next;
//生成构造和set,get等
public ConsumerNode(Integer no, String name) {
this.no = no;
this.name = name;
}
public ConsumerNode() {
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ConsumerNode getNext() {
return next;
}
public void setNext(ConsumerNode next) {
this.next = next;
}
@Override
public String toString() {
return "ConsumerNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
单链表常见面试题:
单链表的反转:
//单链表的反转,类似于头插法,返回一个新链表的头节点,不影响原链表结构
public void resver(){
ConsumerNode curr = headNode.getNext();//用于遍历的指针节点
ConsumerNode temp = null;//后面用于存储curr的后驱节点的
ConsumerNode newlinkedHead = new ConsumerNode();
//循环拆解原链表,并拼接到新链表的头节点上
if(curr.getNext() == null || curr.getNext().getNext() == null){
return ;//只有一个节点或者空链表
}
while(true){
if(curr == null){
break;//到链表结尾
}
if(curr != null){
//先将curr的下一个节点保存起来
temp = curr.getNext();
//设置curr的下一个节点不再是原先链表的下一个节点了,而是新链表头节点的下一个节点
curr.setNext(newlinkedHead.getNext());
//新节点的下一个节点就是curr
newlinkedHead.setNext(curr);
//让curr后移到原先链表的后一个节点
curr = temp;//这个相当于原先的 curr = curr.getNext();
}
}
//将新链表的所有节点穿到旧链表的头节点上,这样输出旧链表就已经反转了
headNode.setNext(newlinkedHead.getNext());
}
测试:
public class SingleLinkedlistDemo4 {
public static void main(String[] args) {
//创建一个空链表
ConsumerLinkedlist consumerLinkedlist = new ConsumerLinkedlist();
//添加顺序1->2->3->4->5
consumerLinkedlist.addLast(new ConsumerNode(1,"小一"));
consumerLinkedlist.addLast(new ConsumerNode(2,"小二"));
consumerLinkedlist.addLast(new ConsumerNode(3,"张三"));
consumerLinkedlist.addLast(new ConsumerNode(4,"李四"));
consumerLinkedlist.addLast(new ConsumerNode(5,"李四"));
System.out.println("\n反转链表:--");
consumerLinkedlist.resver();
consumerLinkedlist.show();
}
}
对于从尾部到头部打印单链表,可以先反转,后调用show方法即可;