Java数据结构与算法之单链表SingleLinkedList

视频参考

1.顺序插入的单链表

代码组成:
HeroNode.java
SingleLinkedList.java
Main1.java

1.1 HeroNode.java(单链表中的节点类)


public 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;
	}
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
}

1.2 SingleLinkedList.java(单链表类,每个对象对应一个单链表)

package com.zhanglei.linkedlist;
public class SingleLinkedList{
	private HeroNode head;
	public SingleLinkedList() {
	//根节点作为链表的起始位置,设定好了就不能动了,其具体内容无需设置
		this.head=new HeroNode(0," "," "); 
	}
	
	public void addLinkedList(HeroNode heronode) {
		HeroNode temp1=head;
		while(true) {//写死循环首先要考虑结束条件
			if(temp1.next==null) {
				temp1.next=heronode;
				break;
			}else if(temp1.next.no==heronode.no) {
				System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
				break;
			}else if(temp1.next.no>heronode.no) {
				HeroNode temp2=temp1.next;
				temp1.next=heronode;
				heronode.next=temp2;			
				break;
			}
			temp1=temp1.next;
		}
	}
	
	public void list() {
		HeroNode temp1=head;
		while(temp1.next!=null) {
			System.out.println(temp1.next);
			temp1=temp1.next;//不可忽略,否则无限打印
		}
	}
}
1.2.1 代码分析

一开始的addLinkedList()的写法:

	public void addLinkedList(HeroNode heronode) {
		HeroNode temp1=head;
		while(true) {//写死循环首先要考虑结束条件
			if(temp1.next==null) {
				temp1.next=heronode;
				break;
			}else if(temp1.next.no==heronode.no) {
				System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
				break;
			}else if(temp1.next.no>heronode.no) {
				HeroNode temp2=temp1.next;
				temp1.next=heronode;
				heronode.next=temp2;			
				break;
			}else if(temp1.next.no<heronode.no){
				//发现这里的逻辑好复杂
				break;
			}
			temp1=temp1.next;
		}
	}
  1. 仔细看上面的while循环,发现if条件将4种情况都考虑了,
    并且都各自有break,那如此temp1=temp1.next()永远都无法执行;
    如果把复杂逻辑的代码去掉,这样temp1=temp1.next()就能替代复杂逻辑的情况
    这样只用前三种情况就可以解决单链表的顺序插入问题(这个确实很妙!)
  2. 在第三种情况下我一开始的代码是:
				temp1.next=heronode;
				heronode.next=temp1.next.next;
	这里有两个问题:
	1.根本用不到next.next
	2.需要用额外的遍历来存储temp1.next,否则在temp1.next重新赋值后原来的指向关系就消失了

1.3 Main1.java(测试类)

package com.zhanglei.linkedlist;

public class Main1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
        SingleLinkedList st=new SingleLinkedList();
		HeroNode h1=new HeroNode(1,"林冲","豹子头");
		HeroNode h2=new HeroNode(7,"宋江","及时雨");
		HeroNode h3=new HeroNode(2,"吴用","智多星");
		HeroNode h4=new HeroNode(5,"鲁智深","花和尚");
		HeroNode h5=new HeroNode(4,"卢俊义","玉麒麟");
		HeroNode h6=new HeroNode(3,"柴进","小旋风");
		HeroNode h7=new HeroNode(6,"武松","行者");
		HeroNode h8=new HeroNode(8,"李逵","黑旋风");
		HeroNode h9=new HeroNode(3,"关胜","大刀");
		HeroNode h10=new HeroNode(2,"杨志","青面兽");
		st.addLinkedList(h1);
		st.addLinkedList(h2);
		st.addLinkedList(h3);
		st.addLinkedList(h4);
		st.addLinkedList(h5);
		st.addLinkedList(h6);
		st.addLinkedList(h7);
		st.addLinkedList(h8);
		st.addLinkedList(h9);
		st.addLinkedList(h10);
		st.list();
	}

}

