数据结构和算法三:单链表

文章目录

  • 1.简介
  • 2.应用实例
    • 2.1直接添加到链表尾部
    • 2.2根据排名将英雄插入到指定位置
    • 2.3修改节点信息(根据编号修改)
    • 2.4删除节点
  • 3.总结思考
  • 4.面试题
  • 5.完整代码

1.简介

  1. 链表是以节点的方式来存储
  2. 每个节点包含data域,next域:指向下一个节点
  3. 内存中的物理位置是不连续的
  4. 链表分带头节点的链表和无头节点的链表
    数据结构和算法三:单链表_第1张图片

2.应用实例

使用带head头的单向链表实现–水浒英雄排行榜管理

  1. 完成对英雄人物的增删改查操作
  2. 第一种:直接添加到链表尾部
  3. 第二种:根据排名将英雄插入到指定位置(如果对应位置已经存在英雄,则添加失败,给出提示)

2.1直接添加到链表尾部

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {

    public static void main(String[] args) {
        //创建几个节点
        HeroNode hero1 = new HeroNode(1, "松江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
        SingleLinkedList linkedList = new SingleLinkedList();
        linkedList.add(hero1);
        linkedList.add(hero2);
        linkedList.add(hero3);
        linkedList.add(hero4);
        linkedList.list();

    }


}

//定义SingleLinkedList
class SingleLinkedList {
    //定义头节点
    private HeroNode head = new HeroNode(0, "", "");

    //添加节点到单向链表
    public void add(HeroNode heroNode) {
        //保存头节点
        HeroNode temp = head;
        //遍历链表,找到最后一个节点
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        //循环跳出时,temp指向最后一个节点,然后将新节点添加即可
        temp.next = heroNode;
    }

    //显示链表
    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.next;
        }

    }

}
//定义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;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

2.2根据排名将英雄插入到指定位置


    //根据排名进行添加
    public void add2(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            //如果一直找到最后一个位置,直接添加即可
            if (temp.next == null) {
                temp.next = heroNode;
                break;
            }
            //当前节点已存在
            if (temp.no == heroNode.no) {
                flag = true;
                break;
            }
            //找到了位置
            if (temp.no < heroNode.no && temp.next.no > heroNode.no) {
                heroNode.next = temp.next;
                temp.next = heroNode;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("当前节点已存在,添加失败");
        }
    }

    public void add3(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("已经存在,不可添加");
        }else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

2.3修改节点信息(根据编号修改)

public void update(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            heroNode.next = temp.next.next;
            temp.next = heroNode;
        } else {
            System.out.println("未找到目标节点,更新失败");
        }
    }

2.4删除节点

public void delete(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("未找到目标节点,删除失败");
        }
    }

3.总结思考

在对单链表进行操作时,很重要的一点是,要确认你要找的元素时哪一个元素
比如:你想在某个位置插入一个元素,或者是删除某一位置的元素,此时你要找的是这个位置元素的 前一个 元素。
而在后面—合并两个有序的单链表,合并之后的链表依旧有序—这个试题中
是要遍历每一个节点,此时,应该直接找到你要遍历的那个节点

4.面试题

  1. 求单链表中节点的个数
  2. 查找单链表中倒数第k个节点
  3. 单链表的反转
  4. 从尾到头打印单链表
  5. 合并两个有序的单链表,合并之后的链表依旧有序

求单链表中节点的个数

	/**
    * 获取单链表节点的个数(有头节点的,不统计头节点)
    * @param head 头节点
    * @return 有效节点个数
    */
   public static int getLength(HeroNode head) {
       int length = 0;
       while (true) {
           if (head.next == null) {
               break;
           }
           length++;
           head = head.next;
       }
       return length;
   }

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

/**
     * 查找单链表中倒数第k个节点
     * @param k
     * @return 倒数第K个节点
     */
    public HeroNode getK(int k) {
        int length = getLength(head);
        int k1 = length - k + 1;
        if (k1 <= 0) {
            System.out.println("没有倒数第" + k + "个节点");
            return null;
        }
        HeroNode temp = head;
        while (k1 != 0) {
            temp = temp.next;
            k1--;
        }
        return temp;
    }

