1.3.1 链表介绍
-
链表是以节点的方式来存储数据的,是链式存储
-
单链表每一个节点包含data数据域,next域存放下一个节点的地址(双链表的会多一个prev指向前一个节点的地址)
-
因为是链式存储,所以所有节点并不是连续存储的
-
链表还分为带头结点和不带头结点的链表
-
单链表(带头结点)的逻辑示意图如下:
1.3.2 单链表代码实例01
-
该单链表为带头结点的单链表
-
该实例先假定插入链表都是从链表最后添加
-
该实例实现链表的增删改查
-
遍历链表到尾的条件是节点的next == null
-
判断链表为空的条件是head.next == null
实现代码如下:
import java.util.Objects;
class HeroNode{
private Integer no; // 排名
private String name;// 姓名
private String nickname;// 外号
private HeroNode next;
public HeroNode(Integer no,String name,String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
if (next!=null)
return "[No: " +no+", name: "+name+", nickName: "+nickname+", nextNo:"+next.getNo()+
"]";
else
return "[No: " +no+", name: "+name+", nickName: "+nickname+", nextNo:"+
" null]";
}
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 String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public HeroNode getNext() {
return next;
}
public void setNext(HeroNode next) {
this.next = next;
}
}
class singleLinkList{
private HeroNode head = new HeroNode(0,"","");
public HeroNode getHead(){
return head;
}
public boolean isEmpty(){ // 判断链表是否为空
return head.getNext() == null;
}
public void addNode(HeroNode heroNode){ // 添加链表节点
HeroNode temp = head;
while (temp.getNext() != null){ // 当前节点的next为null的时候遍历结束
temp = temp.getNext();
}
// 将最后一个节点的next指向新节点
temp.setNext(heroNode);
}
public void show(){ // 遍历链表
System.out.println("显示全部节点: ");
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.getNext();
while (temp!=null){
System.out.println(temp);
temp = temp.getNext();
}
}
public HeroNode getOne(Integer no){ // 根据no查询节点
if (isEmpty()) {
System.out.println("链表为空!");
return null;
}
HeroNode temp = head.getNext();
while(!Objects.equals(temp.getNo(), no)){
temp = temp.getNext();
if (temp == null){ // 如果遍历链表也查不到 那么返回null
System.out.println("查无此节点!");
return null;
}
}
return temp;
}
public void update(Integer no,String name,String nickName){ // 修改节点data域
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode heroNode = getOne(no);
heroNode.setName(name);
heroNode.setNickname(nickName);
}
public void deleteNode(Integer no){ // 删除节点
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head;
/**
* 想要删除一个节点: 1.将该节点的前一个节点的next指向删除节点的下一个节点
* 2. 将该节点删除(释放内存,在Java里这一步由JVM垃圾回收器执行)
*/
while (!Objects.equals(temp.getNext().getNo(), no)){
temp = temp.getNext();
if(temp.getNext() == null){
System.out.println("查出此节点!");
break;
}
}
temp.setNext(temp.getNext().getNext());
}
}
public class linkListDemo01 {
public static void main(String[] args) {
singleLinkList singleLinkList = new singleLinkList();
singleLinkList.addNode(new HeroNode(1,"小李","小黑子1号"));
singleLinkList.addNode(new HeroNode(2,"小王","小黑子2号"));
singleLinkList.addNode(new HeroNode(3,"小张","小黑子3号"));
singleLinkList.addNode(new HeroNode(4,"小黑","小黑子4号"));
singleLinkList.show();
System.out.println("查询节点: "+singleLinkList.getOne(3));
System.out.println("修改节点: "+2);
singleLinkList.update(2,"大王","小黑子22号");
System.out.println("删除节点: "+3);
singleLinkList.deleteNode(3);
singleLinkList.getOne(3);
singleLinkList.show();
System.out.println("添加节点: "+3);
singleLinkList.addNode(new HeroNode(3,"小张","小黑子3号"));
singleLinkList.show();
}
}
输出结果:
1.3.3 单链表代码实例02
-
本实例较于实例01,add的节点会自动根据no排序
-
本实例限定no是unique的,链表内无法同时存在多个相同no的节点
-
示意图:
实现代码:
本实例代码的变化只是多了一个addNodeByOrder(HeroNode heroNode) 和 main的一点变化
// addNodeByOrder(HeroNode heroNode)
public void addNodeByOrder(HeroNode heroNode){
HeroNode temp = head;
while(true){
if(Objects.equals(temp.getNext().getNo(), heroNode.getNo())){
System.out.println("链表内已有该no的节点!");
return;
}
if(heroNode.getNo() > temp.getNext().getNo()){
temp = temp.getNext();
} else if (heroNode.getNo() < temp.getNext().getNo()) {
HeroNode oldNode = temp.getNext();
heroNode.setNext(oldNode);
temp.setNext(heroNode);
break;
}
}
}
// main
public static void main(String[] args) {
singleLinkList singleLinkList = new singleLinkList();
singleLinkList.addNode(new HeroNode(1,"小李","小黑子1号"));
singleLinkList.addNode(new HeroNode(2,"小王","小黑子2号"));
singleLinkList.addNode(new HeroNode(3,"小张","小黑子3号"));
singleLinkList.addNode(new HeroNode(4,"小黑","小黑子4号"));
singleLinkList.show();
System.out.println("查询节点: "+singleLinkList.getOne(3));
System.out.println("修改节点: "+2);
singleLinkList.update(2,"大王","小黑子22号");
System.out.println("删除节点: "+3);
singleLinkList.deleteNode(3);
singleLinkList.getOne(3);
singleLinkList.show();
System.out.println("添加节点: "+3);
singleLinkList.addNodeByOrder(new HeroNode(3,"小张","小黑子3号"));
singleLinkList.show();
}
输出结果:
1.3.4 单链表—面试题01
题目:
- 求单链表中有效节点的个数
- 查找单链表中的倒数第k个节点
思路:
- 有效节点数目只需要简单遍历计算即可
- 查找倒数第k个节点
- 1.先遍历单链表得到链表全部有效节点数目nodeCount(不包含头节点head)
- 2.因为单链表无法倒序遍历,所以只能正序遍历,既然已经得到全部节点数目,那么只需要从第一个有效节点开始遍历nodeCount-k个节点,则节点指针自然就指向倒数第k个节点
- 示意图:
实现代码:
/**
*
* @param head
* @return 以传入节点为单链表头节点,计算该单链表节点数目
*/
public Integer getNodesCount(HeroNode head){
HeroNode temp = head;
Integer count = 0;
while (temp.getNext() != null){
count++;
temp = temp.getNext();
}
return count;
}
/**
* 查找单链表的倒数第k个节点
*/
public HeroNode getLastIndexNode(HeroNode head,Integer index){
HeroNode temp = head.getNext();
// 1. 获取该单链表的节点数目
Integer nodesCount = getNodesCount(head);
// 2. 检验index数值是否合理
if(index<=0 || index > nodesCount){
System.out.println("输入参数不合理!");
return null;
}
测试代码及其输出结果
public static void main(String[] args) {
singleLinkList singleLinkList = new singleLinkList();
HeroNode head = singleLinkList.getHead();
singleLinkList.addNode(new HeroNode(1,"小李","小黑子1号"));
singleLinkList.addNode(new HeroNode(2,"小王","小黑子2号"));
singleLinkList.addNode(new HeroNode(3,"小张","小黑子3号"));
singleLinkList.addNode(new HeroNode(4,"小黑","小黑子4号"));
singleLinkList.show();
System.out.println("该链表节点数目: "+singleLinkList.getNodesCount(head));
System.out.println("该链表倒数第2个节点: "+singleLinkList.getLastIndexNode(head,2));
System.out.println("该链表倒数第4个节点: "+singleLinkList.getLastIndexNode(head,5));
}
输出结果:
1.3.5 单链表—面试题02
题目:
-
实现单链表的反转
思路: -
使用头插法解决
-
首先创建一个临时头结点用于记录反转过程中的链表;
-
遍历单链表,每遍历到一个有效结点,就让该有效结点指向临时头结点指向的结点;
-
临时头结点再指向该有效结点,
-
原单链表遍历结束之后,再让原头结点指向临时头结点指向的结点。
示意图:
实现代码与输出结果:
/**
* Java对象只有被new才是真正创建出来的内存空间, 其余都是对该内存空间的引用(指针)
* 直接将旧链表反转
*/
public static void reversalSingleLink(singleLinkList singleLinkList){
// 若单链表是否只有一个节点或者为空,直接返回
if(singleLinkList.isEmpty()){
return;
}else {
HeroNode tempHead = new HeroNode(); // new 一个临时头节点
tempHead.setNext(null);
HeroNode temp = null; // 用于指向currentNode的下一个节点
HeroNode currentNode = singleLinkList.getHead().getNext();// 指向当前遍历节点
while (currentNode != null){
// 先临时保存节点 否则无法利用current指针实现链表的遍历
temp = currentNode.getNext();
// 头插法
currentNode.setNext(tempHead.getNext());
tempHead.setNext(currentNode);
currentNode = temp;
}
singleLinkList.getHead().setNext(tempHead.getNext());
}
}
测试代码及其输出结果:
public static void main(String[] args) {
singleLinkList singleLinkList1 = new singleLinkList();
HeroNode head = singleLinkList1.getHead();
singleLinkList1.addNode(new HeroNode(1,"小李","小黑子1号"));
singleLinkList1.addNode(new HeroNode(2,"小王","小黑子2号"));
singleLinkList1.addNode(new HeroNode(3,"小张","小黑子3号"));
singleLinkList1.addNode(new HeroNode(4,"小黑","小黑子4号"));
System.out.println("原链表:");
singleLinkList1.show();
singleLinkList.reversalSingleLink(singleLinkList1);
System.out.println("反转后链表: ");
singleLinkList1.show();
}
1.3.6 单链表—面试题03
题目:
- 从尾到头打印整个单链表
思路:
- 方法一:利用栈先进后出的特性(不会破坏链表的结构)
- 方法二:使用上一题的方法反转链表后打印(会破坏原链表的结构,本题不推荐使用)
- 方法三:使用递归实现
方法一:示意图:
方法一:实现代码:
/**
* 1. 在不破坏链表的结构下反向打印链表
* 2. 使用栈
* 3. 先遍历链表 将链表节点全部压栈 之后再全部出栈
*/
public void reversalPrintLinkList(){
HeroNode currentNode = head.getNext();
if (currentNode.getNext() == null){ // 若单链表只有一个有效节点,直接输出
System.out.println(currentNode);
return;
}
Stack<HeroNode> stack = new Stack<>();
while (currentNode != null){
stack.push(currentNode);
currentNode = currentNode.getNext();
}
while (stack.size() > 0)
System.out.println(stack.pop());
}
方法一:测试代码及其输出结果:
/**
* 1. 在不破坏链表的结构下反向打印链表
* 2. 使用栈
* 3. 先遍历链表 将链表节点全部压栈 之后再全部出栈
*/
public void reversalPrintLinkList(){
HeroNode currentNode = head.getNext();
if (currentNode.getNext() == null){ // 若单链表只有一个有效节点,直接输出
System.out.println(currentNode);
return;
}
Stack<HeroNode> stack = new Stack<>();
while (currentNode != null){
stack.push(currentNode);
currentNode = currentNode.getNext();
}
while (stack.size() > 0)
System.out.println(stack.pop());
}
方法三:实现代码:
/**
* 使用递归打印结点
* @Param [node] 链表的第一个结点,即 head.next
*/
public static void printReverse(HeroNode node){
// 这里一定要先递归调用再打印
if (node.next != null){
printReverse(node.next);
}
System.out.println(node);
}
1.3.7 两条单链表合并
题目:
- 两条带头节点的单链表合并 (要求合并的节点要按照no来进行顺序排序)
思路:
- 使用递归
- 两条单链表设为a和b,假定a从第一个节点开始与b的节点进行比对,如果a.no > b.no 那么递归调用方法使a.next.no 与 b.no进行比对,反之亦然。
- 递归方法结束会返回节点的引用(指针)
- 递归的关键是对head.next的赋值 和 a、b链表之间节点的比对
- 递归结束的条件是链表a或链表b节点全部遍历完(head1 == null or head2 == null)
示意图:
实现代码:
public static HeroNode mergeLinkList(HeroNode head1,HeroNode head2){
// 1. 判断是否结束遍历
if(head1 == null) return head2; // 链表1遍历结束,新链表剩下节点都是链表2的,就不需要再遍历了
if(head2 == null) return head1;
System.out.println("head1:"+head1.getNo()+" head2:"+head2.getNo());
// 2. 比对节点no
//要特别注意当两个节点的no相同的情况,我是定义no一样的话以head2的为准
if(head1.getNo() >= head2.getNo()){ // head2.no < head1.no情况下
// System.out.println(1);
head2.setNext((Objects.equals(head1.getNo(), head2.getNo())) // 使用三元表达式来简化代码
?mergeLinkList(head1.getNext(),head2.getNext()):mergeLinkList(head1,head2.getNext()));
return head2;
}else { // head1.no < head2.no时
head1.setNext(mergeLinkList(head1.getNext(),head2));
return head1;
}
}
public static void showLinkListByHead(HeroNode head){ // 根据传入的头节点遍历单链表
System.out.println("遍历new单链表: ");
if (head.getNext() == null) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.getNext();
while (temp!=null){
System.out.println(temp);
temp = temp.getNext();
}
}
测试代码与输出结果:
public static void main(String[] args) {
singleLinkList singleLinkList1 = new singleLinkList();
singleLinkList singleLinkList2 = new singleLinkList();
singleLinkList1.addNode(new HeroNode(1,"小李1","小黑子1号"));
singleLinkList1.addNode(new HeroNode(3,"小王1","小黑子3号"));
singleLinkList1.addNode(new HeroNode(5,"小张1","小黑子5号"));
singleLinkList1.addNode(new HeroNode(7,"小黑1","小黑子7号"));
singleLinkList2.addNode(new HeroNode(2,"小李2","小黑子2号"));
singleLinkList2.addNode(new HeroNode(3,"t1","t1"));
singleLinkList2.addNode(new HeroNode(4,"小王2","小黑子4号"));
singleLinkList2.addNode(new HeroNode(6,"小张2","小黑子6号"));
singleLinkList2.addNode(new HeroNode(8,"小黑2","小黑子8号"));
HeroNode head1 = singleLinkList1.getHead();
HeroNode head2 = singleLinkList2.getHead();
System.out.println("no比对记录: ");
HeroNode heroNode = singleLinkList.mergeLinkList(head1, head2);
singleLinkList.showLinkListByHead(heroNode);
}
1.3.8 完整代码
import java.util.Objects;
import java.util.Stack;
class HeroNode{
private Integer no; // 排名
private String name;// 姓名
private String nickname;// 外号
private HeroNode next;
public HeroNode(){}
public HeroNode(Integer no,String name,String nickname){
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
if (next!=null)
return "[No: " +no+", name: "+name+", nickName: "+nickname+", nextNo:"+next.getNo()+
"]";
else
return "[No: " +no+", name: "+name+", nickName: "+nickname+", nextNo:"+
" null]";
}
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 String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public HeroNode getNext() {
return next;
}
public void setNext(HeroNode next) {
this.next = next;
}
}
class singleLinkList{
private HeroNode head = new HeroNode(0,"","");
public HeroNode getHead(){
return head;
}
public void addNodeByOrder(HeroNode heroNode){
HeroNode temp = head;
while(true){
if(Objects.equals(temp.getNext().getNo(), heroNode.getNo())){
System.out.println("链表内已有该no的节点!");
return;
}
if(heroNode.getNo() > temp.getNext().getNo()){
temp = temp.getNext();
} else if (heroNode.getNo() < temp.getNext().getNo()) {
HeroNode oldNode = temp.getNext();
heroNode.setNext(oldNode);
temp.setNext(heroNode);
break;
}
}
}
public boolean isEmpty(){ // 判断链表是否为空
return head.getNext() == null;
}
public void addNode(HeroNode heroNode){ // 添加链表节点
HeroNode temp = head;
while (temp.getNext() != null){ // 当前节点的next为null的时候遍历结束
temp = temp.getNext();
}
// 将最后一个节点的next指向新节点
temp.setNext(heroNode);
}
public void show(){ // 遍历链表
System.out.println("显示全部节点: ");
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.getNext();
while (temp!=null){
System.out.println(temp);
temp = temp.getNext();
}
}
public static void showLinkListByHead(HeroNode head){ // 根据传入的头节点遍历单链表
System.out.println("遍历new单链表: ");
if (head.getNext() == null) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.getNext();
while (temp!=null){
System.out.println(temp);
temp = temp.getNext();
}
}
public HeroNode getOne(Integer no){ // 根据no查询节点
if (isEmpty()) {
System.out.println("链表为空!");
return null;
}
HeroNode temp = head.getNext();
while(!Objects.equals(temp.getNo(), no)){
temp = temp.getNext();
if (temp == null){ // 如果遍历链表也查不到 那么返回null
System.out.println("查无此节点!");
return null;
}
}
return temp;
}
public void update(Integer no,String name,String nickName){ // 修改节点data域
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode heroNode = getOne(no);
heroNode.setName(name);
heroNode.setNickname(nickName);
}
public void deleteNode(Integer no){ // 删除节点
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head;
/**
* 想要删除一个节点: 1.将该节点的前一个节点的next指向删除节点的下一个节点
* 2. 将该节点删除(释放内存,在Java里这一步由JVM垃圾回收器执行)
*/
while (!Objects.equals(temp.getNext().getNo(), no)){
temp = temp.getNext();
if(temp.getNext() == null){
System.out.println("查出此节点!");
break;
}
}
temp.setNext(temp.getNext().getNext());
}
/**
*
* @param head
* @return 以传入节点为单链表头节点,计算该单链表节点数目
*/
public Integer getNodesCount(HeroNode head){
HeroNode temp = head;
Integer count = 0;
while (temp.getNext() != null){
count++;
temp = temp.getNext();
}
return count;
}
/**
* 查找单链表的倒数第k个节点
*/
public HeroNode getLastIndexNode(HeroNode head,Integer index){
HeroNode temp = head.getNext();
// 1. 获取该单链表的节点数目
Integer nodesCount = getNodesCount(head);
// 2. 检验index数值是否合理
if(index<=0 || index > nodesCount){
System.out.println("输入参数不合理!");
return null;
}
// 3. 想要获取倒数第index的链表节点只需要遍历nodeCount-index个节点即可
for(int i=0;i<nodesCount-index;i++){
temp = temp.getNext();
}
return temp;
}
/**
* Java对象只有被new才是真正创建出来的内存空间, 其余都是对该内存空间的引用(指针)
* 直接将旧链表反转
*/
public static void reversalSingleLink(singleLinkList singleLinkList){
// 若单链表是否只有一个节点或者为空,直接返回
if(singleLinkList.isEmpty()){
return;
}else {
HeroNode tempHead = new HeroNode(); // new 一个临时头节点
tempHead.setNext(null);
HeroNode temp = null; // 用于指向currentNode的下一个节点
HeroNode currentNode = singleLinkList.getHead().getNext();// 指向当前遍历节点
while (currentNode != null){
// 先临时保存节点 否则无法利用current指针实现链表的遍历
temp = currentNode.getNext();
// 头插法
currentNode.setNext(tempHead.getNext());
tempHead.setNext(currentNode);
currentNode = temp;
}
singleLinkList.getHead().setNext(tempHead.getNext());
}
}
/**
* 1. 在不破坏链表的结构下反向打印链表
* 2. 使用栈
* 3. 先遍历链表 将链表节点全部压栈 之后再全部出栈
*/
public void reversalPrintLinkList(){
HeroNode currentNode = head.getNext();
if (currentNode.getNext() == null){ // 若单链表只有一个有效节点,直接输出
System.out.println(currentNode);
return;
}
Stack<HeroNode> stack = new Stack<>();
while (currentNode != null){
stack.push(currentNode);
currentNode = currentNode.getNext();
}
while (stack.size() > 0)
System.out.println(stack.pop());
}
/**
* 实现两条单链表之间的有序合并
*/
public static HeroNode mergeLinkList(HeroNode head1,HeroNode head2){
// 1. 判断是否结束遍历
if(head1 == null) return head2; // 链表1遍历结束,新链表剩下节点都是链表2的,就不需要再遍历了
if(head2 == null) return head1;
System.out.println("head1:"+head1.getNo()+" head2:"+head2.getNo());
// 2. 比对节点no
//要特别注意当两个节点的no相同的情况,我是定义no一样的话以head2的为准
if(head1.getNo() >= head2.getNo()){ // head2.no < head1.no情况下
// System.out.println(1);
head2.setNext((Objects.equals(head1.getNo(), head2.getNo())) // 使用三元表达式来简化代码
?mergeLinkList(head1.getNext(),head2.getNext()):mergeLinkList(head1,head2.getNext()));
return head2;
}else { // head1.no < head2.no时
head1.setNext(mergeLinkList(head1.getNext(),head2));
return head1;
}
}
}
public class linkListDemo01 {
public static void main(String[] args) {
singleLinkList singleLinkList1 = new singleLinkList();
singleLinkList singleLinkList2 = new singleLinkList();
singleLinkList1.addNode(new HeroNode(1,"小李1","小黑子1号"));
singleLinkList1.addNode(new HeroNode(3,"小王1","小黑子3号"));
singleLinkList1.addNode(new HeroNode(5,"小张1","小黑子5号"));
singleLinkList1.addNode(new HeroNode(7,"小黑1","小黑子7号"));
singleLinkList2.addNode(new HeroNode(2,"小李2","小黑子2号"));
singleLinkList2.addNode(new HeroNode(3,"t1","t1"));
singleLinkList2.addNode(new HeroNode(4,"小王2","小黑子4号"));
singleLinkList2.addNode(new HeroNode(6,"小张2","小黑子6号"));
singleLinkList2.addNode(new HeroNode(8,"小黑2","小黑子8号"));
HeroNode head1 = singleLinkList1.getHead();
HeroNode head2 = singleLinkList2.getHead();
System.out.println("no比对记录: ");
HeroNode heroNode = singleLinkList.mergeLinkList(head1, head2);
singleLinkList.showLinkListByHead(heroNode);
// System.out.println("原链表:");
// singleLinkList1.show();
// System.out.println("逆向打印链表: ");
// singleLinkList1.reversalPrintLinkList();
// singleLinkList.reversalSingleLink(singleLinkList1);
// System.out.println("反转后链表: ");
// singleLinkList1.show();
// System.out.println("该链表节点数目: "+singleLinkList.getNodesCount(head));
// System.out.println("该链表倒数第2个节点: "+singleLinkList.getLastIndexNode(head,2));
// System.out.println("该链表倒数第4个节点: "+singleLinkList.getLastIndexNode(head,5));
// System.out.println("查询节点: "+singleLinkList.getOne(3));
// System.out.println("修改节点: "+2);
// singleLinkList.update(2,"大王","小黑子22号");
// System.out.println("删除节点: "+3);
// singleLinkList.deleteNode(3);
// singleLinkList.getOne(3);
// singleLinkList.show();
// System.out.println("添加节点: "+3);
// singleLinkList.addNodeByOrder(new HeroNode(3,"小张","小黑子3号"));
// singleLinkList.show();
}
}