1.3.1 结果分析
编码3已存在,无法重复插入!
编码2已存在,无法重复插入!
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=柴进, nickname=小旋风]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
  1. 一个SingleLinkedList对象就是一个单链表
  2. 单链表的组成是节点,故可以将节点单独作为一个节点类
  3. 启发就是碰到问题先看看这个问题由什么组成,将大问题转换成多个小问题来解决
  4. 当乱序插入链表时,打印链表得到的结果是有序的
1.3.2 原始代码(对象不可以作为左值,引用可以)
public class SingleLinkedList {
	private int no;
	private String name;
	private String nickname;
	private SingleLinkedList head;
	public SingleLinkedList next() {
		return new SingleLinkedList();
	}
	public void addLinkedList(SingleLinkedList st) {
		SingleLinkedList temp1=head;
		if(temp1.next()==null) {
			//下句报错:the left-hand side of an assignment must be an variable
			//原因:可以把对象赋值给引用,但是不能把引用或者对象赋值给对象
			temp1.next()=st;
		}
	}
}
  1. 一开始自己写就写出来上面的代码,这里把单链表类当成节点类来写了
  2. 这里用next()方法来返回一个对象,此时却不能用于赋值语句的左值
  3. 正确的代码是将next设置为属性,而不是方法(好像现在我更倾向于使用方法而非属性)

2.添加额外功能(学会1,则2就水到渠成)

2.1 修改指定no的节点和删除指定no的节点

在SingleLinkedList.java中添加如下两个方法:

    /*
	 * 修改链表中指定no的节点
	 */
	public void rectifyLinkedList(HeroNode heronode) {
		HeroNode temp1=head;
		
		while(true) {
			if(temp1.next==null) {
				System.out.println("修改失败!");
				break;
			}
			if(temp1.next.no==heronode.no) {
				temp1.next.name=heronode.name;
				temp1.next.nickname=heronode.nickname;
				break;
			}
			temp1=temp1.next;
		}
	}
	/*
	 * 删除链表中指定no的节点
	 */
	public void deleteLinkedList(HeroNode heronode) {
		HeroNode temp1=head;
		while(true) {
			if(temp1.next==null) {
				System.out.println("删除失败!");
				break;
			}
			if(temp1.next.no==heronode.no) {
				temp1.next=temp1.next.next;
				break;
			}
			temp1=temp1.next;
		}
	}
	
	

2.2 此时的Main1.java

package com.zhanglei.linkedlist;

public class Main1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SingleLinkedList st=new SingleLinkedList();
		HeroNode h1=new HeroNode(1,"林冲","豹子头");
		HeroNode h2=new HeroNode(7,"宋江","及时雨");
		HeroNode h3=new HeroNode(2,"吴用","智多星");
		HeroNode h4=new HeroNode(5,"鲁智深","花和尚");
		HeroNode h5=new HeroNode(4,"卢俊义","玉麒麟");
		HeroNode h6=new HeroNode(3,"柴进","小旋风");
		HeroNode h7=new HeroNode(6,"武松","行者");
		HeroNode h8=new HeroNode(8,"李逵","黑旋风");
		HeroNode h9=new HeroNode(3,"关胜","大刀");
		HeroNode h10=new HeroNode(2,"杨志","青面兽");
		st.addLinkedList(h1);
		st.addLinkedList(h2);
		st.addLinkedList(h3);
		st.addLinkedList(h4);
		st.addLinkedList(h5);
		st.addLinkedList(h6);
		st.addLinkedList(h7);
		st.addLinkedList(h8);
		st.addLinkedList(h9);
		st.addLinkedList(h10);
		st.list();
		System.out.println("修改指定元素后:");
		HeroNode h11=new HeroNode(3,"小关胜","小大刀");
		st.rectifyLinkedList(h11);
		st.list();
		System.out.println("删除指定元素后:");
		HeroNode h12=new HeroNode(3,"小关胜","小大刀");
		st.deleteLinkedList(h12);
		st.list();
		
	}

}

2.3 结果展示

