数据结构与算法(Java)之链表

Posted 达少Rising

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法(Java)之链表相关的知识,希望对你有一定的参考价值。

单向链表

package com.weeks.linkedlist;

import java.util.Stack;

/**
 * @author 达少
 * @version 1.0
 * 实现链表,保存水浒传英雄人物
 */
public class LinkedListDemo {
    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, "林冲", "豹子头");
        HeroNode hero5 = new HeroNode(5, "关胜", "大刀");
        HeroNode hero6 = new HeroNode(6, "戴宗", "神行太保");
        HeroNode hero7 = new HeroNode(7, "李逵", "黑旋风");


        //创建链表
//        LinkedList linkedList = new LinkedList();

//        //添加数据到链表
//        linkedList.push(hero1);
//        linkedList.push(hero4);
//        linkedList.push(hero2);
//        linkedList.push(hero3);

        //按id升序插入
//        linkedList.pushOrderById(hero1);
//        linkedList.pushOrderById(hero4);
//        linkedList.pushOrderById(hero3);
//        linkedList.pushOrderById(hero2);
//        //显示链表
//        System.out.println("======原来的链表======");
//        linkedList.list();
        /*
        //修改节点
        linkedList.update(new HeroNode(2, "吴用", "智多星"));
        System.out.println("修改后的链表~~~~");
        linkedList.list();

        //删除节点
        linkedList.delete(1);
//        linkedList.delete(4);
        linkedList.delete(3);
//        linkedList.delete(2);
//        linkedList.delete(5);
        System.out.println("删除节点后的链表~~~");
        linkedList.list();

        //测试获取链表测长度
        int size = LinkedList.getSize(linkedList.getHead());
        System.out.println("当前链表的长度为:" + size);

        //测试返回倒数第k个元素
        int k = 2;
        HeroNode lastK = LinkedList.findLastK(linkedList.getHead(), k);
        System.out.println("链表的倒数第" + k + "个元素是:" + lastK);
        */


        //反转链表
//        LinkedList.reverse(linkedList.getHead());
//        System.out.println("======反转后的链表======");
//        linkedList.list();

        //逆序输出链表元素
//        System.out.println("======逆序输出链表元素(不改变链表的结构~)======");
//        LinkedList.reversePrint(linkedList.getHead());

        //创建两个链表
        LinkedList linkedList1 = new LinkedList();
        LinkedList linkedList2 = new LinkedList();

        //向两个链表中添加数据
        linkedList1.pushOrderById(hero1);
        linkedList1.pushOrderById(hero3);
        linkedList1.pushOrderById(hero5);
        linkedList1.pushOrderById(hero7);

        linkedList2.pushOrderById(hero2);
        linkedList2.pushOrderById(hero4);
        linkedList2.pushOrderById(hero6);

        System.out.println("原来的链表1:");
        linkedList1.list();

        System.out.println("原来的链表2:");
        linkedList2.list();

        //合并两个链表
        HeroNode merge = LinkedList.merge(linkedList1.getHead(), linkedList2.getHead());
        LinkedList mergeLinkedList = new LinkedList(merge);
        System.out.println("合并后的链表:");
        mergeLinkedList.list();

        System.out.println("原来的链表1:");
        linkedList1.list();

        System.out.println("原来的链表2:");
        linkedList2.list();
    }
}

class LinkedList {
    //头节点,不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    public LinkedList(){}

    public LinkedList(HeroNode head){
        this.head = head;
    }

    public HeroNode getHead() {
        return head;
    }

    //添加节点(尾插法)
    public void push(HeroNode heroNode) {
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode p = head;
        //遍历链表
        while (true) {
            //判断链表是否为空,为空则退出循环
            if (p.next == null) {
                break;
            }
            //不空则p向后移,直到找到尾节点
            p = p.next;
        }
        //当退出循环p已经指向尾节点,在p后添加节点
        p.next = heroNode;
    }