单链表的反转
思路:

  1. 首先定义一个节点 reverseHead = new HeroNode()
  2. 遍历旧链表,逐个获取每个节点
  3. 将获取的节点,插入到 reverseHead后边
    /**
     * 单链表的反转
     * @param list 原始单链表
     * @return 反转后的单链表
     */
    public static SingleLinkedList reverse(SingleLinkedList list) {
        int length = getLength(list.head);
        if (length < 2) {
            System.out.println("链表长度小于2,无需进行反转");
            return list;
        }
        SingleLinkedList newList = new SingleLinkedList();
        HeroNode reverseNode;
        //这里只要遍历list即可~~懒得改了
        while ((reverseNode = list.deleteNode(1)) != null) {
            newList.addNode(reverseNode, 1);
        }
        return newList;
    }

    /**
     * 删除第i个节点
     * @param i
     * @return 第i个节点
     */
    public HeroNode deleteNode(int i) {
        if (i > getLength(head)) {
            System.out.println("没有第" + i + "个节点");
            return null;
        }
        //找i前面的节点
        i = i - 1;
        HeroNode temp = head;
        while (i != 0) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            i--;
        }
        //将第i个节点存储,作为返回值
        HeroNode result = temp.next;
        //删除
        temp.next = result.next;
        return result;
    }

    /**
     * 将 Node 添加到第i个位置
     * @param node
     * @param i
     */
    public void addNode(HeroNode node, int i) {
        if (i > getLength(head) + 1) {
            System.out.println("无法将当前节点加入链表,因为位置超出链表长度");
            return;
        }
        //找i前面一个位置
        i = i - 1;
        HeroNode temp = head;
        while (i != 0) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            i--;
        }
        //判断是否已经找到
        if (i == 0) {
            node.next = temp.next;
            temp.next = node;
        }
    }
    
    /**
     * 获取单链表节点的个数(有头节点的,不统计头节点)
     * @param head 头节点
     * @return 有效节点个数
     */
    public static int getLength(HeroNode head) {
        int length = 0;
        while (true) {
            if (head.next == null) {
                break;
            }
            length++;
            head = head.next;
        }
        return length;
    }

从尾到头打印单链表
利用栈先进后出的特点,实现逆序打印

    /**
     * 使用stack进行逆序打印
     * @param head
     */
    public static void reversePrint(HeroNode head) {
        //链表空,不打印
        if (head.next == null) {
            return;
        }
        Stack<HeroNode> stack = new Stack<>();
        while (head.next != null) {
            stack.push(head.next);
            head = head.next;
        }
        while (stack.size() > 0) {
            System.out.println(stack.pop());
        }
    }

合并两个有序的单链表,合并之后的链表依旧有序
合并链表的时候注意不要用头节点去next,注意引用同一个对象时,操作的是同一块内存,防止在生成新的链表的时候,h1,h2被改变

   /**
     * 合并两个链表
     * @param head1
     * @param head2
     * @return 合并后的新链表
     */
    public static SingleLinkedList merge(HeroNode head1, HeroNode head2) {
        if (head1.next == null || head2.next == null) {
            return null;
        }
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //这里非常有必要让head比h1,h2慢一步
        //否则在修改head的时候,h1,h2会跟着改变
        HeroNode head = singleLinkedList.getHead();
        HeroNode h1 = head1.next;
        HeroNode h2 = head2.next;
        while (h1 != null && h2 != null) {
            if (h1.no < h2.no) {
                head.next = h1;
                h1 = h1.next;
            }else {
                head.next = h2;
                h2 = h2.next;
            }
            head = head.next;
        }
        if (h1 == null) {
            head.next = h2;
        }
        if (h2 == null) {
            head.next = h1;
        }
        return singleLinkedList;
    }

思维误区
如果初始化时,三个指针都指向头节点。
思路是这样:每次我们使用指针的next进行比较,找到较小的节点(比如就是p1.next)就移动指针,对应步骤1
之后,我们将p3.next指向p1,对应步骤2
接着,p2.next.no 我们一定p2,对应步骤三
然后将p3.next = p2
注意:此时的p1这个链表已将发生了变化
这时执行p1.next,指向的是2这个节点,p1再也无法指向3这个节点
数据结构和算法三:单链表_第2张图片
正确解法
关键在于,初始化时,p1,p2直接指向第一个元素
当p1.no <= p2.no 时
p3.next = p1
p1 = p1.next
p3 = p3.next
这样,更改p3.next时,就不会导致p1,p2的混乱
数据结构和算法三:单链表_第3张图片