编码3已存在,无法重复插入!
编码2已存在,无法重复插入!
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=柴进, nickname=小旋风]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
修改指定元素后:
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=小关胜, nickname=小大刀]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
删除指定元素后:
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]

3.单链表常见面试题

3.1 求单链表中的节点个数

3.1.1 思路
  • 依旧是利用while()来遍历链表,并使用辅助变量来记录遍历次数
  • 添加方法如下:
	/*
	 * 计算链表中的节点数目(根节点不包括在内)
	 */
	public int calNodeNumber() {
		HeroNode temp1=head;
		int length=0;
		while(temp1.next!=null) {
			length++;
			temp1=temp1.next;
		}
		return length;
	}

3.2 查找单链表中的倒数第k个节点

3.2.1 思路
  • 最直观的是先遍历一遍得到长度length,然后再遍历(length-k)次得到倒数第k个节点
  • 如此把代码3.1.1稍加利用即可
/*
	 * 得到倒数第k个节点
	 */
	public HeroNode getKlast(int k) {
		HeroNode temp1=head;
		int length=0;
		while(temp1.next!=null) {
			length++;
			temp1=temp1.next;
		}
		temp1=head;
		if(k>length) {
			System.out.println("链表节点数目达不到要求!");
		}else {
			for(int i=0;i<=length-k;i++) {
				temp1=temp1.next;
			}
		}
		return temp1;
	}

3.3 单链表的反转

3.3.1 思路
  • 自己想不出来什么思路
  • 通用的思路是:创建一个新的根节点,然后遍历原来的链表,每遍历一个节点就把该节点放在新的根节点对应的链表的最前端
  • 最后再将旧的根节点指向新的根节点的下一个节点
/*
	 * 反转单链表
	 */
	public SingleLinkedList reverseLinkedList() {
		HeroNode temp1=this.head;
		HeroNode auxiliary=new HeroNode(0," "," ");
		HeroNode temp2=null;
		while(temp1.next!=null) {
			temp2=temp1.next.next;//temp2指向下下个地址,这里四个等式左右两边都是引用,即地址
			temp1.next.next=auxiliary.next;//此时下下个地址被改变,所以temp2来提前保存
			auxiliary.next=temp1.next;
			temp1.next=temp2;			
		}
		this.head.next=auxiliary.next;
		return this;
	}
  1. 引用即地址
  2. 当要改变链表节点的指向时,为了继续遍历原来的链表需要提前保存好节点原来的指向
  3. 当SingleLinkedList对象调用该方法时,返回的是对象本身,(对象会在函数内部得到修改)
  4. head为SingleLinkedList的私有属性,但由于reverseLinkedList()是SingleLinkedList的成员方法,故this.head可以访问该私有属性
  5. 这里的this的灵活使用挺好的

3.4 从尾到头打印单链表[方式1:先反转再遍历,方式2:栈]

  • 方式1会改变原链表的结构,不可取
  • 方式2调用内置Stack函数

3.5 合并两个有序的单链表,合并后的单链表仍旧有序

3.5.1思路
  • 自己想不出来什么思路
  • 通用的思路:与反转单链表类似,先创建一个新的根节点,然后同时对两个单链表进行遍历,此时两个单链表中较小的节点就会被连接到新的根节点的后面,然后进行下次遍历,依旧判断两个单链表中此时遍历到的节点中的最小者,将其放到新链表的最后
  • 技巧点是必然会有一个单链表率先遍历结束,此时尚未遍历结束的链表无需继续遍历,可以直接全部移到新链表的后面

这个思路我没有做出来,原因就是同时遍历的时候耦合性太高,各种next都混了
然后我看了看别人写的代码:原文链接

