数据结构与算法(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 + '\'' +
                '}';
    }
}

双向链表

package com.weeks.linkedlist;

/**
 * @author 达少
 * @version 1.0
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
        HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
        HeroNode2 hero3 = new HeroNode2(3, "武松", "行者");
        HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");

        //创建双向链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

        //添加
//        doubleLinkedList.push(hero1);
//        doubleLinkedList.push(hero2);
//        doubleLinkedList.push(hero3);
//        doubleLinkedList.push(hero4);

        //测试按id顺序添加
        doubleLinkedList.pushOrderById(hero1);
        doubleLinkedList.pushOrderById(hero4);
        doubleLinkedList.pushOrderById(hero2);
        doubleLinkedList.pushOrderById(hero3);

        //显示链表
        doubleLinkedList.list();

        //修改
        doubleLinkedList.update(new HeroNode2(4, "公孙胜", "入云龙"));
        System.out.println("修改后的链表~");
        doubleLinkedList.list();

        //删除
        doubleLinkedList.delete(1);
        doubleLinkedList.delete(4);
        System.out.println("修改后的链表~");
        doubleLinkedList.list();
    }
}

@SuppressWarnings({"all"})
class DoubleLinkedList{
    //头节点,不存储具体的数据
    private HeroNode2 head = new HeroNode2(0, "", "");

    public HeroNode2 getHead() {
        return head;
    }

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

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

    //修改链表节点的信息,根据id查找节点,所以id不能改
    public void update(HeroNode2 newHeroNode) {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空,没有节点可以修改!!!");
            return;
        }
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode2 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;
        }
        //要在尾部添加节点,首先要找到尾部节点
        //因为头节点不能动,所以需要临时变量来遍历链表
        HeroNode2 p = head.next;
        boolean flag = false;//标记是否找到要删除节点的前一个节点
        //遍历链表
        while (true) {
            if (p == null) {//说明已经遍历到了尾节点
                break;
            }
            if (p.id == id){//说明找到了要删除的节点的前一个节点
                flag = true;
                break;
            }
            //其他情况后移
            p = p.next;
        }
        //删除节点
        if(flag){
            p.pre.next = p.next;
            if(p.next != null) {//如果不是尾节点,才执行下面的操作,否则报空指针异常
                p.next.pre = p.pre;
            }
        }else{
            System.out.printf("id为%d的节点不存在,本次删除操作失败!", id);
        }
    }

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

class HeroNode2 {
    public int id;
    public String name;
    public String nickName;
    public HeroNode2 next;
    public HeroNode2 pre;

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

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

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

单向循环链表

package com.weeks.linkedlist;

/**
 * @author 达少
 * @version 1.0
 * 解决约瑟夫问题(约瑟夫环)josefu:
 * 一群小孩围成一圈形成一个闭环,指定一个小孩作为第一个小孩开始数数,哪个数到m时
 * 哪个小孩就出列。问当所有的小孩都出列时,最后的出列顺序是什么?
 */
public class CircleSingleLinkedListDemo {
    public static void main(String[] args) {
        //创建单项循环链表
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addNode(5);
        //显示链表
        circleSingleLinkedList.list();
        //输出节点出圈的问题
        System.out.println("出圈顺序~~");
        circleSingleLinkedList.outLinked(1, 2, 5);//2->4->1->5->3
    }
}

//创建单项循环链表,表示小孩围成的闭环
class CircleSingleLinkedList{
    private Node first = null;//表示指定的第一个小孩

    //给定一个数n,创建长度为n的闭环
    public void addNode(int n){
        //数据校验,判断传如的数据是否
        if(n<1){
            System.out.println("输入的数据没有意义!");
            return;
        }
        //创建辅助变量,辅助常见环形链表
        Node cur = null;
        //使用for循环创建环形链表
        for (int i = 1; i <= n; i++) {
            if(i == 1){//当创建第一个节点时
                first = new Node(i);
                first.setNext(first);
                cur = first;
            }else{
                Node node = new Node(i);
                cur.setNext(node);
                node.setNext(first);
                cur = cur.getNext();
            }
        }
    }

    //遍历链表并显示
    public void list(){
        //判断链表是否为空
        if(first == null){
            System.out.println("链表为空,没有任何元素可以遍历!");
            return;
        }
        //创建辅助变量遍历链表
        Node cur = first;
        while(true){
            System.out.println(cur);
            if(cur.getNext() == first){
                break;
            }
            //cur后移
            cur = cur.getNext();
        }
    }

    //完成node出圈的问题
    public void outLinked(int startNo, int step, int nodes){
        //数据校验
        if(startNo < 0 || step < 0 || startNo > nodes){
            System.out.println("输入的数据有误!!!");
            return;
        }
        //定义辅助变量,帮助节点出圈
        Node helper = first;
        //将helper变量指向链表的最后一个链表
        while(helper.getNext() != first){
            helper = helper.getNext();
        }
        //将first移动到id为startNo的节点上,helper移动到first的前一个节点上
        for (int i = 0; i < startNo - 1; i++){
            first = first.getNext();
            helper = helper.getNext();
        }

        //数数将first和helper移动step-1次
        while(true) {
            if(first.getNext() == first){
                break;
            }
            for (int i = 0; i < step - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //first已经指向了要出圈的节点,将其输出
            System.out.println(first);
            first = first.getNext();
            helper.setNext(first);
        }
        //将最后的一个节点输出
        System.out.println(first);
    }
}


//创建Node类,一个node对象代表一个小孩
class Node{
    private int id;
    private Node next;

    public Node(int id){
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值