5.完整代码

package com.atguigu.linkedlist;

import java.util.Stack;

public class SingleLinkedListDemo {

    public static void main(String[] args) {
        //创建几个节点
        HeroNode hero1 = new HeroNode(1, "松江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
//        SingleLinkedList linkedList = new SingleLinkedList();
//        linkedList.addByNo(hero2);
//        linkedList.addByNo(hero1);
//        linkedList.addByNo(hero4);
//        linkedList.addByNo(hero3);
//        linkedList.addByNo(hero3);
//        linkedList.list();

//        System.out.println("---------另一种写法--------------");
//        linkedList.add2ByNo(hero2);
//        linkedList.add2ByNo(hero1);
//        linkedList.add2ByNo(hero4);
//        linkedList.add2ByNo(hero3);
//        linkedList.add2ByNo(hero3);
//        linkedList.list();

        HeroNode hero5 = new HeroNode(5, "林冲冲", "豹子头");
        HeroNode hero6 = new HeroNode(6, "吴用", "智多星");
//        linkedList.update(hero5);
//        linkedList.update(hero6);
//        linkedList.list();
//
//        System.out.println("-----删除后的节点-------");
//        linkedList.delete(hero5);
//        linkedList.delete(hero6);
//        linkedList.list();

//        System.out.println("---------获取有效节点的个数----------");
//        System.out.println(SingleLinkedList.getLength(linkedList.getHead()));
//
//        System.out.println("----------获取链表指定位置的数据-------------");
//        System.out.println(linkedList.getK(1));
//        System.out.println(linkedList.getK(2));
//        System.out.println(linkedList.getK(3));
//        System.out.println(linkedList.getK(4));
//
//        System.out.println("------------测试删除和添加------------");
//        System.out.println(SingleLinkedList.getLength(linkedList.getHead()));
//        linkedList.addNode(hero6, 5);
//        linkedList.deleteNode(0);
//        linkedList.list();

//
//        System.out.println("--------输出原始链表------------");
//        linkedList.list();
//        System.out.println("--------反转这个链表------------");
//        SingleLinkedList reverse = SingleLinkedList.reverse(linkedList);
//        reverse.list();
//        System.out.println("--------经过反转后的原始链表--------");
//        linkedList.list();
//
//        System.out.println("--------逆序打印链表--------");
//        SingleLinkedList.reversePrint(linkedList.getHead());


        System.out.println("--------合并两个有序单链表--------");
        //创建两个单链表
        SingleLinkedList linkedList1 = new SingleLinkedList();
        SingleLinkedList linkedList2 = new SingleLinkedList();
        //随意添加几个元素
        linkedList1.addByNo(hero4);
        linkedList1.addByNo(hero6);
        linkedList1.addByNo(hero2);

        linkedList2.add2ByNo(hero3);
        linkedList2.add2ByNo(hero5);
        linkedList2.add2ByNo(hero1);
        //输出两个链表
        System.out.println("--------list1--------");
        linkedList1.list();
        System.out.println("--------list2--------");
        linkedList2.list();
        //合并
        System.out.println("--------merge--------");
        SingleLinkedList mergeList = SingleLinkedList.merge(linkedList1.getHead(), linkedList2.getHead());
        mergeList.list();

    }


}

//定义SingleLinkedList
class SingleLinkedList {
    //定义头节点
    private HeroNode head = new HeroNode(0, "", "");

    public HeroNode getHead() {
        return head;
    }


    /**
     * 合并两个链表
     * @param head1
     * @param head2
     * @return 合并后的新链表
     */
    public static SingleLinkedList merge(HeroNode head1, HeroNode head2) {
        if (head1.next == null || head2.next == null) {
            return null;
        }
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        HeroNode head = singleLinkedList.getHead();
        HeroNode h1 = head1.next;
        HeroNode h2 = head2.next;
        while (h1 != null && h2 != null) {
            if (h1.no < h2.no) {
                head.next = h1;
                h1 = h1.next;
            }else {
                head.next = h2;
                h2 = h2.next;
            }
            head = head.next;
        }
        if (h1 == null) {
            head.next = h2;
        }
        if (h2 == null) {
            head.next = h1;
        }
        return singleLinkedList;
    }