// twoLinkedList方法
	// 传入待合并的两个链表的头节点以及第三个单链表的头节点
	public static void twoLinkedList(HeroNode head1, HeroNode head2, HeroNode head3) {
		// 如果两个链表均为空,则无需合并,直接返回
		if(head1.next == null && head2.next == null) {
			return;
		}
		// 如果链表1为空,则将head3.next指向head2.next,实现链表2中的节点连接到链表3
		if(head1.next == null) {
			head3.next = head2.next;
		} else {
			// 将head3.next指向head1.next,实现链表1中的节点连接到链表3
			head3.next = head1.next;
			// 定义一个辅助的指针(变量),帮助我们遍历链表2
			HeroNode cur2 = head2.next;
			// 定义一个辅助的指针(变量),帮助我们遍历链表3
			HeroNode cur3 = head3;
			HeroNode next = null;
			// 遍历链表2,将其节点按顺序连接至链表3
			while(cur2 != null) {
				// 链表3遍历完毕后,可以直接将链表2剩下的节点连接至链表3的末尾
				if(cur3.next == null) {
					cur3.next = cur2;
					break;
				}
				// 在链表3中,找到第一个大于链表2中的节点编号的节点
				// 因为是单链表,找到的节点是位于添加位置的前一个节点,否则无法插入
				if(cur2.no <= cur3.next.no) {
					next = cur2.next;  // 先暂时保存链表2中当前节点的下一个节点,方便后续使用
					cur2.next = cur3.next;  // 将cur2的下一个节点指向cur3的下一个节点
					cur3.next = cur2;  // 将cur2连接到链表3上
					cur2 = next;  // 让cur2后移
				}
				// 遍历链表3
				cur3 = cur3.next;
			}
		}
	}

分析:
这段代码是正确的,然后我查看它的核心代码:

					next = cur2.next;  // 先暂时保存链表2中当前节点的下一个节点,方便后续使用
					cur2.next = cur3.next;  // 将cur2的下一个节点指向cur3的下一个节点
					cur3.next = cur2;  // 将cur2连接到链表3上
					cur2 = next;  // 让cur2后移

我感觉很妙,但是理解得很慢
然后我试着画图,就理解了
于是我利用画图这个工具,尝试按新的思路来写
新的思路:
遍历第二个链表,将第二个链表按顺序插入到第一个链表中去,遍历结束后将第三个待生成的链表指向第一个链表

	/*
	 * 合并两个有序链表为一个新的有序链表:我写了一天!!!!!!!!!!!而且还是参考别人的才写出来!!总结技巧用自己的思路写出来。
	 */
	public static SingleLinkedList combine2LinkedList(SingleLinkedList st1,SingleLinkedList st2) {
		HeroNode temp2=st2.head;
		SingleLinkedList st=new SingleLinkedList();
		HeroNode temp=st.head;
		//第二种思路:遍历第二个链表,将其插入到第一个链表中去
		while(temp2.next!=null) {
			temp2=st1.addHelp(temp2);
//			temp2=temp2.next;			
		}
		temp.next=st1.head.next;
		return st;
	}
	/*
	 * 辅助合并两个链表的函数
	 */
	public HeroNode addHelp(HeroNode heronode) {
		HeroNode temp1=this.head;
		while(true) {//写死循环首先要考虑结束条件
			if(temp1.next==null) {
				HeroNode temp5=heronode.next.next;
				temp1.next=heronode.next;//当temp1.next得到新的赋值时,temp2对应链表的节点个数要减一,对应上下两行的操作
				heronode.next=temp5;
				break;
			}else if(temp1.next.no==heronode.next.no) {
				System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
				break;
			}else if(temp1.next.no>heronode.next.no) {
				HeroNode temp5=heronode.next.next;
//				temp1.next=heronode.next;//与下行代码有先后顺序
				heronode.next.next=temp1.next;
				temp1.next=heronode.next;//当temp1.next得到新的赋值时,temp2对应链表的节点个数要减一		
				heronode.next=temp5;
				break;
			}
			temp1=temp1.next;
		}
		return heronode;
	}
	
  • 启发:写链表相关题目是一定要画图!!!!
  • 启发:写链表相关题目是一定要画图!!!!
  • 启发:写链表相关题目是一定要画图!!!!
  • 启发:写链表相关题目是一定要画图!!!!
  • 启发:写链表相关题目是一定要画图!!!!

不同的链表构造方式时对应的合并两个链表
递归调用实现合并两个链表

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值