    //添加节点的时候要按id的顺序升序添加,如果id已经存在则提示:加入的英雄人物编号已经存在不能添加
    public void pushOrderById(HeroNode heroNode) {
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode p = head;
        boolean flag = false;//标志添加的id英雄任务是否已经存在,默认不存在为false
        //首先要找到添加的位置
        while (true) {//循环遍历,找到添加的位置
            if (p.next == null) {//说明已经到了尾节点
                break;
            }
            if (p.next.id > heroNode.id) {//说明已经找到插入的位置,位与p与p.next之间
                break;
            }
            if (p.next.id == heroNode.id) {//说明id已存在
                flag = true;
                break;
            }
            //其他情况将p后移
            p = p.next;
        }
        //如果不存在则将节点添加到指定的位置
        if (!flag) {
            heroNode.next = p.next;
            p.next = heroNode;
        } else {
            System.out.println("加入的英雄人物编号已经存在不能添加!!!");
        }
    }

    //修改链表节点的信息,根据id查找节点,所以id不能改
    public void update(HeroNode newHeroNode) {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空,没有节点可以修改!!!");
            return;
        }
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode p = head.next;
        boolean flag = false;//标志是否找到要修改的节点
        //循环遍历俩链表找到要修改的节点
        while (true) {
            if (p == null) {//说明已经遍历完整链表
                break;
            }
            if (p.id == newHeroNode.id) {
                flag = true;
                break;
            }
            //其他情况p后移
            p = p.next;
        }