    /**
     * 使用stack进行逆序打印
     * @param head
     */
    public static void reversePrint(HeroNode head) {
        //链表空,不打印
        if (head.next == null) {
            return;
        }
        Stack<HeroNode> stack = new Stack<>();
        while (head.next != null) {
            stack.push(head.next);
            head = head.next;
        }
        while (stack.size() > 0) {
            System.out.println(stack.pop());
        }
    }


    /**
     * 单链表的反转
     * @param list 原始单链表
     * @return 反转后的单链表
     */
    public static SingleLinkedList reverse(SingleLinkedList list) {
        int length = getLength(list.head);
        if (length < 2) {
            System.out.println("链表长度小于2,无需进行反转");
            return list;
        }
        SingleLinkedList newList = new SingleLinkedList();
        HeroNode reverseNode;
        //这里只要遍历list即可~~懒得改了
        while ((reverseNode = list.deleteNode(1)) != null) {
            newList.addNode(reverseNode, 1);
        }
        return newList;
    }

    /**
     * 查找单链表中倒数第k个节点
     * @param k
     * @return 倒数第K个节点
     */
    public HeroNode getK(int k) {
        if (k <= 0) {
            System.out.println("非法参数");
            return null;
        }
        int length = getLength(head);
        int k1 = length - k + 1;
        if (k1 <= 0) {
            System.out.println("没有倒数第" + k + "个节点");
            return null;
        }
        HeroNode temp = head;
        while (k1 != 0) {
            temp = temp.next;
            k1--;
        }
        return temp;
    }

    /**
     * 获取单链表节点的个数(有头节点的,不统计头节点)
     * @param head 头节点
     * @return 有效节点个数
     */
    public static int getLength(HeroNode head) {
        int length = 0;
        while (true) {
            if (head.next == null) {
                break;
            }
            length++;
            head = head.next;
        }
        return length;
    }

    public void delete(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("未找到目标节点,删除失败");
        }
    }

    /**
     * 删除第i个节点
     * @param i
     * @return 第i个节点
     */
    public HeroNode deleteNode(int i) {
        if (i > getLength(head) || i <= 0) {
            System.out.println("没有第" + i + "个节点");
            return null;
        }
        //找i前面的节点
        i = i - 1;
        HeroNode temp = head;
        while (i != 0) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            i--;
        }
        //将第i个节点存储,作为返回值
        HeroNode result = temp.next;
        //删除
        temp.next = result.next;
        return result;
    }


    public void update(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            heroNode.next = temp.next.next;
            temp.next = heroNode;
        } else {
            System.out.println("未找到目标节点,更新失败");
        }
    }


    //添加节点到单向链表
    public void add(HeroNode heroNode) {
        //保存头节点
        HeroNode temp = head;
        //遍历链表,找到最后一个节点
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        //循环跳出时,temp指向最后一个节点,然后将新节点添加即可
        temp.next = heroNode;
    }

    //根据排名进行添加
    public void addByNo(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            //如果一直找到最后一个位置,直接添加即可
            if (temp.next == null) {
                temp.next = heroNode;
                break;
            }
            //当前节点已存在
            if (temp.no == heroNode.no) {
                flag = true;
                break;
            }
            //找到了位置
            if (temp.no < heroNode.no && temp.next.no > heroNode.no) {
                heroNode.next = temp.next;
                temp.next = heroNode;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("当前节点已存在,添加失败");
        }
    }

    public void add2ByNo(HeroNode heroNode) {
        HeroNode temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("已经存在,不可添加");
        } else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    /**
     * 将 Node 添加到第i个位置
     * @param node
     * @param i
     */
    public void addNode(HeroNode node, int i) {
        if (i > getLength(head) + 1 || i <= 0) {
            System.out.println("参数不合法,无法将当前节点加入链表");
            return;
        }
        //找i前面一个位置
        i = i - 1;
        HeroNode temp = head;
        while (i != 0) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            i--;
        }
        //判断是否已经找到
        if (i == 0) {
            node.next = temp.next;
            temp.next = node;
        }
    }

    //显示链表
    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.next;
        }

    }

}

//定义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;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}


你可能感兴趣的:(数据结构和算法)