        //找到修改该的节点后进行修改
        if (flag) {
            //更改信息
            p.name = newHeroNode.name;
            p.nickName = newHeroNode.nickName;
        } else {
            System.out.printf("没有找到id为%d的节点,本次修改不成功!", newHeroNode.id);
        }
    }

    //根据id删除节点
    public void delete(int id) {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空,没有节点可以删除!!!");
            return;
        }
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode p = head;
        boolean flag = false;//标记是否找到要删除节点的前一个节点
        //遍历链表
        while (true) {
            if (p.next == null) {//说明已经遍历到了尾节点
                break;
            }
            if (p.next.id == id){//说明找到了要删除的节点的前一个节点
                flag = true;
                break;
            }
            //其他情况后移
            p = p.next;
        }
        //删除节点
        if(flag){
            p.next = p.next.next;
        }else{
            System.out.printf("id为%d的节点不存在,本次删除操作失败!", id);
        }
    }

    //遍历显示整个链表
    public void list() {
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode p = head.next;
        //遍历链表
        while (true) {
            //判断链表是否为空,为空则退出循环
            if (p == null) {
                break;
            }
            //显示节点
            System.out.println(p);
            //后移
            p = p.next;
        }
    }

    //获取链表的长度
    public static int getSize(HeroNode head){
        //定义变量保存有效节点的个数(头节点不是有效节点)
        int size = 0;
        //定义变量作为游标,遍历链表
        HeroNode cur = head.next;
        //遍历链表
        while(cur != null){
            size++;
            //cur后移
            cur = cur.next;
        }
        return size;
    }

    //获取单链表的倒数第k个节点元素【新浪面试题】
    /**
     * 分析:
     * 1.首先要知道这个链表的总长度size,可以使用getSize()获取
     * 2.获取要遍历的长度为size-k
     * 3.返回倒数第k个元素
     */
    public static HeroNode findLastK(HeroNode head, int k){
        //判断链表是否为空
        if(head.next == null){
            return null;
        }
        //获取当前链表的长度
        int size = getSize(head);
        //判断输入的k值是否符合要求
        if(k <= 0 || k > size){
            return null;
        }
        //定义临时变量遍历链表
        HeroNode cur = head.next;
        //遍历前size-k个元素
        for (int i = 0; i < (size - k); i++) {
            //后移cur
            cur = cur.next;
        }
        return cur;
    }

    //实现单链表的反转【腾讯的面试题】
    /**
     * 分析:
     * 1.定义一个头节点reverseHead
     * 2.从原来的链表中遍历获取节点,每获得一个节点就将其从原来的链表中摘取下来
     * 3.将摘取下来的节点插入到新的头节点的下一个位置(头插法)
     * 4.最后head.next = reverse.next
     */
    public static void reverse(HeroNode head){
        //判断当前链表是否为空或者是否只有一个元素
        if(head.next == null || head.next.next == null){
            return;
        }
        //定义两个变量,分别指向原来链表的当前节点和当前节点的下一个节点
        HeroNode cur = head.next;
        HeroNode next = null;
        //定义新的头节点
        HeroNode reverseHead = new HeroNode(0, "", "");
        //遍历原来的链表
        while(cur != null){
            //将next指向当前节点的下一个节点,记录下一个摘取位置
            next = cur.next;
            //将cur从原来的列表中摘取下来
            head.next = cur.next;
            //将摘取下来的节点插入新的头节点的下一个位置
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            //将原来链表的下一个节点赋值给cur
            cur = next;
        }
        //最后将新的头节点next赋值给原来的头节点next,完成反转
        head.next = reverseHead.next;
    }

    //将单链表按原来的顺序逆序打印出来【百度面试题】
    /**
     * 分析:
     * 方式1:将原来的链表进行反转后再进行遍历操作,但是这会改变链表的结构,所以不推荐
     * 方式2:利用栈的先进后出特性,反向打印链表,不改变链表的原来结构
     */
    public static void reversePrint(HeroNode head){
        //判断链表是否为空
        if (head.next == null){
            return;
        }
        //定义一个栈
        Stack<HeroNode> stack = new Stack<>();
        //定义游标遍历链表
        HeroNode cur = head.next;
        //遍历链表
        while(cur != null){
            //将当前节点压入栈中
            stack.push(cur);
            //将cur后移
            cur = cur.next;
        }
        //输出栈中的元素
        while(stack.size() > 0){
            System.out.println(stack.pop());
        }
    }

    //合并两个有序链表,并保证合并后的链表还是有序的(按id大小升序排序),不能破坏原来的两个链表(深拷贝)
    /**
     * 分析:
     * 1.新建一个头节点:newHead
     * 2.比较两个链表的元素id大小,将id小的链表节点添加到新的的链表中
     * 3.返回新的链表头节点
     */
    public static HeroNode merge(HeroNode head1, HeroNode head2){
        //判断链表1,2是否为空
        if(head1.next == null && head2.next == null){
            return null;
        }
        if(head1.next == null){
            return head2;
        }
        if(head2.next == null){
            return head1;
        }
        //定义新的头节点
        HeroNode new_head = new HeroNode(0, "", "");
        //定义两个游标,遍历两个链表
        HeroNode cur1 = head1.next;
        HeroNode cur2 = head2.next;
        //定义变量存储要插入到新链表的元素
        HeroNode temp = null;
        //定义变量记录新链表的尾部节点
        HeroNode rear = new_head;
        while(cur1 != null && cur2 != null){
            if(cur1.id < cur2.id){
                temp = new HeroNode(cur1);
                //将取出的元素插入到新链表的尾部
                rear.next = temp;
                //rear向后移
                rear = temp;
                //cur1向后移
                cur1 = cur1.next;
            }else if(cur1.id > cur2.id){
                temp = new HeroNode(cur2);
                //将取出的元素插入到新链表的尾部
                rear.next = temp;
                //rear向后移
                rear = temp;
                //cur2向后移
                cur2 = cur2.next;
            }else {//这种情况是cur1.id==cur2.id,所以只允许插入其中一个,选择插入cur1
                temp = new HeroNode(cur1);
                //将取出的元素插入到新链表的尾部
                rear.next = temp;
                //rear向后移
                rear = temp;
                //cur1和cur2都要向后移
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
        }
        //当其中有一个链表还没遍历完,要继续遍历添加到新链表中
        while(cur1 != null){
            temp = new HeroNode(cur1);
            //将取出的元素插入到新链表的尾部
            rear.next = temp;
            //rear向后移
            rear = temp;
            //cur1和cur2都要向后移
            cur1 = cur1.next;
        }
        while(cur2 != null){
            temp = new HeroNode(cur2);
            //将取出的元素插入到新链表的尾部
            rear.next = temp;
            //rear向后移
            rear = temp;
            //cur2向后移
            cur2 = cur2.next;
        }
        return new_head;
    }
}

class HeroNode {
    public int id;
    public String name;
    public String nickName;
    public HeroNode next;

    public HeroNode(int id, String name, String nickName) {
        this.id = id;
        this.name = name;
        this.nickName = nickName;
    }

    //深拷贝方法
    public HeroNode(HeroNode heroNode){
        this.id = heroNode.id;
        this.name = heroNode.name;
        this.nickName = heroNode.nickName;
        this.next = null;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", nickName='" + nickName + 以上是关于数据结构与算法(Java)之链表的主要内容,如果未能解决你的问题,请参考以下文章

java数据结构与算法之链表找环入口

java数据结构与算法之链表相交问题

java之数据结构之链表及包装类包

数据结构与算法之链表

数据结构与算法之链表

数据结构与算法之链表